diff --git a/awsagentprovider/build.gradle.kts b/awsagentprovider/build.gradle.kts index 6b9b75e3d5..7e9211052e 100644 --- a/awsagentprovider/build.gradle.kts +++ b/awsagentprovider/build.gradle.kts @@ -41,12 +41,9 @@ dependencies { // Import AWS SDK v1 core for ARN parsing utilities implementation("com.amazonaws:aws-java-sdk-core:1.12.773") // Export configuration - implementation("io.opentelemetry:opentelemetry-exporter-otlp") + compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // For Udp emitter compileOnly("io.opentelemetry:opentelemetry-exporter-otlp-common") - // For HTTP SigV4 emitter - implementation("software.amazon.awssdk:auth:2.30.14") - implementation("software.amazon.awssdk:http-auth-aws:2.30.14") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java index 9f023c119f..b3d04a7a8c 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java @@ -51,7 +51,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Pattern; /** * This customizer performs the following customizations: @@ -71,8 +70,6 @@ public class AwsApplicationSignalsCustomizerProvider implements AutoConfigurationCustomizerProvider { static final String AWS_LAMBDA_FUNCTION_NAME_CONFIG = "AWS_LAMBDA_FUNCTION_NAME"; - private static final String XRAY_OTLP_ENDPOINT_PATTERN = - "^https://xray\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/traces$"; private static final Duration DEFAULT_METRIC_EXPORT_INTERVAL = Duration.ofMinutes(1); private static final Logger logger = @@ -124,16 +121,6 @@ static boolean isLambdaEnvironment() { return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) != null; } - static boolean isXrayOtlpEndpoint(String otlpEndpoint) { - if (otlpEndpoint == null) { - return false; - } - - return Pattern.compile(XRAY_OTLP_ENDPOINT_PATTERN) - .matcher(otlpEndpoint.toLowerCase()) - .matches(); - } - private boolean isApplicationSignalsEnabled(ConfigProperties configProps) { return configProps.getBoolean( APPLICATION_SIGNALS_ENABLED_CONFIG, @@ -234,10 +221,6 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder( return tracerProviderBuilder; } - if (isXrayOtlpEndpoint(System.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG))) { - return tracerProviderBuilder; - } - // Construct meterProvider MetricExporter metricsExporter = ApplicationSignalsExporterProvider.INSTANCE.createExporter(configProps); @@ -303,14 +286,6 @@ private SpanExporter customizeSpanExporter( .build(); } } - // When running OTLP endpoint for X-Ray backend, use custom exporter for SigV4 authentication - else if (spanExporter instanceof OtlpHttpSpanExporter - && isXrayOtlpEndpoint(System.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG))) { - spanExporter = - new OtlpAwsSpanExporter( - (OtlpHttpSpanExporter) spanExporter, - System.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG)); - } if (isApplicationSignalsEnabled(configProps)) { return AwsMetricAttributesSpanExporterBuilder.create( diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporter.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporter.java deleted file mode 100644 index c4a777dfe5..0000000000 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporter.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.opentelemetry.javaagent.providers; - -import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler; -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; -import javax.annotation.concurrent.Immutable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import software.amazon.awssdk.auth.credentials.AwsCredentials; -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.http.SdkHttpFullRequest; -import software.amazon.awssdk.http.SdkHttpMethod; -import software.amazon.awssdk.http.SdkHttpRequest; -import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; -import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; - -/** - * This exporter extends the functionality of the OtlpHttpSpanExporter to allow spans to be exported - * to the XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the AWSSDK - * library to sign and directly inject SigV4 Authentication to the exported request's headers. ... - */ -@Immutable -public class OtlpAwsSpanExporter implements SpanExporter { - private static final String SERVICE_NAME = "xray"; - private static final Logger logger = LoggerFactory.getLogger(OtlpAwsSpanExporter.class); - - private final OtlpHttpSpanExporter parentExporter; - private final String awsRegion; - private final String endpoint; - private Collection spanData; - - public OtlpAwsSpanExporter(String endpoint) { - this.parentExporter = - OtlpHttpSpanExporter.builder() - .setEndpoint(endpoint) - .setHeaders(new SigV4AuthHeaderSupplier()) - .build(); - - this.awsRegion = endpoint.split("\\.")[1]; - this.endpoint = endpoint; - this.spanData = new ArrayList<>(); - } - - public OtlpAwsSpanExporter(OtlpHttpSpanExporter parentExporter, String endpoint) { - this.parentExporter = - parentExporter.toBuilder() - .setEndpoint(endpoint) - .setHeaders(new SigV4AuthHeaderSupplier()) - .build(); - - this.awsRegion = endpoint.split("\\.")[1]; - this.endpoint = endpoint; - this.spanData = new ArrayList<>(); - } - - /** - * Overrides the upstream implementation of export. All behaviors are the same except if the - * endpoint is an XRay OTLP endpoint, we will sign the request with SigV4 in headers before - * sending it to the endpoint. Otherwise, we will skip signing. - */ - @Override - public CompletableResultCode export(Collection spans) { - this.spanData = spans; - return this.parentExporter.export(spans); - } - - @Override - public CompletableResultCode flush() { - return this.parentExporter.flush(); - } - - @Override - public CompletableResultCode shutdown() { - return this.parentExporter.shutdown(); - } - - @Override - public String toString() { - return this.parentExporter.toString(); - } - - private final class SigV4AuthHeaderSupplier implements Supplier> { - - @Override - public Map get() { - try { - ByteArrayOutputStream encodedSpans = new ByteArrayOutputStream(); - TraceRequestMarshaler.create(OtlpAwsSpanExporter.this.spanData).writeBinaryTo(encodedSpans); - - SdkHttpRequest httpRequest = - SdkHttpFullRequest.builder() - .uri(URI.create(OtlpAwsSpanExporter.this.endpoint)) - .method(SdkHttpMethod.POST) - .putHeader("Content-Type", "application/x-protobuf") - .contentStreamProvider(() -> new ByteArrayInputStream(encodedSpans.toByteArray())) - .build(); - - AwsCredentials credentials = DefaultCredentialsProvider.create().resolveCredentials(); - - SignedRequest signedRequest = - AwsV4HttpSigner.create() - .sign( - b -> - b.identity(credentials) - .request(httpRequest) - .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, SERVICE_NAME) - .putProperty( - AwsV4HttpSigner.REGION_NAME, OtlpAwsSpanExporter.this.awsRegion) - .payload(() -> new ByteArrayInputStream(encodedSpans.toByteArray()))); - - Map result = new HashMap<>(); - - Map> headers = signedRequest.request().headers(); - headers.forEach( - (key, values) -> { - if (!values.isEmpty()) { - result.put(key, values.get(0)); - } - }); - - return result; - - } catch (Exception e) { - logger.error( - "Failed to sign/authenticate the given exported Span request to OTLP CloudWatch endpoint with error: {}", - e.getMessage()); - - return new HashMap<>(); - } - } - } -} diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporterTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporterTest.java deleted file mode 100644 index 252ae3e900..0000000000 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/OtlpAwsSpanExporterTest.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.opentelemetry.javaagent.providers; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.when; - -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.net.URI; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Supplier; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentials; -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.http.SdkHttpFullRequest; -import software.amazon.awssdk.http.SdkHttpMethod; -import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; -import software.amazon.awssdk.http.auth.spi.signer.SignRequest.Builder; -import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; -import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; - -@ExtendWith(MockitoExtension.class) -public class OtlpAwsSpanExporterTest { - private static final String XRAY_OTLP_ENDPOINT = "https://xray.us-east-1.amazonaws.com/v1/traces"; - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String X_AMZ_DATE_HEADER = "X-Amz-Date"; - private static final String X_AMZ_SECURITY_TOKEN_HEADER = "X-Amz-Security-Token"; - - private static final String EXPECTED_AUTH_HEADER = - "AWS4-HMAC-SHA256 Credential=test_key/some_date/us-east-1/xray/aws4_request"; - private static final String EXPECTED_AUTH_X_AMZ_DATE = "some_date"; - private static final String EXPECTED_AUTH_SECURITY_TOKEN = "test_token"; - - AwsCredentials credentials = AwsBasicCredentials.create("test_access_key", "test_secret_key"); - SignedRequest signedRequest = - SignedRequest.builder() - .request( - SdkHttpFullRequest.builder() - .method(SdkHttpMethod.POST) - .uri(URI.create(XRAY_OTLP_ENDPOINT)) - .putHeader(AUTHORIZATION_HEADER, EXPECTED_AUTH_HEADER) - .putHeader(X_AMZ_DATE_HEADER, EXPECTED_AUTH_X_AMZ_DATE) - .putHeader(X_AMZ_SECURITY_TOKEN_HEADER, EXPECTED_AUTH_SECURITY_TOKEN) - .build()) - .build(); - - private MockedStatic mockDefaultCredentialsProvider; - private MockedStatic mockAwsV4HttpSigner; - private MockedStatic otlpSpanExporterMock; - - @Mock private DefaultCredentialsProvider credentialsProvider; - @Mock private AwsV4HttpSigner signer; - @Mock private OtlpHttpSpanExporterBuilder mockBuilder; - @Mock private OtlpHttpSpanExporter mockExporter; - - private ArgumentCaptor>> headersCaptor; - - @BeforeEach - void setup() { - this.mockDefaultCredentialsProvider = mockStatic(DefaultCredentialsProvider.class); - this.mockDefaultCredentialsProvider - .when(DefaultCredentialsProvider::create) - .thenReturn(credentialsProvider); - - this.mockAwsV4HttpSigner = mockStatic(AwsV4HttpSigner.class); - this.mockAwsV4HttpSigner.when(AwsV4HttpSigner::create).thenReturn(this.signer); - - this.otlpSpanExporterMock = mockStatic(OtlpHttpSpanExporter.class); - - this.headersCaptor = ArgumentCaptor.forClass(Supplier.class); - - when(OtlpHttpSpanExporter.builder()).thenReturn(mockBuilder); - when(this.mockBuilder.setEndpoint(any())).thenReturn(mockBuilder); - when(this.mockBuilder.setHeaders(headersCaptor.capture())).thenReturn(mockBuilder); - when(this.mockBuilder.build()).thenReturn(mockExporter); - when(this.mockExporter.export(any())).thenReturn(CompletableResultCode.ofSuccess()); - } - - @AfterEach - void afterEach() { - reset(this.signer, this.credentialsProvider); - this.mockDefaultCredentialsProvider.close(); - this.mockAwsV4HttpSigner.close(); - this.otlpSpanExporterMock.close(); - } - - @Test - void testAwsSpanExporterAddsSigV4Headers() { - - SpanExporter exporter = new OtlpAwsSpanExporter(XRAY_OTLP_ENDPOINT); - when(this.credentialsProvider.resolveCredentials()).thenReturn(this.credentials); - when(this.signer.sign((Consumer>) any())) - .thenReturn(this.signedRequest); - - exporter.export(List.of()); - - Map headers = this.headersCaptor.getValue().get(); - - assertTrue(headers.containsKey(X_AMZ_DATE_HEADER)); - assertTrue(headers.containsKey(AUTHORIZATION_HEADER)); - assertTrue(headers.containsKey(X_AMZ_SECURITY_TOKEN_HEADER)); - - assertEquals(EXPECTED_AUTH_HEADER, headers.get(AUTHORIZATION_HEADER)); - assertEquals(EXPECTED_AUTH_X_AMZ_DATE, headers.get(X_AMZ_DATE_HEADER)); - assertEquals(EXPECTED_AUTH_SECURITY_TOKEN, headers.get(X_AMZ_SECURITY_TOKEN_HEADER)); - } - - @Test - void testAwsSpanExporterExportCorrectlyAddsDifferentSigV4Headers() { - SpanExporter exporter = new OtlpAwsSpanExporter(XRAY_OTLP_ENDPOINT); - - for (int i = 0; i < 10; i += 1) { - String newAuthHeader = EXPECTED_AUTH_HEADER + i; - String newXAmzDate = EXPECTED_AUTH_X_AMZ_DATE + i; - String newXAmzSecurityToken = EXPECTED_AUTH_SECURITY_TOKEN + i; - - SignedRequest newSignedRequest = - SignedRequest.builder() - .request( - SdkHttpFullRequest.builder() - .method(SdkHttpMethod.POST) - .uri(URI.create(XRAY_OTLP_ENDPOINT)) - .putHeader(AUTHORIZATION_HEADER, newAuthHeader) - .putHeader(X_AMZ_DATE_HEADER, newXAmzDate) - .putHeader(X_AMZ_SECURITY_TOKEN_HEADER, newXAmzSecurityToken) - .build()) - .build(); - - when(this.credentialsProvider.resolveCredentials()).thenReturn(this.credentials); - doReturn(newSignedRequest).when(this.signer).sign(any(Consumer.class)); - - exporter.export(List.of()); - - Map headers = this.headersCaptor.getValue().get(); - - assertTrue(headers.containsKey(X_AMZ_DATE_HEADER)); - assertTrue(headers.containsKey(AUTHORIZATION_HEADER)); - assertTrue(headers.containsKey(X_AMZ_SECURITY_TOKEN_HEADER)); - - assertEquals(newAuthHeader, headers.get(AUTHORIZATION_HEADER)); - assertEquals(newXAmzDate, headers.get(X_AMZ_DATE_HEADER)); - assertEquals(newXAmzSecurityToken, headers.get(X_AMZ_SECURITY_TOKEN_HEADER)); - } - } - - @Test - void testAwsSpanExporterDoesNotAddSigV4HeadersIfFailureToRetrieveCredentials() { - - when(this.credentialsProvider.resolveCredentials()) - .thenThrow(SdkClientException.builder().message("bad credentials").build()); - - SpanExporter exporter = new OtlpAwsSpanExporter(XRAY_OTLP_ENDPOINT); - - exporter.export(List.of()); - - Supplier> headersSupplier = headersCaptor.getValue(); - Map headers = headersSupplier.get(); - - assertFalse(headers.containsKey(X_AMZ_DATE_HEADER)); - assertFalse(headers.containsKey(AUTHORIZATION_HEADER)); - assertFalse(headers.containsKey(X_AMZ_SECURITY_TOKEN_HEADER)); - - verifyNoInteractions(this.signer); - } - - @Test - void testAwsSpanExporterDoesNotAddSigV4HeadersIfFailureToSignHeaders() { - - when(this.credentialsProvider.resolveCredentials()).thenReturn(this.credentials); - when(this.signer.sign((Consumer>) any())) - .thenThrow(SdkClientException.builder().message("bad signature").build()); - - SpanExporter exporter = new OtlpAwsSpanExporter(XRAY_OTLP_ENDPOINT); - - exporter.export(List.of()); - - Map headers = this.headersCaptor.getValue().get(); - - assertFalse(headers.containsKey(X_AMZ_DATE_HEADER)); - assertFalse(headers.containsKey(AUTHORIZATION_HEADER)); - assertFalse(headers.containsKey(X_AMZ_SECURITY_TOKEN_HEADER)); - } -}