Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SSH public key authentication #17

Merged
merged 4 commits into from
Aug 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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