Skip to content

Feat Add TLS & mTLS support for gRPC with root CA and insecure mode #1361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 14, 2025
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
17 changes: 17 additions & 0 deletions sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,23 @@
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>

<build>
Expand Down
35 changes: 35 additions & 0 deletions sdk/src/main/java/io/dapr/config/Properties.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,41 @@ public class Properties {
"DAPR_GRPC_PORT",
DEFAULT_GRPC_PORT);

/**
* GRPC TLS cert path for Dapr after checking system property and environment variable.
*/
public static final Property<String> GRPC_TLS_CERT_PATH = new StringProperty(
"dapr.grpc.tls.cert.path",
"DAPR_GRPC_TLS_CERT_PATH",
null);

/**
* GRPC TLS key path for Dapr after checking system property and environment variable.
*/
public static final Property<String> GRPC_TLS_KEY_PATH = new StringProperty(
"dapr.grpc.tls.key.path",
"DAPR_GRPC_TLS_KEY_PATH",
null);

/**
* GRPC TLS CA cert path for Dapr after checking system property and environment variable.
* This is used for TLS connections to servers with self-signed certificates.
*/
public static final Property<String> GRPC_TLS_CA_PATH = new StringProperty(
"dapr.grpc.tls.ca.path",
"DAPR_GRPC_TLS_CA_PATH",
null);

/**
* Use insecure TLS mode which still uses TLS but doesn't verify certificates.
* This uses InsecureTrustManagerFactory to trust all certificates.
* This should only be used for testing or in secure environments.
*/
public static final Property<Boolean> GRPC_TLS_INSECURE = new BooleanProperty(
"dapr.grpc.tls.insecure",
"DAPR_GRPC_TLS_INSECURE",
false);

/**
* GRPC endpoint for remote sidecar connectivity.
*/
Expand Down
132 changes: 113 additions & 19 deletions sdk/src/main/java/io/dapr/utils/NetworkUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,34 @@
package io.dapr.utils;

import io.dapr.config.Properties;
import io.dapr.exceptions.DaprError;
import io.dapr.exceptions.DaprException;
import io.grpc.ChannelCredentials;
import io.grpc.ClientInterceptor;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.TlsChannelCredentials;
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.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.regex.Pattern;

import static io.dapr.config.Properties.GRPC_ENDPOINT;
import static io.dapr.config.Properties.GRPC_PORT;
import static io.dapr.config.Properties.GRPC_TLS_CA_PATH;
import static io.dapr.config.Properties.GRPC_TLS_CERT_PATH;
import static io.dapr.config.Properties.GRPC_TLS_INSECURE;
import static io.dapr.config.Properties.GRPC_TLS_KEY_PATH;
import static io.dapr.config.Properties.SIDECAR_IP;


/**
* Utility methods for network, internal to Dapr SDK.
*/
Expand Down Expand Up @@ -56,19 +69,20 @@
private static final String GRPC_ENDPOINT_HOSTNAME_REGEX_PART = "(([A-Za-z0-9_\\-\\.]+)|(\\[" + IPV6_REGEX + "\\]))";

private static final String GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART =
"(?<dnsWithAuthority>dns://)(?<authorityEndpoint>" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)?/";
"(?<dnsWithAuthority>dns://)(?<authorityEndpoint>"
+ GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)?/";

private static final String GRPC_ENDPOINT_PARAM_REGEX_PART = "(\\?(?<param>tls\\=((true)|(false))))?";

private static final String GRPC_ENDPOINT_SOCKET_REGEX_PART =
"(?<socket>((unix:)|(unix://)|(unix-abstract:))" + GRPC_ENDPOINT_FILENAME_REGEX_PART + ")";
private static final String GRPC_ENDPOINT_SOCKET_REGEX_PART = "(?<socket>((unix:)|(unix://)|(unix-abstract:))"
+ GRPC_ENDPOINT_FILENAME_REGEX_PART + ")";

private static final String GRPC_ENDPOINT_VSOCKET_REGEX_PART =
"(?<vsocket>vsock:" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)";
private static final String GRPC_ENDPOINT_HOST_REGEX_PART =
"((?<http>http://)|(?<https>https://)|(?<dns>dns:)|(" + GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART + "))?"
+ "(?<hostname>" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ")?+"
+ "(:(?<port>[0-9]+))?";
private static final String GRPC_ENDPOINT_VSOCKET_REGEX_PART = "(?<vsocket>vsock:" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART
+ ":[0-9]+)";
private static final String GRPC_ENDPOINT_HOST_REGEX_PART = "((?<http>http://)|(?<https>https://)|(?<dns>dns:)|("
+ GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART + "))?"
+ "(?<hostname>" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ")?+"
+ "(:(?<port>[0-9]+))?";

private static final String GRPC_ENDPOINT_REGEX = "^("
+ "(" + GRPC_ENDPOINT_HOST_REGEX_PART + ")|"
Expand Down Expand Up @@ -107,17 +121,76 @@

