diff --git a/src/main/java/com/github/dockerjava/core/DockerClientConfig.java b/src/main/java/com/github/dockerjava/core/DockerClientConfig.java index ff1329c69..db27940a4 100644 --- a/src/main/java/com/github/dockerjava/core/DockerClientConfig.java +++ b/src/main/java/com/github/dockerjava/core/DockerClientConfig.java @@ -6,11 +6,12 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.Serializable; import java.net.URI; import java.util.Map; import java.util.Properties; -public class DockerClientConfig { +public class DockerClientConfig implements Serializable { private static final String DOCKER_HOST_PROPERTY = "DOCKER_HOST"; private static final String DOCKER_CERT_PATH_PROPERTY = "DOCKER_CERT_PATH"; private static final String DOCKER_IO_URL_PROPERTY = "docker.io.url"; @@ -41,21 +42,22 @@ public class DockerClientConfig { .build(); private static final String DOCKER_IO_PROPERTIES_PROPERTY = "docker.io.properties"; private final URI uri; - private final String version, username, password, email, serverAddress, dockerCertPath, dockerCfgPath; + private final String version, username, password, email, serverAddress, dockerCfgPath; private final Integer readTimeout; private final boolean loggingFilterEnabled; + private final SSLConfig sslConfig; - DockerClientConfig(URI uri, String version, String username, String password, String email, String serverAddress, String dockerCertPath, String dockerCfgPath, Integer readTimeout, boolean loggingFilterEnabled) { + DockerClientConfig(URI uri, String version, String username, String password, String email, String serverAddress, String dockerCfgPath, Integer readTimeout, boolean loggingFilterEnabled, SSLConfig sslConfig) { this.uri = uri; this.version = version; this.username = username; this.password = password; this.email = email; this.serverAddress = serverAddress; - this.dockerCertPath = dockerCertPath; this.dockerCfgPath = dockerCfgPath; this.readTimeout = readTimeout; this.loggingFilterEnabled = loggingFilterEnabled; + this.sslConfig = sslConfig; } private static Properties loadIncludedDockerProperties(Properties systemProperties) { @@ -212,15 +214,15 @@ public boolean isLoggingFilterEnabled() { return loggingFilterEnabled; } - public String getDockerCertPath() { - return dockerCertPath; + public SSLConfig getSslConfig() { + return sslConfig; } public String getDockerCfgPath() { return dockerCfgPath; } - @Override + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -228,7 +230,7 @@ public boolean equals(Object o) { DockerClientConfig that = (DockerClientConfig) o; if (loggingFilterEnabled != that.loggingFilterEnabled) return false; - if (dockerCertPath != null ? !dockerCertPath.equals(that.dockerCertPath) : that.dockerCertPath != null) + if (sslConfig != null ? !sslConfig.equals(that.sslConfig) : that.sslConfig != null) return false; if (dockerCfgPath != null ? !dockerCfgPath.equals(that.dockerCfgPath) : that.dockerCfgPath != null) return false; @@ -252,8 +254,8 @@ public int hashCode() { result = 31 * result + (password != null ? password.hashCode() : 0); result = 31 * result + (email != null ? email.hashCode() : 0); result = 31 * result + (serverAddress != null ? serverAddress.hashCode() : 0); - result = 31 * result + (dockerCertPath != null ? dockerCertPath.hashCode() : 0); result = 31 * result + (dockerCfgPath != null ? dockerCfgPath.hashCode() : 0); + result = 31 * result + (sslConfig != null ? sslConfig.hashCode() : 0); result = 31 * result + (readTimeout != null ? readTimeout.hashCode() : 0); result = 31 * result + (loggingFilterEnabled ? 1 : 0); return result; @@ -268,8 +270,8 @@ public String toString() { ", password='" + password + '\'' + ", email='" + email + '\'' + ", serverAddress='" + serverAddress + '\'' + - ", dockerCertPath='" + dockerCertPath + '\'' + ", dockerCfgPath='" + dockerCfgPath + '\'' + + ", sslConfig='" + sslConfig + '\'' + ", readTimeout=" + readTimeout + ", loggingFilterEnabled=" + loggingFilterEnabled + '}'; @@ -277,9 +279,10 @@ public String toString() { public static class DockerClientConfigBuilder { private URI uri; - private String version, username, password, email, serverAddress, dockerCertPath, dockerCfgPath; + private String version, username, password, email, serverAddress, dockerCfgPath; private Integer readTimeout; private boolean loggingFilterEnabled; + private SSLConfig sslConfig; /** * This will set all fields in the builder to those contained in the Properties object. The Properties object @@ -342,7 +345,7 @@ public final DockerClientConfigBuilder withLoggingFilter(boolean loggingFilterEn } public final DockerClientConfigBuilder withDockerCertPath(String dockerCertPath) { - this.dockerCertPath = dockerCertPath; + this.sslConfig = new LocalDirectorySSLConfig(dockerCertPath); return this; } @@ -352,6 +355,11 @@ public final DockerClientConfigBuilder withDockerCfgPath(String dockerCfgPath) { } + public final DockerClientConfigBuilder withSSLConfig(SSLConfig config) { + this.sslConfig = config; + return this; + } + public DockerClientConfig build() { return new DockerClientConfig( uri, @@ -360,10 +368,10 @@ public DockerClientConfig build() { password, email, serverAddress, - dockerCertPath, dockerCfgPath, readTimeout, - loggingFilterEnabled + loggingFilterEnabled, + sslConfig ); } } diff --git a/src/main/java/com/github/dockerjava/core/KeystoreSSLConfig.java b/src/main/java/com/github/dockerjava/core/KeystoreSSLConfig.java new file mode 100644 index 000000000..269b87658 --- /dev/null +++ b/src/main/java/com/github/dockerjava/core/KeystoreSSLConfig.java @@ -0,0 +1,133 @@ +package com.github.dockerjava.core; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * An SSL Config that is based on an pre-existing or pre-loaded KeyStore. + */ +public class KeystoreSSLConfig implements SSLConfig, Serializable { + + private final KeyStore keystore; + private final String keystorePassword; + + /** + * @param keystore a KeyStore + * @param keystorePassword key password + */ + public KeystoreSSLConfig(KeyStore keystore, String keystorePassword) { + this.keystorePassword = keystorePassword; + Preconditions.checkNotNull(keystore); + this.keystore = keystore; + } + + /** + * + * @param pfxFile a PKCS12 file + * @param password Password for the keystore + * @throws KeyStoreException + * @throws IOException + * @throws CertificateException + * @throws NoSuchAlgorithmException + */ + public KeystoreSSLConfig(File pfxFile, String password) + throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + Preconditions.checkNotNull(pfxFile); + Preconditions.checkNotNull(password); + keystore = KeyStore.getInstance("pkcs12"); + keystore.load(new FileInputStream(pfxFile), password.toCharArray()); + keystorePassword = password; + } + + + /** + * Get the SSL Context out of the keystore. + * @return java SSLContext + * @throws KeyManagementException + * @throws UnrecoverableKeyException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + */ + @Override + public SSLContext getSSLContext() + throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, + KeyStoreException { + + final SSLContext context = SSLContext.getInstance("TLS"); + + String httpProtocols = System.getProperty("https.protocols"); + System.setProperty("https.protocols", "TLSv1"); + + if (httpProtocols != null) + System.setProperty("https.protocols", httpProtocols); + + final KeyManagerFactory + keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keystore, keystorePassword.toCharArray()); + context.init(keyManagerFactory.getKeyManagers(), new TrustManager[]{ + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + + @Override + public void checkClientTrusted(final X509Certificate[] arg0, final String arg1) { + + } + + @Override + public void checkServerTrusted(final X509Certificate[] arg0, final String arg1) { + + } + } + }, new SecureRandom()); + + return context; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + KeystoreSSLConfig that = (KeystoreSSLConfig) o; + + return keystore.equals(that.keystore); + + } + + @Override + public int hashCode() { + return keystore.hashCode(); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("keystore", keystore) + .toString(); + } +} diff --git a/src/main/java/com/github/dockerjava/core/LocalDirectorySSLConfig.java b/src/main/java/com/github/dockerjava/core/LocalDirectorySSLConfig.java new file mode 100644 index 000000000..ef57a0de3 --- /dev/null +++ b/src/main/java/com/github/dockerjava/core/LocalDirectorySSLConfig.java @@ -0,0 +1,101 @@ +package com.github.dockerjava.core; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; + +import com.github.dockerjava.api.DockerClientException; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.glassfish.jersey.SslConfigurator; + +import java.io.Serializable; +import java.security.KeyStore; +import java.security.Security; + +import javax.net.ssl.SSLContext; + +/** + * SSL Config from local files. + */ +public class LocalDirectorySSLConfig implements SSLConfig, Serializable { + + private final String dockerCertPath; + + public LocalDirectorySSLConfig(String dockerCertPath) { + Preconditions.checkNotNull(dockerCertPath); + this.dockerCertPath = dockerCertPath; + } + + public String getDockerCertPath() { + return dockerCertPath; + } + + @Override + public SSLContext getSSLContext() { + + boolean certificatesExist = CertificateUtils.verifyCertificatesExist(dockerCertPath); + + if (certificatesExist) { + + try { + + Security.addProvider(new BouncyCastleProvider()); + + KeyStore keyStore = CertificateUtils.createKeyStore(dockerCertPath); + KeyStore trustStore = CertificateUtils.createTrustStore(dockerCertPath); + + // properties acrobatics not needed for java > 1.6 + String httpProtocols = System.getProperty("https.protocols"); + System.setProperty("https.protocols", "TLSv1"); + SslConfigurator sslConfig = SslConfigurator.newInstance(true); + if (httpProtocols != null) { + System.setProperty("https.protocols", httpProtocols); + } + + sslConfig.keyStore(keyStore); + sslConfig.keyStorePassword("docker"); + sslConfig.trustStore(trustStore); + + return sslConfig.createSSLContext(); + + + } catch (Exception e) { + throw new DockerClientException(e.getMessage(), e); + } + + } + + return null; + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LocalDirectorySSLConfig that = (LocalDirectorySSLConfig) o; + + if (!dockerCertPath.equals(that.dockerCertPath)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return dockerCertPath.hashCode(); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("dockerCertPath", dockerCertPath) + .toString(); + } +} diff --git a/src/main/java/com/github/dockerjava/core/SSLConfig.java b/src/main/java/com/github/dockerjava/core/SSLConfig.java new file mode 100644 index 000000000..21024c4f6 --- /dev/null +++ b/src/main/java/com/github/dockerjava/core/SSLConfig.java @@ -0,0 +1,22 @@ +package com.github.dockerjava.core; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +import javax.net.ssl.SSLContext; + +/** + * Get an SSL Config. Allows for various different implementations. + */ +public interface SSLConfig { + + /** + * Get the SSL Context, from wherever it comes (file, keystore). + * @return an SSL context. + */ + SSLContext getSSLContext() + throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, + KeyStoreException; +} diff --git a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java index 862824fcc..64222a972 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java +++ b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java @@ -52,43 +52,16 @@ public void init(DockerClientConfig dockerClientConfig) { ClientBuilder clientBuilder = ClientBuilder.newBuilder().withConfig(clientConfig); - String dockerCertPath = dockerClientConfig.getDockerCertPath(); - - if (dockerCertPath != null) { - boolean certificatesExist = CertificateUtils.verifyCertificatesExist(dockerCertPath); - - if (certificatesExist) { - - try { - - Security.addProvider(new BouncyCastleProvider()); - - KeyStore keyStore = CertificateUtils.createKeyStore(dockerCertPath); - KeyStore trustStore = CertificateUtils.createTrustStore(dockerCertPath); - - // properties acrobatics not needed for java > 1.6 - String httpProtocols = System.getProperty("https.protocols"); - System.setProperty("https.protocols", "TLSv1"); - SslConfigurator sslConfig = SslConfigurator.newInstance(true); - if (httpProtocols != null) System.setProperty("https.protocols", httpProtocols); - - sslConfig.keyStore(keyStore); - sslConfig.keyStorePassword("docker"); - sslConfig.trustStore(trustStore); - - SSLContext sslContext = sslConfig.createSSLContext(); - - - clientBuilder.sslContext(sslContext); - - } catch (Exception e) { - throw new DockerClientException(e.getMessage(), e); - } - - } + try { + SSLContext ssl = dockerClientConfig.getSslConfig().getSSLContext(); + if (ssl != null) + clientBuilder.sslContext(ssl); + } catch(Exception ex) { + throw new DockerClientException("Error in SSL Configuration", ex); } + client = clientBuilder.build(); WebTarget webResource = client.target(dockerClientConfig.getUri()); diff --git a/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java b/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java index 06bb1ea12..7b5e4e1f2 100644 --- a/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java +++ b/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java @@ -16,12 +16,12 @@ public class DockerClientConfigTest { public static final DockerClientConfig EXAMPLE_CONFIG = newExampleConfig(); private static DockerClientConfig newExampleConfig() { - return new DockerClientConfig(URI.create("http://foo"), "bar", "baz", "qux", "blam", "wham", "flim", "flam", 877, false); + return new DockerClientConfig(URI.create("http://foo"), "bar", "baz", "qux", "blam", "wham", "flam", 877, false, new LocalDirectorySSLConfig("flim")); } @Test public void string() throws Exception { - assertEquals("DockerClientConfig{uri=http://foo, version='bar', username='baz', password='qux', email='blam', serverAddress='wham', dockerCertPath='flim', dockerCfgPath='flam', readTimeout=877, loggingFilterEnabled=false}", + assertEquals("DockerClientConfig{uri=http://foo, version='bar', username='baz', password='qux', email='blam', serverAddress='wham', dockerCfgPath='flam', sslConfig='LocalDirectorySSLConfig{dockerCertPath=flim}', readTimeout=877, loggingFilterEnabled=false}", EXAMPLE_CONFIG.toString()); } @@ -104,8 +104,8 @@ public void defaults() throws Exception { assertEquals(config.getServerAddress(), AuthConfig.DEFAULT_SERVER_ADDRESS); assertEquals(config.getVersion(), null); assertEquals(config.isLoggingFilterEnabled(), true); - assertEquals(config.getDockerCertPath(), "someHomeDir/.docker"); assertEquals(config.getDockerCfgPath(), "someHomeDir/.dockercfg"); + assertEquals( ((LocalDirectorySSLConfig)config.getSslConfig()).getDockerCertPath(), "someHomeDir/.docker"); } @Test diff --git a/src/test/java/com/github/dockerjava/core/DockerClientImplTest.java b/src/test/java/com/github/dockerjava/core/DockerClientImplTest.java index c31db1ba9..a91fa109e 100644 --- a/src/test/java/com/github/dockerjava/core/DockerClientImplTest.java +++ b/src/test/java/com/github/dockerjava/core/DockerClientImplTest.java @@ -10,7 +10,7 @@ public class DockerClientImplTest { @Test public void configuredInstanceAuthConfig() throws Exception { // given a config with null serverAddress - DockerClientConfig dockerClientConfig = new DockerClientConfig(null, null, "", "", "", null, null, null, 0, false); + DockerClientConfig dockerClientConfig = new DockerClientConfig(null, null, "", "", "", null, null, 0, false, null); DockerClientImpl dockerClient = DockerClientImpl.getInstance(dockerClientConfig); // when we get the auth config