Skip to content

Commit d669f20

Browse files
author
Marcus Linke
committed
Merge branch 'ssl-support' of https://github.com/sabre1041/docker-java into sabre1041-ssl-support
2 parents 1301d5e + 077df9f commit d669f20

File tree

4 files changed

+233
-18
lines changed

4 files changed

+233
-18
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
<jsr305.version>1.3.9</jsr305.version>
6464
<jnr.unixsocket.version>0.3</jnr.unixsocket.version>
6565
<guava.version>18.0</guava.version>
66+
<bouncycastle.version>1.51</bouncycastle.version>
6667

6768
<!--test dependencies -->
6869
<version.logback>1.0.1</version.logback>
@@ -142,6 +143,12 @@
142143
<artifactId>guava</artifactId>
143144
<version>${guava.version}</version>
144145
</dependency>
146+
147+
<dependency>
148+
<groupId>org.bouncycastle</groupId>
149+
<artifactId>bcpkix-jdk15on</artifactId>
150+
<version>${bouncycastle.version}</version>
151+
</dependency>
145152

146153
<!-- /// Test /////////////////////////// -->
147154
<dependency>
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package com.github.dockerjava.core;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.nio.charset.Charset;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.nio.file.Paths;
9+
import java.security.KeyFactory;
10+
import java.security.KeyPair;
11+
import java.security.KeyStore;
12+
import java.security.KeyStoreException;
13+
import java.security.NoSuchAlgorithmException;
14+
import java.security.PrivateKey;
15+
import java.security.PublicKey;
16+
import java.security.cert.Certificate;
17+
import java.security.cert.CertificateException;
18+
import java.security.spec.InvalidKeySpecException;
19+
import java.security.spec.PKCS8EncodedKeySpec;
20+
import java.security.spec.X509EncodedKeySpec;
21+
22+
import org.apache.commons.io.IOUtils;
23+
import org.bouncycastle.cert.X509CertificateHolder;
24+
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
25+
import org.bouncycastle.openssl.PEMKeyPair;
26+
import org.bouncycastle.openssl.PEMParser;
27+
28+
public class CertificateUtils {
29+
30+
public static boolean verifyCertificatesExist(String dockerCertPath) {
31+
String[] files = {"ca.pem", "cert.pem", "key.pem"};
32+
for (String file : files) {
33+
Path path = Paths.get(dockerCertPath, file);
34+
boolean exists = Files.exists(path);
35+
if(!exists) {
36+
return false;
37+
}
38+
}
39+
40+
return true;
41+
}
42+
43+
public static KeyStore createKeyStore(final String dockerCertPath) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, CertificateException, KeyStoreException {
44+
KeyPair keyPair = loadPrivateKey(dockerCertPath);
45+
Certificate privateCertificate = loadCertificate(dockerCertPath);
46+
47+
KeyStore keyStore = KeyStore.getInstance("JKS");
48+
keyStore.load(null);
49+
50+
keyStore.setKeyEntry("docker", keyPair.getPrivate(), "docker".toCharArray(), new Certificate[]{privateCertificate});
51+
return keyStore;
52+
}
53+
54+
public static KeyStore createTrustStore(final String dockerCertPath) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
55+
Path caPath = Paths.get(dockerCertPath, "ca.pem");
56+
BufferedReader reader = Files.newBufferedReader(caPath, Charset.defaultCharset());
57+
PEMParser pemParser = null;
58+
59+
try {
60+
pemParser = new PEMParser(reader);
61+
X509CertificateHolder certificateHolder = (X509CertificateHolder) pemParser.readObject();
62+
Certificate caCertificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
63+
64+
KeyStore trustStore = KeyStore.getInstance("JKS");
65+
trustStore.load(null);
66+
trustStore.setCertificateEntry("ca", caCertificate);
67+
return trustStore;
68+
69+
}
70+
finally {
71+
if(pemParser != null) {
72+
IOUtils.closeQuietly(pemParser);
73+
}
74+
75+
if(reader != null) {
76+
IOUtils.closeQuietly(reader);
77+
}
78+
}
79+
80+
}
81+
82+
private static Certificate loadCertificate(final String dockerCertPath) throws IOException, CertificateException {
83+
Path certificate = Paths.get(dockerCertPath, "cert.pem");
84+
BufferedReader reader = Files.newBufferedReader(certificate, Charset.defaultCharset());
85+
PEMParser pemParser = null;
86+
87+
try {
88+
pemParser = new PEMParser(reader);
89+
X509CertificateHolder certificateHolder = (X509CertificateHolder) pemParser.readObject();
90+
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
91+
}
92+
finally {
93+
if(pemParser != null) {
94+
IOUtils.closeQuietly(pemParser);
95+
}
96+
97+
if(reader != null) {
98+
IOUtils.closeQuietly(reader);
99+
}
100+
}
101+
102+
}
103+
104+
private static KeyPair loadPrivateKey(final String dockerCertPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
105+
Path certificate = Paths.get(dockerCertPath, "key.pem");
106+
BufferedReader reader = Files.newBufferedReader(certificate, Charset.defaultCharset());
107+
108+
PEMParser pemParser = null;
109+
110+
try {
111+
pemParser = new PEMParser(reader);
112+
113+
PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject();
114+
115+
byte[] pemPrivateKeyEncoded = pemKeyPair.getPrivateKeyInfo().getEncoded();
116+
byte[] pemPublicKeyEncoded = pemKeyPair.getPublicKeyInfo().getEncoded();
117+
118+
KeyFactory factory = KeyFactory.getInstance("RSA");
119+
120+
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pemPublicKeyEncoded);
121+
PublicKey publicKey = factory.generatePublic(publicKeySpec);
122+
123+
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pemPrivateKeyEncoded);
124+
PrivateKey privateKey = factory.generatePrivate(privateKeySpec);
125+
126+
return new KeyPair(publicKey, privateKey);
127+
128+
}
129+
finally {
130+
if(pemParser != null) {
131+
IOUtils.closeQuietly(pemParser);
132+
}
133+
134+
if(reader != null) {
135+
IOUtils.closeQuietly(reader);
136+
}
137+
}
138+
139+
140+
}
141+
142+
}

