This is an automated email from the ASF dual-hosted git repository.
szetszwo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ratis.git
The following commit(s) were added to refs/heads/master by this push:
new ee40e300c RATIS-2537. Support configurable gRPC TLS provider and
cipher suites (#1462)
ee40e300c is described below
commit ee40e300ca91db36bd90988dbe48e477ea6a4f9c
Author: Haonan <[email protected]>
AuthorDate: Fri May 22 12:00:49 2026 +0800
RATIS-2537. Support configurable gRPC TLS provider and cipher suites (#1462)
---
.../java/org/apache/ratis/security/TlsConf.java | 86 +++++++++++++++++---
.../java/org/apache/ratis/grpc/GrpcTlsConfig.java | 93 +++++++++++++++++++++-
.../main/java/org/apache/ratis/grpc/GrpcUtil.java | 67 +++++++++++++++-
.../org/apache/ratis/grpc/TestGrpcTlsConfig.java | 65 +++++++++++++++
4 files changed, 299 insertions(+), 12 deletions(-)
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 30cf67c83..b44212459 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 javax.net.ssl.TrustManager;
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 class TlsConf {
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<String> protocols;
+ private final List<String> 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 class TlsConf {
return mutualTls;
}
+ public SslProvider getSslProvider() {
+ return sslProvider;
+ }
+
+ public String getJsseProviderName() {
+ return jsseProviderName;
+ }
+
+ public List<String> getProtocols() {
+ return copy(protocols);
+ }
+
+ public List<String> getCipherSuites() {
+ return copy(cipherSuites);
+ }
+
@Override
public String toString() {
return name;
@@ -206,6 +232,14 @@ public class TlsConf {
return new Builder();
}
+ private static List<String> copy(List<String> values) {
+ return values != null ? Collections.unmodifiableList(new
ArrayList<>(values)) : null;
+ }
+
+ private static List<String> 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 class TlsConf {
private boolean mutualTls;
private KeyManager keyManager;
private TrustManager trustManager;
+ private SslProvider sslProvider;
+ private String jsseProviderName;
+ private List<String> protocols;
+ private List<String> cipherSuites;
public Builder setName(String name) {
this.name = name;
@@ -251,6 +289,36 @@ public class TlsConf {
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<String> protocols) {
+ this.protocols = copy(protocols);
+ return this;
+ }
+
+ public Builder setCipherSuites(String... cipherSuites) {
+ this.cipherSuites = copy(cipherSuites);
+ return this;
+ }
+
+ public Builder setCipherSuites(List<String> cipherSuites) {
+ this.cipherSuites = copy(cipherSuites);
+ return this;
+ }
+
private boolean isMutualTls() {
return mutualTls;
}
@@ -285,4 +353,4 @@ public class TlsConf {
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 ff540c3cc..c6b8dce59 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 @@ public class GrpcTlsConfig extends TlsConf {
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<String> protocols) {
+ super.setProtocols(protocols);
+ return this;
+ }
+
+ @Override
+ public Builder setCipherSuites(String... cipherSuites) {
+ super.setCipherSuites(cipherSuites);
+ return this;
+ }
+
+ @Override
+ public Builder setCipherSuites(List<String> 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 df076875b..36b8e9d65 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 @@ package org.apache.ratis.grpc;
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.Status;
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.KeyManager;
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<String> 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 @@ public interface GrpcUtil {
}
}
+ 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<String> protocols = tlsConf.getProtocols();
+ if (protocols != null && !protocols.isEmpty()) {
+ b.protocols(protocols.toArray(new String[0]));
+ }
+ final List<String> 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 @@ public interface GrpcUtil {
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 @@ public interface GrpcUtil {
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 000000000..d3c6be8e2
--- /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<String> protocols = new
ArrayList<>(Collections.singletonList("TLSv1.2"));
+ final List<String> 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");
+ }
+ }
+}