Skip to content

Commit 0bb5933

Browse files
fix cross-account support issue in EKS
1 parent 214b938 commit 0bb5933

File tree

1 file changed

+74
-51
lines changed

1 file changed

+74
-51
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { StepFunctionsServiceExtension } from './aws/services/step-functions';
3737
import type { AwsLambdaInstrumentation } from '@opentelemetry/instrumentation-aws-lambda';
3838
import type { Command as AwsV3Command } from '@aws-sdk/types';
3939
import { LoggerProvider } from '@opentelemetry/api-logs';
40+
import { suppressTracing } from '@opentelemetry/core';
4041

4142
export const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID';
4243
export const AWSXRAY_TRACE_ID_HEADER_CAPITALIZED = 'X-Amzn-Trace-Id';
@@ -417,67 +418,89 @@ const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.clien
417418
type V3PluginCommand = AwsV3Command<any, any, any, any, any> & {
418419
[V3_CLIENT_CONFIG_KEY]?: any;
419420
};
421+
// Symbol to prevent infinite recursion during credential capture
422+
// When we extract credentials, the AWS SDK may need to make additional AWS API calls
423+
// (e.g., sts:AssumeRoleWithWebIdentity) which go through the same instrumented 'send' method.
424+
// Without this flag, each credential request would trigger another credential extraction attempt,
425+
// creating an infinite loop of nested AWS SDK calls.
426+
const SKIP_CREDENTIAL_CAPTURE_KEY = Symbol('skip-credential-capture');
420427
function patchAwsSdkInstrumentation(instrumentation: Instrumentation): void {
421428
if (instrumentation) {
422429
(instrumentation as AwsInstrumentation)['_getV3SmithyClientSendPatch'] = function (
423430
original: (...args: unknown[]) => Promise<any>
424431
) {
425432
return function send(this: any, command: V3PluginCommand, ...args: unknown[]): Promise<any> {
426-
this.middlewareStack?.add(
427-
(next: any, context: any) => async (middlewareArgs: any) => {
428-
propagation.inject(otelContext.active(), middlewareArgs.request.headers, defaultTextMapSetter);
429-
// Need to set capitalized version of the trace id to ensure that the Recursion Detection Middleware
430-
// of aws-sdk-js-v3 will detect the propagated X-Ray Context
431-
// See: https://github.com/aws/aws-sdk-js-v3/blob/v3.768.0/packages/middleware-recursion-detection/src/index.ts#L13
432-
const xrayTraceId = middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
433-
434-
if (xrayTraceId) {
435-
middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER_CAPITALIZED] = xrayTraceId;
436-
delete middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
433+
// Only add middleware once per client instance to reduce overhead
434+
// AWS SDK clients may call 'send' multiple times, but we only need to patch once
435+
// Even with override=true, adding middleware still causes overhead as it replaces existing stack entries
436+
if (!this.__adotMiddlewarePatched) {
437+
this.middlewareStack?.add(
438+
(next: any, context: any) => async (middlewareArgs: any) => {
439+
propagation.inject(otelContext.active(), middlewareArgs.request.headers, defaultTextMapSetter);
440+
// Need to set capitalized version of the trace id to ensure that the Recursion Detection Middleware
441+
// of aws-sdk-js-v3 will detect the propagated X-Ray Context
442+
// See: https://github.com/aws/aws-sdk-js-v3/blob/v3.768.0/packages/middleware-recursion-detection/src/index.ts#L13
443+
const xrayTraceId = middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
444+
445+
if (xrayTraceId) {
446+
middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER_CAPITALIZED] = xrayTraceId;
447+
delete middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
448+
}
449+
const result = await next(middlewareArgs);
450+
return result;
451+
},
452+
{
453+
step: 'build',
454+
name: '_adotInjectXrayContextMiddleware',
455+
override: true,
437456
}
438-
const result = await next(middlewareArgs);
439-
return result;
440-
},
441-
{
442-
step: 'build',
443-
name: '_adotInjectXrayContextMiddleware',
444-
override: true,
445-
}
446-
);
447-
448-
this.middlewareStack?.add(
449-
(next: any, context: any) => async (middlewareArgs: any) => {
450-
const activeContext = otelContext.active();
451-
const span = trace.getSpan(activeContext);
452-
453-
if (span) {
454-
try {
455-
const credsProvider = this.config.credentials;
456-
if (credsProvider instanceof Function) {
457-
const credentials = await credsProvider();
458-
if (credentials?.accessKeyId) {
459-
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY, credentials.accessKeyId);
460-
}
461-
}
462-
if (this.config.region instanceof Function) {
463-
const region = await this.config.region();
464-
if (region) {
465-
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_REGION, region);
457+
);
458+
459+
this.middlewareStack?.add(
460+
(next: any, context: any) => async (middlewareArgs: any) => {
461+
const activeContext = otelContext.active();
462+
// Skip credential extraction if this is a nested call from another credential extraction
463+
// This prevents infinite recursion when credential providers make AWS API calls
464+
if (activeContext.getValue(SKIP_CREDENTIAL_CAPTURE_KEY)) {
465+
return await next(middlewareArgs);
466+
}
467+
const span = trace.getSpan(activeContext);
468+
469+
if (span) {
470+
// suppressTracing prevents span generation for internal credential extraction calls
471+
// which are implementation details and not relevant to the application's telemetry
472+
const suppressedContext = suppressTracing(activeContext).setValue(SKIP_CREDENTIAL_CAPTURE_KEY, true);
473+
await otelContext.with(suppressedContext, async () => {
474+
try {
475+
const credsProvider = this.config.credentials;
476+
if (credsProvider instanceof Function) {
477+
const credentials = await credsProvider();
478+
if (credentials?.accessKeyId) {
479+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY, credentials.accessKeyId);
480+
}
481+
}
482+
if (this.config.region instanceof Function) {
483+
const region = await this.config.region();
484+
if (region) {
485+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_REGION, region);
486+
}
487+
}
488+
} catch (err) {
489+
diag.debug('Failed to get auth account access key and region:', err);
466490
}
467-
}
468-
} catch (err) {
469-
diag.debug('Failed to get auth account access key and region:', err);
491+
});
470492
}
471-
}
472493

473-
return await next(middlewareArgs);
474-
},
475-
{
476-
step: 'build',
477-
name: '_adotExtractSignerCredentials',
478-
override: true,
479-
}
480-
);
494+
return await next(middlewareArgs);
495+
},
496+
{
497+
step: 'build',
498+
name: '_adotExtractSignerCredentials',
499+
override: true,
500+
}
501+
);
502+
this.__adotMiddlewarePatched = true;
503+
}
481504

482505
command[V3_CLIENT_CONFIG_KEY] = this.config;
483506
return original.apply(this, [command, ...args]);

0 commit comments

Comments
 (0)