Skip to content

Commit 7f1a447

Browse files
fix cross-account support issue in EKS
1 parent c93750e commit 7f1a447

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
@@ -36,6 +36,7 @@ import { SecretsManagerServiceExtension } from './aws/services/secretsmanager';
3636
import { StepFunctionsServiceExtension } from './aws/services/step-functions';
3737
import { AwsLambdaInstrumentation } from '@opentelemetry/instrumentation-aws-lambda';
3838
import type { Command as AwsV3Command } from '@aws-sdk/types';
39+
import { suppressTracing } from '@opentelemetry/core';
3940

4041
export const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID';
4142
export const AWSXRAY_TRACE_ID_HEADER_CAPITALIZED = 'X-Amzn-Trace-Id';
@@ -364,67 +365,89 @@ const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.clien
364365
type V3PluginCommand = AwsV3Command<any, any, any, any, any> & {
365366
[V3_CLIENT_CONFIG_KEY]?: any;
366367
};
368+
// Symbol to prevent infinite recursion during credential capture
369+
// When we extract credentials, the AWS SDK may need to make additional AWS API calls
370+
// (e.g., sts:AssumeRoleWithWebIdentity) which go through the same instrumented 'send' method.
371+
// Without this flag, each credential request would trigger another credential extraction attempt,
372+
// creating an infinite loop of nested AWS SDK calls.
373+
const SKIP_CREDENTIAL_CAPTURE_KEY = Symbol('skip-credential-capture');
367374
function patchAwsSdkInstrumentation(instrumentation: Instrumentation): void {
368375
if (instrumentation) {
369376
(instrumentation as AwsInstrumentation)['_getV3SmithyClientSendPatch'] = function (
370377
original: (...args: unknown[]) => Promise<any>
371378
) {
372379
return function send(this: any, command: V3PluginCommand, ...args: unknown[]): Promise<any> {
373-
this.middlewareStack?.add(
374-
(next: any, context: any) => async (middlewareArgs: any) => {
375-
propagation.inject(otelContext.active(), middlewareArgs.request.headers, defaultTextMapSetter);
376-
// Need to set capitalized version of the trace id to ensure that the Recursion Detection Middleware
377-
// of aws-sdk-js-v3 will detect the propagated X-Ray Context
378-
// See: https://github.com/aws/aws-sdk-js-v3/blob/v3.768.0/packages/middleware-recursion-detection/src/index.ts#L13
379-
const xrayTraceId = middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
380-
381-
if (xrayTraceId) {
382-
middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER_CAPITALIZED] = xrayTraceId;
383-
delete middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
380+
// Only add middleware once per client instance to reduce overhead
381+
// AWS SDK clients may call 'send' multiple times, but we only need to patch once
382+
// Even with override=true, adding middleware still causes overhead as it replaces existing stack entries
383+
if (!this.__adotMiddlewarePatched) {
384+
this.middlewareStack?.add(
385+
(next: any, context: any) => async (middlewareArgs: any) => {
386+
propagation.inject(otelContext.active(), middlewareArgs.request.headers, defaultTextMapSetter);
387+
// Need to set capitalized version of the trace id to ensure that the Recursion Detection Middleware
388+
// of aws-sdk-js-v3 will detect the propagated X-Ray Context
389+
// See: https://github.com/aws/aws-sdk-js-v3/blob/v3.768.0/packages/middleware-recursion-detection/src/index.ts#L13
390+
const xrayTraceId = middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
391+
392+
if (xrayTraceId) {
393+
middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER_CAPITALIZED] = xrayTraceId;
394+
delete middlewareArgs.request.headers[AWSXRAY_TRACE_ID_HEADER];
395+
}
396+
const result = await next(middlewareArgs);
397+
return result;
398+
},
399+
{
400+
step: 'build',
401+
name: '_adotInjectXrayContextMiddleware',
402+
override: true,
384403
}
385-
const result = await next(middlewareArgs);
386-
return result;
387-
},
388-
{
389-
step: 'build',
390-
name: '_adotInjectXrayContextMiddleware',
391-
override: true,
392-
}
393-
);
394-
395-
this.middlewareStack?.add(
396-
(next: any, context: any) => async (middlewareArgs: any) => {
397-
const activeContext = otelContext.active();
398-
const span = trace.getSpan(activeContext);
399-
400-
if (span) {
401-
try {
402-
const credsProvider = this.config.credentials;
403-
if (credsProvider instanceof Function) {
404-
const credentials = await credsProvider();
405-
if (credentials?.accessKeyId) {
406-
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY, credentials.accessKeyId);
407-
}
408-
}
409-
if (this.config.region instanceof Function) {
410-
const region = await this.config.region();
411-
if (region) {
412-
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_REGION, region);
404+
);
405+
406+
this.middlewareStack?.add(
407+
(next: any, context: any) => async (middlewareArgs: any) => {
408+
const activeContext = otelContext.active();
409+
// Skip credential extraction if this is a nested call from another credential extraction
410+
// This prevents infinite recursion when credential providers make AWS API calls
411+
if (activeContext.getValue(SKIP_CREDENTIAL_CAPTURE_KEY)) {
412+
return await next(middlewareArgs);
413+
}
414+
const span = trace.getSpan(activeContext);
415+
416+
if (span) {
417+
// suppressTracing prevents span generation for internal credential extraction calls
418+
// which are implementation details and not relevant to the application's telemetry
419+
const suppressedContext = suppressTracing(activeContext).setValue(SKIP_CREDENTIAL_CAPTURE_KEY, true);
420+
await otelContext.with(suppressedContext, async () => {
421+
try {
422+
const credsProvider = this.config.credentials;
423+
if (credsProvider instanceof Function) {
424+
const credentials = await credsProvider();
425+
if (credentials?.accessKeyId) {
426+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY, credentials.accessKeyId);
427+
}
428+
}
429+
if (this.config.region instanceof Function) {
430+
const region = await this.config.region();
431+
if (region) {
432+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_REGION, region);
433+
}
434+
}
435+
} catch (err) {
436+
diag.debug('Failed to get auth account access key and region:', err);
413437
}
414-
}
415-
} catch (err) {
416-
diag.debug('Failed to get auth account access key and region:', err);
438+
});
417439
}
418-
}
419440

420-
return await next(middlewareArgs);
421-
},
422-
{
423-
step: 'build',
424-
name: '_adotExtractSignerCredentials',
425-
override: true,
426-
}
427-
);
441+
return await next(middlewareArgs);
442+
},
443+
{
444+
step: 'build',
445+
name: '_adotExtractSignerCredentials',
446+
override: true,
447+
}
448+
);
449+
this.__adotMiddlewarePatched = true;
450+
}
428451

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

0 commit comments

Comments
 (0)