diff options
author | Bjørn Christian Seime <bjorn.christian@seime.no> | 2018-09-10 13:56:01 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-10 13:56:01 +0200 |
commit | e3d76a10ea55c9e195bb19fd7c67a760b23a15c4 (patch) | |
tree | a68bd11a29878c24855826245b52e62351136c31 | |
parent | 6abeddec87a58f4d116e9cff5bfc7350a7e12485 (diff) | |
parent | 535c6287196378cd51b6ccb2106a7f5bf182708d (diff) |
Merge pull request #6868 from vespa-engine/bjorncs/transport-security-options
Bjorncs/transport security options
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 |