Skip to content

Instantly share code, notes, and snippets.

@hanserya
Last active March 2, 2022 15:23
Show Gist options
  • Save hanserya/43b00162741fa3022481301db60e8acd to your computer and use it in GitHub Desktop.
Save hanserya/43b00162741fa3022481301db60e8acd to your computer and use it in GitHub Desktop.
Use Mounted Volume for SSH Key-Based Authentication in Dockerized Spring Cloud Config Server
# This is only needed if you want to run the server on your machine without setting environment variables.
spring:
cloud:
config:
server:
git:
uri: ssh://your-repo
@EnableConfigServer
@SpringBootApplication
@Import(SshLocationOverrideConfiguration.class)
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServiceApplication.class, args)
.start(); //<-- THIS IS IMPORTANT!
}
}
version: '3.4'
services:
config-server:
image: config-server
container_name: config-server
environment:
spring.cloud.config.server.git.uri: ssh://your-repo
SPRING_CLOUD_CONFIG_SERVER_GIT_SSHLOCATION: /var/ssh-config
build:
context: .
dockerfile: ./Dockerfile
target: app
args:
sourceCodeLocation: '.'
settingsFile: 'maven-settings.xml'
volumes:
- <redirectedSshLocation>:/var/ssh-config:ro
ports:
- "8888:8080"
- "4000:4000"
FROM maven:3.2-jdk-8 AS build
ARG sourceCodeLocation=.
ARG settingsFile=settings.xml
COPY ${sourceCodeLocation} /src
COPY ${settingsFile} /root/.m2/settings.xml
WORKDIR /src
RUN mvn package
FROM openjdk:8 AS app
COPY --from=build /<app.target directory>/<yourModuleName>*.jar /usr/app/server.jar
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.4.0/wait /wait
RUN chmod +x /wait
ENTRYPOINT /wait && java -jar $JAVA_OPTS /usr/app/server.jar
//This class may or may not be needed in your implementation. The Spring Boot application was not starting the same way from
// my IDE as it was within the Docker container. The inconsistency led to a situation where the invocation of
// JGitEnvironmentRepository.afterPropertiesSet was overriding my attempt to inject the RedirectedSshKeyLocationSessionFactory.
//The end result was the the first call to the config server running within the Docker container always failed
// with an ssh authenticatin exception. This "hack" resolves that problem.
//I actually have a .NET background and am unsure of whether or not there is another way that I can accomplish this without such
// an ugly implementation - but it works =\
public class RedirectedSshKeyLocationContextStartedListener implements ApplicationListener<ContextStartedEvent> {
@Override
public void onApplicationEvent(ContextStartedEvent contextStartedEvent) {
String sshKeyLocation = contextStartedEvent.getApplicationContext().getEnvironment().getProperty("spring.cloud.config.server.git.sshLocation");
if(isBlank(sshKeyLocation)) {
return;
}
JschConfigSessionFactory sessionFactory = (JschConfigSessionFactory) SshSessionFactory.getInstance();
if(sessionFactory.getClass() != OverriddenSshKeyLocationSessionFactory.class) {
SshSessionFactory sessionFactoryDecorator = new OverriddenSshKeyLocationSessionFactory(sshKeyLocation, sessionFactory);
SshSessionFactory.setInstance(sessionFactoryDecorator);
}
}
}
public class RedirectedSshKeyLocationSessionFactory extends JschConfigSessionFactory {
private final String sshKeyLocation;
private final BiConsumer<OpenSshConfig.Host, Session> configureInvoker;
RedirectedSshKeyLocationSessionFactory(String sshKeyLocation, JschConfigSessionFactory decoratedSessionFactory) {
this.sshKeyLocation = sshKeyLocation;
this.configureInvoker = createConfigureInvoker(decoratedSessionFactory);
}
@Override
protected void configure(OpenSshConfig.Host host, Session session) {
configureInvoker.accept(host, session);
}
@Override
protected JSch getJSch(OpenSshConfig.Host hc, FS fs) {
try {
String identity = getFileAbsolutePath("id_rsa");
String knownHosts = getFileAbsolutePath("known_hosts");
JSch jsch = super.getJSch(hc, fs);
jsch.removeAllIdentity();
jsch.addIdentity(identity);
jsch.setKnownHosts(knownHosts);
return jsch;
} catch (Exception ex) {
throw new RuntimeException("Unable to redirect the Jsch identity provider to the specified key location due to an unexpected exception.", ex);
}
}
private String getFileAbsolutePath(String fileName) {
return Paths
.get(sshKeyLocation, fileName)
.toAbsolutePath()
.toString();
}
private BiConsumer<OpenSshConfig.Host, Session> createConfigureInvoker(final JschConfigSessionFactory decoratedSessionFactory) {
return (host, session) -> {
try {
Class<?> parentClass = decoratedSessionFactory.getClass();
Method configureMethod = parentClass.getDeclaredMethod("configure", OpenSshConfig.Host.class, Session.class);
configureMethod.setAccessible(true);
configureMethod.invoke(decoratedSessionFactory, host, session);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
};
}
}
public class RedirectedSshLocationTransportConfigCallback extends FileBasedSshTransportConfigCallback {
private final String sshLocation;
RedirectedSshLocationTransportConfigCallback(MultipleJGitEnvironmentProperties environmentProperties, String sshKeyLocation) {
super(environmentProperties);
this.sshKeyLocation = sshKeyLocation;
}
@Override
public void configure(Transport transport) {
super.configure(transport);
JschConfigSessionFactory sessionFactory = (JschConfigSessionFactory) SshSessionFactory.getInstance();
SshSessionFactory sessionFactoryDecorator = new RedirectedSshKeyLocationSessionFactory(sshKeyLocation, sessionFactory);
SshSessionFactory.setInstance(sessionFactoryDecorator);
}
}
org.springframework.context.ApplicationListener=\
<fully qualified package>.RedirectedSshKeyLocationContextStartedListener
@Configuration
@AutoConfigureBefore(ConfigServerConfiguration.class)
@ConditionalOnProperty("spring.cloud.config.server.git.sshLocation")
public class SshLocationOverrideConfiguration {
@Bean
public TransportConfigCallback customTransportCallback(
MultipleJGitEnvironmentProperties environmentProperties,
@Value("${spring.cloud.config.server.git.sshLocation}") String sshKeyLocation) {
Path path = Paths.get(sshKeyLocation);
if(Files.notExists(path)) {
throw new RuntimeException("The ssh location does not exist!");
}
path = Paths.get(sshKeyLocation, "id_rsa");
if(Files.notExists(path) && !Files.isReadable(path)) {
throw new RuntimeException("The private key does not exist within the specified ssh directory!");
}
path = Paths.get(sshKeyLocation, "known_hosts");
if(Files.notExists(path) && !Files.isReadable(path)) {
throw new RuntimeException("The known hosts file does not exist within the specified ssh directory!");
}
return new RedirectedSshLocationTransportConfigCallback(environmentProperties, sshKeyLocation);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment