From 9ad6fc0c3b7e0930036f14ce0135cb7382117e1c Mon Sep 17 00:00:00 2001 From: Ning Sun Date: Tue, 5 Mar 2024 17:52:22 +0800 Subject: [PATCH 1/6] feat: add option to connect to secure grpc port --- .../main/java/io/greptime/rpc/GrpcClient.java | 41 +++++++- .../io/greptime/options/GreptimeOptions.java | 13 +++ .../main/java/io/greptime/rpc/RpcOptions.java | 16 +++ .../main/java/io/greptime/rpc/TlsOptions.java | 98 +++++++++++++++++++ 4 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 ingester-rpc/src/main/java/io/greptime/rpc/TlsOptions.java diff --git a/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java b/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java index 26b734a..de14d5f 100644 --- a/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java +++ b/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java @@ -50,6 +50,8 @@ import io.grpc.MethodDescriptor; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.shaded.io.netty.channel.ChannelOption; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; import io.grpc.protobuf.ProtoUtils; import io.grpc.stub.ClientCalls; import io.grpc.stub.StreamObserver; @@ -68,6 +70,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import javax.net.ssl.SSLException; /** * Grpc client implementation. @@ -542,11 +545,41 @@ private Channel getCheckedChannel(Endpoint endpoint, Consumer onFaile return null; } + private SslContext newSslContext(TlsOptions tlsOptions) { + + try { + SslContextBuilder builder = SslContextBuilder.forClient(); + + if (tlsOptions.getClientCertChain().isPresent() && tlsOptions.getPrivateKey().isPresent()) { + if (tlsOptions.getPrivateKeyPassword().isPresent()) { + builder.keyManager(tlsOptions.getClientCertChain().get(), tlsOptions.getPrivateKey().get()); + } else { + builder.keyManager(tlsOptions.getClientCertChain().get(), tlsOptions.getPrivateKey().get(), + tlsOptions.getPrivateKeyPassword().get()); + } + } + + if (tlsOptions.getRootCerts().isPresent()) { + builder.trustManager(tlsOptions.getRootCerts().get()); + } + + return builder.build(); + } catch (SSLException e) { + throw new RuntimeException("Failed to configure SslContext", e); + } + } + private IdChannel newChannel(Endpoint endpoint) { - ManagedChannel innerChannel = NettyChannelBuilder // - .forAddress(endpoint.getAddr(), endpoint.getPort()) // - .usePlaintext() // - .executor(this.asyncPool) // + NettyChannelBuilder innerChannelBuilder = + NettyChannelBuilder.forAddress(endpoint.getAddr(), endpoint.getPort()); + + if (this.opts.getTlsOptions().isEmpty()) { + innerChannelBuilder.usePlaintext(); + } else { + innerChannelBuilder.useTransportSecurity().sslContext(newSslContext(this.opts.getTlsOptions().get())); + } + + ManagedChannel innerChannel = innerChannelBuilder.executor(this.asyncPool) // .intercept(this.interceptors) // .maxInboundMessageSize(this.opts.getMaxInboundMessageSize()) // .flowControlWindow(this.opts.getFlowControlWindow()) // diff --git a/ingester-protocol/src/main/java/io/greptime/options/GreptimeOptions.java b/ingester-protocol/src/main/java/io/greptime/options/GreptimeOptions.java index 43f7597..f6214b6 100644 --- a/ingester-protocol/src/main/java/io/greptime/options/GreptimeOptions.java +++ b/ingester-protocol/src/main/java/io/greptime/options/GreptimeOptions.java @@ -22,6 +22,7 @@ import io.greptime.limit.LimitedPolicy; import io.greptime.models.AuthInfo; import io.greptime.rpc.RpcOptions; +import io.greptime.rpc.TlsOptions; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -279,6 +280,18 @@ public Builder router(Router router) { return this; } + /** + * Set `TlsOptions` to use secure connection between client and server. Set to `null` to use + * plaintext connection instead. + * + * @param tlsOptions for configure secure connection, set to null to use plaintext + * @return this builder + */ + public Builder tlsOptions(TlsOptions tlsOptions) { + this.rpcOptions.setTlsOptions(tlsOptions); + return this; + } + /** * A good start, happy coding. * diff --git a/ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java b/ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java index 2764371..2b65375 100644 --- a/ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java +++ b/ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java @@ -16,6 +16,7 @@ package io.greptime.rpc; import io.greptime.common.Copiable; +import java.util.Optional; import java.util.concurrent.TimeUnit; /** @@ -100,6 +101,20 @@ public class RpcOptions implements Copiable { private boolean enableMetricInterceptor = false; + /** + * Set `TlsOptions` to use secure connection between client and server. Set to `null` to use + * plaintext connection instead. + */ + private Optional tlsOptions = Optional.empty(); + + public Optional getTlsOptions() { + return tlsOptions; + } + + public void setTlsOptions(TlsOptions tlsOptions) { + this.tlsOptions = Optional.ofNullable(tlsOptions); + } + public boolean isUseRpcSharedPool() { return useRpcSharedPool; } @@ -269,6 +284,7 @@ public String toString() { ", blockOnLimit=" + blockOnLimit + // ", logOnLimitChange=" + logOnLimitChange + // ", enableMetricInterceptor=" + enableMetricInterceptor + // + ", tlsOptions=" + tlsOptions + // '}'; } diff --git a/ingester-rpc/src/main/java/io/greptime/rpc/TlsOptions.java b/ingester-rpc/src/main/java/io/greptime/rpc/TlsOptions.java new file mode 100644 index 0000000..4a27424 --- /dev/null +++ b/ingester-rpc/src/main/java/io/greptime/rpc/TlsOptions.java @@ -0,0 +1,98 @@ +/* + * Copyright 2023 Greptime Team + * + * Licensed 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 io.greptime.rpc; + +import io.greptime.common.Copiable; +import java.io.File; +import java.util.Optional; + +/** + * GreptimeDB secure connection options + * + * @author Ning Sun + */ +public class TlsOptions implements Copiable { + + private Optional clientCertChain; + + private Optional privateKey; + + private Optional privateKeyPassword; + + private Optional rootCerts; + + @Override + public TlsOptions copy() { + TlsOptions that = new TlsOptions(); + + that.setClientCertChain(this.getClientCertChain()); + that.setPrivateKey(this.getPrivateKey()); + that.setPrivateKeyPassword(this.getPrivateKeyPassword()); + that.setRootCerts(this.getRootCerts()); + + return that; + } + + public Optional getClientCertChain() { + return clientCertChain; + } + + public void setClientCertChain(Optional clientCertChain) { + this.clientCertChain = clientCertChain; + } + + public Optional getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(Optional privateKey) { + this.privateKey = privateKey; + } + + public Optional getPrivateKeyPassword() { + return privateKeyPassword; + } + + public void setPrivateKeyPassword(Optional privateKeyPassword) { + this.privateKeyPassword = privateKeyPassword; + } + + public Optional getRootCerts() { + return rootCerts; + } + + public void setRootCerts(Optional rootCerts) { + this.rootCerts = rootCerts; + } + + @Override + public String toString() { + return "TlsOptions{" + + // + "clientCertChain=" + + this.clientCertChain + + // + ", privateKey=" + + this.privateKey + + // + ", privateKeyPassword=" + + this.privateKeyPassword.map((v) -> "****") + + // + ", rootCerts=" + + this.rootCerts + + '}'; + } +} From ca530f335e224b82b6f67c185c11aa86e4d058fa Mon Sep 17 00:00:00 2001 From: Ning Sun Date: Tue, 5 Mar 2024 18:00:14 +0800 Subject: [PATCH 2/6] feat: correct behaviour for ssl private key password --- ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java b/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java index de14d5f..562a938 100644 --- a/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java +++ b/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java @@ -552,10 +552,10 @@ private SslContext newSslContext(TlsOptions tlsOptions) { if (tlsOptions.getClientCertChain().isPresent() && tlsOptions.getPrivateKey().isPresent()) { if (tlsOptions.getPrivateKeyPassword().isPresent()) { - builder.keyManager(tlsOptions.getClientCertChain().get(), tlsOptions.getPrivateKey().get()); - } else { builder.keyManager(tlsOptions.getClientCertChain().get(), tlsOptions.getPrivateKey().get(), tlsOptions.getPrivateKeyPassword().get()); + } else { + builder.keyManager(tlsOptions.getClientCertChain().get(), tlsOptions.getPrivateKey().get()); } } From 7148e0adac7dbd72f47bd6dbea406b94065ef167 Mon Sep 17 00:00:00 2001 From: Ning Sun Date: Tue, 5 Mar 2024 18:06:32 +0800 Subject: [PATCH 3/6] fix: copy tlsOption when copying rpcOption --- ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java b/ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java index 2b65375..6df2aee 100644 --- a/ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java +++ b/ingester-rpc/src/main/java/io/greptime/rpc/RpcOptions.java @@ -262,6 +262,7 @@ public RpcOptions copy() { opts.blockOnLimit = this.blockOnLimit; opts.logOnLimitChange = this.logOnLimitChange; opts.enableMetricInterceptor = this.enableMetricInterceptor; + opts.tlsOptions = this.tlsOptions; return opts; } From f620045c459a07a1c553c2497dcfd703b1e74962 Mon Sep 17 00:00:00 2001 From: Ning Sun Date: Tue, 5 Mar 2024 18:10:41 +0800 Subject: [PATCH 4/6] fix: use java 1.8 methods --- ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java b/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java index 562a938..18ac1cf 100644 --- a/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java +++ b/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java @@ -573,10 +573,10 @@ private IdChannel newChannel(Endpoint endpoint) { NettyChannelBuilder innerChannelBuilder = NettyChannelBuilder.forAddress(endpoint.getAddr(), endpoint.getPort()); - if (this.opts.getTlsOptions().isEmpty()) { - innerChannelBuilder.usePlaintext(); - } else { + if (this.opts.getTlsOptions().isPresent()) { innerChannelBuilder.useTransportSecurity().sslContext(newSslContext(this.opts.getTlsOptions().get())); + } else { + innerChannelBuilder.usePlaintext(); } ManagedChannel innerChannel = innerChannelBuilder.executor(this.asyncPool) // From ce52cd59fa344d47ae1d2767f5ce893f5037d01b Mon Sep 17 00:00:00 2001 From: Ning Sun Date: Fri, 8 Mar 2024 17:17:47 -0800 Subject: [PATCH 5/6] fix: npe for uninitialized optional fields --- .../src/main/java/io/greptime/rpc/TlsOptions.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ingester-rpc/src/main/java/io/greptime/rpc/TlsOptions.java b/ingester-rpc/src/main/java/io/greptime/rpc/TlsOptions.java index 4a27424..954b37f 100644 --- a/ingester-rpc/src/main/java/io/greptime/rpc/TlsOptions.java +++ b/ingester-rpc/src/main/java/io/greptime/rpc/TlsOptions.java @@ -26,13 +26,13 @@ */ public class TlsOptions implements Copiable { - private Optional clientCertChain; + private Optional clientCertChain = Optional.empty(); - private Optional privateKey; + private Optional privateKey = Optional.empty(); - private Optional privateKeyPassword; + private Optional privateKeyPassword = Optional.empty(); - private Optional rootCerts; + private Optional rootCerts = Optional.empty(); @Override public TlsOptions copy() { From 4e442dfdf96cab8c93d6f442736283bedbfc78e5 Mon Sep 17 00:00:00 2001 From: Ning Sun Date: Fri, 8 Mar 2024 17:54:05 -0800 Subject: [PATCH 6/6] fix: correct sslcontext setup --- ingester-example/src/main/java/io/greptime/TestConnector.java | 2 ++ ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ingester-example/src/main/java/io/greptime/TestConnector.java b/ingester-example/src/main/java/io/greptime/TestConnector.java index 471a553..f0bd59f 100644 --- a/ingester-example/src/main/java/io/greptime/TestConnector.java +++ b/ingester-example/src/main/java/io/greptime/TestConnector.java @@ -85,6 +85,8 @@ public static GreptimeDB connectToDefaultDB() { .router(null) // Sets authentication information. If the DB is not required to authenticate, we can ignore this. .authInfo(AuthInfo.noAuthorization()) + // Enable TLS connection when remote port is secured by TLS + // .tlsOptions(new TlsOptions()) // A good start ^_^ .build(); diff --git a/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java b/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java index 18ac1cf..94d7f44 100644 --- a/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java +++ b/ingester-grpc/src/main/java/io/greptime/rpc/GrpcClient.java @@ -48,6 +48,7 @@ import io.grpc.ConnectivityState; import io.grpc.ManagedChannel; import io.grpc.MethodDescriptor; +import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.shaded.io.netty.channel.ChannelOption; import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; @@ -548,7 +549,7 @@ private Channel getCheckedChannel(Endpoint endpoint, Consumer onFaile private SslContext newSslContext(TlsOptions tlsOptions) { try { - SslContextBuilder builder = SslContextBuilder.forClient(); + SslContextBuilder builder = GrpcSslContexts.forClient(); if (tlsOptions.getClientCertChain().isPresent() && tlsOptions.getPrivateKey().isPresent()) { if (tlsOptions.getPrivateKeyPassword().isPresent()) {