From cb758485eb5d7595698e740a7ea1b7b7a37421e5 Mon Sep 17 00:00:00 2001 From: Javier Aliaga Date: Wed, 14 May 2025 17:40:37 +0200 Subject: [PATCH 01/11] feat: Support for GRPC ssl Signed-off-by: Javier Aliaga --- .../main/java/io/dapr/config/Properties.java | 16 ++++ .../main/java/io/dapr/utils/NetworkUtils.java | 76 ++++++++++++++----- 2 files changed, 73 insertions(+), 19 deletions(-) diff --git a/sdk/src/main/java/io/dapr/config/Properties.java b/sdk/src/main/java/io/dapr/config/Properties.java index 14b0a4fb26..2ceaecbc24 100644 --- a/sdk/src/main/java/io/dapr/config/Properties.java +++ b/sdk/src/main/java/io/dapr/config/Properties.java @@ -102,6 +102,22 @@ 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 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 6ce15782c8..e2fc2f5541 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -14,11 +14,18 @@ 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 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 +33,10 @@ import static io.dapr.config.Properties.GRPC_ENDPOINT; import static io.dapr.config.Properties.GRPC_PORT; +import static io.dapr.config.Properties.GRPC_TLS_CERT_PATH; +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. */ @@ -55,20 +63,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]+)?/"; + private static final String GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART = "(?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,14 +115,36 @@ public static void waitForSocket(String host, int port, int timeoutInMillisecond /** * 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()); + + String clientKeyPath = settings.tlsPrivateKeyPath; + String clientCertPath = settings.tlsCertPath; + + ManagedChannelBuilder builder = ManagedChannelBuilder.forTarget(settings.endpoint); + + if (clientCertPath != null && clientKeyPath != null) { + try { + InputStream clientCertInputStream = new FileInputStream(clientCertPath); + InputStream clientKeyInputStream = new FileInputStream(clientKeyPath); + + ChannelCredentials credentials = TlsChannelCredentials.newBuilder() + .keyManager(clientCertInputStream, clientKeyInputStream) + .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"), e); + } + } + + builder.userAgent(Version.getSdkVersion()); + if (!settings.secure) { builder = builder.usePlaintext(); } @@ -128,15 +158,23 @@ public static ManagedChannel buildGrpcManagedChannel(Properties properties, Clie static final class GrpcEndpointSettings { final String endpoint; final boolean secure; + final String tlsPrivateKeyPath; + final String tlsCertPath; - private GrpcEndpointSettings(String endpoint, boolean secure) { + private GrpcEndpointSettings(String endpoint, boolean secure, String tlsPrivateKeyPath, String tlsCertPath) { this.endpoint = endpoint; this.secure = secure; + this.tlsPrivateKeyPath = tlsPrivateKeyPath; + this.tlsCertPath = tlsCertPath; } 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); + + boolean secure = false; String grpcEndpoint = properties.getValue(GRPC_ENDPOINT); if ((grpcEndpoint != null) && !grpcEndpoint.isEmpty()) { @@ -172,21 +210,21 @@ static GrpcEndpointSettings parse(Properties properties) { 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); } var socket = matcher.group("socket"); if (socket != null) { - return new GrpcEndpointSettings(socket, secure); + return new GrpcEndpointSettings(socket, secure, clientKeyPath, clientCertPath); } var vsocket = matcher.group("vsocket"); if (vsocket != null) { - return new GrpcEndpointSettings(vsocket, secure); + return new GrpcEndpointSettings(vsocket, secure, clientKeyPath, clientCertPath); } } - return new GrpcEndpointSettings(String.format("dns:///%s:%d", address, port), secure); + return new GrpcEndpointSettings(String.format("dns:///%s:%d", address, port), secure, clientKeyPath, clientCertPath); } } From 5ae14936483b92ba143455192837106de91eb54a Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 14:41:08 -0500 Subject: [PATCH 02/11] add tests Signed-off-by: Cassandra Coyle --- sdk/pom.xml | 12 ++ .../main/java/io/dapr/utils/NetworkUtils.java | 9 +- .../java/io/dapr/utils/NetworkUtilsTest.java | 186 +++++++++++++++++- 3 files changed, 200 insertions(+), 7 deletions(-) diff --git a/sdk/pom.xml b/sdk/pom.xml index e26fc7dae8..d4393275df 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -132,6 +132,18 @@ assertj-core ${assertj.version} + + org.bouncycastle + bcprov-jdk15on + 1.70 + test + + + org.bouncycastle + bcpkix-jdk15on + 1.70 + test + diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java index e2fc2f5541..3d4a8cacea 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -127,7 +127,6 @@ public static ManagedChannel buildGrpcManagedChannel(Properties properties, Clie String clientCertPath = settings.tlsCertPath; ManagedChannelBuilder builder = ManagedChannelBuilder.forTarget(settings.endpoint); - if (clientCertPath != null && clientKeyPath != null) { try { InputStream clientCertInputStream = new FileInputStream(clientCertPath); @@ -136,18 +135,18 @@ public static ManagedChannel buildGrpcManagedChannel(Properties properties, Clie ChannelCredentials credentials = TlsChannelCredentials.newBuilder() .keyManager(clientCertInputStream, clientKeyInputStream) .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"), e); } + } else if (!settings.secure) { + builder = builder.usePlaintext(); } builder.userAgent(Version.getSdkVersion()); - if (!settings.secure) { - builder = builder.usePlaintext(); - } if (interceptors != null && interceptors.length > 0) { builder = builder.intercept(interceptors); } @@ -174,7 +173,6 @@ static GrpcEndpointSettings parse(Properties properties) { String clientKeyPath = properties.getValue(GRPC_TLS_KEY_PATH); String clientCertPath = properties.getValue(GRPC_TLS_CERT_PATH); - boolean secure = false; String grpcEndpoint = properties.getValue(GRPC_ENDPOINT); if ((grpcEndpoint != null) && !grpcEndpoint.isEmpty()) { @@ -226,7 +224,6 @@ static GrpcEndpointSettings parse(Properties properties) { return new GrpcEndpointSettings(String.format("dns:///%s:%d", address, port), secure, clientKeyPath, clientCertPath); } - } private static void callWithRetry(Runnable function, long retryTimeoutMilliseconds) throws InterruptedException { diff --git a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java index f044cd728f..d9eb4c18d9 100644 --- a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java +++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java @@ -1,20 +1,76 @@ 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.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.Map; - +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; + // 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() { if (channel != null && !channel.isShutdown()) { @@ -66,6 +122,134 @@ public void testBuildGrpcManagedChannel_httpsEndpointWithPort() { 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); + 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); + 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); + // If we get here, Unix sockets are supported + Assertions.assertEquals("", channel.authority()); + } 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); + Assertions.assertEquals("example.com:443", channel.authority()); + } finally { + certFile.delete(); + keyFile.delete(); + } + } + @Test public void testGrpcEndpointParsing() { testGrpcEndpointParsingScenario(":5000", "dns:///127.0.0.1:5000", false); From 6f3122ff02f3affe68f1b6bf3066843ae8df0144 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 14:55:05 -0500 Subject: [PATCH 03/11] fix CI Signed-off-by: Cassandra Coyle --- .../main/java/io/dapr/utils/NetworkUtils.java | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java index 3d4a8cacea..0d06f33884 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -63,7 +63,8 @@ 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://)(?" + private static final String GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART = + "(?dns://)(?" + GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)?/"; private static final String GRPC_ENDPOINT_PARAM_REGEX_PART = "(\\?(?tls\\=((true)|(false))))?"; @@ -127,26 +128,27 @@ public static ManagedChannel buildGrpcManagedChannel(Properties properties, Clie String clientCertPath = settings.tlsCertPath; ManagedChannelBuilder builder = ManagedChannelBuilder.forTarget(settings.endpoint); - if (clientCertPath != null && clientKeyPath != null) { - try { - InputStream clientCertInputStream = new FileInputStream(clientCertPath); - InputStream clientKeyInputStream = new FileInputStream(clientKeyPath); + if (clientCertPath != null && clientKeyPath != null) { + try ( + InputStream clientCertInputStream = new FileInputStream(clientCertPath); + InputStream clientKeyInputStream = new FileInputStream(clientKeyPath) + ) { ChannelCredentials credentials = TlsChannelCredentials.newBuilder() .keyManager(clientCertInputStream, clientKeyInputStream) .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"), e); } - } else if (!settings.secure) { - builder = builder.usePlaintext(); } builder.userAgent(Version.getSdkVersion()); + if (!settings.secure) { + builder = builder.usePlaintext(); + } if (interceptors != null && interceptors.length > 0) { builder = builder.intercept(interceptors); } @@ -173,6 +175,7 @@ static GrpcEndpointSettings parse(Properties properties) { String clientKeyPath = properties.getValue(GRPC_TLS_KEY_PATH); String clientCertPath = properties.getValue(GRPC_TLS_CERT_PATH); + boolean secure = false; String grpcEndpoint = properties.getValue(GRPC_ENDPOINT); if ((grpcEndpoint != null) && !grpcEndpoint.isEmpty()) { @@ -208,7 +211,13 @@ static GrpcEndpointSettings parse(Properties properties) { var authorityEndpoint = matcher.group("authorityEndpoint"); if (authorityEndpoint != null) { - return new GrpcEndpointSettings(String.format("dns://%s/%s:%d", authorityEndpoint, address, port), secure, clientKeyPath, clientCertPath); + return new GrpcEndpointSettings( + String.format( + "dns://%s/%s:%d", + authorityEndpoint, + address, + port + ), secure, clientKeyPath, clientCertPath); } var socket = matcher.group("socket"); @@ -222,8 +231,13 @@ static GrpcEndpointSettings parse(Properties properties) { } } - return new GrpcEndpointSettings(String.format("dns:///%s:%d", address, port), secure, clientKeyPath, clientCertPath); + return new GrpcEndpointSettings(String.format( + "dns:///%s:%d", + address, + port + ), secure, clientKeyPath, clientCertPath); } + } private static void callWithRetry(Runnable function, long retryTimeoutMilliseconds) throws InterruptedException { From 19317ed8b9d5cedac903bb38bc9f6108e22534ac Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 14:59:38 -0500 Subject: [PATCH 04/11] add back else if Signed-off-by: Cassandra Coyle --- sdk/src/main/java/io/dapr/utils/NetworkUtils.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java index 0d06f33884..3c88b57bf8 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -142,13 +142,12 @@ public static ManagedChannel buildGrpcManagedChannel(Properties properties, Clie throw new DaprException( new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR").setMessage("Failed to create TLS credentials"), e); } + } else if (!settings.secure) { + builder = builder.usePlaintext(); } builder.userAgent(Version.getSdkVersion()); - if (!settings.secure) { - builder = builder.usePlaintext(); - } if (interceptors != null && interceptors.length > 0) { builder = builder.intercept(interceptors); } From 06b1722bd7922ebff02bf388e153137f97bc18cc Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 15:09:36 -0500 Subject: [PATCH 05/11] channel cleanup Signed-off-by: Cassandra Coyle --- .../java/io/dapr/utils/NetworkUtilsTest.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java index d9eb4c18d9..7d0234795f 100644 --- a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java +++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java @@ -4,6 +4,7 @@ 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; @@ -17,7 +18,9 @@ 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; @@ -29,6 +32,7 @@ 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 { @@ -78,9 +82,20 @@ public void tearDown() { } } + @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()); @@ -90,6 +105,7 @@ public void testBuildGrpcManagedChannel() { 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()); @@ -99,6 +115,7 @@ public void testBuildGrpcManagedChannel_httpEndpointNoPort() { 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()); @@ -108,6 +125,7 @@ public void testBuildGrpcManagedChannel_httpEndpointWithPort() { 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()); @@ -117,6 +135,7 @@ public void testBuildGrpcManagedChannel_httpsEndpointNoPort() { 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()); @@ -140,6 +159,7 @@ public void testBuildGrpcManagedChannelWithTls() throws Exception { )); channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); Assertions.assertEquals(expectedAuthority, channel.authority()); } finally { @@ -167,6 +187,7 @@ public void testBuildGrpcManagedChannelWithTlsAndEndpoint() throws Exception { )); channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); Assertions.assertEquals("example.com:443", channel.authority()); } finally { certFile.delete(); @@ -212,8 +233,9 @@ public void testBuildGrpcManagedChannelWithTlsAndUnixSocket() throws Exception { // 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.assertEquals("", channel.authority()); + 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")); @@ -243,6 +265,7 @@ public void testBuildGrpcManagedChannelWithTlsAndDnsAuthority() throws Exception )); channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); Assertions.assertEquals("example.com:443", channel.authority()); } finally { certFile.delete(); From 462fe4df2bc23ef6a2137b5ff19df89ef6b91a1d Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 15:40:36 -0500 Subject: [PATCH 06/11] add root ca support Signed-off-by: Cassandra Coyle --- .../main/java/io/dapr/config/Properties.java | 9 + .../main/java/io/dapr/utils/NetworkUtils.java | 40 ++++- .../java/io/dapr/utils/NetworkUtilsTest.java | 161 ++++++++++++++++++ 3 files changed, 201 insertions(+), 9 deletions(-) diff --git a/sdk/src/main/java/io/dapr/config/Properties.java b/sdk/src/main/java/io/dapr/config/Properties.java index 2ceaecbc24..b053e7c067 100644 --- a/sdk/src/main/java/io/dapr/config/Properties.java +++ b/sdk/src/main/java/io/dapr/config/Properties.java @@ -117,6 +117,15 @@ public class Properties { "dapr.grpc.tls.key.path", "DAPR_GRPC_TLS_KEY_PATH", null); + + /** + * Path to the TLS CA certificate for gRPC communication after checking system property + * and environment variable. + */ + public static final Property GRPC_TLS_CA_PATH = new StringProperty( + "dapr.grpc.tls.ca.path", + "DAPR_GRPC_TLS_CA_PATH", + null); /** * 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 3c88b57bf8..0441ca06d2 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -35,6 +35,7 @@ import static io.dapr.config.Properties.GRPC_PORT; import static io.dapr.config.Properties.GRPC_TLS_CERT_PATH; import static io.dapr.config.Properties.GRPC_TLS_KEY_PATH; +import static io.dapr.config.Properties.GRPC_TLS_CA_PATH; import static io.dapr.config.Properties.SIDECAR_IP; /** @@ -126,21 +127,40 @@ public static ManagedChannel buildGrpcManagedChannel(Properties properties, Clie 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 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() - .keyManager(clientCertInputStream, clientKeyInputStream) + .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"), e); + new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR") + .setMessage("Failed to create TLS credentials with CA cert"), e); } } else if (!settings.secure) { builder = builder.usePlaintext(); @@ -160,12 +180,14 @@ static final class GrpcEndpointSettings { final boolean secure; final String tlsPrivateKeyPath; final String tlsCertPath; + final String tlsCaPath; - private GrpcEndpointSettings(String endpoint, boolean secure, String tlsPrivateKeyPath, String tlsCertPath) { + 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) { @@ -173,7 +195,7 @@ static GrpcEndpointSettings parse(Properties properties) { 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); @@ -216,17 +238,17 @@ static GrpcEndpointSettings parse(Properties properties) { authorityEndpoint, address, port - ), secure, clientKeyPath, clientCertPath); + ), secure, clientKeyPath, clientCertPath, caCertPath); } var socket = matcher.group("socket"); if (socket != null) { - return new GrpcEndpointSettings(socket, secure, clientKeyPath, clientCertPath); + return new GrpcEndpointSettings(socket, secure, clientKeyPath, clientCertPath, caCertPath); } var vsocket = matcher.group("vsocket"); if (vsocket != null) { - return new GrpcEndpointSettings(vsocket, secure, clientKeyPath, clientCertPath); + return new GrpcEndpointSettings(vsocket, secure, clientKeyPath, clientCertPath, caCertPath); } } @@ -234,7 +256,7 @@ static GrpcEndpointSettings parse(Properties properties) { "dns:///%s:%d", address, port - ), secure, clientKeyPath, clientCertPath); + ), 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 7d0234795f..09594bfa88 100644 --- a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java +++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java @@ -1,3 +1,16 @@ +/* + * 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; @@ -273,6 +286,98 @@ public void testBuildGrpcManagedChannelWithTlsAndDnsAuthority() throws Exception } } + @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); @@ -353,4 +458,60 @@ private static void testGrpcEndpointParsingErrorScenario(String grpcEndpointEnvV // 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(); + } + } } From b81c32c042a33c2c63ee4840c8c08e2fb9aebce3 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 15:44:45 -0500 Subject: [PATCH 07/11] checkstyles Signed-off-by: Cassandra Coyle --- sdk/src/main/java/io/dapr/utils/NetworkUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java index 0441ca06d2..b9cccc6489 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -33,9 +33,9 @@ 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_KEY_PATH; -import static io.dapr.config.Properties.GRPC_TLS_CA_PATH; import static io.dapr.config.Properties.SIDECAR_IP; /** @@ -182,7 +182,8 @@ static final class GrpcEndpointSettings { final String tlsCertPath; final String tlsCaPath; - private GrpcEndpointSettings(String endpoint, boolean secure, String tlsPrivateKeyPath, String tlsCertPath, String tlsCaPath) { + private GrpcEndpointSettings( + String endpoint, boolean secure, String tlsPrivateKeyPath, String tlsCertPath, String tlsCaPath) { this.endpoint = endpoint; this.secure = secure; this.tlsPrivateKeyPath = tlsPrivateKeyPath; From 3f1879ec91e42651384d378ae4b8da045a1cd5e3 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 15:55:03 -0500 Subject: [PATCH 08/11] add insecure Signed-off-by: Cassandra Coyle --- .../main/java/io/dapr/config/Properties.java | 15 ++++- .../main/java/io/dapr/utils/NetworkUtils.java | 12 ++++ .../java/io/dapr/utils/NetworkUtilsTest.java | 56 +++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/java/io/dapr/config/Properties.java b/sdk/src/main/java/io/dapr/config/Properties.java index b053e7c067..07b5069c4c 100644 --- a/sdk/src/main/java/io/dapr/config/Properties.java +++ b/sdk/src/main/java/io/dapr/config/Properties.java @@ -119,14 +119,23 @@ public class Properties { null); /** - * Path to the TLS CA certificate for gRPC communication after checking system property - * and environment variable. + * 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); - + + /** + * Force insecure (plaintext) mode for gRPC communication, regardless of other TLS settings. + * This should only be used for testing or in secure environments. + */ + public static final Property GRPC_INSECURE = new BooleanProperty( + "dapr.grpc.insecure", + "DAPR_GRPC_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 b9cccc6489..7a23ee652d 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -33,6 +33,7 @@ import static io.dapr.config.Properties.GRPC_ENDPOINT; import static io.dapr.config.Properties.GRPC_PORT; +import static io.dapr.config.Properties.GRPC_INSECURE; 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_KEY_PATH; @@ -125,6 +126,17 @@ public static void waitForSocket(String host, int port, int timeoutInMillisecond public static ManagedChannel buildGrpcManagedChannel(Properties properties, ClientInterceptor... interceptors) { var settings = GrpcEndpointSettings.parse(properties); + boolean forceInsecure = properties.getValue(GRPC_INSECURE); + if (forceInsecure) { + ManagedChannelBuilder builder = ManagedChannelBuilder.forTarget(settings.endpoint) + .usePlaintext(); + builder.userAgent(Version.getSdkVersion()); + if (interceptors != null && interceptors.length > 0) { + builder = builder.intercept(interceptors); + } + return builder.build(); + } + String clientKeyPath = settings.tlsPrivateKeyPath; String clientCertPath = settings.tlsCertPath; String caCertPath = settings.tlsCaPath; diff --git a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java index 09594bfa88..06294bf1f5 100644 --- a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java +++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java @@ -514,4 +514,60 @@ public void testBuildGrpcManagedChannelWithCaCertAndDnsAuthority() throws Except caCertFile.delete(); } } + + @Test + public void testBuildGrpcManagedChannelWithForceInsecure() throws Exception { + 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); + + // Force insecure overrides all TLS settings + var properties = new Properties(Map.of( + Properties.GRPC_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(), "https://example.com:443" // Even with HTTPS + )); + + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + + // Verify the channel is created with the correct authority + Assertions.assertEquals("example.com:443", channel.authority()); + + Assertions.assertFalse(channel.isTerminated(), "Channel should be active"); + } finally { + caCertFile.delete(); + clientCertFile.delete(); + clientKeyFile.delete(); + } + } + + @Test + public void testBuildGrpcManagedChannelWithInsecureOnly() { + // Test insecure mode with no TLS settings + var properties = new Properties(Map.of( + Properties.GRPC_INSECURE.getName(), "true" + )); + + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + + // Verify the channel is created with the default authority + String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); + Assertions.assertEquals(expectedAuthority, channel.authority()); + + // Verify the channel is active + Assertions.assertFalse(channel.isTerminated(), "Channel should be active"); + } } From 04f7924fa32925256db194e19cee60fb732e4500 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 15:56:07 -0500 Subject: [PATCH 09/11] fix checkstyles Signed-off-by: Cassandra Coyle --- sdk/src/main/java/io/dapr/utils/NetworkUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java index 7a23ee652d..743ed869dd 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -32,8 +32,8 @@ 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_INSECURE; +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_KEY_PATH; From 731bc617647814951b6367599bb14b45344f3665 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 16:21:03 -0500 Subject: [PATCH 10/11] use InsecureTrustManagerFactory Signed-off-by: Cassandra Coyle --- sdk/pom.xml | 5 +++ .../main/java/io/dapr/config/Properties.java | 9 ++-- .../main/java/io/dapr/utils/NetworkUtils.java | 29 +++++++++---- .../java/io/dapr/utils/NetworkUtilsTest.java | 43 ++++++++++++++----- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/sdk/pom.xml b/sdk/pom.xml index d4393275df..14cb9e08d5 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -144,6 +144,11 @@ 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 07b5069c4c..98cccf2c48 100644 --- a/sdk/src/main/java/io/dapr/config/Properties.java +++ b/sdk/src/main/java/io/dapr/config/Properties.java @@ -128,12 +128,13 @@ public class Properties { null); /** - * Force insecure (plaintext) mode for gRPC communication, regardless of other TLS settings. + * 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_INSECURE = new BooleanProperty( - "dapr.grpc.insecure", - "DAPR_GRPC_INSECURE", + public static final Property GRPC_TLS_INSECURE = new BooleanProperty( + "dapr.grpc.tls.insecure", + "DAPR_GRPC_TLS_INSECURE", false); /** diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java index 743ed869dd..522b3e5d7c 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -22,6 +22,9 @@ 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; @@ -32,10 +35,10 @@ import java.util.regex.Pattern; import static io.dapr.config.Properties.GRPC_ENDPOINT; -import static io.dapr.config.Properties.GRPC_INSECURE; 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; @@ -126,15 +129,23 @@ public static void waitForSocket(String host, int port, int timeoutInMillisecond public static ManagedChannel buildGrpcManagedChannel(Properties properties, ClientInterceptor... interceptors) { var settings = GrpcEndpointSettings.parse(properties); - boolean forceInsecure = properties.getValue(GRPC_INSECURE); - if (forceInsecure) { - ManagedChannelBuilder builder = ManagedChannelBuilder.forTarget(settings.endpoint) - .usePlaintext(); - builder.userAgent(Version.getSdkVersion()); - if (interceptors != null && interceptors.length > 0) { - builder = builder.intercept(interceptors); + 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); } - return builder.build(); } String clientKeyPath = settings.tlsPrivateKeyPath; diff --git a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java index 06294bf1f5..9ee857301b 100644 --- a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java +++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java @@ -516,7 +516,26 @@ public void testBuildGrpcManagedChannelWithCaCertAndDnsAuthority() throws Except } @Test - public void testBuildGrpcManagedChannelWithForceInsecure() throws Exception { + public void testBuildGrpcManagedChannelWithInsecureTls() throws Exception { + // Test insecure TLS mode with HTTPS endpoint + var properties = new Properties(Map.of( + Properties.GRPC_TLS_INSECURE.getName(), "true", + Properties.GRPC_ENDPOINT.getName(), "https://example.com:443" // Using HTTPS to ensure TLS is used + )); + + 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(); @@ -530,13 +549,14 @@ public void testBuildGrpcManagedChannelWithForceInsecure() throws Exception { writeCertificateToFile(clientCert, clientCertFile); writePrivateKeyToFile(clientKeyPair, clientKeyFile); - // Force insecure overrides all TLS settings + // 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_INSECURE.getName(), "true", + 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(), "https://example.com:443" // Even with HTTPS + Properties.GRPC_ENDPOINT.getName(), "https://example.com:443" )); channel = NetworkUtils.buildGrpcManagedChannel(properties); @@ -545,6 +565,7 @@ public void testBuildGrpcManagedChannelWithForceInsecure() throws Exception { // 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(); @@ -554,20 +575,20 @@ public void testBuildGrpcManagedChannelWithForceInsecure() throws Exception { } @Test - public void testBuildGrpcManagedChannelWithInsecureOnly() { - // Test insecure mode with no TLS settings + 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_INSECURE.getName(), "true" + 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 default authority - String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); - Assertions.assertEquals(expectedAuthority, channel.authority()); + // Verify the channel is created with the correct authority + Assertions.assertEquals("example.com:443", channel.authority()); - // Verify the channel is active + // Verify the channel is active and using TLS (not plaintext) Assertions.assertFalse(channel.isTerminated(), "Channel should be active"); } } From 3bf1e7039146b614c9d6ce121b174dc9f368fd18 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 14 May 2025 16:25:22 -0500 Subject: [PATCH 11/11] fix test Signed-off-by: Cassandra Coyle --- sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java index 9ee857301b..2b4929abd0 100644 --- a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java +++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java @@ -517,10 +517,10 @@ public void testBuildGrpcManagedChannelWithCaCertAndDnsAuthority() throws Except @Test public void testBuildGrpcManagedChannelWithInsecureTls() throws Exception { - // Test insecure TLS mode with HTTPS endpoint + // Test insecure TLS mode with a secure endpoint var properties = new Properties(Map.of( Properties.GRPC_TLS_INSECURE.getName(), "true", - Properties.GRPC_ENDPOINT.getName(), "https://example.com:443" // Using HTTPS to ensure TLS is used + Properties.GRPC_ENDPOINT.getName(), "dns:///example.com:443?tls=true" )); channel = NetworkUtils.buildGrpcManagedChannel(properties); @@ -556,7 +556,7 @@ public void testBuildGrpcManagedChannelWithInsecureTlsAndMtls() throws Exception 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(), "https://example.com:443" + Properties.GRPC_ENDPOINT.getName(), "dns:///example.com:443?tls=true" )); channel = NetworkUtils.buildGrpcManagedChannel(properties);