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);
+    }
+
+}

Reply via email to