This is an automated email from the ASF dual-hosted git repository.
albumenj pushed a commit to branch 3.2
in repository https://gitbox.apache.org/repos/asf/dubbo.git
The following commit(s) were added to refs/heads/3.2 by this push:
new 5f776a7a40 rest netty server add tls (#12207)
5f776a7a40 is described below
commit 5f776a7a40e5853f466bbe6b5aee8d3ee69404c3
Author: suncairong163 <[email protected]>
AuthorDate: Wed May 3 09:09:26 2023 +0800
rest netty server add tls (#12207)
Co-authored-by: Albumen Kevin <[email protected]>
---
.../rpc/protocol/rest/NettyHttpRestServer.java | 9 +-
.../rpc/protocol/rest/netty/ssl/SslContexts.java | 158 +++++++++++++++++++++
.../rest/netty/ssl/SslServerTlsHandler.java | 123 ++++++++++++++++
3 files changed, 286 insertions(+), 4 deletions(-)
diff --git
a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyHttpRestServer.java
b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyHttpRestServer.java
index d65c368ce6..af3b4f80e2 100644
---
a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyHttpRestServer.java
+++
b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyHttpRestServer.java
@@ -35,6 +35,7 @@ import
org.apache.dubbo.rpc.protocol.rest.handler.NettyHttpHandler;
import org.apache.dubbo.rpc.protocol.rest.netty.NettyServer;
import org.apache.dubbo.rpc.protocol.rest.netty.RestHttpRequestDecoder;
import org.apache.dubbo.rpc.protocol.rest.netty.UnSharedHandlerCreator;
+import org.apache.dubbo.rpc.protocol.rest.netty.ssl.SslServerTlsHandler;
import java.util.ArrayList;
import java.util.Arrays;
@@ -124,15 +125,15 @@ public class NettyHttpRestServer implements
RestProtocolServer {
@Override
public List<ChannelHandler> getUnSharedHandlers(URL url) {
return Arrays.asList(
- // TODO add SslServerTlsHandler
-// channelPipeline.addLast(ch.pipeline().addLast("negotiation",
new SslServerTlsHandler(url)));
-
+ // add SslServerTlsHandler
+ new SslServerTlsHandler(url),
new HttpRequestDecoder(
url.getParameter(RestConstant.MAX_INITIAL_LINE_LENGTH_PARAM,
RestConstant.MAX_INITIAL_LINE_LENGTH),
url.getParameter(RestConstant.MAX_HEADER_SIZE_PARAM,
RestConstant.MAX_HEADER_SIZE),
url.getParameter(RestConstant.MAX_CHUNK_SIZE_PARAM,
RestConstant.MAX_CHUNK_SIZE)),
new
HttpObjectAggregator(url.getParameter(RestConstant.MAX_REQUEST_SIZE_PARAM,
RestConstant.MAX_REQUEST_SIZE)),
- new HttpResponseEncoder(), new RestHttpRequestDecoder(new
NettyHttpHandler(pathAndInvokerMapper, exceptionMapper), url));
+ new HttpResponseEncoder(), new RestHttpRequestDecoder(new
NettyHttpHandler(pathAndInvokerMapper, exceptionMapper), url))
+ ;
}
};
}
diff --git
a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslContexts.java
b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslContexts.java
new file mode 100644
index 0000000000..12dd3d712b
--- /dev/null
+++
b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslContexts.java
@@ -0,0 +1,158 @@
+/*
+ * 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.dubbo.rpc.protocol.rest.netty.ssl;
+
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.ssl.AuthPolicy;
+import org.apache.dubbo.common.ssl.Cert;
+import org.apache.dubbo.common.ssl.CertManager;
+import org.apache.dubbo.common.ssl.ProviderCert;
+
+import javax.net.ssl.SSLException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Provider;
+import java.security.Security;
+
+import static
org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_CLOSE_STREAM;
+
+public class SslContexts {
+
+ private static final ErrorTypeAwareLogger logger =
LoggerFactory.getErrorTypeAwareLogger(SslContexts.class);
+
+ public static SslContext buildServerSslContext(ProviderCert
providerConnectionConfig) {
+ SslContextBuilder sslClientContextBuilder;
+ InputStream serverKeyCertChainPathStream = null;
+ InputStream serverPrivateKeyPathStream = null;
+ InputStream serverTrustCertStream = null;
+ try {
+ serverKeyCertChainPathStream =
providerConnectionConfig.getKeyCertChainInputStream();
+ serverPrivateKeyPathStream =
providerConnectionConfig.getPrivateKeyInputStream();
+ serverTrustCertStream =
providerConnectionConfig.getTrustCertInputStream();
+ String password = providerConnectionConfig.getPassword();
+ if (password != null) {
+ sslClientContextBuilder =
SslContextBuilder.forServer(serverKeyCertChainPathStream,
+ serverPrivateKeyPathStream, password);
+ } else {
+ sslClientContextBuilder =
SslContextBuilder.forServer(serverKeyCertChainPathStream,
+ serverPrivateKeyPathStream);
+ }
+
+ if (serverTrustCertStream != null) {
+ sslClientContextBuilder.trustManager(serverTrustCertStream);
+ if (providerConnectionConfig.getAuthPolicy() ==
AuthPolicy.CLIENT_AUTH) {
+ sslClientContextBuilder.clientAuth(ClientAuth.REQUIRE);
+ } else {
+ sslClientContextBuilder.clientAuth(ClientAuth.OPTIONAL);
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Could not find certificate
file or the certificate is invalid.", e);
+ } finally {
+ safeCloseStream(serverTrustCertStream);
+ safeCloseStream(serverKeyCertChainPathStream);
+ safeCloseStream(serverPrivateKeyPathStream);
+ }
+ try {
+ return
sslClientContextBuilder.sslProvider(findSslProvider()).build();
+ } catch (SSLException e) {
+ throw new IllegalStateException("Build SslSession failed.", e);
+ }
+ }
+
+ public static SslContext buildClientSslContext(URL url) {
+ CertManager certManager =
url.getOrDefaultFrameworkModel().getBeanFactory().getBean(CertManager.class);
+ Cert consumerConnectionConfig =
certManager.getConsumerConnectionConfig(url);
+ if (consumerConnectionConfig == null) {
+ return null;
+ }
+
+ SslContextBuilder builder = SslContextBuilder.forClient();
+ InputStream clientTrustCertCollectionPath = null;
+ InputStream clientCertChainFilePath = null;
+ InputStream clientPrivateKeyFilePath = null;
+ try {
+ clientTrustCertCollectionPath =
consumerConnectionConfig.getTrustCertInputStream();
+ if (clientTrustCertCollectionPath != null) {
+ builder.trustManager(clientTrustCertCollectionPath);
+ }
+
+ clientCertChainFilePath =
consumerConnectionConfig.getKeyCertChainInputStream();
+ clientPrivateKeyFilePath =
consumerConnectionConfig.getPrivateKeyInputStream();
+ if (clientCertChainFilePath != null && clientPrivateKeyFilePath !=
null) {
+ String password = consumerConnectionConfig.getPassword();
+ if (password != null) {
+ builder.keyManager(clientCertChainFilePath,
clientPrivateKeyFilePath, password);
+ } else {
+ builder.keyManager(clientCertChainFilePath,
clientPrivateKeyFilePath);
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Could not find certificate
file or find invalid certificate.", e);
+ } finally {
+ safeCloseStream(clientTrustCertCollectionPath);
+ safeCloseStream(clientCertChainFilePath);
+ safeCloseStream(clientPrivateKeyFilePath);
+ }
+ try {
+ return builder.sslProvider(findSslProvider()).build();
+ } catch (SSLException e) {
+ throw new IllegalStateException("Build SslSession failed.", e);
+ }
+ }
+
+ /**
+ * Returns OpenSSL if available, otherwise returns the JDK provider.
+ */
+ private static SslProvider findSslProvider() {
+ if (OpenSsl.isAvailable()) {
+ logger.debug("Using OPENSSL provider.");
+ return SslProvider.OPENSSL;
+ }
+ if (checkJdkProvider()) {
+ logger.debug("Using JDK provider.");
+ return SslProvider.JDK;
+ }
+ throw new IllegalStateException(
+ "Could not find any valid TLS provider, please check your
dependency or deployment environment, " +
+ "usually netty-tcnative, Conscrypt, or Jetty NPN/ALPN is
needed.");
+ }
+
+ private static boolean checkJdkProvider() {
+ Provider[] jdkProviders = Security.getProviders("SSLContext.TLS");
+ return (jdkProviders != null && jdkProviders.length > 0);
+ }
+
+ private static void safeCloseStream(InputStream stream) {
+ if (stream == null) {
+ return;
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ logger.warn(TRANSPORT_FAILED_CLOSE_STREAM, "", "", "Failed to
close a stream.", e);
+ }
+ }
+
+}
diff --git
a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslServerTlsHandler.java
b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslServerTlsHandler.java
new file mode 100644
index 0000000000..756c429c40
--- /dev/null
+++
b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslServerTlsHandler.java
@@ -0,0 +1,123 @@
+/*
+ * 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.dubbo.rpc.protocol.rest.netty.ssl;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.SslHandshakeCompletionEvent;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.ssl.AuthPolicy;
+import org.apache.dubbo.common.ssl.CertManager;
+import org.apache.dubbo.common.ssl.ProviderCert;
+
+import javax.net.ssl.SSLSession;
+import java.util.List;
+
+import static
org.apache.dubbo.common.constants.LoggerCodeConstants.INTERNAL_ERROR;
+
+public class SslServerTlsHandler extends ByteToMessageDecoder {
+ private static final ErrorTypeAwareLogger logger =
LoggerFactory.getErrorTypeAwareLogger(SslServerTlsHandler.class);
+
+ private final URL url;
+
+ private final boolean sslDetected;
+
+ public SslServerTlsHandler(URL url) {
+ this.url = url;
+ this.sslDetected = false;
+ }
+
+ public SslServerTlsHandler(URL url, boolean sslDetected) {
+ this.url = url;
+ this.sslDetected = sslDetected;
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
+ logger.error(INTERNAL_ERROR, "unknown error in remoting module", "",
"TLS negotiation failed when trying to accept new connection.", cause);
+ }
+
+ @Override
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
throws Exception {
+ if (evt instanceof SslHandshakeCompletionEvent) {
+ SslHandshakeCompletionEvent handshakeEvent =
(SslHandshakeCompletionEvent) evt;
+ if (handshakeEvent.isSuccess()) {
+ SSLSession session =
ctx.pipeline().get(SslHandler.class).engine().getSession();
+ logger.info("TLS negotiation succeed with: " +
session.getPeerHost());
+ // Remove after handshake success.
+ ctx.pipeline().remove(this);
+ } else {
+ logger.error(INTERNAL_ERROR, "", "", "TLS negotiation failed
when trying to accept new connection.", handshakeEvent.cause());
+ ctx.close();
+ }
+ }
+ super.userEventTriggered(ctx, evt);
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf
byteBuf, List<Object> list) throws Exception {
+ // Will use the first five bytes to detect a protocol.
+ if (byteBuf.readableBytes() < 5) {
+ return;
+ }
+
+ if (sslDetected) {
+ return;
+ }
+
+ CertManager certManager =
url.getOrDefaultFrameworkModel().getBeanFactory().getBean(CertManager.class);
+ ProviderCert providerConnectionConfig =
certManager.getProviderConnectionConfig(url,
channelHandlerContext.channel().remoteAddress());
+
+ if (providerConnectionConfig == null) {
+ ChannelPipeline p = channelHandlerContext.pipeline();
+ p.remove(this);
+ return;
+ }
+
+ if (isSsl(byteBuf)) {
+ SslContext sslContext =
SslContexts.buildServerSslContext(providerConnectionConfig);
+ enableSsl(channelHandlerContext, sslContext);
+ return;
+ }
+
+ if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.NONE) {
+ ChannelPipeline p = channelHandlerContext.pipeline();
+ p.remove(this);
+ }
+
+ logger.error(INTERNAL_ERROR, "", "", "TLS negotiation failed when
trying to accept new connection.");
+ channelHandlerContext.close();
+ }
+
+ private boolean isSsl(ByteBuf buf) {
+ return SslHandler.isEncrypted(buf);
+ }
+
+ private void enableSsl(ChannelHandlerContext ctx, SslContext sslContext) {
+ ChannelPipeline p = ctx.pipeline();
+ ctx.pipeline().addAfter(ctx.name(), null,
sslContext.newHandler(ctx.alloc()));
+ p.addLast("unificationA", new SslServerTlsHandler(url, true));
+ p.remove(this);
+ }
+
+}