Skip to content

Commit 199b1e4

Browse files
committed
Add TLS support for gRPC client
Signed-off-by: Cassandra Coyle <[email protected]>
1 parent 77f1212 commit 199b1e4

File tree

5 files changed

+467
-7
lines changed

5 files changed

+467
-7
lines changed

CONTRIBUTING.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Build & test:
2+
3+
```shell
4+
./gradlew build
5+
```

client/build.gradle

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ plugins {
1010
}
1111

1212
group 'io.dapr'
13-
version = '1.5.3'
13+
version = '1.5.4'
1414
archivesBaseName = 'durabletask-client'
1515

16-
def grpcVersion = '1.59.0'
17-
def protocVersion = '3.19.0'
16+
def grpcVersion = '1.69.0'
17+
def protocVersion = '3.25.5'
1818
def jacksonVersion = '2.15.3'
1919
// When build on local, you need to set this value to your local jdk11 directory.
2020
// Java11 is used to compile and run all the tests.
@@ -38,6 +38,19 @@ dependencies {
3838

3939
testImplementation(platform('org.junit:junit-bom:5.7.2'))
4040
testImplementation('org.junit.jupiter:junit-jupiter')
41+
42+
// Netty dependencies for TLS
43+
implementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
44+
implementation "io.netty:netty-handler:4.1.94.Final"
45+
implementation "io.netty:netty-tcnative-boringssl-static:2.0.59.Final"
46+
47+
// Add Netty dependencies to test classpath
48+
testImplementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
49+
testImplementation "io.netty:netty-handler:4.1.94.Final"
50+
testImplementation "io.netty:netty-tcnative-boringssl-static:2.0.59.Final"
51+
52+
testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70'
53+
testImplementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
4154
}
4255

