diff --git a/pom.xml b/pom.xml index 13c71b63f..4b3d7cc0d 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ 1.3.9 0.3 18.0 + 1.51 1.0.1 @@ -142,6 +143,12 @@ guava ${guava.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + diff --git a/src/main/java/com/github/dockerjava/core/CertificateUtils.java b/src/main/java/com/github/dockerjava/core/CertificateUtils.java new file mode 100644 index 000000000..89722a94d --- /dev/null +++ b/src/main/java/com/github/dockerjava/core/CertificateUtils.java @@ -0,0 +1,142 @@ +package com.github.dockerjava.core; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import org.apache.commons.io.IOUtils; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; + +public class CertificateUtils { + + public static boolean verifyCertificatesExist(String dockerCertPath) { + String[] files = {"ca.pem", "cert.pem", "key.pem"}; + for (String file : files) { + Path path = Paths.get(dockerCertPath, file); + boolean exists = Files.exists(path); + if(!exists) { + return false; + } + } + + return true; + } + + public static KeyStore createKeyStore(final String dockerCertPath) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, CertificateException, KeyStoreException { + KeyPair keyPair = loadPrivateKey(dockerCertPath); + Certificate privateCertificate = loadCertificate(dockerCertPath); + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null); + + keyStore.setKeyEntry("docker", keyPair.getPrivate(), "docker".toCharArray(), new Certificate[]{privateCertificate}); + return keyStore; + } + + public static KeyStore createTrustStore(final String dockerCertPath) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException { + Path caPath = Paths.get(dockerCertPath, "ca.pem"); + BufferedReader reader = Files.newBufferedReader(caPath, Charset.defaultCharset()); + PEMParser pemParser = null; + + try { + pemParser = new PEMParser(reader); + X509CertificateHolder certificateHolder = (X509CertificateHolder) pemParser.readObject(); + Certificate caCertificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder); + + KeyStore trustStore = KeyStore.getInstance("JKS"); + trustStore.load(null); + trustStore.setCertificateEntry("ca", caCertificate); + return trustStore; + + } + finally { + if(pemParser != null) { + IOUtils.closeQuietly(pemParser); + } + + if(reader != null) { + IOUtils.closeQuietly(reader); + } + } + + } + + private static Certificate loadCertificate(final String dockerCertPath) throws IOException, CertificateException { + Path certificate = Paths.get(dockerCertPath, "cert.pem"); + BufferedReader reader = Files.newBufferedReader(certificate, Charset.defaultCharset()); + PEMParser pemParser = null; + + try { + pemParser = new PEMParser(reader); + X509CertificateHolder certificateHolder = (X509CertificateHolder) pemParser.readObject(); + return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder); + } + finally { + if(pemParser != null) { + IOUtils.closeQuietly(pemParser); + } + + if(reader != null) { + IOUtils.closeQuietly(reader); + } + } + + } + + private static KeyPair loadPrivateKey(final String dockerCertPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + Path certificate = Paths.get(dockerCertPath, "key.pem"); + BufferedReader reader = Files.newBufferedReader(certificate, Charset.defaultCharset()); + + PEMParser pemParser = null; + + try { + pemParser = new PEMParser(reader); + + PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject(); + + byte[] pemPrivateKeyEncoded = pemKeyPair.getPrivateKeyInfo().getEncoded(); + byte[] pemPublicKeyEncoded = pemKeyPair.getPublicKeyInfo().getEncoded(); + + KeyFactory factory = KeyFactory.getInstance("RSA"); + + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pemPublicKeyEncoded); + PublicKey publicKey = factory.generatePublic(publicKeySpec); + + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pemPrivateKeyEncoded); + PrivateKey privateKey = factory.generatePrivate(privateKeySpec); + + return new KeyPair(publicKey, privateKey); + + } + finally { + if(pemParser != null) { + IOUtils.closeQuietly(pemParser); + } + + if(reader != null) { + IOUtils.closeQuietly(reader); + } + } + + + } + +} diff --git a/src/main/java/com/github/dockerjava/core/DockerClientConfig.java b/src/main/java/com/github/dockerjava/core/DockerClientConfig.java index b2adea7fc..6e3795e97 100644 --- a/src/main/java/com/github/dockerjava/core/DockerClientConfig.java +++ b/src/main/java/com/github/dockerjava/core/DockerClientConfig.java @@ -10,7 +10,7 @@ public class DockerClientConfig { private final URI uri; - private final String version, username, password, email; + private final String version, username, password, email, dockerCertPath; private final Integer readTimeout; private final boolean loggingFilterEnabled; @@ -22,6 +22,7 @@ private DockerClientConfig(DockerClientConfigBuilder builder) { this.email = builder.email; this.readTimeout = builder.readTimeout; this.loggingFilterEnabled = builder.loggingFilterEnabled; + this.dockerCertPath = builder.dockerCertPath; } public URI getUri() { @@ -51,6 +52,10 @@ public Integer getReadTimeout() { public boolean isLoggingFilterEnabled() { return loggingFilterEnabled; } + + public String getDockerCertPath() { + return dockerCertPath; + } public static Properties loadIncludedDockerProperties() { try { @@ -97,7 +102,7 @@ public static Properties overrideDockerPropertiesWithSystemProperties(Properties overriddenProperties.putAll(p); // TODO Add all values from system properties that begin with docker.io.* - for (String s : new String[]{ "url", "version", "username", "password", "email", "readTimeout", "enableLoggingFilter"}) { + for (String s : new String[]{ "url", "version", "username", "password", "email", "readTimeout", "enableLoggingFilter", "dockerCertPath"}) { final String key = "docker.io." + s; if (System.getProperties().containsKey(key)) { overriddenProperties.setProperty(key, System.getProperty(key)); @@ -115,7 +120,7 @@ public static DockerClientConfigBuilder createDefaultConfigBuilder() { public static class DockerClientConfigBuilder { private URI uri; - private String version, username, password, email; + private String version, username, password, email, dockerCertPath; private Integer readTimeout; private boolean loggingFilterEnabled; @@ -124,7 +129,7 @@ public DockerClientConfigBuilder() { /** * This will set all fields in the builder to those contained in the Properties object. The Properties object - * should contain the following docker.io.* keys: url, version, username, password, and email. If + * should contain the following docker.io.* keys: url, version, username, password, email, and dockerCertPath. If * docker.io.readTimeout or docker.io.enableLoggingFilter are not contained, they will be set to 1000 and true, * respectively. * @@ -138,7 +143,8 @@ public DockerClientConfigBuilder withProperties(Properties p) { .withPassword(p.getProperty("docker.io.password")) .withEmail(p.getProperty("docker.io.email")) .withReadTimeout(Integer.valueOf(p.getProperty("docker.io.readTimeout", "0"))) - .withLoggingFilter(Boolean.valueOf(p.getProperty("docker.io.enableLoggingFilter", "true"))); + .withLoggingFilter(Boolean.valueOf(p.getProperty("docker.io.enableLoggingFilter", "true"))) + .withDockerCertPath(p.getProperty("docker.io.dockerCertPath")); } public final DockerClientConfigBuilder withUri(String uri) { @@ -170,6 +176,10 @@ public final DockerClientConfigBuilder withLoggingFilter(boolean loggingFilterEn this.loggingFilterEnabled = loggingFilterEnabled; return this; } + public final DockerClientConfigBuilder withDockerCertPath(String dockerCertPath) { + this.dockerCertPath = dockerCertPath; + return this; + } public DockerClientConfig build() { return new DockerClientConfig(this); } diff --git a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java index 74d3073f0..58450c8a6 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java +++ b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java @@ -1,18 +1,7 @@ package com.github.dockerjava.jaxrs; -import java.io.IOException; -import java.util.logging.Logger; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.WebTarget; - -import com.github.dockerjava.api.command.EventsCmd; -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.client.ClientProperties; -import org.glassfish.jersey.CommonProperties; - import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; +import com.github.dockerjava.api.DockerClientException; import com.github.dockerjava.api.command.AttachContainerCmd; import com.github.dockerjava.api.command.AuthCmd; import com.github.dockerjava.api.command.BuildImageCmd; @@ -22,6 +11,7 @@ import com.github.dockerjava.api.command.CreateContainerCmd; import com.github.dockerjava.api.command.CreateImageCmd; import com.github.dockerjava.api.command.DockerCmdExecFactory; +import com.github.dockerjava.api.command.EventsCmd; import com.github.dockerjava.api.command.InfoCmd; import com.github.dockerjava.api.command.InspectContainerCmd; import com.github.dockerjava.api.command.InspectImageCmd; @@ -44,12 +34,30 @@ import com.github.dockerjava.api.command.UnpauseContainerCmd; import com.github.dockerjava.api.command.VersionCmd; import com.github.dockerjava.api.command.WaitContainerCmd; +import com.github.dockerjava.core.CertificateUtils; import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.jaxrs.util.JsonClientFilter; import com.github.dockerjava.jaxrs.util.ResponseStatusExceptionFilter; import com.github.dockerjava.jaxrs.util.SelectiveLoggingFilter; import com.google.common.base.Preconditions; +import java.io.File; +import java.io.IOException; +import java.security.KeyStore; +import java.security.Security; +import java.util.logging.Logger; + +import javax.net.ssl.SSLContext; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.SslConfigurator; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; + public class DockerCmdExecFactoryImpl implements DockerCmdExecFactory { private Client client; @@ -78,7 +86,55 @@ public void init(DockerClientConfig dockerClientConfig) { int readTimeout = dockerClientConfig.getReadTimeout(); clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeout); } - client = ClientBuilder.newClient(clientConfig); + + ClientBuilder clientBuilder = ClientBuilder.newBuilder().withConfig(clientConfig); + + // Attempt to load Docker SSL certificates from location in following order: + // 1. User Defined + // 2. Environment Variable + // 3. User Home Directory + String dockerCertPath = dockerClientConfig.getDockerCertPath(); + + if(dockerCertPath == null) { + dockerCertPath = System.getenv("DOCKER_CERT_PATH"); + } + + if(dockerCertPath == null) { + dockerCertPath = System.getProperty("USER_HOME") + File.separator + ".docker"; + } + + if(dockerCertPath != null) { + boolean certificatesExist = CertificateUtils.verifyCertificatesExist(dockerCertPath); + + if(certificatesExist) { + + try { + + Security.addProvider(new BouncyCastleProvider()); + + SslConfigurator sslConfig = SslConfigurator.newInstance(); + + KeyStore keyStore = CertificateUtils.createKeyStore(dockerCertPath); + KeyStore trustStore = CertificateUtils.createTrustStore(dockerCertPath); + + 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); + } + + } + + } + + client = clientBuilder.build(); WebTarget webResource = client.target(dockerClientConfig.getUri());