Skip to content

Commit

Permalink
Merge pull request #17 from sparsick/pubKeyAuth
Browse files Browse the repository at this point in the history
Add SSH public key authentication
  • Loading branch information
sparsick authored Aug 27, 2023
2 parents 0223d0c + 42aae13 commit d36e359
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.sparsick.testcontainers.gitserver;

import com.github.dockerjava.api.command.InspectContainerResponse;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;
Expand All @@ -16,6 +17,7 @@ public class GitServerContainer extends GenericContainer<GitServerContainer> {
private static final String GIT_PASSWORD_KEY = "GIT_PASSWORD";
private static DockerImageName DEFAULT_DOCKER_IMAGE_NAME = DockerImageName.parse("rockstorm/git-server");
private String gitRepoName = "testRepo";
private SshIdentity sshClientIdentity;

/**
*
Expand All @@ -29,6 +31,7 @@ public GitServerContainer(DockerImageName dockerImageName) {
} else {
withExposedPorts(22);
}
withCommand("/usr/sbin/sshd", "-D", "-e");
}

/**
Expand Down Expand Up @@ -57,6 +60,28 @@ public GitServerContainer withGitRepo(String gitRepoName) {
return this;
}

/**
* Enabled SSH public key authentication.
*
* @return instance of the git server container
*/
public GitServerContainer withSshKeyAuth() {
try {
sshClientIdentity = new SshIdentity(
this.getClass().getClassLoader().getResourceAsStream("id_client").readAllBytes(),
this.getClass().getClassLoader().getResourceAsStream("id_client.pub").readAllBytes(),
new byte[0]);

withClasspathResourceMapping("id_client.pub", "/home/git/.ssh/authorized_keys", BindMode.READ_ONLY);
withClasspathResourceMapping("sshd_config", "/etc/ssh/sshd_config", BindMode.READ_ONLY);


} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}

/**
* Return the SSH URI for git repo.
*
Expand Down Expand Up @@ -91,4 +116,15 @@ public String getGitPassword() {
var password = getEnvMap().get(GIT_PASSWORD_KEY);
return password != null ? password : "12345";
}

/**
* Return the identity information for public key authentication.
*
* If {@code withSshKeyAuth} was not called, then it returns null.
*
* @return identity information for a public key authentication
*/
public SshIdentity getSshClientIdentity() {
return sshClientIdentity;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.github.sparsick.testcontainers.gitserver;

import org.testcontainers.utility.DockerImageName;

/**
* List of supported Git server version based on the docker image "rockstorm/git-server"
*
*/
public enum GitServerVersions {

/**
* rockstorm/git-server:2.40
*/
V2_40(DockerImageName.parse("rockstorm/git-server:2.40")),
/**
* rockstorm/git-server:2.38
*/
V2_38(DockerImageName.parse("rockstorm/git-server:2.38")),
/**
* rockstorm/git-server:2.36
*/
V2_36(DockerImageName.parse("rockstorm/git-server:2.36")),
/**
* rockstorm/git-server:2.34.2
*/
V2_34_2(DockerImageName.parse("rockstorm/git-server:2.34.2")),
/**
* rockstorm/git-server:2.34
*/
V2_34(DockerImageName.parse("rockstorm/git-server:2.34"));

private final DockerImageName dockerImageName;

GitServerVersions(DockerImageName dockerImageName) {
this.dockerImageName = dockerImageName;
}

/**
*
* @return docker image name
*/
public DockerImageName getDockerImageName() {
return dockerImageName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.github.sparsick.testcontainers.gitserver;

/**
* Value object for identity information for a public key authentication.
*/
public class SshIdentity {
private byte[] privateKey;
private byte[] publicKey;
private byte[] passphrase;

/**
* Identity information for a public key authentication.
*
* @param privateKey SSH private key
* @param publicKey SSH public key
* @param passphrase password for private key
*/
public SshIdentity(byte[] privateKey, byte[] publicKey, byte[] passphrase) {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.passphrase = passphrase;
}

/**
* SSH private key
*
* @return SSH private key
*/
public byte[] getPrivateKey() {
return privateKey;
}

/**
* SSH public key
*
* @return SSH public key
*/
public byte[] getPublicKey() {
return publicKey;
}

/**
* Password for the SSH private key
*
* @return Password for the SSH private key
*/
public byte[] getPassphrase() {
return passphrase;
}
}
5 changes: 5 additions & 0 deletions src/main/resources/id_client
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAv2zdBPnakBt4UCoTDRMLDKOrm0JiPc9CwRFqvcha5coAoGCCqGSM49
AwEHoUQDQgAEKArvTWElvX1Erm/essBeGKQzsGrHPJjGJprwEOqV/MKjUPZ1jxl3
iZL9m/PqZrKc94PGgpalIFdxOcdrgwTLMg==
-----END EC PRIVATE KEY-----
1 change: 1 addition & 0 deletions src/main/resources/id_client.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCgK701hJb19RK5v3rLAXhikM7BqxzyYxiaa8BDqlfzCo1D2dY8Zd4mS/Zvz6maynPeDxoKWpSBXcTnHa4MEyzI= sparsick@Thinkpad-T14
118 changes: 118 additions & 0 deletions src/main/resources/sshd_config
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# $OpenBSD: sshd_config,v 1.104 2021/07/02 05:11:21 dtucker Exp $

# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.

# This sshd was compiled with PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.

#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key

# Ciphers and keying
#RekeyLimit default none

# Logging
#SyslogFacility AUTH
#LogLevel DEBUG

# Authentication:

#LoginGraceTime 2m
#PermitRootLogin prohibit-password
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10

#PubkeyAuthentication yes
#PubkeyAcceptedAlgorithms ssh-rsa

# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile .ssh/authorized_keys

#AuthorizedPrincipalsFile none

#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody

# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes

# To disable tunneled clear text passwords, change to no here!
#PasswordAuthentication yes
#PermitEmptyPasswords no

# Change to no to disable s/key passwords
#KbdInteractiveAuthentication yes

# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no

# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes

# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the KbdInteractiveAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via KbdInteractiveAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and KbdInteractiveAuthentication to 'no'.
#UsePAM no

#AllowAgentForwarding yes
# Feel free to re-enable these if your use case requires them.
AllowTcpForwarding no
GatewayPorts no
X11Forwarding no
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
#PrintMotd yes
#PrintLastLog yes
#TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none

# no default banner path
#Banner none

# override default of no subsystems
Subsystem sftp /usr/lib/ssh/sftp-server

# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
public class GitServerContainerJUnit5IntegrationTest {

@Container
private GitServerContainer containerUnderTest = new GitServerContainer(DockerImageName.parse("rockstorm/git-server:2.38"));
private GitServerContainer containerUnderTest = new GitServerContainer(DockerImageName.parse("rockstorm/git-server"));

@TempDir
private File tempDir;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.github.sparsick.testcontainers.gitserver;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.transport.SshTransport;
import org.eclipse.jgit.transport.ssh.jsch.JschConfigSessionFactory;
import org.eclipse.jgit.transport.ssh.jsch.OpenSshConfig;
import org.eclipse.jgit.util.FS;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.testcontainers.shaded.com.github.dockerjava.core.DockerContextMetaFile;
import org.junit.jupiter.params.provider.EnumSource;
import org.testcontainers.utility.DockerImageName;

import java.io.File;
Expand Down Expand Up @@ -67,9 +69,9 @@ void exposedPortIs22() {
}

@ParameterizedTest
@ArgumentsSource(SupportedGitServerImages.class)
void containerStarted(DockerImageName dockerImageName) {
var containerUnderTest = new GitServerContainer(dockerImageName);
@EnumSource(GitServerVersions.class)
void containerStarted(GitServerVersions gitServer) {
var containerUnderTest = new GitServerContainer(gitServer.getDockerImageName());

containerUnderTest.start();

Expand All @@ -88,9 +90,9 @@ void gitRepoURI() {
}

@ParameterizedTest
@ArgumentsSource(SupportedGitServerImages.class)
void setupGitRepo(DockerImageName dockerImageName) {
var containerUnderTest = new GitServerContainer(dockerImageName).withGitRepo("testRepoName");
@EnumSource(GitServerVersions.class)
void setupGitRepo(GitServerVersions gitServer) {
var containerUnderTest = new GitServerContainer(gitServer.getDockerImageName()).withGitRepo("testRepoName");

containerUnderTest.start();

Expand All @@ -114,4 +116,42 @@ protected void configure(OpenSshConfig.Host hc, Session session) {
.call()
);
}

@ParameterizedTest
@EnumSource(GitServerVersions.class)
void pubKeyAuth(GitServerVersions gitServer) {
var containerUnderTest = new GitServerContainer(gitServer.getDockerImageName()).withSshKeyAuth();

containerUnderTest.start();

URI gitRepoURI = containerUnderTest.getGitRepoURIAsSSH();

assertThatNoException().isThrownBy(() ->
Git.cloneRepository()
.setURI(gitRepoURI.toString())
.setDirectory(tempDir)
.setBranch("main")
.setTransportConfigCallback(transport -> {
var sshTransport = (SshTransport) transport;
sshTransport.setSshSessionFactory(new JschConfigSessionFactory() {

@Override
protected JSch createDefaultJSch(FS fs ) throws JSchException {
JSch defaultJSch = super.createDefaultJSch( fs );
SshIdentity sshIdentity = containerUnderTest.getSshClientIdentity();
byte[] privateKey = sshIdentity.getPrivateKey();
byte[] publicKey = sshIdentity.getPublicKey();
byte[] passphrase = sshIdentity.getPassphrase();
defaultJSch.addIdentity("git-server", privateKey, publicKey, passphrase);
return defaultJSch;
}
@Override
protected void configure(OpenSshConfig.Host hc, Session session) {
session.setConfig("StrictHostKeyChecking", "no");
}
});
})
.call()
);
}
}
Loading

0 comments on commit d36e359

Please sign in to comment.