/**
* Creates a GRPC managed channel.
* @param properties instance to set up the GrpcEndpoint
*
* @param properties instance to set up the GrpcEndpoint
* @param interceptors Optional interceptors to add to the channel.
* @return GRPC managed channel to communicate with the sidecar.
*/
public static ManagedChannel buildGrpcManagedChannel(Properties properties, ClientInterceptor... interceptors) {
var settings = GrpcEndpointSettings.parse(properties);
ManagedChannelBuilder<?> builder = ManagedChannelBuilder.forTarget(settings.endpoint)
.userAgent(Version.getSdkVersion());
if (!settings.secure) {

boolean insecureTls = properties.getValue(GRPC_TLS_INSECURE);
if (insecureTls) {
try {
ManagedChannelBuilder<?> builder = NettyChannelBuilder.forTarget(settings.endpoint)
.sslContext(GrpcSslContexts.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build());
builder.userAgent(Version.getSdkVersion());
if (interceptors != null && interceptors.length > 0) {
builder = builder.intercept(interceptors);

Check warning on line 141 in sdk/src/main/java/io/dapr/utils/NetworkUtils.java

View check run for this annotation

Codecov / codecov/patch

sdk/src/main/java/io/dapr/utils/NetworkUtils.java#L141

Added line #L141 was not covered by tests
}
return builder.build();
} catch (Exception e) {
throw new DaprException(
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
.setMessage("Failed to create insecure TLS credentials"), e);

Check warning on line 147 in sdk/src/main/java/io/dapr/utils/NetworkUtils.java

View check run for this annotation

Codecov / codecov/patch

sdk/src/main/java/io/dapr/utils/NetworkUtils.java#L144-L147

Added lines #L144 - L147 were not covered by tests
}
}

String clientKeyPath = settings.tlsPrivateKeyPath;
String clientCertPath = settings.tlsCertPath;
String caCertPath = settings.tlsCaPath;

ManagedChannelBuilder<?> builder = ManagedChannelBuilder.forTarget(settings.endpoint);

if (clientCertPath != null && clientKeyPath != null) {
// mTLS case - using client cert and key, with optional CA cert for server authentication
try (
InputStream clientCertInputStream = new FileInputStream(clientCertPath);
InputStream clientKeyInputStream = new FileInputStream(clientKeyPath);
InputStream caCertInputStream = caCertPath != null ? new FileInputStream(caCertPath) : null
) {
TlsChannelCredentials.Builder builderCreds = TlsChannelCredentials.newBuilder()
.keyManager(clientCertInputStream, clientKeyInputStream); // For client authentication
if (caCertInputStream != null) {
builderCreds.trustManager(caCertInputStream); // For server authentication
}
ChannelCredentials credentials = builderCreds.build();
builder = Grpc.newChannelBuilder(settings.endpoint, credentials);
} catch (IOException e) {
throw new DaprException(
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
.setMessage("Failed to create mTLS credentials" + (caCertPath != null ? " with CA cert" : "")), e);
}
} else if (caCertPath != null) {
// Simple TLS case - using CA cert only for server authentication
try (InputStream caCertInputStream = new FileInputStream(caCertPath)) {
ChannelCredentials credentials = TlsChannelCredentials.newBuilder()
.trustManager(caCertInputStream)
.build();
builder = Grpc.newChannelBuilder(settings.endpoint, credentials);
} catch (IOException e) {
throw new DaprException(
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
.setMessage("Failed to create TLS credentials with CA cert"), e);
}
} else if (!settings.secure) {
builder = builder.usePlaintext();
}

builder.userAgent(Version.getSdkVersion());

if (interceptors != null && interceptors.length > 0) {
builder = builder.intercept(interceptors);
}
Expand All @@ -128,15 +201,26 @@
static final class GrpcEndpointSettings {
final String endpoint;
final boolean secure;
final String tlsPrivateKeyPath;
final String tlsCertPath;
final String tlsCaPath;

private GrpcEndpointSettings(String endpoint, boolean secure) {
private GrpcEndpointSettings(
String endpoint, boolean secure, String tlsPrivateKeyPath, String tlsCertPath, String tlsCaPath) {
this.endpoint = endpoint;
this.secure = secure;
this.tlsPrivateKeyPath = tlsPrivateKeyPath;
this.tlsCertPath = tlsCertPath;
this.tlsCaPath = tlsCaPath;
}

static GrpcEndpointSettings parse(Properties properties) {
String address = properties.getValue(SIDECAR_IP);
int port = properties.getValue(GRPC_PORT);
String clientKeyPath = properties.getValue(GRPC_TLS_KEY_PATH);
String clientCertPath = properties.getValue(GRPC_TLS_CERT_PATH);
String caCertPath = properties.getValue(GRPC_TLS_CA_PATH);

boolean secure = false;
String grpcEndpoint = properties.getValue(GRPC_ENDPOINT);
if ((grpcEndpoint != null) && !grpcEndpoint.isEmpty()) {
Expand Down Expand Up @@ -172,21 +256,31 @@

var authorityEndpoint = matcher.group("authorityEndpoint");
if (authorityEndpoint != null) {
return new GrpcEndpointSettings(String.format("dns://%s/%s:%d", authorityEndpoint, address, port), secure);
return new GrpcEndpointSettings(
String.format(
"dns://%s/%s:%d",
authorityEndpoint,
address,
port
), secure, clientKeyPath, clientCertPath, caCertPath);
}

var socket = matcher.group("socket");
if (socket != null) {
return new GrpcEndpointSettings(socket, secure);
return new GrpcEndpointSettings(socket, secure, clientKeyPath, clientCertPath, caCertPath);
}

var vsocket = matcher.group("vsocket");
if (vsocket != null) {
return new GrpcEndpointSettings(vsocket, secure);
return new GrpcEndpointSettings(vsocket, secure, clientKeyPath, clientCertPath, caCertPath);
}
}

return new GrpcEndpointSettings(String.format("dns:///%s:%d", address, port), secure);
return new GrpcEndpointSettings(String.format(
"dns:///%s:%d",
address,
port
), secure, clientKeyPath, clientCertPath, caCertPath);
}

}
Expand Down
Loading
Loading