Skip to content

Commit daf4c8b

Browse files
Feat Add TLS & mTLS support for gRPC with root CA and insecure mode (#1361)
* feat: Support for GRPC ssl Signed-off-by: Javier Aliaga <[email protected]> * add tests Signed-off-by: Cassandra Coyle <[email protected]> * fix CI Signed-off-by: Cassandra Coyle <[email protected]> * add back else if Signed-off-by: Cassandra Coyle <[email protected]> * channel cleanup Signed-off-by: Cassandra Coyle <[email protected]> * add root ca support Signed-off-by: Cassandra Coyle <[email protected]> * checkstyles Signed-off-by: Cassandra Coyle <[email protected]> * add insecure Signed-off-by: Cassandra Coyle <[email protected]> * fix checkstyles Signed-off-by: Cassandra Coyle <[email protected]> * use InsecureTrustManagerFactory Signed-off-by: Cassandra Coyle <[email protected]> * fix test Signed-off-by: Cassandra Coyle <[email protected]> --------- Signed-off-by: Javier Aliaga <[email protected]> Signed-off-by: Cassandra Coyle <[email protected]> Co-authored-by: Javier Aliaga <[email protected]>
1 parent 551d205 commit daf4c8b

File tree

4 files changed

+611
-20
lines changed

4 files changed

+611
-20
lines changed

sdk/pom.xml

+17
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,23 @@
132132
<artifactId>assertj-core</artifactId>
133133
<version>${assertj.version}</version>
134134
</dependency>
135+
<dependency>
136+
<groupId>org.bouncycastle</groupId>
137+
<artifactId>bcprov-jdk15on</artifactId>
138+
<version>1.70</version>
139+
<scope>test</scope>
140+
</dependency>
141+
<dependency>
142+
<groupId>org.bouncycastle</groupId>
143+
<artifactId>bcpkix-jdk15on</artifactId>
144+
<version>1.70</version>
145+
<scope>test</scope>
146+
</dependency>
147+
<dependency>
148+
<groupId>io.grpc</groupId>
149+
<artifactId>grpc-netty-shaded</artifactId>
150+
<version>${grpc.version}</version>
151+
</dependency>
135152
</dependencies>
136153

137154
<build>

sdk/src/main/java/io/dapr/config/Properties.java

+35
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,41 @@ public class Properties {
102102
"DAPR_GRPC_PORT",
103103
DEFAULT_GRPC_PORT);
104104

105+
/**
106+
* GRPC TLS cert path for Dapr after checking system property and environment variable.
107+
*/
108+
public static final Property<String> GRPC_TLS_CERT_PATH = new StringProperty(
109+
"dapr.grpc.tls.cert.path",
110+
"DAPR_GRPC_TLS_CERT_PATH",
111+
null);
112+
113+
/**
114+
* GRPC TLS key path for Dapr after checking system property and environment variable.
115+
*/
116+
public static final Property<String> GRPC_TLS_KEY_PATH = new StringProperty(
117+
"dapr.grpc.tls.key.path",
118+
"DAPR_GRPC_TLS_KEY_PATH",
119+
null);
120+
121+
/**
122+
* GRPC TLS CA cert path for Dapr after checking system property and environment variable.
123+
* This is used for TLS connections to servers with self-signed certificates.
124+
*/
125+
public static final Property<String> GRPC_TLS_CA_PATH = new StringProperty(
126+
"dapr.grpc.tls.ca.path",
127+
"DAPR_GRPC_TLS_CA_PATH",
128+
null);
129+
130+
/**
131+
* Use insecure TLS mode which still uses TLS but doesn't verify certificates.
132+
* This uses InsecureTrustManagerFactory to trust all certificates.
133+
* This should only be used for testing or in secure environments.
134+
*/
135+
public static final Property<Boolean> GRPC_TLS_INSECURE = new BooleanProperty(
136+
"dapr.grpc.tls.insecure",
137+
"DAPR_GRPC_TLS_INSECURE",
138+
false);
139+
105140
/**
106141
* GRPC endpoint for remote sidecar connectivity.
107142
*/

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

+113-19
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,34 @@
1414
package io.dapr.utils;
1515

1616
import io.dapr.config.Properties;
17+
import io.dapr.exceptions.DaprError;
18+
import io.dapr.exceptions.DaprException;
19+
import io.grpc.ChannelCredentials;
1720
import io.grpc.ClientInterceptor;
21+
import io.grpc.Grpc;
1822
import io.grpc.ManagedChannel;
1923
import io.grpc.ManagedChannelBuilder;
24+
import io.grpc.TlsChannelCredentials;
25+
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
26+
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
27+
import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
2028

29+
import java.io.FileInputStream;
2130
import java.io.IOException;
31+
import java.io.InputStream;
2232
import java.net.InetAddress;
2333
import java.net.InetSocketAddress;
2434
import java.net.Socket;
2535
import java.util.regex.Pattern;
2636

2737
import static io.dapr.config.Properties.GRPC_ENDPOINT;
2838
import static io.dapr.config.Properties.GRPC_PORT;
39+
import static io.dapr.config.Properties.GRPC_TLS_CA_PATH;
40+
import static io.dapr.config.Properties.GRPC_TLS_CERT_PATH;
41+
import static io.dapr.config.Properties.GRPC_TLS_INSECURE;
42+
import static io.dapr.config.Properties.GRPC_TLS_KEY_PATH;
2943
import static io.dapr.config.Properties.SIDECAR_IP;
3044

31-
3245
/**
3346
* Utility methods for network, internal to Dapr SDK.
3447
*/
@@ -56,19 +69,20 @@ public final class NetworkUtils {
5669
private static final String GRPC_ENDPOINT_HOSTNAME_REGEX_PART = "(([A-Za-z0-9_\\-\\.]+)|(\\[" + IPV6_REGEX + "\\]))";
5770

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

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

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

66-
private static final String GRPC_ENDPOINT_VSOCKET_REGEX_PART =
67-
"(?<vsocket>vsock:" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)";
68-
private static final String GRPC_ENDPOINT_HOST_REGEX_PART =
69-
"((?<http>http://)|(?<https>https://)|(?<dns>dns:)|(" + GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART + "))?"
70-
+ "(?<hostname>" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ")?+"
71-
+ "(:(?<port>[0-9]+))?";
80+
private static final String GRPC_ENDPOINT_VSOCKET_REGEX_PART = "(?<vsocket>vsock:" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART
81+
+ ":[0-9]+)";
82+
private static final String GRPC_ENDPOINT_HOST_REGEX_PART = "((?<http>http://)|(?<https>https://)|(?<dns>dns:)|("
83+
+ GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART + "))?"
84+
+ "(?<hostname>" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ")?+"
85+
+ "(:(?<port>[0-9]+))?";
7286

7387
private static final String GRPC_ENDPOINT_REGEX = "^("
7488
+ "(" + GRPC_ENDPOINT_HOST_REGEX_PART + ")|"
@@ -107,17 +121,76 @@ public static void waitForSocket(String host, int port, int timeoutInMillisecond
107121

108122
/**
109123
* Creates a GRPC managed channel.
110-
* @param properties instance to set up the GrpcEndpoint
124+
*
125+
* @param properties instance to set up the GrpcEndpoint
111126
* @param interceptors Optional interceptors to add to the channel.
112127
* @return GRPC managed channel to communicate with the sidecar.
113128
*/
114129
public static ManagedChannel buildGrpcManagedChannel(Properties properties, ClientInterceptor... interceptors) {
115130
var settings = GrpcEndpointSettings.parse(properties);
116-
ManagedChannelBuilder<?> builder = ManagedChannelBuilder.forTarget(settings.endpoint)
117-
.userAgent(Version.getSdkVersion());
118-
if (!settings.secure) {
131+
132+
boolean insecureTls = properties.getValue(GRPC_TLS_INSECURE);
133+
if (insecureTls) {
134+
try {
135+
ManagedChannelBuilder<?> builder = NettyChannelBuilder.forTarget(settings.endpoint)
136+
.sslContext(GrpcSslContexts.forClient()
137+
.trustManager(InsecureTrustManagerFactory.INSTANCE)
138+
.build());
139+
builder.userAgent(Version.getSdkVersion());
140+
if (interceptors != null && interceptors.length > 0) {
141+
builder = builder.intercept(interceptors);
142+
}
143+
return builder.build();
144+
} catch (Exception e) {
145+
throw new DaprException(
146+
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
147+
.setMessage("Failed to create insecure TLS credentials"), e);
148+
}
149+
}
150+
151+
String clientKeyPath = settings.tlsPrivateKeyPath;
152+
String clientCertPath = settings.tlsCertPath;
153+
String caCertPath = settings.tlsCaPath;
154+
155+
ManagedChannelBuilder<?> builder = ManagedChannelBuilder.forTarget(settings.endpoint);
156+
157+
if (clientCertPath != null && clientKeyPath != null) {
158+
// mTLS case - using client cert and key, with optional CA cert for server authentication
159+
try (
160+
InputStream clientCertInputStream = new FileInputStream(clientCertPath);
161+
InputStream clientKeyInputStream = new FileInputStream(clientKeyPath);
162+
InputStream caCertInputStream = caCertPath != null ? new FileInputStream(caCertPath) : null
163+
) {
164+
TlsChannelCredentials.Builder builderCreds = TlsChannelCredentials.newBuilder()
165+
.keyManager(clientCertInputStream, clientKeyInputStream); // For client authentication
166+
if (caCertInputStream != null) {
167+
builderCreds.trustManager(caCertInputStream); // For server authentication
168+
}
169+
ChannelCredentials credentials = builderCreds.build();
170+
builder = Grpc.newChannelBuilder(settings.endpoint, credentials);
171+
} catch (IOException e) {
172+
throw new DaprException(
173+
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
174+
.setMessage("Failed to create mTLS credentials" + (caCertPath != null ? " with CA cert" : "")), e);
175+
}
176+
} else if (caCertPath != null) {
177+
// Simple TLS case - using CA cert only for server authentication
178+
try (InputStream caCertInputStream = new FileInputStream(caCertPath)) {
179+
ChannelCredentials credentials = TlsChannelCredentials.newBuilder()
180+
.trustManager(caCertInputStream)
181+
.build();
182+
builder = Grpc.newChannelBuilder(settings.endpoint, credentials);
183+
} catch (IOException e) {
184+
throw new DaprException(
185+
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
186+
.setMessage("Failed to create TLS credentials with CA cert"), e);
187+
}
188+
} else if (!settings.secure) {
119189
builder = builder.usePlaintext();
120190
}
191+
192+
builder.userAgent(Version.getSdkVersion());
193+
121194
if (interceptors != null && interceptors.length > 0) {
122195
builder = builder.intercept(interceptors);
123196
}
@@ -128,15 +201,26 @@ public static ManagedChannel buildGrpcManagedChannel(Properties properties, Clie
128201
static final class GrpcEndpointSettings {
129202
final String endpoint;
130203
final boolean secure;
204+
final String tlsPrivateKeyPath;
205+
final String tlsCertPath;
206+
final String tlsCaPath;
131207

132-
private GrpcEndpointSettings(String endpoint, boolean secure) {
208+
private GrpcEndpointSettings(
209+
String endpoint, boolean secure, String tlsPrivateKeyPath, String tlsCertPath, String tlsCaPath) {
133210
this.endpoint = endpoint;
134211
this.secure = secure;
212+
this.tlsPrivateKeyPath = tlsPrivateKeyPath;
213+
this.tlsCertPath = tlsCertPath;
214+
this.tlsCaPath = tlsCaPath;
135215
}
136216

137217
static GrpcEndpointSettings parse(Properties properties) {
138218
String address = properties.getValue(SIDECAR_IP);
139219
int port = properties.getValue(GRPC_PORT);
220+
String clientKeyPath = properties.getValue(GRPC_TLS_KEY_PATH);
221+
String clientCertPath = properties.getValue(GRPC_TLS_CERT_PATH);
222+
String caCertPath = properties.getValue(GRPC_TLS_CA_PATH);
223+
140224
boolean secure = false;
141225
String grpcEndpoint = properties.getValue(GRPC_ENDPOINT);
142226
if ((grpcEndpoint != null) && !grpcEndpoint.isEmpty()) {
@@ -172,21 +256,31 @@ static GrpcEndpointSettings parse(Properties properties) {
172256

173257
var authorityEndpoint = matcher.group("authorityEndpoint");
174258
if (authorityEndpoint != null) {
175-
return new GrpcEndpointSettings(String.format("dns://%s/%s:%d", authorityEndpoint, address, port), secure);
259+
return new GrpcEndpointSettings(
260+
String.format(
261+
"dns://%s/%s:%d",
262+
authorityEndpoint,
263+
address,
264+
port
265+
), secure, clientKeyPath, clientCertPath, caCertPath);
176266
}
177267

178268
var socket = matcher.group("socket");
179269
if (socket != null) {
180-
return new GrpcEndpointSettings(socket, secure);
270+
return new GrpcEndpointSettings(socket, secure, clientKeyPath, clientCertPath, caCertPath);
181271
}
182272

183273
var vsocket = matcher.group("vsocket");
184274
if (vsocket != null) {
185-
return new GrpcEndpointSettings(vsocket, secure);
275+
return new GrpcEndpointSettings(vsocket, secure, clientKeyPath, clientCertPath, caCertPath);
186276
}
187277
}
188278

189-
return new GrpcEndpointSettings(String.format("dns:///%s:%d", address, port), secure);
279+
return new GrpcEndpointSettings(String.format(
280+
"dns:///%s:%d",
281+
address,
282+
port
283+
), secure, clientKeyPath, clientCertPath, caCertPath);
190284
}
191285

192286
}

0 commit comments

Comments
 (0)