diff --git a/sdk/pom.xml b/sdk/pom.xml
index e26fc7dae..14cb9e08d 100644
--- a/sdk/pom.xml
+++ b/sdk/pom.xml
@@ -132,6 +132,23 @@
assertj-core
${assertj.version}
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.70
+ test
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+ 1.70
+ test
+
+
+ io.grpc
+ grpc-netty-shaded
+ ${grpc.version}
+
diff --git a/sdk/src/main/java/io/dapr/config/Properties.java b/sdk/src/main/java/io/dapr/config/Properties.java
index 14b0a4fb2..98cccf2c4 100644
--- a/sdk/src/main/java/io/dapr/config/Properties.java
+++ b/sdk/src/main/java/io/dapr/config/Properties.java
@@ -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 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 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 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 GRPC_TLS_INSECURE = new BooleanProperty(
+ "dapr.grpc.tls.insecure",
+ "DAPR_GRPC_TLS_INSECURE",
+ false);
+
/**
* GRPC endpoint for remote sidecar connectivity.
*/
diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java
index 6ce15782c..522b3e5d7 100644
--- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java
+++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java
@@ -14,11 +14,21 @@ limitations under the License.
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;
@@ -26,9 +36,12 @@ 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.
*/
@@ -56,19 +69,20 @@ public final class NetworkUtils {
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 =
- "(?dns://)(?" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)?/";
+ "(?dns://)(?"
+ + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)?/";
private static final String GRPC_ENDPOINT_PARAM_REGEX_PART = "(\\?(?tls\\=((true)|(false))))?";
- private static final String GRPC_ENDPOINT_SOCKET_REGEX_PART =
- "(?((unix:)|(unix://)|(unix-abstract:))" + GRPC_ENDPOINT_FILENAME_REGEX_PART + ")";
+ private static final String GRPC_ENDPOINT_SOCKET_REGEX_PART = "(?((unix:)|(unix://)|(unix-abstract:))"
+ + GRPC_ENDPOINT_FILENAME_REGEX_PART + ")";
- private static final String GRPC_ENDPOINT_VSOCKET_REGEX_PART =
- "(?vsock:" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)";
- private static final String GRPC_ENDPOINT_HOST_REGEX_PART =
- "((?http://)|(?https://)|(?dns:)|(" + GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART + "))?"
- + "(?" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ")?+"
- + "(:(?[0-9]+))?";
+ private static final String GRPC_ENDPOINT_VSOCKET_REGEX_PART = "(?vsock:" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART
+ + ":[0-9]+)";
+ private static final String GRPC_ENDPOINT_HOST_REGEX_PART = "((?http://)|(?https://)|(?dns:)|("
+ + GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART + "))?"
+ + "(?" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ")?+"
+ + "(:(?[0-9]+))?";
private static final String GRPC_ENDPOINT_REGEX = "^("
+ "(" + GRPC_ENDPOINT_HOST_REGEX_PART + ")|"
@@ -107,17 +121,76 @@ public final class NetworkUtils {
/**
* 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);
+ }
+ return builder.build();
+ } catch (Exception e) {
+ throw new DaprException(
+ new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
+ .setMessage("Failed to create insecure TLS credentials"), e);
+ }
+ }
+
+ 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);
}
@@ -128,15 +201,26 @@ public final class NetworkUtils {
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()) {
@@ -172,21 +256,31 @@ public final class NetworkUtils {
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);
}
}
diff --git a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java
index f044cd728..2b4929abd 100644
--- a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java
+++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java
@@ -1,19 +1,92 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * 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 sppecific language governing permissions and
+limitations under the License.
+*/
+
package io.dapr.utils;
import io.dapr.config.Properties;
+import io.dapr.exceptions.DaprException;
import io.grpc.ManagedChannel;
import org.junit.Assert;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import java.io.File;
+import java.nio.file.Files;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.List;
import java.util.Map;
-
+import java.util.ArrayList;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
public class NetworkUtilsTest {
private final int defaultGrpcPort = 50001;
private final String defaultSidecarIP = "127.0.0.1";
private ManagedChannel channel;
+ private static final List channels = new ArrayList<>();
+
+ // Helper method to generate a self-signed certificate for testing
+ private static KeyPair generateKeyPair() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ return keyPairGenerator.generateKeyPair();
+ }
+
+ private static X509Certificate generateCertificate(KeyPair keyPair) throws Exception {
+ X500Name issuer = new X500Name("CN=Test Certificate");
+ X500Name subject = new X500Name("CN=Test Certificate");
+ Date notBefore = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
+ Date notAfter = new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L);
+ SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
+ X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
+ issuer,
+ java.math.BigInteger.valueOf(System.currentTimeMillis()),
+ notBefore,
+ notAfter,
+ subject,
+ publicKeyInfo
+ );
+
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate());
+ X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer));
+ return cert;
+ }
+
+ private static void writeCertificateToFile(X509Certificate cert, File file) throws Exception {
+ String certPem = "-----BEGIN CERTIFICATE-----\n" +
+ java.util.Base64.getEncoder().encodeToString(cert.getEncoded()) +
+ "\n-----END CERTIFICATE-----";
+ Files.write(file.toPath(), certPem.getBytes());
+ }
+
+ private static void writePrivateKeyToFile(KeyPair keyPair, File file) throws Exception {
+ String keyPem = "-----BEGIN PRIVATE KEY-----\n" +
+ java.util.Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()) +
+ "\n-----END PRIVATE KEY-----";
+ Files.write(file.toPath(), keyPem.getBytes());
+ }
@AfterEach
public void tearDown() {
@@ -22,9 +95,20 @@ public class NetworkUtilsTest {
}
}
+ @AfterAll
+ public static void tearDownAll() {
+ for (ManagedChannel ch : channels) {
+ if (ch != null && !ch.isShutdown()) {
+ ch.shutdown();
+ }
+ }
+ channels.clear();
+ }
+
@Test
public void testBuildGrpcManagedChannel() {
channel = NetworkUtils.buildGrpcManagedChannel(new Properties());
+ channels.add(channel);
String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
Assertions.assertEquals(expectedAuthority, channel.authority());
@@ -34,6 +118,7 @@ public class NetworkUtilsTest {
public void testBuildGrpcManagedChannel_httpEndpointNoPort() {
var properties = new Properties(Map.of(Properties.GRPC_ENDPOINT.getName(), "http://example.com"));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
String expectedAuthority = "example.com:80";
Assertions.assertEquals(expectedAuthority, channel.authority());
@@ -43,6 +128,7 @@ public class NetworkUtilsTest {
public void testBuildGrpcManagedChannel_httpEndpointWithPort() {
var properties = new Properties(Map.of(Properties.GRPC_ENDPOINT.getName(), "http://example.com:3000"));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
String expectedAuthority = "example.com:3000";
Assertions.assertEquals(expectedAuthority, channel.authority());
@@ -52,6 +138,7 @@ public class NetworkUtilsTest {
public void testBuildGrpcManagedChannel_httpsEndpointNoPort() {
var properties = new Properties(Map.of(Properties.GRPC_ENDPOINT.getName(), "https://example.com"));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
String expectedAuthority = "example.com:443";
Assertions.assertEquals(expectedAuthority, channel.authority());
@@ -61,11 +148,236 @@ public class NetworkUtilsTest {
public void testBuildGrpcManagedChannel_httpsEndpointWithPort() {
var properties = new Properties(Map.of(Properties.GRPC_ENDPOINT.getName(), "https://example.com:3000"));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
String expectedAuthority = "example.com:3000";
Assertions.assertEquals(expectedAuthority, channel.authority());
}
+ @Test
+ public void testBuildGrpcManagedChannelWithTls() throws Exception {
+ // Generate test certificate and key
+ KeyPair keyPair = generateKeyPair();
+ X509Certificate cert = generateCertificate(keyPair);
+
+ File certFile = File.createTempFile("test-cert", ".pem");
+ File keyFile = File.createTempFile("test-key", ".pem");
+ try {
+ writeCertificateToFile(cert, certFile);
+ writePrivateKeyToFile(keyPair, keyFile);
+
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
+ Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath()
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
+ Assertions.assertEquals(expectedAuthority, channel.authority());
+ } finally {
+ certFile.delete();
+ keyFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithTlsAndEndpoint() throws Exception {
+ // Generate test certificate and key
+ KeyPair keyPair = generateKeyPair();
+ X509Certificate cert = generateCertificate(keyPair);
+
+ File certFile = File.createTempFile("test-cert", ".pem");
+ File keyFile = File.createTempFile("test-key", ".pem");
+ try {
+ writeCertificateToFile(cert, certFile);
+ writePrivateKeyToFile(keyPair, keyFile);
+
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
+ Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(),
+ Properties.GRPC_ENDPOINT.getName(), "https://example.com:443"
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ Assertions.assertEquals("example.com:443", channel.authority());
+ } finally {
+ certFile.delete();
+ keyFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithInvalidTlsCert() {
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CERT_PATH.getName(), "/nonexistent/cert.pem",
+ Properties.GRPC_TLS_KEY_PATH.getName(), "/nonexistent/key.pem"
+ ));
+
+ Assertions.assertThrows(DaprException.class, () -> {
+ NetworkUtils.buildGrpcManagedChannel(properties);
+ });
+ }
+
+ @Test
+ @EnabledOnOs({OS.LINUX, OS.MAC}) // Unix domain sockets are only supported on Linux and macOS
+ public void testBuildGrpcManagedChannelWithTlsAndUnixSocket() throws Exception {
+ // Skip test if Unix domain sockets are not supported
+ Assumptions.assumeTrue(System.getProperty("os.name").toLowerCase().contains("linux") ||
+ System.getProperty("os.name").toLowerCase().contains("mac"));
+
+ // Generate test certificate and key
+ KeyPair keyPair = generateKeyPair();
+ X509Certificate cert = generateCertificate(keyPair);
+
+ File certFile = File.createTempFile("test-cert", ".pem");
+ File keyFile = File.createTempFile("test-key", ".pem");
+ try {
+ writeCertificateToFile(cert, certFile);
+ writePrivateKeyToFile(keyPair, keyFile);
+
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
+ Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(),
+ Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock"
+ ));
+
+ // For Unix sockets, we expect an exception if the platform doesn't support it
+ try {
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ // If we get here, Unix sockets are supported
+ Assertions.assertNotNull(channel.authority(), "Channel authority should not be null");
+ } catch (Exception e) {
+ // If we get here, Unix sockets are not supported
+ Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress"));
+ }
+ } finally {
+ certFile.delete();
+ keyFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithTlsAndDnsAuthority() throws Exception {
+ // Generate test certificate and key
+ KeyPair keyPair = generateKeyPair();
+ X509Certificate cert = generateCertificate(keyPair);
+
+ File certFile = File.createTempFile("test-cert", ".pem");
+ File keyFile = File.createTempFile("test-key", ".pem");
+ try {
+ writeCertificateToFile(cert, certFile);
+ writePrivateKeyToFile(keyPair, keyFile);
+
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
+ Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(),
+ Properties.GRPC_ENDPOINT.getName(), "dns://authority:53/example.com:443"
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ Assertions.assertEquals("example.com:443", channel.authority());
+ } finally {
+ certFile.delete();
+ keyFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithTlsAndCaCert() throws Exception {
+ // Generate test CA certificate
+ KeyPair caKeyPair = generateKeyPair();
+ X509Certificate caCert = generateCertificate(caKeyPair);
+
+ File caCertFile = File.createTempFile("test-ca-cert", ".pem");
+ try {
+ writeCertificateToFile(caCert, caCertFile);
+
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath()
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
+ Assertions.assertEquals(expectedAuthority, channel.authority());
+ } finally {
+ caCertFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithTlsAndCaCertAndEndpoint() throws Exception {
+ // Generate test CA certificate
+ KeyPair caKeyPair = generateKeyPair();
+ X509Certificate caCert = generateCertificate(caKeyPair);
+
+ File caCertFile = File.createTempFile("test-ca-cert", ".pem");
+ try {
+ writeCertificateToFile(caCert, caCertFile);
+
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
+ Properties.GRPC_ENDPOINT.getName(), "https://example.com:443"
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ Assertions.assertEquals("example.com:443", channel.authority());
+ } finally {
+ caCertFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithInvalidCaCert() {
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CA_PATH.getName(), "/nonexistent/ca.pem"
+ ));
+
+ Assertions.assertThrows(DaprException.class, () -> {
+ NetworkUtils.buildGrpcManagedChannel(properties);
+ });
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithMtlsAndCaCert() throws Exception {
+ // Generate test certificates
+ KeyPair caKeyPair = generateKeyPair();
+ X509Certificate caCert = generateCertificate(caKeyPair);
+ KeyPair clientKeyPair = generateKeyPair();
+ X509Certificate clientCert = generateCertificate(clientKeyPair);
+
+ File caCertFile = File.createTempFile("test-ca-cert", ".pem");
+ File clientCertFile = File.createTempFile("test-client-cert", ".pem");
+ File clientKeyFile = File.createTempFile("test-client-key", ".pem");
+ try {
+ writeCertificateToFile(caCert, caCertFile);
+ writeCertificateToFile(clientCert, clientCertFile);
+ writePrivateKeyToFile(clientKeyPair, clientKeyFile);
+
+ // Test mTLS with both client certs and CA cert
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
+ Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(),
+ Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath()
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
+ Assertions.assertEquals(expectedAuthority, channel.authority());
+ Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
+ } finally {
+ caCertFile.delete();
+ clientCertFile.delete();
+ clientKeyFile.delete();
+ }
+ }
+
@Test
public void testGrpcEndpointParsing() {
testGrpcEndpointParsingScenario(":5000", "dns:///127.0.0.1:5000", false);
@@ -146,4 +458,137 @@ public class NetworkUtilsTest {
// Expected
}
}
+
+ @Test
+ public void testBuildGrpcManagedChannelWithCaCertAndUnixSocket() throws Exception {
+ // Skip test if Unix domain sockets are not supported
+ Assumptions.assumeTrue(System.getProperty("os.name").toLowerCase().contains("linux") ||
+ System.getProperty("os.name").toLowerCase().contains("mac"));
+
+ // Generate test CA certificate
+ KeyPair caKeyPair = generateKeyPair();
+ X509Certificate caCert = generateCertificate(caKeyPair);
+
+ File caCertFile = File.createTempFile("test-ca-cert", ".pem");
+ try {
+ writeCertificateToFile(caCert, caCertFile);
+
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
+ Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock"
+ ));
+
+ // For Unix sockets, we expect an exception if the platform doesn't support it
+ try {
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ Assertions.assertNotNull(channel.authority(), "Channel authority should not be null");
+ } catch (Exception e) {
+ // If we get here, Unix sockets are not supported
+ Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress"));
+ }
+ } finally {
+ caCertFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithCaCertAndDnsAuthority() throws Exception {
+ // Generate test CA certificate
+ KeyPair caKeyPair = generateKeyPair();
+ X509Certificate caCert = generateCertificate(caKeyPair);
+
+ File caCertFile = File.createTempFile("test-ca-cert", ".pem");
+ try {
+ writeCertificateToFile(caCert, caCertFile);
+
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
+ Properties.GRPC_ENDPOINT.getName(), "dns://authority:53/example.com:443"
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+ Assertions.assertEquals("example.com:443", channel.authority());
+ } finally {
+ caCertFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithInsecureTls() throws Exception {
+ // Test insecure TLS mode with a secure endpoint
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_INSECURE.getName(), "true",
+ Properties.GRPC_ENDPOINT.getName(), "dns:///example.com:443?tls=true"
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+
+ // Verify the channel is created with the correct authority
+ Assertions.assertEquals("example.com:443", channel.authority());
+
+ // Verify the channel is active and using TLS (not plaintext)
+ Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithInsecureTlsAndMtls() throws Exception {
+ // Generate test certificates
+ KeyPair caKeyPair = generateKeyPair();
+ X509Certificate caCert = generateCertificate(caKeyPair);
+ KeyPair clientKeyPair = generateKeyPair();
+ X509Certificate clientCert = generateCertificate(clientKeyPair);
+
+ File caCertFile = File.createTempFile("test-ca-cert", ".pem");
+ File clientCertFile = File.createTempFile("test-client-cert", ".pem");
+ File clientKeyFile = File.createTempFile("test-client-key", ".pem");
+ try {
+ writeCertificateToFile(caCert, caCertFile);
+ writeCertificateToFile(clientCert, clientCertFile);
+ writePrivateKeyToFile(clientKeyPair, clientKeyFile);
+
+ // Test that insecure TLS still works with mTLS settings
+ // The client certs should be ignored since we're using InsecureTrustManagerFactory
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_INSECURE.getName(), "true",
+ Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
+ Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(),
+ Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath(),
+ Properties.GRPC_ENDPOINT.getName(), "dns:///example.com:443?tls=true"
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+
+ // Verify the channel is created with the correct authority
+ Assertions.assertEquals("example.com:443", channel.authority());
+
+ // Verify the channel is active and using TLS (not plaintext)
+ Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
+ } finally {
+ caCertFile.delete();
+ clientCertFile.delete();
+ clientKeyFile.delete();
+ }
+ }
+
+ @Test
+ public void testBuildGrpcManagedChannelWithInsecureTlsAndCustomEndpoint() throws Exception {
+ // Test insecure TLS with a custom endpoint that would normally require TLS
+ var properties = new Properties(Map.of(
+ Properties.GRPC_TLS_INSECURE.getName(), "true",
+ Properties.GRPC_ENDPOINT.getName(), "dns://authority:53/example.com:443?tls=true"
+ ));
+
+ channel = NetworkUtils.buildGrpcManagedChannel(properties);
+ channels.add(channel);
+
+ // Verify the channel is created with the correct authority
+ Assertions.assertEquals("example.com:443", channel.authority());
+
+ // Verify the channel is active and using TLS (not plaintext)
+ Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
+ }
}