4356
compileJava {

client/src/main/java/io/dapr/durabletask/DurableTaskGrpcClient.java

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import io.dapr.durabletask.implementation.protobuf.TaskHubSidecarServiceGrpc.*;
1010

1111
import io.grpc.*;
12+
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
13+
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
14+
import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
15+
import java.io.FileInputStream;
16+
import java.io.InputStream;
1217

1318
import javax.annotation.Nullable;
1419
import java.time.Duration;
@@ -17,13 +22,18 @@
1722
import java.util.concurrent.TimeUnit;
1823
import java.util.concurrent.TimeoutException;
1924
import java.util.logging.Logger;
25+
import java.io.IOException;
2026

2127
/**
2228
* Durable Task client implementation that uses gRPC to connect to a remote "sidecar" process.
2329
*/
2430
public final class DurableTaskGrpcClient extends DurableTaskClient {
2531
private static final int DEFAULT_PORT = 4001;
2632
private static final Logger logger = Logger.getLogger(DurableTaskGrpcClient.class.getPackage().getName());
33+
private static final String GRPC_TLS_CA_PATH = "DAPR_GRPC_TLS_CA_PATH";
34+
private static final String GRPC_TLS_CERT_PATH = "DAPR_GRPC_TLS_CERT_PATH";
35+
private static final String GRPC_TLS_KEY_PATH = "DAPR_GRPC_TLS_KEY_PATH";
36+
private static final String GRPC_TLS_INSECURE = "DAPR_GRPC_TLS_INSECURE";
2737

2838
private final DataConverter dataConverter;
2939
private final ManagedChannel managedSidecarChannel;
@@ -44,11 +54,60 @@ public final class DurableTaskGrpcClient extends DurableTaskClient {
4454
port = builder.port;
4555
}
4656

57+
String endpoint = "localhost:" + port;
58+
ManagedChannelBuilder<?> channelBuilder;
59+
60+
// Get TLS configuration from builder or environment variables
61+
String tlsCaPath = builder.tlsCaPath != null ? builder.tlsCaPath : System.getenv(GRPC_TLS_CA_PATH);
62+
String tlsCertPath = builder.tlsCertPath != null ? builder.tlsCertPath : System.getenv(GRPC_TLS_CERT_PATH);
63+
String tlsKeyPath = builder.tlsKeyPath != null ? builder.tlsKeyPath : System.getenv(GRPC_TLS_KEY_PATH);
64+
boolean insecure = builder.insecure || Boolean.parseBoolean(System.getenv(GRPC_TLS_INSECURE));
65+
66+
if (insecure) {
67+
// Insecure mode - uses TLS but doesn't verify certificates
68+
try {
69+
channelBuilder = NettyChannelBuilder.forTarget(endpoint)
70+
.sslContext(GrpcSslContexts.forClient()
71+
.trustManager(InsecureTrustManagerFactory.INSTANCE)
72+
.build());
73+
} catch (Exception e) {
74+
throw new RuntimeException("Failed to create insecure TLS credentials", e);
75+
}
76+
} else if (tlsCertPath != null && tlsKeyPath != null) {
77+
// mTLS case - using client cert and key, with optional CA cert for server authentication
78+
try (
79+
InputStream clientCertInputStream = new FileInputStream(tlsCertPath);
80+
InputStream clientKeyInputStream = new FileInputStream(tlsKeyPath);
81+
InputStream caCertInputStream = tlsCaPath != null ? new FileInputStream(tlsCaPath) : null
82+
) {
83+
TlsChannelCredentials.Builder tlsBuilder = TlsChannelCredentials.newBuilder()
84+
.keyManager(clientCertInputStream, clientKeyInputStream); // For client authentication
85+
if (caCertInputStream != null) {
86+
tlsBuilder.trustManager(caCertInputStream); // For server authentication
87+
}
88+
ChannelCredentials credentials = tlsBuilder.build();
89+
channelBuilder = Grpc.newChannelBuilder(endpoint, credentials);
90+
} catch (IOException e) {
91+
throw new RuntimeException("Failed to create mTLS credentials" +
92+
(tlsCaPath != null ? " with CA cert" : ""), e);
93+
}
94+
} else if (tlsCaPath != null) {
95+
// Simple TLS case - using CA cert only for server authentication
96+
try (InputStream caCertInputStream = new FileInputStream(tlsCaPath)) {
97+
ChannelCredentials credentials = TlsChannelCredentials.newBuilder()
98+
.trustManager(caCertInputStream)
99+
.build();
100+
channelBuilder = Grpc.newChannelBuilder(endpoint, credentials);
101+
} catch (IOException e) {
102+
throw new RuntimeException("Failed to create TLS credentials with CA cert", e);
103+
}
104+
} else {
105+
// No TLS config provided, use plaintext
106+
channelBuilder = ManagedChannelBuilder.forTarget(endpoint).usePlaintext();
107+
}
108+
47109
// Need to keep track of this channel so we can dispose it on close()
48-
this.managedSidecarChannel = ManagedChannelBuilder
49-
.forAddress("localhost", port)
50-
.usePlaintext()
51-
.build();
110+
this.managedSidecarChannel = channelBuilder.build();
52111
sidecarGrpcChannel = this.managedSidecarChannel;
53112
}
54113

client/src/main/java/io/dapr/durabletask/DurableTaskGrpcClientBuilder.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public final class DurableTaskGrpcClientBuilder {
1212
DataConverter dataConverter;
1313
int port;
1414
Channel channel;
15+
String tlsCaPath;
16+
String tlsCertPath;
17+
String tlsKeyPath;
18+
boolean insecure;
1519

1620
/**
1721
* Sets the {@link DataConverter} to use for converting serializable data payloads.
@@ -53,6 +57,55 @@ public DurableTaskGrpcClientBuilder port(int port) {
5357
return this;
5458
}
5559

60+
/**
61+
* Sets the path to the TLS CA certificate file for server authentication.
62+
* If not set, the system's default CA certificates will be used.
63+
*
64+
* @param tlsCaPath path to the TLS CA certificate file
65+
* @return this builder object
66+
*/
67+
public DurableTaskGrpcClientBuilder tlsCaPath(String tlsCaPath) {
68+
this.tlsCaPath = tlsCaPath;
69+
return this;
70+
}
71+
72+
/**
73+
* Sets the path to the TLS client certificate file for client authentication.
74+
* This is used for mTLS (mutual TLS) connections.
75+
*
76+
* @param tlsCertPath path to the TLS client certificate file
77+
* @return this builder object
78+
*/
79+
public DurableTaskGrpcClientBuilder tlsCertPath(String tlsCertPath) {
80+
this.tlsCertPath = tlsCertPath;
81+
return this;
82+
}
83+
84+
/**
85+
* Sets the path to the TLS client key file for client authentication.
86+
* This is used for mTLS (mutual TLS) connections.
87+
*
88+
* @param tlsKeyPath path to the TLS client key file
89+
* @return this builder object
90+
*/
91+
public DurableTaskGrpcClientBuilder tlsKeyPath(String tlsKeyPath) {
92+
this.tlsKeyPath = tlsKeyPath;
93+
return this;
94+
}
95+
96+
/**
97+
* Sets whether to use insecure (plaintext) mode for gRPC communication.
98+
* When set to true, TLS will be disabled and communication will be unencrypted.
99+
* This should only be used for development/testing.
100+
*
101+
* @param insecure whether to use insecure mode
102+
* @return this builder object
103+
*/
104+
public DurableTaskGrpcClientBuilder insecure(boolean insecure) {
105+
this.insecure = insecure;
106+
return this;
107+
}
108+
56109
/**
57110
* Initializes a new {@link DurableTaskClient} object with the settings specified in the current builder object.
58111
* @return a new {@link DurableTaskClient} object

0 commit comments

Comments
 (0)