From 645812e4f88e8ac2d4163d0372694ec0696e20a5 Mon Sep 17 00:00:00 2001 From: Sandra Parsick Date: Wed, 30 Aug 2023 17:36:08 +0200 Subject: [PATCH] feature: add host key information for better host key verification --- .../gitserver/GitServerContainer.java | 16 +++++ .../testcontainers/gitserver/SshHostKey.java | 62 +++++++++++++++++++ .../gitserver/GitServerContainerTest.java | 53 ++++++++++++++-- 3 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/github/sparsick/testcontainers/gitserver/SshHostKey.java 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 3668a2d..5ed1cc7 100644 --- a/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerContainer.java +++ b/src/main/java/com/github/sparsick/testcontainers/gitserver/GitServerContainer.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.net.URI; +import java.util.Base64; /** * Container for a plain Git Server based on the Docker image "rockstorm/git-server". @@ -18,6 +19,7 @@ public class GitServerContainer extends GenericContainer { private static DockerImageName DEFAULT_DOCKER_IMAGE_NAME = DockerImageName.parse("rockstorm/git-server"); private String gitRepoName = "testRepo"; private SshIdentity sshClientIdentity; + private SshHostKey hostKey; /** * @@ -100,6 +102,11 @@ protected void containerIsStarted(InspectContainerResponse containerInfo) { execInContainer("mkdir", "-p", gitRepoPath); execInContainer("git", "init", "--bare", gitRepoPath); execInContainer("chown", "-R", "git:git", "/srv"); + + ExecResult result = execInContainer("cat", "/etc/ssh/ssh_host_ecdsa_key.pub"); + String[] catResult = result.getStdout().split(" "); + hostKey = new SshHostKey("localhost", Base64.getDecoder().decode(catResult[1])); + } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } @@ -127,4 +134,13 @@ public String getGitPassword() { public SshIdentity getSshClientIdentity() { return sshClientIdentity; } + + /** + * Return the public host key information. + * + * @return public host key + */ + public SshHostKey getHostKey() { + return hostKey; + } } diff --git a/src/main/java/com/github/sparsick/testcontainers/gitserver/SshHostKey.java b/src/main/java/com/github/sparsick/testcontainers/gitserver/SshHostKey.java new file mode 100644 index 0000000..1fe3156 --- /dev/null +++ b/src/main/java/com/github/sparsick/testcontainers/gitserver/SshHostKey.java @@ -0,0 +1,62 @@ +package com.github.sparsick.testcontainers.gitserver; + +import java.util.Objects; + + +/** + * Value object for SSH Host key information. + */ +public class SshHostKey { + + private String hostname; + private byte[] key; + + /** + * SSH Host Key information + * @param hostname host + * @param key keystring + */ + public SshHostKey(String hostname, byte[] key) { + this.key = key; + this.hostname = hostname; + } + + /** + * Public key of the host key. + * + * @return key string + */ + public byte[] getKey() { + return key; + } + + /** + * Name of the host + * + * @return name of the host + */ + public String getHostname() { + return hostname; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SshHostKey)) return false; + SshHostKey hostKey = (SshHostKey) o; + return Objects.equals(key, hostKey.key) && Objects.equals(hostname, hostKey.hostname); + } + + @Override + public int hashCode() { + return Objects.hash(key, hostname); + } + + @Override + public String toString() { + return "HostKey{" + + "key='" + key + '\'' + + ", hostname='" + hostname + '\'' + + '}'; + } +} 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 d939767..6b0847c 100644 --- a/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerTest.java +++ b/src/test/java/com/github/sparsick/testcontainers/gitserver/GitServerContainerTest.java @@ -1,5 +1,6 @@ package com.github.sparsick.testcontainers.gitserver; +import com.jcraft.jsch.HostKey; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; @@ -138,11 +139,7 @@ void pubKeyAuth(GitServerVersions gitServer) { @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); + configureSshIdentity(defaultJSch, containerUnderTest); return defaultJSch; } @Override @@ -154,4 +151,50 @@ protected void configure(OpenSshConfig.Host hc, Session session) { .call() ); } + + @ParameterizedTest + @EnumSource(GitServerVersions.class) + void strictHostKeyVerifivation(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 ); + configureSshIdentity(defaultJSch, containerUnderTest); + configureHostKeyRepository(defaultJSch, containerUnderTest); + return defaultJSch; + } + }); + }) + .call() + ); + } + + private void configureSshIdentity(JSch defaultJSch, GitServerContainer containerUnderTest) throws JSchException { + SshIdentity sshIdentity = containerUnderTest.getSshClientIdentity(); + byte[] privateKey = sshIdentity.getPrivateKey(); + byte[] publicKey = sshIdentity.getPublicKey(); + byte[] passphrase = sshIdentity.getPassphrase(); + defaultJSch.addIdentity("git-server", privateKey, publicKey, passphrase); + } + + private void configureHostKeyRepository(JSch defaultJSch, GitServerContainer containerUnderTest) throws JSchException { + SshHostKey hostKey = containerUnderTest.getHostKey(); + String host = hostKey.getHostname(); + byte[] key = hostKey.getKey(); + defaultJSch.getHostKeyRepository().add(new HostKey(host, key), null); + } }