Skip to content

Commit 8600230

Browse files
committed
SigV4 Support
1 parent 7017193 commit 8600230

File tree

4 files changed

+287
-3
lines changed

4 files changed

+287
-3
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
"@aws-sdk/client-sfn": "^3.632.0",
8080
"@aws-sdk/client-sns": "^3.632.0",
8181
"@opentelemetry/contrib-test-utils": "0.41.0",
82+
"@smithy/protocol-http": "^5.0.1",
83+
"@smithy/signature-v4": "^5.0.1",
8284
"@types/mocha": "7.0.2",
8385
"@types/node": "18.6.5",
8486
"@types/sinon": "10.0.18",

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,13 @@ import { AwsBatchUnsampledSpanProcessor } from './aws-batch-unsampled-span-proce
5757
import { AwsMetricAttributesSpanExporterBuilder } from './aws-metric-attributes-span-exporter-builder';
5858
import { AwsSpanMetricsProcessorBuilder } from './aws-span-metrics-processor-builder';
5959
import { OTLPUdpSpanExporter } from './otlp-udp-exporter';
60+
import { OTLPAwsSpanExporter } from './otlp-aws-span-exporter';
6061
import { AwsXRayRemoteSampler } from './sampler/aws-xray-remote-sampler';
6162
// This file is generated via `npm run compile`
6263
import { LIB_VERSION } from './version';
6364

65+
const XRAY_OTLP_ENDPOINT_PATTERN = '^https://xray.([a-z0-9-]+).amazonaws.com/v1/traces$';
66+
6467
const APPLICATION_SIGNALS_ENABLED_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED';
6568
const APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT';
6669
const METRIC_EXPORT_INTERVAL_CONFIG: string = 'OTEL_METRIC_EXPORT_INTERVAL';
@@ -422,6 +425,7 @@ export class AwsSpanProcessorProvider {
422425
private resource: Resource;
423426

424427
static configureOtlp(): SpanExporter {
428+
const otlp_exporter_traces_endpoint = process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'];
425429
// eslint-disable-next-line @typescript-eslint/typedef
426430
let protocol = this.getOtlpProtocol();
427431

@@ -438,12 +442,18 @@ export class AwsSpanProcessorProvider {
438442
case 'http/json':
439443
return new OTLPHttpTraceExporter();
440444
case 'http/protobuf':
445+
if (otlp_exporter_traces_endpoint && isXrayOtlpEndpoint(otlp_exporter_traces_endpoint)) {
446+
return new OTLPAwsSpanExporter(otlp_exporter_traces_endpoint);
447+
}
441448
return new OTLPProtoTraceExporter();
442449
case 'udp':
443450
diag.debug('Detected AWS Lambda environment and enabling UDPSpanExporter');
444451
return new OTLPUdpSpanExporter(getXrayDaemonEndpoint(), FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX);
445452
default:
446453
diag.warn(`Unsupported OTLP traces protocol: ${protocol}. Using http/protobuf.`);
454+
if (otlp_exporter_traces_endpoint && isXrayOtlpEndpoint(otlp_exporter_traces_endpoint)) {
455+
return new OTLPAwsSpanExporter(otlp_exporter_traces_endpoint);
456+
}
447457
return new OTLPProtoTraceExporter();
448458
}
449459
}
@@ -652,4 +662,8 @@ function getXrayDaemonEndpoint() {
652662
return process.env[AWS_XRAY_DAEMON_ADDRESS_CONFIG];
653663
}
654664

665+
function isXrayOtlpEndpoint(otlpEndpoint: string | undefined) {
666+
return otlpEndpoint && new RegExp(XRAY_OTLP_ENDPOINT_PATTERN).test(otlpEndpoint.toLowerCase());
667+
}
668+
655669
// END The OpenTelemetry Authors code
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { OTLPTraceExporter as OTLPProtoTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
4+
import { diag } from '@opentelemetry/api';
5+
import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base';
6+
import { ExportResult } from '@opentelemetry/core';
7+
import { defaultProvider } from '@aws-sdk/credential-provider-node';
8+
import { Sha256 } from '@aws-crypto/sha256-js';
9+
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
10+
import { SignatureV4 } from '@smithy/signature-v4';
11+
import { ProtobufTraceSerializer } from '@opentelemetry/otlp-transformer';
12+
import { HttpRequest } from '@smithy/protocol-http';
13+
14+
const SERVICE_NAME = 'xray';
15+
16+
/**
17+
* This exporter extends the functionality of the OTLPProtoTraceExporter to allow spans to be exported
18+
* to the XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the aws-sdk
19+
* library to sign and directly inject SigV4 Authentication to the exported request's headers. <a
20+
* href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html">...</a>
21+
*/
22+
export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter {
23+
private endpoint: string;
24+
private region: string;
25+
26+
constructor(endpoint: string, config?: OTLPExporterNodeConfigBase) {
27+
super(config);
28+
this.region = endpoint.split('.')[1];
29+
this.endpoint = endpoint;
30+
}
31+
32+
/**
33+
* Overrides the upstream implementation of export. All behaviors are the same except if the
34+
* endpoint is an XRay OTLP endpoint, we will sign the request with SigV4 in headers before
35+
* sending it to the endpoint. Otherwise, we will skip signing.
36+
*/
37+
public override async export(items: ReadableSpan[], resultCallback: (result: ExportResult) => void): Promise<void> {
38+
const url = new URL(this.endpoint);
39+
const serializedSpans: Uint8Array | undefined = ProtobufTraceSerializer.serializeRequest(items);
40+
41+
if (serializedSpans === undefined) {
42+
return;
43+
}
44+
45+
/*
46+
This is bad practice but there is no other way to access and inject SigV4 headers
47+
into the request headers before the traces get exported.
48+
*/
49+
const oldHeaders = (this as any)._transport._transport._parameters.headers;
50+
51+
const request = new HttpRequest({
52+
method: 'POST',
53+
protocol: 'https',
54+
hostname: url.hostname,
55+
path: url.pathname,
56+
body: serializedSpans,
57+
headers: {
58+
...oldHeaders,
59+
host: url.hostname,
60+
},
61+
});
62+
63+
try {
64+
const signer = new SignatureV4({
65+
credentials: defaultProvider(),
66+
region: this.region,
67+
service: SERVICE_NAME,
68+
sha256: Sha256,
69+
});
70+
71+
const signedRequest = await signer.sign(request);
72+
73+
(this as any)._transport._transport._parameters.headers = signedRequest.headers;
74+
} catch (exception) {
75+
diag.debug(
76+
`Failed to sign/authenticate the given exported Span request to OTLP XRay endpoint with error: ${exception}`
77+
);
78+
}
79+
80+
await super.export(items, resultCallback);
81+
}
82+
}

package-lock.json

Lines changed: 189 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)