diff --git a/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerContainer.java b/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerContainer.java index 038e3bc..3668a2d 100644 --- a/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerContainer.java +++ b/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerContainer.java @@ -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; @@ -16,6 +17,7 @@ public class GitServerContainer extends GenericContainer { 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; /** * @@ -29,6 +31,7 @@ public GitServerContainer(DockerImageName dockerImageName) { } else { withExposedPorts(22); } + withCommand("/usr/sbin/sshd", "-D", "-e"); } /** @@ -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. * @@ -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; + } } diff --git a/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerVersions.java b/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerVersions.java new file mode 100644 index 0000000..a578d7c --- /dev/null +++ b/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerVersions.java @@ -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; + } +} diff --git a/src/main/java/com/github/sparsick/testcontainers/gitserver/SshIdentity.java b/src/main/java/com/github/sparsick/testcontainers/gitserver/SshIdentity.java new file mode 100644 index 0000000..5e27e23 --- /dev/null +++ b/src/main/java/com/github/sparsick/testcontainers/gitserver/SshIdentity.java @@ -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; + } +} diff --git a/src/main/resources/id_client b/src/main/resources/id_client new file mode 100644 index 0000000..121753d --- /dev/null +++ b/src/main/resources/id_client @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIAv2zdBPnakBt4UCoTDRMLDKOrm0JiPc9CwRFqvcha5coAoGCCqGSM49 +AwEHoUQDQgAEKArvTWElvX1Erm/essBeGKQzsGrHPJjGJprwEOqV/MKjUPZ1jxl3 +iZL9m/PqZrKc94PGgpalIFdxOcdrgwTLMg== +-----END EC PRIVATE KEY----- diff --git a/src/main/resources/id_client.pub b/src/main/resources/id_client.pub new file mode 100644 index 0000000..64a8e63 --- /dev/null +++ b/src/main/resources/id_client.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCgK701hJb19RK5v3rLAXhikM7BqxzyYxiaa8BDqlfzCo1D2dY8Zd4mS/Zvz6maynPeDxoKWpSBXcTnHa4MEyzI= sparsick@Thinkpad-T14 diff --git a/src/main/resources/sshd_config b/src/main/resources/sshd_config new file mode 100644 index 0000000..674420f --- /dev/null +++ b/src/main/resources/sshd_config @@ -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 \ No newline at end of file diff --git a/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerJUnit5IntegrationTest.java b/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerJUnit5IntegrationTest.java index b94aeff..d0c5aff 100644 --- a/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerJUnit5IntegrationTest.java +++ b/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerJUnit5IntegrationTest.java @@ -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; diff --git a/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerTest.java b/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerTest.java index df6d247..d939767 100644 --- a/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerTest.java +++ b/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerTest.java @@ -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; @@ -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(); @@ -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(); @@ -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() + ); + } } diff --git a/src/test/java/com/github/sparsick/testcontainers/gitserver/SupportedGitServerImages.java b/src/test/java/com/github/sparsick/testcontainers/gitserver/SupportedGitServerImages.java deleted file mode 100644 index f4e8a00..0000000 --- a/src/test/java/com/github/sparsick/testcontainers/gitserver/SupportedGitServerImages.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.sparsick.testcontainers.gitserver; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.testcontainers.utility.DockerImageName; - -import java.util.stream.Stream; - -public class SupportedGitServerImages implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext extensionContext) { - return Stream.of("2.40", "2.38", "2.36", "2.34", "2.34.2").map(version -> DockerImageName.parse("rockstorm/git-server:" + version)).map(Arguments::of); - } -} diff --git a/src/main/resources/logback-test.xml b/src/test/resources/logback-test.xml similarity index 100% rename from src/main/resources/logback-test.xml rename to src/test/resources/logback-test.xml