diff --git a/ratis-common/src/main/java/org/apache/ratis/security/TlsConf.java b/ratis-common/src/main/java/org/apache/ratis/security/TlsConf.java index 30cf67c83b..b442124596 100644 --- a/ratis-common/src/main/java/org/apache/ratis/security/TlsConf.java +++ b/ratis-common/src/main/java/org/apache/ratis/security/TlsConf.java @@ -17,6 +17,7 @@ */ package org.apache.ratis.security; +import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslProvider; import org.apache.ratis.util.JavaUtils; import org.apache.ratis.util.Preconditions; @@ -25,8 +26,11 @@ import java.io.File; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -170,16 +174,22 @@ public KeyManager getKeyManager() { private final KeyManagerConf keyManager; private final TrustManagerConf trustManager; private final boolean mutualTls; - - private TlsConf(String name, KeyManagerConf keyManager, TrustManagerConf trustManager, boolean mutualTls) { - this.name = JavaUtils.getClassSimpleName(getClass()) + COUNT.getAndIncrement() + (name == null? "": "-" + name); - this.keyManager = keyManager; - this.trustManager = trustManager; - this.mutualTls = mutualTls; - } + private final SslProvider sslProvider; + private final String jsseProviderName; + private final List protocols; + private final List cipherSuites; protected TlsConf(Builder b) { - this(b.buildName(), b.buildKeyManagerConf(), b.buildTrustManagerConf(), b.isMutualTls()); + final String buildName = b.buildName(); + this.name = JavaUtils.getClassSimpleName(getClass()) + COUNT.getAndIncrement() + + (buildName == null? "": "-" + buildName); + this.keyManager = b.buildKeyManagerConf(); + this.trustManager = b.buildTrustManagerConf(); + this.mutualTls = b.isMutualTls(); + this.sslProvider = b.sslProvider; + this.jsseProviderName = b.jsseProviderName; + this.protocols = copy(b.protocols); + this.cipherSuites = copy(b.cipherSuites); } /** @return the key manager configuration. */ @@ -197,6 +207,22 @@ public boolean isMutualTls() { return mutualTls; } + public SslProvider getSslProvider() { + return sslProvider; + } + + public String getJsseProviderName() { + return jsseProviderName; + } + + public List getProtocols() { + return copy(protocols); + } + + public List getCipherSuites() { + return copy(cipherSuites); + } + @Override public String toString() { return name; @@ -206,6 +232,14 @@ public static Builder newBuilder() { return new Builder(); } + private static List copy(List values) { + return values != null ? Collections.unmodifiableList(new ArrayList<>(values)) : null; + } + + private static List copy(String[] values) { + return values != null ? copy(Arrays.asList(values)) : null; + } + /** For building {@link TlsConf}. */ public static class Builder { private String name; @@ -215,6 +249,10 @@ public static class Builder { private boolean mutualTls; private KeyManager keyManager; private TrustManager trustManager; + private SslProvider sslProvider; + private String jsseProviderName; + private List protocols; + private List cipherSuites; public Builder setName(String name) { this.name = name; @@ -251,6 +289,36 @@ public Builder setMutualTls(boolean mutualTls) { return this; } + public Builder setSslProvider(SslProvider sslProvider) { + this.sslProvider = sslProvider; + return this; + } + + public Builder setJsseProviderName(String jsseProviderName) { + this.jsseProviderName = jsseProviderName; + return this; + } + + public Builder setProtocols(String... protocols) { + this.protocols = copy(protocols); + return this; + } + + public Builder setProtocols(List protocols) { + this.protocols = copy(protocols); + return this; + } + + public Builder setCipherSuites(String... cipherSuites) { + this.cipherSuites = copy(cipherSuites); + return this; + } + + public Builder setCipherSuites(List cipherSuites) { + this.cipherSuites = copy(cipherSuites); + return this; + } + private boolean isMutualTls() { return mutualTls; } @@ -285,4 +353,4 @@ public TlsConf build() { return new TlsConf(this); } } -} \ No newline at end of file +} diff --git a/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcTlsConfig.java b/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcTlsConfig.java index ff540c3cc2..c6b8dce594 100644 --- a/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcTlsConfig.java +++ b/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcTlsConfig.java @@ -18,6 +18,7 @@ package org.apache.ratis.grpc; import org.apache.ratis.security.TlsConf; +import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslProvider; import javax.net.ssl.KeyManager; import javax.net.ssl.TrustManager; @@ -131,4 +132,94 @@ private static Builder newBuilder(File privateKeyFile, File certChainFile, File private static Builder newBuilder(KeyManager keyManager, TrustManager trustManager, boolean mTlsEnabled) { return newBuilder().setMutualTls(mTlsEnabled).setKeyManager(keyManager).setTrustManager(trustManager); } -} \ No newline at end of file + + public static Builder newBuilder() { + return new Builder(); + } + + /** For building {@link GrpcTlsConfig}. */ + public static class Builder extends TlsConf.Builder { + @Override + public Builder setName(String name) { + super.setName(name); + return this; + } + + @Override + public Builder setTrustCertificates(CertificatesConf trustCertificates) { + super.setTrustCertificates(trustCertificates); + return this; + } + + @Override + public Builder setPrivateKey(PrivateKeyConf privateKey) { + super.setPrivateKey(privateKey); + return this; + } + + @Override + public Builder setKeyCertificates(CertificatesConf keyCertificates) { + super.setKeyCertificates(keyCertificates); + return this; + } + + @Override + public Builder setKeyManager(KeyManager keyManager) { + super.setKeyManager(keyManager); + return this; + } + + @Override + public Builder setTrustManager(TrustManager trustManager) { + super.setTrustManager(trustManager); + return this; + } + + @Override + public Builder setMutualTls(boolean mutualTls) { + super.setMutualTls(mutualTls); + return this; + } + + @Override + public Builder setSslProvider(SslProvider sslProvider) { + super.setSslProvider(sslProvider); + return this; + } + + @Override + public Builder setJsseProviderName(String jsseProviderName) { + super.setJsseProviderName(jsseProviderName); + return this; + } + + @Override + public Builder setProtocols(String... protocols) { + super.setProtocols(protocols); + return this; + } + + @Override + public Builder setProtocols(List protocols) { + super.setProtocols(protocols); + return this; + } + + @Override + public Builder setCipherSuites(String... cipherSuites) { + super.setCipherSuites(cipherSuites); + return this; + } + + @Override + public Builder setCipherSuites(List cipherSuites) { + super.setCipherSuites(cipherSuites); + return this; + } + + @Override + public GrpcTlsConfig build() { + return new GrpcTlsConfig(this, false); + } + } +} diff --git a/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcUtil.java b/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcUtil.java index df076875bf..36b8e9d65c 100644 --- a/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcUtil.java +++ b/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcUtil.java @@ -20,6 +20,7 @@ import java.util.function.Consumer; import org.apache.ratis.protocol.exceptions.ServerNotReadyException; import org.apache.ratis.protocol.exceptions.TimeoutIOException; +import org.apache.ratis.security.TlsConf; import org.apache.ratis.security.TlsConf.TrustManagerConf; import org.apache.ratis.security.TlsConf.CertificatesConf; import org.apache.ratis.security.TlsConf.PrivateKeyConf; @@ -30,9 +31,12 @@ import org.apache.ratis.thirdparty.io.grpc.StatusRuntimeException; import org.apache.ratis.thirdparty.io.grpc.netty.GrpcSslContexts; import org.apache.ratis.thirdparty.io.grpc.stub.StreamObserver; +import org.apache.ratis.thirdparty.io.netty.handler.ssl.ApplicationProtocolConfig; import org.apache.ratis.thirdparty.io.netty.handler.ssl.ClientAuth; +import org.apache.ratis.thirdparty.io.netty.handler.ssl.IdentityCipherSuiteFilter; import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslContext; import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslContextBuilder; +import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslProvider; import org.apache.ratis.util.IOUtils; import org.apache.ratis.util.JavaUtils; import org.apache.ratis.util.LogUtils; @@ -45,6 +49,9 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import java.io.IOException; +import java.security.Provider; +import java.security.Security; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -64,6 +71,12 @@ public interface GrpcUtil { Metadata.Key HEARTBEAT = Metadata.Key.of("heartbeat", Metadata.ASCII_STRING_MARSHALLER); + ApplicationProtocolConfig ALPN = + new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + "h2"); + static StatusRuntimeException wrapException(Throwable t) { return wrapException(t, -1); } @@ -306,6 +319,56 @@ static void setKeyManager(SslContextBuilder b, KeyManagerConf keyManagerConfig) } } + static SslContextBuilder configureSslContextBuilder( + SslContextBuilder b, TlsConf tlsConf, SslProvider defaultSslProvider) { + final Provider jsseProvider = getJsseProvider(tlsConf); + if (jsseProvider != null) { + b = configureJsseProvider(b, jsseProvider); + } else { + final SslProvider sslProvider = tlsConf.getSslProvider() != null + ? tlsConf.getSslProvider() : defaultSslProvider; + if (sslProvider == SslProvider.OPENSSL_REFCNT) { + b = GrpcSslContexts.configure(b, OPENSSL).sslProvider(SslProvider.OPENSSL_REFCNT); + } else { + b = sslProvider != null ? GrpcSslContexts.configure(b, sslProvider) : GrpcSslContexts.configure(b); + } + } + final List protocols = tlsConf.getProtocols(); + if (protocols != null && !protocols.isEmpty()) { + b.protocols(protocols.toArray(new String[0])); + } + final List cipherSuites = tlsConf.getCipherSuites(); + if (cipherSuites != null && !cipherSuites.isEmpty()) { + b.ciphers(cipherSuites, IdentityCipherSuiteFilter.INSTANCE); + } + return b; + } + + static SslContextBuilder configureJsseProvider(SslContextBuilder b, Provider provider) { + try { + return GrpcSslContexts.configure(b, provider); + } catch (IllegalArgumentException e) { + if (!String.valueOf(e.getMessage()).contains("Unknown provider")) { + throw e; + } + return b.sslProvider(SslProvider.JDK) + .applicationProtocolConfig(ALPN) + .sslContextProvider(provider); + } + } + + static Provider getJsseProvider(TlsConf tlsConf) { + final String providerName = tlsConf.getJsseProviderName(); + if (providerName == null || providerName.trim().isEmpty()) { + return null; + } + final Provider namedProvider = Security.getProvider(providerName.trim()); + if (namedProvider == null) { + throw new IllegalArgumentException("JSSE provider not found: " + providerName); + } + return namedProvider; + } + static SslContext buildSslContextForServer(GrpcTlsConfig tlsConf) { if (tlsConf == null) { return null; @@ -315,7 +378,7 @@ static SslContext buildSslContextForServer(GrpcTlsConfig tlsConf) { b.clientAuth(ClientAuth.REQUIRE); setTrustManager(b, tlsConf.getTrustManager()); } - b = GrpcSslContexts.configure(b, OPENSSL); + b = configureSslContextBuilder(b, tlsConf, OPENSSL); try { return b.build(); } catch (Exception e) { @@ -328,7 +391,7 @@ static SslContext buildSslContextForClient(GrpcTlsConfig tlsConf) { return null; } - final SslContextBuilder b = GrpcSslContexts.forClient(); + final SslContextBuilder b = configureSslContextBuilder(SslContextBuilder.forClient(), tlsConf, null); setTrustManager(b, tlsConf.getTrustManager()); if (tlsConf.getMtlsEnabled()) { setKeyManager(b, tlsConf.getKeyManager()); diff --git a/ratis-grpc/src/test/java/org/apache/ratis/grpc/TestGrpcTlsConfig.java b/ratis-grpc/src/test/java/org/apache/ratis/grpc/TestGrpcTlsConfig.java new file mode 100644 index 0000000000..d3c6be8e2c --- /dev/null +++ b/ratis-grpc/src/test/java/org/apache/ratis/grpc/TestGrpcTlsConfig.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ratis.grpc; + +import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslContextBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLException; +import java.security.Provider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TestGrpcTlsConfig { + @Test + public void testGrpcTlsConfigCopiesLists() { + final List protocols = new ArrayList<>(Collections.singletonList("TLSv1.2")); + final List cipherSuites = new ArrayList<>(Collections.singletonList("TLS_AES_128_GCM_SHA256")); + + final GrpcTlsConfig conf = GrpcTlsConfig.newBuilder() + .setProtocols(protocols) + .setCipherSuites(cipherSuites) + .build(); + + protocols.add("TLSv1.3"); + cipherSuites.add("TLS_AES_256_GCM_SHA384"); + + Assertions.assertEquals(Collections.singletonList("TLSv1.2"), conf.getProtocols()); + Assertions.assertEquals(Collections.singletonList("TLS_AES_128_GCM_SHA256"), conf.getCipherSuites()); + Assertions.assertThrows(UnsupportedOperationException.class, () -> conf.getProtocols().add("TLSv1.3")); + Assertions.assertThrows(UnsupportedOperationException.class, + () -> conf.getCipherSuites().add("TLS_AES_256_GCM_SHA384")); + } + + @Test + public void testUnknownJsseProviderUsesGenericJdkConfiguration() { + final SslContextBuilder builder = GrpcUtil.configureJsseProvider( + SslContextBuilder.forClient(), new TestProvider()); + Assertions.assertThrows(SSLException.class, builder::build); + } + + private static class TestProvider extends Provider { + private static final long serialVersionUID = 1L; + + TestProvider() { + super("TestJSSE", 1.0, "Test JSSE provider"); + } + } +}