Skip to content
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
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
<jsr305.version>1.3.9</jsr305.version>
<jnr.unixsocket.version>0.3</jnr.unixsocket.version>
<guava.version>18.0</guava.version>
<bouncycastle.version>1.51</bouncycastle.version>

<!--test dependencies -->
<version.logback>1.0.1</version.logback>
Expand Down Expand Up @@ -142,6 +143,12 @@
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>

<!-- /// Test /////////////////////////// -->
<dependency>
Expand Down
142 changes: 142 additions & 0 deletions src/main/java/com/github/dockerjava/core/CertificateUtils.java
Original file line number Diff line number Diff line change
@@ -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);
}
}


}

}
20 changes: 15 additions & 5 deletions src/main/java/com/github/dockerjava/core/DockerClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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() {
Expand Down Expand Up @@ -51,6 +52,10 @@ public Integer getReadTimeout() {
public boolean isLoggingFilterEnabled() {
return loggingFilterEnabled;
}

public String getDockerCertPath() {
return dockerCertPath;
}

public static Properties loadIncludedDockerProperties() {
try {
Expand Down Expand Up @@ -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));
Expand All @@ -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;

Expand All @@ -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.
*
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool. Do we want to use a common set of env properties for configuration for consistency? E.g. DOCKER_USERNAME etc.?

}

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());

Expand Down