diff --git a/src/main/java/com/microsoft/azure/functions/worker/Application.java b/src/main/java/com/microsoft/azure/functions/worker/Application.java index fb1c8095..554e0dbd 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/Application.java +++ b/src/main/java/com/microsoft/azure/functions/worker/Application.java @@ -1,8 +1,8 @@ package com.microsoft.azure.functions.worker; -import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; +import java.util.Locale; import java.util.logging.*; import javax.annotation.*; @@ -157,7 +157,8 @@ public int getPort() { return this.port; } - public String getUri() { + @Override + public String getFunctionsUri() { return this.uri; } @@ -185,17 +186,44 @@ private boolean isCommandlineValid() { private String parseUri(String uri) throws ParseException { try { - URL url = new URL(uri); - url.toURI(); - this.host = url.getHost(); - this.port = url.getPort(); + URI parsedUri = new URI(uri); + String host = parsedUri.getHost(); + String scheme = parsedUri.getScheme(); + int port = parsedUri.getPort(); + + if (scheme == null || scheme.isEmpty()) { + throw new IllegalArgumentException("URI scheme is missing"); + } + + switch (scheme.toLowerCase(Locale.ROOT)) { + case "http": + if (port == -1) { + port = 80; + } + break; + case "https": + if (port == -1) { + port = 443; + } + break; + default: + throw new IllegalArgumentException("Unsupported URI scheme"); + } + + if (host == null || host.isEmpty()) { + throw new IllegalArgumentException("URI host is missing"); + } + if (port < 1 || port > 65535) { throw new IndexOutOfBoundsException("port number out of range"); } + + this.host = host; + this.port = port; return uri; - } catch (MalformedURLException | URISyntaxException | IndexOutOfBoundsException e) { + } catch (URISyntaxException | IllegalArgumentException | IndexOutOfBoundsException e) { throw new ParseException(String.format( - "Error parsing URI \"%s\". Please provide a valid URI", uri)); + "Error parsing URI \"%s\". Please provide a valid http or https URI", uri)); } } diff --git a/src/main/java/com/microsoft/azure/functions/worker/IApplication.java b/src/main/java/com/microsoft/azure/functions/worker/IApplication.java index aebbc583..9417c060 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/IApplication.java +++ b/src/main/java/com/microsoft/azure/functions/worker/IApplication.java @@ -5,4 +5,7 @@ public interface IApplication { String getHost(); int getPort(); Integer getMaxMessageSize(); + default String getFunctionsUri() { + return null; + } } diff --git a/src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java b/src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java index 540c078d..478102b2 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java +++ b/src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java @@ -5,7 +5,7 @@ import java.util.concurrent.atomic.*; import java.util.function.*; import java.util.logging.*; -import javax.annotation.*; +import javax.annotation.PostConstruct; import io.grpc.*; import io.grpc.stub.*; @@ -24,7 +24,12 @@ public class JavaWorkerClient implements AutoCloseable { public JavaWorkerClient(IApplication app) { WorkerLogManager.initialize(this, app.logToConsole()); - ManagedChannelBuilder chanBuilder = ManagedChannelBuilder.forAddress(app.getHost(), app.getPort()).usePlaintext(); + ManagedChannelBuilder chanBuilder = ManagedChannelBuilder.forAddress(app.getHost(), app.getPort()); + if (useTransportSecurity(app.getFunctionsUri())) { + chanBuilder.useTransportSecurity(); + } else { + chanBuilder.usePlaintext(); + } chanBuilder.maxInboundMessageSize(Integer.MAX_VALUE); this.channel = chanBuilder.build(); @@ -138,4 +143,12 @@ private synchronized void send(String requestId, MessageHandler marshaller private final AtomicReference peer; private final Map>> handlerSuppliers; private final ClassLoaderProvider classPathProvider; + + /** + * @param functionsUri Host endpoint URI, or null for legacy startup args that only provide host and port. + */ + static boolean useTransportSecurity(String functionsUri) { + return functionsUri != null + && functionsUri.regionMatches(true, 0, "https://", 0, "https://".length()); + } } diff --git a/src/test/java/com/microsoft/azure/functions/worker/ApplicationTest.java b/src/test/java/com/microsoft/azure/functions/worker/ApplicationTest.java new file mode 100644 index 00000000..7f468e60 --- /dev/null +++ b/src/test/java/com/microsoft/azure/functions/worker/ApplicationTest.java @@ -0,0 +1,135 @@ +package com.microsoft.azure.functions.worker; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ApplicationTest { + @Test + public void legacyImplementationsDefaultFunctionsUriToNull() { + IApplication application = new LegacyApplication(); + + assertNull(application.getFunctionsUri()); + } + + @Test + public void prefixedStartupArgumentsTakePrecedenceOverLegacyValues() throws Exception { + Application application = createApplication( + "--functions-uri", "https://functions.example:8443", + "--host", "legacy.example", + "--port", "7071", + "--functions-worker-id", "prefixed-worker", + "--workerId", "legacy-worker", + "--functions-request-id", "prefixed-request", + "--requestId", "legacy-request", + "--functions-grpc-max-message-length", "2048", + "--grpcMaxMessageLength", "1024"); + + assertTrue(isCommandLineValid(application)); + assertEquals("https://functions.example:8443", application.getFunctionsUri()); + assertEquals("functions.example", application.getHost()); + assertEquals(8443, application.getPort()); + assertEquals(Integer.valueOf(2048), application.getMaxMessageSize()); + assertEquals("prefixed-worker", invokePrivateStringAccessor(application, "getWorkerId")); + assertEquals("prefixed-request", invokePrivateStringAccessor(application, "getRequestId")); + } + + @Test + public void legacyHostAndPortRemainAvailableWhenFunctionsUriIsAbsent() throws Exception { + Application application = createApplication( + "--host", "localhost", + "--port", "7001", + "--workerId", "legacy-worker", + "--requestId", "legacy-request", + "--grpcMaxMessageLength", "4096"); + + assertTrue(isCommandLineValid(application)); + assertNull(application.getFunctionsUri()); + assertEquals("localhost", application.getHost()); + assertEquals(7001, application.getPort()); + assertEquals(Integer.valueOf(4096), application.getMaxMessageSize()); + } + + @Test + public void httpsFunctionsUriDefaultsToPort443WhenPortIsMissing() throws Exception { + Application application = createApplication( + "--functions-uri", "https://functions.example/path", + "--functions-worker-id", "prefixed-worker", + "--functions-request-id", "prefixed-request", + "--functions-grpc-max-message-length", "2048"); + + assertTrue(isCommandLineValid(application)); + assertEquals("functions.example", application.getHost()); + assertEquals(443, application.getPort()); + } + + @Test + public void httpFunctionsUriDefaultsToPort80WhenPortIsMissing() throws Exception { + Application application = createApplication( + "--functions-uri", "http://functions.example/path", + "--functions-worker-id", "prefixed-worker", + "--functions-request-id", "prefixed-request", + "--functions-grpc-max-message-length", "2048"); + + assertTrue(isCommandLineValid(application)); + assertEquals("functions.example", application.getHost()); + assertEquals(80, application.getPort()); + } + + @Test + public void unsupportedFunctionsUriSchemeFailsCommandLineValidation() throws Exception { + Application application = createApplication( + "--functions-uri", "unix:///tmp/functions.sock", + "--functions-worker-id", "prefixed-worker", + "--functions-request-id", "prefixed-request", + "--functions-grpc-max-message-length", "2048"); + + assertFalse(isCommandLineValid(application)); + } + + private static Application createApplication(String... args) throws Exception { + Constructor constructor = Application.class.getDeclaredConstructor(String[].class); + constructor.setAccessible(true); + return constructor.newInstance((Object) args); + } + + private static boolean isCommandLineValid(Application application) throws Exception { + Method method = Application.class.getDeclaredMethod("isCommandlineValid"); + method.setAccessible(true); + return (boolean) method.invoke(application); + } + + private static String invokePrivateStringAccessor(Application application, String methodName) throws Exception { + Method method = Application.class.getDeclaredMethod(methodName); + method.setAccessible(true); + return (String) method.invoke(application); + } + + private static final class LegacyApplication implements IApplication { + @Override + public boolean logToConsole() { + return false; + } + + @Override + public String getHost() { + return "localhost"; + } + + @Override + public int getPort() { + return 7071; + } + + @Override + public Integer getMaxMessageSize() { + return null; + } + } +} diff --git a/src/test/java/com/microsoft/azure/functions/worker/JavaWorkerClientTest.java b/src/test/java/com/microsoft/azure/functions/worker/JavaWorkerClientTest.java new file mode 100644 index 00000000..7309a567 --- /dev/null +++ b/src/test/java/com/microsoft/azure/functions/worker/JavaWorkerClientTest.java @@ -0,0 +1,28 @@ +package com.microsoft.azure.functions.worker; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JavaWorkerClientTest { + @Test + public void legacyStartupDefaultsToPlaintextTransport() { + assertFalse(JavaWorkerClient.useTransportSecurity(null)); + } + + @Test + public void httpFunctionsUriUsesPlaintextTransport() { + assertFalse(JavaWorkerClient.useTransportSecurity("http://functions.example:7071")); + } + + @Test + public void httpsFunctionsUriUsesTransportSecurity() { + assertTrue(JavaWorkerClient.useTransportSecurity("https://functions.example:8443")); + } + + @Test + public void httpsFunctionsUriSchemeIsCaseInsensitive() { + assertTrue(JavaWorkerClient.useTransportSecurity("HTTPS://functions.example:8443")); + } +} diff --git a/src/test/java/com/microsoft/azure/functions/worker/functional/tests/GrpcTransportTest.java b/src/test/java/com/microsoft/azure/functions/worker/functional/tests/GrpcTransportTest.java new file mode 100644 index 00000000..95c6d813 --- /dev/null +++ b/src/test/java/com/microsoft/azure/functions/worker/functional/tests/GrpcTransportTest.java @@ -0,0 +1,141 @@ +package com.microsoft.azure.functions.worker.functional.tests; + +import java.net.*; +import java.util.concurrent.*; +import javax.net.ssl.*; + +import com.microsoft.azure.functions.rpc.messages.*; +import com.microsoft.azure.functions.worker.test.utilities.*; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class GrpcTransportTest extends FunctionsTestBase { + private static final String RETURN_VALUE = "transport-ok"; + private static final String TRUSTSTORE_RESOURCE = "grpc-tls/localhost-truststore.p12"; + private static final String TRUSTSTORE_PASSWORD = "changeit"; + + public String ReturnStringFunction() { + return RETURN_VALUE; + } + + @Test + public void legacyPlaintextTransportStillWorks() throws Exception { + try (SkipTestingScope ignored = SkipTestingScope.enable(); + FunctionsTestHost host = new FunctionsTestHost()) { + InvocationResponse response = this.invokeReturnString(host, "plaintext-function", "plaintext-request"); + + assertEquals(TypedData.DataCase.STRING, response.getReturnValue().getDataCase()); + assertEquals(RETURN_VALUE, response.getReturnValue().getString()); + } + } + + @Test + public void trustedHttpsFunctionsUriConnectsToTlsHost() throws Exception { + try (SkipTestingScope ignored = SkipTestingScope.enable(); + TrustStoreScope ignoredTrustStore = TrustStoreScope.use(TRUSTSTORE_RESOURCE, TRUSTSTORE_PASSWORD, "PKCS12"); + FunctionsTestHost host = new FunctionsTestHost(FunctionsTestHost.ServerTransport.TLS, FunctionsTestHost.ClientTransport.HTTPS)) { + InvocationResponse response = this.invokeReturnString(host, "tls-function", "tls-request"); + + assertEquals(TypedData.DataCase.STRING, response.getReturnValue().getDataCase()); + assertEquals(RETURN_VALUE, response.getReturnValue().getString()); + } + } + + @Test + public void httpsFunctionsUriDoesNotDowngradeToPlaintextWhenTlsFails() { + ExecutionException exception = assertThrows(ExecutionException.class, () -> { + try (SkipTestingScope ignored = SkipTestingScope.enable(); + FunctionsTestHost ignoredHost = new FunctionsTestHost(FunctionsTestHost.ServerTransport.PLAINTEXT, FunctionsTestHost.ClientTransport.HTTPS)) { + } + }); + + assertTrue(hasCause(exception, SSLException.class), "Expected TLS failure but got: " + exception); + } + + private InvocationResponse invokeReturnString(FunctionsTestHost host, String functionId, String requestId) throws Exception { + this.loadFunction(host, functionId, "ReturnStringFunction"); + return host.call(requestId, functionId); + } + + private static boolean hasCause(Throwable throwable, Class type) { + Throwable current = throwable; + while (current != null) { + if (type.isInstance(current)) { + return true; + } + current = current.getCause(); + } + return false; + } + + private static final class TrustStoreScope implements AutoCloseable { + private final String originalTrustStore; + private final String originalTrustStorePassword; + private final String originalTrustStoreType; + + private TrustStoreScope(String originalTrustStore, String originalTrustStorePassword, String originalTrustStoreType) { + this.originalTrustStore = originalTrustStore; + this.originalTrustStorePassword = originalTrustStorePassword; + this.originalTrustStoreType = originalTrustStoreType; + } + + static TrustStoreScope use(String resourceName, String password, String storeType) { + String originalTrustStore = System.getProperty("javax.net.ssl.trustStore"); + String originalTrustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword"); + String originalTrustStoreType = System.getProperty("javax.net.ssl.trustStoreType"); + URL resource = GrpcTransportTest.class.getClassLoader().getResource(resourceName); + if (resource == null) { + throw new IllegalStateException("Missing TLS truststore resource: " + resourceName); + } + + try { + System.setProperty("javax.net.ssl.trustStore", new java.io.File(resource.toURI()).getAbsolutePath()); + } catch (URISyntaxException ex) { + throw new IllegalStateException("Invalid TLS truststore resource path: " + resourceName, ex); + } + System.setProperty("javax.net.ssl.trustStorePassword", password); + System.setProperty("javax.net.ssl.trustStoreType", storeType); + return new TrustStoreScope(originalTrustStore, originalTrustStorePassword, originalTrustStoreType); + } + + @Override + public void close() { + restore("javax.net.ssl.trustStore", this.originalTrustStore); + restore("javax.net.ssl.trustStorePassword", this.originalTrustStorePassword); + restore("javax.net.ssl.trustStoreType", this.originalTrustStoreType); + } + + private static void restore(String key, String value) { + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + } + } + + private static final class SkipTestingScope implements AutoCloseable { + private static final String SKIP_TESTING_PROPERTY = "azure.functions.worker.java.skip.testing"; + private final String originalValue; + + private SkipTestingScope(String originalValue) { + this.originalValue = originalValue; + } + + static SkipTestingScope enable() { + String originalValue = System.getProperty(SKIP_TESTING_PROPERTY); + System.setProperty(SKIP_TESTING_PROPERTY, "true"); + return new SkipTestingScope(originalValue); + } + + @Override + public void close() { + if (this.originalValue == null) { + System.clearProperty(SKIP_TESTING_PROPERTY); + } else { + System.setProperty(SKIP_TESTING_PROPERTY, this.originalValue); + } + } + } +} diff --git a/src/test/java/com/microsoft/azure/functions/worker/test/utilities/FunctionsTestHost.java b/src/test/java/com/microsoft/azure/functions/worker/test/utilities/FunctionsTestHost.java index 207f0634..78d73a03 100644 --- a/src/test/java/com/microsoft/azure/functions/worker/test/utilities/FunctionsTestHost.java +++ b/src/test/java/com/microsoft/azure/functions/worker/test/utilities/FunctionsTestHost.java @@ -1,8 +1,10 @@ package com.microsoft.azure.functions.worker.test.utilities; import java.io.*; -import java.net.ServerSocket; +import java.net.*; +import java.nio.file.*; import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.concurrent.locks.*; import java.util.function.*; @@ -14,15 +16,44 @@ import com.microsoft.azure.functions.worker.*; import com.microsoft.azure.functions.rpc.messages.*; import io.grpc.*; +import io.grpc.netty.shaded.io.grpc.netty.*; import io.grpc.stub.*; import org.apache.commons.lang3.tuple.*; public final class FunctionsTestHost implements AutoCloseable, IApplication { + public enum ClientTransport { + LEGACY, + HTTP, + HTTPS + } + + public enum ServerTransport { + PLAINTEXT, + TLS + } + + private static final int RESPONSE_TIMEOUT_SECONDS = 10; + private static final long RESPONSE_POLL_MILLIS = 100L; + private static final String TLS_RESOURCE_ROOT = "grpc-tls/"; + private static final String TLS_CERTIFICATE_RESOURCE = TLS_RESOURCE_ROOT + "localhost-cert.pem"; + private static final String TLS_PRIVATE_KEY_RESOURCE = TLS_RESOURCE_ROOT + "localhost-key.pem"; + private int port; public FunctionsTestHost() throws Exception { + this(ServerTransport.PLAINTEXT, ClientTransport.LEGACY); + } + + public FunctionsTestHost(ServerTransport serverTransport, ClientTransport clientTransport) throws Exception { + this.serverTransport = serverTransport; + this.clientTransport = clientTransport; this.port = populatePort(); - this.initializeServer(); - this.initializeClient(); + try { + this.initializeServer(); + this.initializeClient(); + } catch (Exception ex) { + this.closeQuietly(); + throw ex; + } } private final List list = Arrays.asList(55005, 5005); @@ -37,23 +68,42 @@ private int populatePort() { @PostConstruct private void initializeServer() throws IOException { - ServerBuilder builder = ServerBuilder.forPort(this.getPort()); + ServerBuilder builder = this.serverTransport == ServerTransport.TLS + ? NettyServerBuilder.forPort(this.getPort()) + .sslContext(GrpcSslContexts.forServer(getTlsResource(TLS_CERTIFICATE_RESOURCE), getTlsResource(TLS_PRIVATE_KEY_RESOURCE)).build()) + : ServerBuilder.forPort(this.getPort()); this.grpcHost = new HostGrpcImplementation(); this.server = builder.addService(this.grpcHost).build(); this.server.start(); } @PostConstruct - private void initializeClient() throws InterruptedException { + private void initializeClient() throws Exception { this.client = new JavaWorkerClient(this); - this.client.listen("java-worker-test", HostGrpcImplementation.ESTABLISH_REQID); - this.grpcHost.handleMessage(HostGrpcImplementation.ESTABLISH_REQID, m -> this.grpcHost.initWorker()); + this.listeningTask = this.client.listen("java-worker-test", HostGrpcImplementation.ESTABLISH_REQID); + this.grpcHost.handleMessageWithTimeout( + HostGrpcImplementation.ESTABLISH_REQID, + m -> this.grpcHost.initWorker(), + RESPONSE_TIMEOUT_SECONDS, + TimeUnit.SECONDS); } @Override public void close() throws Exception { - this.client.close(); - this.server.shutdownNow().awaitTermination(); + Exception closeException = null; + if (this.client != null) { + try { + this.client.close(); + } catch (Exception ex) { + closeException = ex; + } + } + if (this.server != null) { + this.server.shutdownNow().awaitTermination(15, TimeUnit.SECONDS); + } + if (closeException != null) { + throw closeException; + } } public void loadFunction(String id, String reflectionName, Map bindings) throws Exception { @@ -80,13 +130,52 @@ public final InvocationResponse call(String reqId, String funcId, Triple listeningTask; private String lastCallReqId = HostGrpcImplementation.LOADFUNC_REQID; + private void closeQuietly() { + try { + this.close(); + } catch (Exception ignored) { + } + } + + private static File getTlsResource(String resourcePath) { + URL resource = FunctionsTestHost.class.getClassLoader().getResource(resourcePath); + if (resource == null) { + throw new IllegalStateException("Missing test TLS resource: " + resourcePath); + } + try { + return Paths.get(resource.toURI()).toFile(); + } catch (URISyntaxException ex) { + throw new IllegalStateException("Invalid test TLS resource path: " + resourcePath, ex); + } + } + + private void throwIfListeningFailed() throws ExecutionException, InterruptedException { + if (this.listeningTask != null && this.listeningTask.isDone()) { + this.listeningTask.get(); + } + } + @ThreadSafe private class HostGrpcImplementation extends FunctionRpcGrpc.FunctionRpcImplBase { @@ -107,25 +196,50 @@ private void setResponse(String requestId, StreamingMessage value, StreamObserve this.getResponseCondition(requestId).signal(); } - void handleMessage(String requestId, Function handler) throws InterruptedException { + void handleMessage(String requestId, Function handler) throws Exception { this.lock.lock(); try { - if (this.responder.get(requestId) == null) { - this.getResponseCondition(requestId).await(); - } - StreamingMessage message = this.respValue.get(requestId); - StreamingMessage response = null; - if (handler != null) { - response = handler.apply(message); + while (this.responder.get(requestId) == null) { + this.getResponseCondition(requestId).await(RESPONSE_POLL_MILLIS, TimeUnit.MILLISECONDS); + FunctionsTestHost.this.throwIfListeningFailed(); } - if (response != null) { - this.responder.get(requestId).onNext(response); + this.respondToMessage(requestId, handler); + } finally { + this.lock.unlock(); + } + } + + void handleMessageWithTimeout(String requestId, Function handler, + long timeout, TimeUnit unit) throws Exception { + this.lock.lock(); + try { + long deadlineNanos = System.nanoTime() + unit.toNanos(timeout); + while (this.responder.get(requestId) == null) { + FunctionsTestHost.this.throwIfListeningFailed(); + long remainingNanos = deadlineNanos - System.nanoTime(); + if (remainingNanos <= 0) { + throw new TimeoutException("Timed out waiting for gRPC request " + requestId); + } + long waitMillis = Math.max(1L, Math.min(TimeUnit.NANOSECONDS.toMillis(remainingNanos), RESPONSE_POLL_MILLIS)); + this.getResponseCondition(requestId).await(waitMillis, TimeUnit.MILLISECONDS); } + this.respondToMessage(requestId, handler); } finally { this.lock.unlock(); } } + private void respondToMessage(String requestId, Function handler) { + StreamingMessage message = this.respValue.get(requestId); + StreamingMessage response = null; + if (handler != null) { + response = handler.apply(message); + } + if (response != null) { + this.responder.get(requestId).onNext(response); + } + } + private StreamingMessage initWorker() { WorkerInitRequest.Builder request = WorkerInitRequest.newBuilder().setHostVersion("2.0.0"); return StreamingMessage.newBuilder().setRequestId(INITWORKER_REQID).setWorkerInitRequest(request).build(); diff --git a/src/test/resources/grpc-tls/localhost-cert.pem b/src/test/resources/grpc-tls/localhost-cert.pem new file mode 100644 index 00000000..4d9d070b --- /dev/null +++ b/src/test/resources/grpc-tls/localhost-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHzCCAgegAwIBAgIUUT0dnmqskpKcIS3wt+Fq+obG5GEwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDUxMjE3MjExMFoXDTM2MDUw +OTE3MjExMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAmRbV1NPyunri/+EN14nfIQhyY7grIRmUS5XTvsJhVQMg +/vP0rZ1H1I8f7mzkmyfDsB0i5putmz01lAHD5/Wfa0JEhVgmnwK20RsufjsWhwpY +tVpbfzgY5LJILSwz5Qw//Q2VDFiFXK7plVkfnmwrutUQ4AXuk55IuLDHlHxAI7vt +v/MxxlCri+EeoZcmTTVkLeMvtTf7Pw3upc8PBNNDYJOJvld4DXxz4e93ZhFmQGck +QMVeGGPsL+jmhu78pdfW+TcwlEJTvKBZ8xYTGaQbks6ZwXslYvuL0mI7XEajTSrM +Pw7vJeaJH85fETtZNfz3/IR4or88pyQQN9Cv0fY3xQIDAQABo2kwZzAdBgNVHQ4E +FgQUQgk8tVR0g3rgA6MRktL39pScY9UwHwYDVR0jBBgwFoAUQgk8tVR0g3rgA6MR +ktL39pScY9UwDwYDVR0TAQH/BAUwAwEB/zAUBgNVHREEDTALgglsb2NhbGhvc3Qw +DQYJKoZIhvcNAQELBQADggEBAJg1IQoiIV8qoLpVLbY5pvgkeClmFQsVwFTB0rLS +lU4ZkVkoIiYowQnb10WFpGdyhfI+WhZcLev2Dmm7PJ9Jp0H0vC5U0ebNLzMJdwjr +HjJcIuueYJDJ3YelEIzO2qa4R977PCvBPLHEi7/KRdSbAOz6lf6Yp+OPe46gJHP1 +XwaKt628oPd7A4FdBTF7lUfcLdPUnW9Glhg7VfB6aX6o5+Mup+QEt96xlXg6Ua2L +xYw8AJ75rAmQLwu05uzyaTGlHE7BXtsP+bEBAz4CXVv1z5i1UJfB6N/aWB1VOHKN +2r1x1fMUCNLpsoTdtmkwAmkmzx4bfyWtIIK+hAxTm7bmjGk= +-----END CERTIFICATE----- diff --git a/src/test/resources/grpc-tls/localhost-key.pem b/src/test/resources/grpc-tls/localhost-key.pem new file mode 100644 index 00000000..976e744b --- /dev/null +++ b/src/test/resources/grpc-tls/localhost-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCZFtXU0/K6euL/ +4Q3Xid8hCHJjuCshGZRLldO+wmFVAyD+8/StnUfUjx/ubOSbJ8OwHSLmm62bPTWU +AcPn9Z9rQkSFWCafArbRGy5+OxaHCli1Wlt/OBjkskgtLDPlDD/9DZUMWIVcrumV +WR+ebCu61RDgBe6Tnki4sMeUfEAju+2/8zHGUKuL4R6hlyZNNWQt4y+1N/s/De6l +zw8E00Ngk4m+V3gNfHPh73dmEWZAZyRAxV4YY+wv6OaG7vyl19b5NzCUQlO8oFnz +FhMZpBuSzpnBeyVi+4vSYjtcRqNNKsw/Du8l5okfzl8RO1k1/Pf8hHiivzynJBA3 +0K/R9jfFAgMBAAECggEANvM/Zdl+MvmRKY+6zDcs5EqH5Mtij8sCs+7fxoU3MrCg +02L13Kur8Nw+9fIYTKkFUN3kfSo8MpDR/oJzs3sy8ekjd0mg80qiHITJN342I9rO +5Km+Vffo14424iAPsJOpFEgfzAKqPA58waLv+omRWMrJ99+pN0uFhuXNfbrruudX +zgCMWtw7gqciTEOrcy2GLyKOClfg6AIn/9F1Z3yrQBrHW7TZjPhyHZi3PeIeXAz4 +hVjj5zHP8GI8P4O/kP6gFfB5x/1PXGErGOSl3HOieKse7JTMu7NCtgLEEshFAjOd +z4EsAGzJb0FdyH1eX9+WhsvVkbTTnek3fhBCMIJ/MQKBgQDLvpbTbRLdbxH6pDIx +I53jBpDU1x+QU5j1lvLZJtvdMnsJFA43zamSqwmIzuIAs9POzEGIP586TCr5PXBi +NrAz9mYZC1ye59ZFq0JVundbCWEO+YxuR5glgqjubWdfwFzFynCyuuZTqmPozGnY +wSOjIE7g0Vw6aJVXJq0eXG1EAwKBgQDAWlUSyXSVZ7INXFr+Dh0gd12HhQMYmxEz +twtXEfZTGwHqlxZOo0/G/LYVWPdIqkUw86aaKorlfgg5yzbJ6tfgxiE71W7VPiBR +X9Cefz16th2rQfM6Y1fszimV1Dpf3HU5TuN/ZK4tlEqsfK01yfjofPCjNL1c2BQr +sHYkUi5elwKBgBy8LZN2D7IRRyzdWYLarhrlwylxia8WSz1f47JCq8GfrACUxoiS +Rfc8jiSwYOmOczH4Vsm7h152fZ0XUDFZ2zII709a7d4vfmXnCH0Exm6dfQXapjar +fEbWDbNK1MiJXcw7h/d9KpzkLCEaK1d5regE13sXq/VE6MMY3lOo33Q3AoGAamsP +mh8+ktIV3fJ0nQ3t62JeqnVaayiPcc8ZRQi5AO12N/Vy7/rGTk7N5i2cUeVx9k02 +pSBYS/NYVbEqFLgKy16SUGoasXt3oc2iu62ls9hBvdf02x7PLEI7G5uY2CQ97oDI +uFhZTPo3/gnUQmgFf4pwD7tD8LPTJQCxvBKDeO0CgYEApeC2lxH4FekJEA4iIBG+ +InRXX20QCLqI2d4xMOHHDEWaulhDEkjs9HEErA+3HpkK5hOTsEIU6GENrpc3P2JY +iFaO/gJiFEErkXsSaDRRaROTKI27qA0JPWzrfDHH4G59qpVVH6KWIYJvWbsHHo49 +MSFfMNCXhE9polVRU6EyWb0= +-----END PRIVATE KEY----- diff --git a/src/test/resources/grpc-tls/localhost-truststore.p12 b/src/test/resources/grpc-tls/localhost-truststore.p12 new file mode 100644 index 00000000..cb4c21c1 Binary files /dev/null and b/src/test/resources/grpc-tls/localhost-truststore.p12 differ