From ba45eefa7e493dd40bce5e4e3f43cfb7043ab185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 3 Sep 2024 21:02:22 +0200 Subject: [PATCH] Support Jaeger TLS communication using registry --- .../quarkus/qe/OpenTelemetryJaegerTlsIT.java | 68 +++++++++++++++++++ .../security/certificate/Certificate.java | 7 ++ .../quarkus/test/bootstrap/JaegerService.java | 6 +- .../test/services/JaegerContainer.java | 5 ++ ...JaegerContainerManagedResourceBuilder.java | 6 ++ ...GenericDockerContainerManagedResource.java | 21 ++++++ 6 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 examples/jaeger/src/test/java/io/quarkus/qe/OpenTelemetryJaegerTlsIT.java diff --git a/examples/jaeger/src/test/java/io/quarkus/qe/OpenTelemetryJaegerTlsIT.java b/examples/jaeger/src/test/java/io/quarkus/qe/OpenTelemetryJaegerTlsIT.java new file mode 100644 index 000000000..e7360f42b --- /dev/null +++ b/examples/jaeger/src/test/java/io/quarkus/qe/OpenTelemetryJaegerTlsIT.java @@ -0,0 +1,68 @@ +package io.quarkus.qe; + +import static io.quarkus.test.services.containers.JaegerGenericDockerContainerManagedResource.CERTIFICATE_CONTEXT_KEY; +import static io.quarkus.test.services.containers.JaegerGenericDockerContainerManagedResource.JAEGER_CLIENT_CERT_CN; +import static io.restassured.RestAssured.given; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; + +import java.util.concurrent.TimeUnit; + +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.bootstrap.JaegerService; +import io.quarkus.test.bootstrap.Protocol; +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.security.certificate.Certificate; +import io.quarkus.test.security.certificate.PemClientCertificate; +import io.quarkus.test.services.JaegerContainer; +import io.quarkus.test.services.QuarkusApplication; + +@QuarkusScenario +public class OpenTelemetryJaegerTlsIT { + + private static final String SERVICE_NAME = "test-traced-service"; + private static final String CLIENT_ENDPOINT = "/client"; + private static final String OPERATION = "GET " + CLIENT_ENDPOINT; + + @JaegerContainer(tls = true) + static JaegerService jaeger = new JaegerService(); + + @QuarkusApplication + static RestService app = new RestService() + .withProperty("quarkus.otel.exporter.otlp.traces.tls-configuration-name", "jaeger") + .withProperty("quarkus.otel.exporter.otlp.traces.endpoint", () -> jaeger.getCollectorUrl(Protocol.HTTPS)) + .withProperty("quarkus.tls.jaeger.key-store.pem.0.cert", () -> getClientCert().certPath()) + .withProperty("quarkus.tls.jaeger.key-store.pem.0.key", () -> getClientCert().keyPath()) + .withProperty("quarkus.tls.jaeger.trust-store.pem.certs", () -> getClientCert().truststorePath()) + .withProperty("quarkus.tls.jaeger.trust-all", "false"); // it's default, but let's make it clear + + private static PemClientCertificate getClientCert() { + return (PemClientCertificate) jaeger. getPropertyFromContext(CERTIFICATE_CONTEXT_KEY) + .getClientCertificateByCn(JAEGER_CLIENT_CERT_CN); + } + + @Test + public void shouldUpdateJaegerAsTracer() { + app.given() + .get(CLIENT_ENDPOINT) + .then() + .statusCode(HttpStatus.SC_OK) + .body(equalTo("I'm a client")); + + await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> given() + .queryParam("service", SERVICE_NAME) + .queryParam("operation", OPERATION) + .get(jaeger.getTraceUrl()) + .then() + .statusCode(HttpStatus.SC_OK) + .body("data", hasSize(1)) + .body("data[0].spans", hasSize(1)) + .body("data[0].spans.operationName", hasItems(OPERATION)) + .body("data[0].spans.logs.fields.value.flatten()", hasItems("ClientResource called"))); + } +} diff --git a/quarkus-test-core/src/main/java/io/quarkus/test/security/certificate/Certificate.java b/quarkus-test-core/src/main/java/io/quarkus/test/security/certificate/Certificate.java index f12c11644..24f146acf 100644 --- a/quarkus-test-core/src/main/java/io/quarkus/test/security/certificate/Certificate.java +++ b/quarkus-test-core/src/main/java/io/quarkus/test/security/certificate/Certificate.java @@ -85,6 +85,13 @@ static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format null, false, null, false)); } + static Certificate.PemCertificate of(String prefix, io.quarkus.test.services.Certificate.Format format, String password, + boolean tlsRegistryEnabled, String tlsConfigName, ClientCertificateRequest[] clientCertRequests) { + return ofInterchangeable(new CertificateOptions(prefix, format, password, false, false, false, + clientCertRequests, createCertsTempDir(prefix), new DefaultContainerMountStrategy(prefix), false, + null, null, null, null, tlsRegistryEnabled, tlsConfigName, false)); + } + static Certificate of(String prefix, io.quarkus.test.services.Certificate.Format format, String password, boolean tlsRegistryEnabled, String tlsConfigName) { return ofInterchangeable(new CertificateOptions(prefix, format, password, false, false, false, diff --git a/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/bootstrap/JaegerService.java b/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/bootstrap/JaegerService.java index 1f18e1f7b..78b3c0817 100644 --- a/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/bootstrap/JaegerService.java +++ b/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/bootstrap/JaegerService.java @@ -14,7 +14,11 @@ public String getRestUrl() { } public String getCollectorUrl() { - return getURI(Protocol.HTTP).withPath(JAEGER_API_PATH).toString(); + return getCollectorUrl(Protocol.HTTP); + } + + public String getCollectorUrl(Protocol protocol) { + return getURI(protocol).withPath(JAEGER_API_PATH).toString(); } public String getTraceUrl() { diff --git a/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/JaegerContainer.java b/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/JaegerContainer.java index 93cee9eab..8ec0fb680 100644 --- a/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/JaegerContainer.java +++ b/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/JaegerContainer.java @@ -34,5 +34,10 @@ String expectedLog() default "server started"; + /** + * @return whether communication between Quarkus OTel exporter and Jaeger OTLP collector should be secured + */ + boolean tls() default false; + Class builder() default JaegerContainerManagedResourceBuilder.class; } diff --git a/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/containers/JaegerContainerManagedResourceBuilder.java b/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/containers/JaegerContainerManagedResourceBuilder.java index 80620e1a0..e38bb2b4a 100644 --- a/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/containers/JaegerContainerManagedResourceBuilder.java +++ b/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/containers/JaegerContainerManagedResourceBuilder.java @@ -22,6 +22,7 @@ public class JaegerContainerManagedResourceBuilder extends ContainerManagedResou private int tracePort; private String expectedLog; private boolean useOtlpCollector; + private boolean tlsEnabled; @Override protected String getImage() { @@ -51,6 +52,10 @@ protected boolean shouldUseOtlpCollector() { return useOtlpCollector; } + protected boolean isTlsEnabled() { + return tlsEnabled; + } + @Override public void init(Annotation annotation) { JaegerContainer metadata = (JaegerContainer) annotation; @@ -59,6 +64,7 @@ public void init(Annotation annotation) { this.tracePort = metadata.tracePort(); this.expectedLog = metadata.expectedLog(); this.useOtlpCollector = metadata.useOtlpCollector(); + this.tlsEnabled = metadata.tls(); } @Override diff --git a/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/containers/JaegerGenericDockerContainerManagedResource.java b/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/containers/JaegerGenericDockerContainerManagedResource.java index 90c76375d..bae07ec8b 100644 --- a/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/containers/JaegerGenericDockerContainerManagedResource.java +++ b/quarkus-test-service-jaeger/src/main/java/io/quarkus/test/services/containers/JaegerGenericDockerContainerManagedResource.java @@ -1,14 +1,20 @@ package io.quarkus.test.services.containers; import static io.quarkus.test.bootstrap.JaegerService.JAEGER_TRACE_URL_PROPERTY; +import static io.quarkus.test.services.Certificate.Format.PEM; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.MountableFile; import io.quarkus.test.bootstrap.Protocol; +import io.quarkus.test.security.certificate.Certificate; +import io.quarkus.test.security.certificate.ClientCertificateRequest; import io.quarkus.test.utils.DockerUtils; public class JaegerGenericDockerContainerManagedResource extends GenericDockerContainerManagedResource { + public static final String CERTIFICATE_CONTEXT_KEY = "io.quarkus.test.services.containers.jaeger.certificate"; + public static final String JAEGER_CLIENT_CERT_CN = "jaeger-client"; private static final String COLLECTOR_OTLP_ENABLED = "COLLECTOR_OTLP_ENABLED"; private final JaegerContainerManagedResourceBuilder model; @@ -29,6 +35,21 @@ protected GenericContainer initContainer() { GenericContainer container = super.initContainer(); container.addExposedPort(model.getTracePort()); container.withCreateContainerCmdModifier(cmd -> cmd.withName(DockerUtils.generateDockerContainerName())); + if (model.isTlsEnabled()) { + var clientCertRequest = new ClientCertificateRequest[] { + new ClientCertificateRequest(JAEGER_CLIENT_CERT_CN, false) }; + var cert = Certificate.of("jaeger-cert", PEM, "jaeger-password", true, "jaeger-tls-config", clientCertRequest); + model.getContext().put(CERTIFICATE_CONTEXT_KEY, cert); + // I found CLI flags used below here: https://www.jaegertracing.io/docs/1.60/cli/ + container.withCreateContainerCmdModifier(cmd -> cmd + .withCmd("--collector.otlp.grpc.tls.enabled=true", + "--collector.otlp.grpc.tls.key=/test-tls/key/tls.key", + "--collector.otlp.grpc.tls.cert=/test-tls/cert/tls.cert", + "--collector.otlp.grpc.tls.client-ca=/test-tls/ca/ca.crt")); + container.withCopyFileToContainer(MountableFile.forHostPath(cert.certPath()), "/test-tls/cert/tls.cert"); + container.withCopyFileToContainer(MountableFile.forHostPath(cert.keyPath()), "/test-tls/key/tls.key"); + container.withCopyFileToContainer(MountableFile.forHostPath(cert.truststorePath()), "/test-tls/ca/ca.crt"); + } if (model.shouldUseOtlpCollector()) { container.addEnv(COLLECTOR_OTLP_ENABLED, "true");