Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Build & test:

```shell
./gradlew build
```
19 changes: 16 additions & 3 deletions client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ plugins {
}

group 'io.dapr'
version = '1.5.3'
version = '1.5.4'
archivesBaseName = 'durabletask-client'

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

testImplementation(platform('org.junit:junit-bom:5.7.2'))
testImplementation('org.junit.jupiter:junit-jupiter')

// Netty dependencies for TLS
implementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
implementation "io.netty:netty-handler:4.1.94.Final"
implementation "io.netty:netty-tcnative-boringssl-static:2.0.59.Final"

// Add Netty dependencies to test classpath
testImplementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
testImplementation "io.netty:netty-handler:4.1.94.Final"
testImplementation "io.netty:netty-tcnative-boringssl-static:2.0.59.Final"

testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70'
testImplementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
}

compileJava {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
import io.dapr.durabletask.implementation.protobuf.TaskHubSidecarServiceGrpc.*;

import io.grpc.*;
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.handler.ssl.util.InsecureTrustManagerFactory;
import java.io.FileInputStream;
import java.io.InputStream;

import javax.annotation.Nullable;
import java.time.Duration;
Expand All @@ -17,13 +22,18 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import java.io.IOException;

/**
* Durable Task client implementation that uses gRPC to connect to a remote "sidecar" process.
*/
public final class DurableTaskGrpcClient extends DurableTaskClient {
private static final int DEFAULT_PORT = 4001;
private static final Logger logger = Logger.getLogger(DurableTaskGrpcClient.class.getPackage().getName());
private static final String GRPC_TLS_CA_PATH = "DAPR_GRPC_TLS_CA_PATH";
private static final String GRPC_TLS_CERT_PATH = "DAPR_GRPC_TLS_CERT_PATH";
private static final String GRPC_TLS_KEY_PATH = "DAPR_GRPC_TLS_KEY_PATH";
private static final String GRPC_TLS_INSECURE = "DAPR_GRPC_TLS_INSECURE";

private final DataConverter dataConverter;
private final ManagedChannel managedSidecarChannel;
Expand All @@ -44,11 +54,60 @@ public final class DurableTaskGrpcClient extends DurableTaskClient {
port = builder.port;
}

String endpoint = "localhost:" + port;
ManagedChannelBuilder<?> channelBuilder;

// Get TLS configuration from builder or environment variables
String tlsCaPath = builder.tlsCaPath != null ? builder.tlsCaPath : System.getenv(GRPC_TLS_CA_PATH);
String tlsCertPath = builder.tlsCertPath != null ? builder.tlsCertPath : System.getenv(GRPC_TLS_CERT_PATH);
String tlsKeyPath = builder.tlsKeyPath != null ? builder.tlsKeyPath : System.getenv(GRPC_TLS_KEY_PATH);
boolean insecure = builder.insecure || Boolean.parseBoolean(System.getenv(GRPC_TLS_INSECURE));

if (insecure) {
// Insecure mode - uses TLS but doesn't verify certificates
try {
channelBuilder = NettyChannelBuilder.forTarget(endpoint)
.sslContext(GrpcSslContexts.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build());
} catch (Exception e) {
throw new RuntimeException("Failed to create insecure TLS credentials", e);
}
} else if (tlsCertPath != null && tlsKeyPath != null) {
// mTLS case - using client cert and key, with optional CA cert for server authentication
try (
InputStream clientCertInputStream = new FileInputStream(tlsCertPath);
InputStream clientKeyInputStream = new FileInputStream(tlsKeyPath);
InputStream caCertInputStream = tlsCaPath != null ? new FileInputStream(tlsCaPath) : null
) {
TlsChannelCredentials.Builder tlsBuilder = TlsChannelCredentials.newBuilder()
.keyManager(clientCertInputStream, clientKeyInputStream); // For client authentication
if (caCertInputStream != null) {
tlsBuilder.trustManager(caCertInputStream); // For server authentication
}
ChannelCredentials credentials = tlsBuilder.build();
channelBuilder = Grpc.newChannelBuilder(endpoint, credentials);
} catch (IOException e) {
throw new RuntimeException("Failed to create mTLS credentials" +
(tlsCaPath != null ? " with CA cert" : ""), e);
}
} else if (tlsCaPath != null) {
// Simple TLS case - using CA cert only for server authentication
try (InputStream caCertInputStream = new FileInputStream(tlsCaPath)) {
ChannelCredentials credentials = TlsChannelCredentials.newBuilder()
.trustManager(caCertInputStream)
.build();
channelBuilder = Grpc.newChannelBuilder(endpoint, credentials);
} catch (IOException e) {
throw new RuntimeException("Failed to create TLS credentials with CA cert", e);
}
} else {
// No TLS config provided, use plaintext
channelBuilder = ManagedChannelBuilder.forTarget(endpoint).usePlaintext();
}

// Need to keep track of this channel so we can dispose it on close()
this.managedSidecarChannel = ManagedChannelBuilder
.forAddress("localhost", port)
.usePlaintext()
.build();
this.managedSidecarChannel = channelBuilder.build();
sidecarGrpcChannel = this.managedSidecarChannel;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public final class DurableTaskGrpcClientBuilder {
DataConverter dataConverter;
int port;
Channel channel;
String tlsCaPath;
String tlsCertPath;
String tlsKeyPath;
boolean insecure;

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

/**
* Sets the path to the TLS CA certificate file for server authentication.
* If not set, the system's default CA certificates will be used.
*
* @param tlsCaPath path to the TLS CA certificate file
* @return this builder object
*/
public DurableTaskGrpcClientBuilder tlsCaPath(String tlsCaPath) {
this.tlsCaPath = tlsCaPath;
return this;
}

/**
* Sets the path to the TLS client certificate file for client authentication.
* This is used for mTLS (mutual TLS) connections.
*
* @param tlsCertPath path to the TLS client certificate file
* @return this builder object
*/
public DurableTaskGrpcClientBuilder tlsCertPath(String tlsCertPath) {
this.tlsCertPath = tlsCertPath;
return this;
}

/**
* Sets the path to the TLS client key file for client authentication.
* This is used for mTLS (mutual TLS) connections.
*
* @param tlsKeyPath path to the TLS client key file
* @return this builder object
*/
public DurableTaskGrpcClientBuilder tlsKeyPath(String tlsKeyPath) {
this.tlsKeyPath = tlsKeyPath;
return this;
}

/**
* Sets whether to use insecure (plaintext) mode for gRPC communication.
* When set to true, TLS will be disabled and communication will be unencrypted.
* This should only be used for development/testing.
*
* @param insecure whether to use insecure mode
* @return this builder object
*/
public DurableTaskGrpcClientBuilder insecure(boolean insecure) {
this.insecure = insecure;
return this;
}

/**
* Initializes a new {@link DurableTaskClient} object with the settings specified in the current builder object.
* @return a new {@link DurableTaskClient} object
Expand Down
Loading
Loading