src/main/java/com/github/dockerjava/core/DockerClientConfig.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
public class DockerClientConfig {
1212
private final URI uri;
13-
private final String version, username, password, email;
13+
private final String version, username, password, email, dockerCertPath;
1414
private final Integer readTimeout;
1515
private final boolean loggingFilterEnabled;
1616

@@ -22,6 +22,7 @@ private DockerClientConfig(DockerClientConfigBuilder builder) {
2222
this.email = builder.email;
2323
this.readTimeout = builder.readTimeout;
2424
this.loggingFilterEnabled = builder.loggingFilterEnabled;
25+
this.dockerCertPath = builder.dockerCertPath;
2526
}
2627

2728
public URI getUri() {
@@ -51,6 +52,10 @@ public Integer getReadTimeout() {
5152
public boolean isLoggingFilterEnabled() {
5253
return loggingFilterEnabled;
5354
}
55+
56+
public String getDockerCertPath() {
57+
return dockerCertPath;
58+
}
5459

5560
public static Properties loadIncludedDockerProperties() {
5661
try {
@@ -97,7 +102,7 @@ public static Properties overrideDockerPropertiesWithSystemProperties(Properties
97102
overriddenProperties.putAll(p);
98103

99104
// TODO Add all values from system properties that begin with docker.io.*
100-
for (String s : new String[]{ "url", "version", "username", "password", "email", "readTimeout", "enableLoggingFilter"}) {
105+
for (String s : new String[]{ "url", "version", "username", "password", "email", "readTimeout", "enableLoggingFilter", "dockerCertPath"}) {
101106
final String key = "docker.io." + s;
102107
if (System.getProperties().containsKey(key)) {
103108
overriddenProperties.setProperty(key, System.getProperty(key));
@@ -115,7 +120,7 @@ public static DockerClientConfigBuilder createDefaultConfigBuilder() {
115120

116121
public static class DockerClientConfigBuilder {
117122
private URI uri;
118-
private String version, username, password, email;
123+
private String version, username, password, email, dockerCertPath;
119124
private Integer readTimeout;
120125
private boolean loggingFilterEnabled;
121126

@@ -124,7 +129,7 @@ public DockerClientConfigBuilder() {
124129

125130
/**
126131
* This will set all fields in the builder to those contained in the Properties object. The Properties object
127-
* should contain the following docker.io.* keys: url, version, username, password, and email. If
132+
* should contain the following docker.io.* keys: url, version, username, password, email, and dockerCertPath. If
128133
* docker.io.readTimeout or docker.io.enableLoggingFilter are not contained, they will be set to 1000 and true,
129134
* respectively.
130135
*
@@ -138,7 +143,8 @@ public DockerClientConfigBuilder withProperties(Properties p) {
138143
.withPassword(p.getProperty("docker.io.password"))
139144
.withEmail(p.getProperty("docker.io.email"))
140145
.withReadTimeout(Integer.valueOf(p.getProperty("docker.io.readTimeout", "0")))
141-
.withLoggingFilter(Boolean.valueOf(p.getProperty("docker.io.enableLoggingFilter", "true")));
146+
.withLoggingFilter(Boolean.valueOf(p.getProperty("docker.io.enableLoggingFilter", "true")))
147+
.withDockerCertPath(p.getProperty("docker.io.dockerCertPath"));
142148
}
143149

144150
public final DockerClientConfigBuilder withUri(String uri) {
@@ -170,6 +176,10 @@ public final DockerClientConfigBuilder withLoggingFilter(boolean loggingFilterEn
170176
this.loggingFilterEnabled = loggingFilterEnabled;
171177
return this;
172178
}
179+
public final DockerClientConfigBuilder withDockerCertPath(String dockerCertPath) {
180+
this.dockerCertPath = dockerCertPath;
181+
return this;
182+
}
173183
public DockerClientConfig build() {
174184
return new DockerClientConfig(this);
175185
}

src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
11
package com.github.dockerjava.jaxrs;
22

3-
import java.io.IOException;
4-
import java.util.logging.Logger;
5-
6-
import javax.ws.rs.client.Client;
7-
import javax.ws.rs.client.ClientBuilder;
8-
import javax.ws.rs.client.WebTarget;
9-
10-
import com.github.dockerjava.api.command.EventsCmd;
11-
import org.glassfish.jersey.client.ClientConfig;
12-
import org.glassfish.jersey.client.ClientProperties;
13-
import org.glassfish.jersey.CommonProperties;
14-
153
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
4+
import com.github.dockerjava.api.DockerClientException;
165
import com.github.dockerjava.api.command.AttachContainerCmd;
176
import com.github.dockerjava.api.command.AuthCmd;
187
import com.github.dockerjava.api.command.BuildImageCmd;
@@ -22,6 +11,7 @@
2211
import com.github.dockerjava.api.command.CreateContainerCmd;
2312
import com.github.dockerjava.api.command.CreateImageCmd;
2413
import com.github.dockerjava.api.command.DockerCmdExecFactory;
14+
import com.github.dockerjava.api.command.EventsCmd;
2515
import com.github.dockerjava.api.command.InfoCmd;
2616
import com.github.dockerjava.api.command.InspectContainerCmd;
2717
import com.github.dockerjava.api.command.InspectImageCmd;
@@ -44,12 +34,30 @@
4434
import com.github.dockerjava.api.command.UnpauseContainerCmd;
4535
import com.github.dockerjava.api.command.VersionCmd;
4636
import com.github.dockerjava.api.command.WaitContainerCmd;
37+
import com.github.dockerjava.core.CertificateUtils;
4738
import com.github.dockerjava.core.DockerClientConfig;
4839
import com.github.dockerjava.jaxrs.util.JsonClientFilter;
4940
import com.github.dockerjava.jaxrs.util.ResponseStatusExceptionFilter;
5041
import com.github.dockerjava.jaxrs.util.SelectiveLoggingFilter;
5142
import com.google.common.base.Preconditions;
5243

44+
import java.io.File;
45+
import java.io.IOException;
46+
import java.security.KeyStore;
47+
import java.security.Security;
48+
import java.util.logging.Logger;
49+
50+
import javax.net.ssl.SSLContext;
51+
import javax.ws.rs.client.Client;
52+
import javax.ws.rs.client.ClientBuilder;
53+
import javax.ws.rs.client.WebTarget;
54+
55+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
56+
import org.glassfish.jersey.CommonProperties;
57+
import org.glassfish.jersey.SslConfigurator;
58+
import org.glassfish.jersey.client.ClientConfig;
59+
import org.glassfish.jersey.client.ClientProperties;
60+
5361
public class DockerCmdExecFactoryImpl implements DockerCmdExecFactory {
5462

5563
private Client client;
@@ -78,7 +86,55 @@ public void init(DockerClientConfig dockerClientConfig) {
7886
int readTimeout = dockerClientConfig.getReadTimeout();
7987
clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeout);
8088
}
81-
client = ClientBuilder.newClient(clientConfig);
89+
90+
ClientBuilder clientBuilder = ClientBuilder.newBuilder().withConfig(clientConfig);
91+
92+
// Attempt to load Docker SSL certificates from location in following order:
93+
// 1. User Defined
94+
// 2. Environment Variable
95+
// 3. User Home Directory
96+
String dockerCertPath = dockerClientConfig.getDockerCertPath();
97+
98+
if(dockerCertPath == null) {
99+
dockerCertPath = System.getenv("DOCKER_CERT_PATH");
100+
}
101+
102+
if(dockerCertPath == null) {
103+
dockerCertPath = System.getProperty("USER_HOME") + File.separator + ".docker";
104+
}
105+
106+
if(dockerCertPath != null) {
107+
boolean certificatesExist = CertificateUtils.verifyCertificatesExist(dockerCertPath);
108+
109+
if(certificatesExist) {
110+
111+
try {
112+
113+
Security.addProvider(new BouncyCastleProvider());
114+
115+
SslConfigurator sslConfig = SslConfigurator.newInstance();
116+
117+
KeyStore keyStore = CertificateUtils.createKeyStore(dockerCertPath);
118+
KeyStore trustStore = CertificateUtils.createTrustStore(dockerCertPath);
119+
120+
sslConfig.keyStore(keyStore);
121+
sslConfig.keyStorePassword("docker");
122+
123+
sslConfig.trustStore(trustStore);
124+
125+
SSLContext sslContext = sslConfig.createSSLContext();
126+
clientBuilder.sslContext(sslContext);
127+
128+
}
129+
catch(Exception e) {
130+
throw new DockerClientException(e.getMessage(), e);
131+
}
132+
133+
}
134+
135+
}
136+
137+
client = clientBuilder.build();
82138

83139
WebTarget webResource = client.target(dockerClientConfig.getUri());
84140

0 commit comments

Comments
 (0)