summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorn.christian@seime.no>2018-09-10 13:56:01 +0200
committerGitHub <noreply@github.com>2018-09-10 13:56:01 +0200
commite3d76a10ea55c9e195bb19fd7c67a760b23a15c4 (patch)
treea68bd11a29878c24855826245b52e62351136c31
parent6abeddec87a58f4d116e9cff5bfc7350a7e12485 (diff)
parent535c6287196378cd51b6ccb2106a7f5bf182708d (diff)
Merge pull request #6868 from vespa-engine/bjorncs/transport-security-options
Bjorncs/transport security options
-rw-r--r--jrt/src/com/yahoo/jrt/CryptoEngine.java12
-rw-r--r--jrt/src/com/yahoo/jrt/TlsCryptoEngine.java20
-rw-r--r--vespajlib/pom.xml20
-rw-r--r--vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java36
-rw-r--r--vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java90
-rw-r--r--vespajlib/src/main/java/com/yahoo/security/tls/package-info.java8
-rw-r--r--vespajlib/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java24
-rw-r--r--vespajlib/src/test/resources/transport-security-options.json7
8 files changed, 203 insertions, 14 deletions
diff --git a/jrt/src/com/yahoo/jrt/CryptoEngine.java b/jrt/src/com/yahoo/jrt/CryptoEngine.java
index 9852d5a88a6..2ef936ec7ed 100644
--- a/jrt/src/com/yahoo/jrt/CryptoEngine.java
+++ b/jrt/src/com/yahoo/jrt/CryptoEngine.java
@@ -2,7 +2,10 @@
package com.yahoo.jrt;
+import com.yahoo.security.tls.TransportSecurityOptions;
+
import java.nio.channels.SocketChannel;
+import java.nio.file.Paths;
/**
@@ -13,5 +16,12 @@ import java.nio.channels.SocketChannel;
**/
public interface CryptoEngine {
public CryptoSocket createCryptoSocket(SocketChannel channel, boolean isServer);
- static public CryptoEngine createDefault() { return new NullCryptoEngine(); }
+ static public CryptoEngine createDefault() { // TODO Move this logic to a dedicated factory class
+ String tlsConfigParameter = System.getenv("VESPA_TLS_CONFIG_FILE");
+ if (tlsConfigParameter != null && !tlsConfigParameter.isEmpty()) {
+ return new TlsCryptoEngine(TransportSecurityOptions.fromJsonFile(Paths.get(tlsConfigParameter)));
+ } else {
+ return new NullCryptoEngine();
+ }
+ }
}
diff --git a/jrt/src/com/yahoo/jrt/TlsCryptoEngine.java b/jrt/src/com/yahoo/jrt/TlsCryptoEngine.java
index 279bf1d0137..b3daf5c296d 100644
--- a/jrt/src/com/yahoo/jrt/TlsCryptoEngine.java
+++ b/jrt/src/com/yahoo/jrt/TlsCryptoEngine.java
@@ -1,9 +1,18 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jrt;
+import com.yahoo.security.SslContextBuilder;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.security.tls.TransportSecurityOptions;
+
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
+import java.io.IOException;
+import java.io.UncheckedIOException;
import java.nio.channels.SocketChannel;
+import java.nio.file.Files;
+import java.security.cert.X509Certificate;
+import java.util.List;
/**
* A {@link CryptoSocket} that creates {@link TlsCryptoSocket} instances.
@@ -18,6 +27,10 @@ public class TlsCryptoEngine implements CryptoEngine {
this.sslContext = sslContext;
}
+ public TlsCryptoEngine(TransportSecurityOptions options) {
+ this(createSslContext(options));
+ }
+
@Override
public TlsCryptoSocket createCryptoSocket(SocketChannel channel, boolean isServer) {
SSLEngine sslEngine = sslContext.createSSLEngine();
@@ -25,4 +38,11 @@ public class TlsCryptoEngine implements CryptoEngine {
sslEngine.setUseClientMode(!isServer);
return new TlsCryptoSocket(channel, sslEngine);
}
+
+ private static SSLContext createSslContext(TransportSecurityOptions options) {
+ return new SslContextBuilder()
+ .withTrustStore(options.getCaCertificatesFile())
+ .withKeyStore(options.getPrivateKeyFile(), options.getCertificatesFile())
+ .build();
+ }
}
diff --git a/vespajlib/pom.xml b/vespajlib/pom.xml
index 3e3c3ae988f..5b9c143a447 100644
--- a/vespajlib/pom.xml
+++ b/vespajlib/pom.xml
@@ -61,6 +61,16 @@
<artifactId>bcpkix-jdk15on</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
<!-- test scope -->
<dependency>
@@ -79,16 +89,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-core</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>com.yahoo.vespa</groupId>
<artifactId>testutil</artifactId>
<version>${project.version}</version>
diff --git a/vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java b/vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java
index 56db9c59146..75ab2417edf 100644
--- a/vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java
+++ b/vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java
@@ -14,6 +14,10 @@ import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
/**
* @author bjorncs
@@ -36,6 +40,24 @@ public class SslContextBuilder {
return this;
}
+ public SslContextBuilder withTrustStore(X509Certificate caCertificate) {
+ return withTrustStore(singletonList(caCertificate));
+ }
+
+ public SslContextBuilder withTrustStore(List<X509Certificate> caCertificates) {
+ this.trustStoreSupplier = () -> createTrustStore(caCertificates);
+ return this;
+ }
+
+ public SslContextBuilder withTrustStore(Path pemEncodedCaCertificates) {
+ this.trustStoreSupplier = () -> {
+ List<X509Certificate> caCertificates =
+ X509CertificateUtils.certificateListFromPem(new String(Files.readAllBytes(pemEncodedCaCertificates)));
+ return createTrustStore(caCertificates);
+ };
+ return this;
+ }
+
public SslContextBuilder withKeyStore(PrivateKey privateKey, X509Certificate certificate) {
char[] pwd = new char[0];
this.keyStoreSupplier = () -> KeyStoreBuilder.withType(KeyStoreType.JKS).withKeyEntry("default", privateKey, certificate).build();
@@ -55,13 +77,13 @@ public class SslContextBuilder {
return this;
}
- public SslContextBuilder withKeyStore(Path privateKeyPemFile, Path certificatePemFile) {
+ public SslContextBuilder withKeyStore(Path privateKeyPemFile, Path certificatesPemFile) {
this.keyStoreSupplier =
() -> {
PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(new String(Files.readAllBytes(privateKeyPemFile)));
- X509Certificate certificate = X509CertificateUtils.fromPem(new String(Files.readAllBytes(certificatePemFile)));
+ List<X509Certificate> certificates = X509CertificateUtils.certificateListFromPem(new String(Files.readAllBytes(certificatesPemFile)));
return KeyStoreBuilder.withType(KeyStoreType.JKS)
- .withKeyEntry("default", privateKey, certificate)
+ .withKeyEntry("default", privateKey, certificates)
.build();
};
this.keyStorePassword = new char[0];
@@ -100,6 +122,14 @@ public class SslContextBuilder {
return keyManagerFactory.getKeyManagers();
}
+ private static KeyStore createTrustStore(List<X509Certificate> caCertificates) {
+ KeyStoreBuilder trustStoreBuilder = KeyStoreBuilder.withType(KeyStoreType.JKS);
+ for (int i = 0; i < caCertificates.size(); i++) {
+ trustStoreBuilder.withCertificateEntry("cert-" + i, caCertificates.get(i));
+ }
+ return trustStoreBuilder.build();
+ }
+
private interface KeyStoreSupplier {
KeyStore get() throws IOException, GeneralSecurityException;
}
diff --git a/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java b/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java
new file mode 100644
index 00000000000..f0d1edd6889
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java
@@ -0,0 +1,90 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security.tls;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Generic TLS configuration for Vespa
+ *
+ * @author bjorncs
+ */
+public class TransportSecurityOptions {
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ private final Path privateKeyFile;
+ private final Path certificatesFile;
+ private final Path caCertificatesFile;
+
+ public TransportSecurityOptions(String privateKeyFile, String certificatesFile, String caCertificatesFile) {
+ this(Paths.get(privateKeyFile), Paths.get(certificatesFile), Paths.get(caCertificatesFile));
+ }
+
+ public TransportSecurityOptions(Path privateKeyFile, Path certificatesFile, Path caCertificatesFile) {
+ this.privateKeyFile = privateKeyFile;
+ this.certificatesFile = certificatesFile;
+ this.caCertificatesFile = caCertificatesFile;
+ }
+
+ public Path getPrivateKeyFile() {
+ return privateKeyFile;
+ }
+
+ public Path getCertificatesFile() {
+ return certificatesFile;
+ }
+
+ public Path getCaCertificatesFile() {
+ return caCertificatesFile;
+ }
+
+ public static TransportSecurityOptions fromJsonFile(Path file) {
+ try {
+ JsonNode root = mapper.readTree(file.toFile());
+ JsonNode filesNode = getField(root, "files");
+ String privateKeyFile = getField(filesNode, "private-key").asText();
+ String certificatesFile = getField(filesNode, "certificates").asText();
+ String caCertificatesFile = getField(filesNode, "ca-certificates").asText();
+ return new TransportSecurityOptions(privateKeyFile, certificatesFile, caCertificatesFile);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static JsonNode getField(JsonNode root, String fieldName) {
+ return Optional.ofNullable(root.get(fieldName))
+ .orElseThrow(() -> new IllegalArgumentException(String.format("'%s' field missing", fieldName)));
+ }
+
+ @Override
+ public String toString() {
+ return "TransportSecurityOptions{" +
+ "privateKeyFile=" + privateKeyFile +
+ ", certificatesFile=" + certificatesFile +
+ ", caCertificatesFile=" + caCertificatesFile +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TransportSecurityOptions that = (TransportSecurityOptions) o;
+ return Objects.equals(privateKeyFile, that.privateKeyFile) &&
+ Objects.equals(certificatesFile, that.certificatesFile) &&
+ Objects.equals(caCertificatesFile, that.caCertificatesFile);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(privateKeyFile, certificatesFile, caCertificatesFile);
+ }
+} \ No newline at end of file
diff --git a/vespajlib/src/main/java/com/yahoo/security/tls/package-info.java b/vespajlib/src/main/java/com/yahoo/security/tls/package-info.java
new file mode 100644
index 00000000000..b5668182f14
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/security/tls/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author bjorncs
+ */
+@ExportPackage
+package com.yahoo.security.tls;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/vespajlib/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java b/vespajlib/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java
new file mode 100644
index 00000000000..ad80c52ae2a
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java
@@ -0,0 +1,24 @@
+package com.yahoo.security.tls;
+
+import org.junit.Test;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author bjorncs
+ */
+public class TransportSecurityOptionsTest {
+
+ private static final Path TEST_CONFIG_FILE = Paths.get("src/test/resources/transport-security-options.json");
+
+ @Test
+ public void can_read_options_from_json_file() {
+ TransportSecurityOptions expectedOptions = new TransportSecurityOptions("myhost.key", "certs.pem", "my_cas.pem");
+ TransportSecurityOptions actualOptions = TransportSecurityOptions.fromJsonFile(TEST_CONFIG_FILE);
+ assertEquals(expectedOptions, actualOptions);
+ }
+
+} \ No newline at end of file
diff --git a/vespajlib/src/test/resources/transport-security-options.json b/vespajlib/src/test/resources/transport-security-options.json
new file mode 100644
index 00000000000..0506c130722
--- /dev/null
+++ b/vespajlib/src/test/resources/transport-security-options.json
@@ -0,0 +1,7 @@
+{
+ "files": {
+ "private-key": "myhost.key",
+ "ca-certificates": "my_cas.pem",
+ "certificates": "certs.pem"
+ }
+} \ No newline at end of file