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 26b734a..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,8 +48,11 @@ 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; +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 +71,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 +546,41 @@ private Channel getCheckedChannel(Endpoint endpoint, Consumer onFaile return null; } + private SslContext newSslContext(TlsOptions tlsOptions) { + + try { + SslContextBuilder builder = GrpcSslContexts.forClient(); + + if (tlsOptions.getClientCertChain().isPresent() && tlsOptions.getPrivateKey().isPresent()) { + if (tlsOptions.getPrivateKeyPassword().isPresent()) { + builder.keyManager(tlsOptions.getClientCertChain().get(), tlsOptions.getPrivateKey().get(), + tlsOptions.getPrivateKeyPassword().get()); + } else { + builder.keyManager(tlsOptions.getClientCertChain().get(), tlsOptions.getPrivateKey().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().isPresent()) { + innerChannelBuilder.useTransportSecurity().sslContext(newSslContext(this.opts.getTlsOptions().get())); + } else { + innerChannelBuilder.usePlaintext(); + } + + 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..6df2aee 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; } @@ -247,6 +262,7 @@ public RpcOptions copy() { opts.blockOnLimit = this.blockOnLimit; opts.logOnLimitChange = this.logOnLimitChange; opts.enableMetricInterceptor = this.enableMetricInterceptor; + opts.tlsOptions = this.tlsOptions; return opts; } @@ -269,6 +285,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..954b37f --- /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 = Optional.empty(); + + private Optional privateKey = Optional.empty(); + + private Optional privateKeyPassword = Optional.empty(); + + private Optional rootCerts = Optional.empty(); + + @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 + + '}'; + } +}