Skip to content

Commit 485e4eb

Browse files
committed
Add Semantic Convention Support for Amazon Secrets Manager Attributes
This PR adds the aws.secretsmanager.secret.arn semantic convention attribute for the following AWS resources: AWS Secrets Manager SDK The Secret ARN is extracted from both request and response objects, and this behavior is covered by unit tests. Tests Run: npm run compile npm run lint npm run test All newly added tests pass, and no regressions were found. Backward Compatibility: This change is fully backward compatible. It introduces instrumentation for an additional AWS resource without modifying existing behavior in the auto-instrumentation library.
1 parent 16979f6 commit 485e4eb

File tree

5 files changed

+194
-0
lines changed

5 files changed

+194
-0
lines changed

packages/instrumentation-aws-sdk/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"@aws-sdk/client-kinesis": "^3.85.0",
6060
"@aws-sdk/client-lambda": "^3.85.0",
6161
"@aws-sdk/client-s3": "^3.85.0",
62+
"@aws-sdk/client-secrets-manager": "3.85.0",
6263
"@aws-sdk/client-sns": "^3.85.0",
6364
"@aws-sdk/client-sqs": "^3.85.0",
6465
"@aws-sdk/types": "^3.370.0",

packages/instrumentation-aws-sdk/src/semconv.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,16 @@ export const GEN_AI_TOKEN_TYPE_VALUE_INPUT = 'input' as const;
159159
*/
160160
export const GEN_AI_TOKEN_TYPE_VALUE_OUTPUT = 'output' as const;
161161

162+
/**
163+
* Originally from '@opentelemetry/semantic-conventions/incubating'
164+
* https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/aws.md#amazon-secrets-manager-attributes
165+
* The ARN of the Secret stored in the Secrets Mangger
166+
* @example arn:aws:secretsmanager:us-east-1:123456789012:secret:SecretName-6RandomCharacters
167+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
168+
*/
169+
export const ATTR_AWS_SECRETSMANAGER_SECRET_ARN =
170+
'aws.secretsmanager.secret.arn' as const;
171+
162172
/**
163173
* Originally from '@opentelemetry/semantic-conventions/incubating'
164174
* https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/aws.md#amazon-sns-attributes

packages/instrumentation-aws-sdk/src/services/ServicesExtensions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from '../types';
2424
import { BedrockRuntimeServiceExtension } from './bedrock-runtime';
2525
import { DynamodbServiceExtension } from './dynamodb';
26+
import { SecretsManagerServiceExtension } from './secretsmanager';
2627
import { SnsServiceExtension } from './sns';
2728
import { LambdaServiceExtension } from './lambda';
2829
import { S3ServiceExtension } from './s3';
@@ -32,6 +33,7 @@ export class ServicesExtensions implements ServiceExtension {
3233
services: Map<string, ServiceExtension> = new Map();
3334

3435
constructor() {
36+
this.services.set('SecretsManager', new SecretsManagerServiceExtension());
3537
this.services.set('SQS', new SqsServiceExtension());
3638
this.services.set('SNS', new SnsServiceExtension());
3739
this.services.set('DynamoDB', new DynamodbServiceExtension());
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { Attributes, Span, SpanKind, Tracer } from '@opentelemetry/api';
17+
import { ATTR_AWS_SECRETSMANAGER_SECRET_ARN } from '../semconv';
18+
import { RequestMetadata, ServiceExtension } from './ServiceExtension';
19+
import {
20+
NormalizedRequest,
21+
NormalizedResponse,
22+
AwsSdkInstrumentationConfig,
23+
} from '../types';
24+
25+
export class SecretsManagerServiceExtension implements ServiceExtension {
26+
requestPreSpanHook(
27+
request: NormalizedRequest,
28+
_config: AwsSdkInstrumentationConfig
29+
): RequestMetadata {
30+
const secretId = request.commandInput?.SecretId;
31+
const spanKind: SpanKind = SpanKind.CLIENT;
32+
let spanName: string | undefined;
33+
const spanAttributes: Attributes = {};
34+
if (
35+
typeof secretId === 'string' &&
36+
secretId.startsWith('arn:aws:secretsmanager:')
37+
) {
38+
spanAttributes[ATTR_AWS_SECRETSMANAGER_SECRET_ARN] = secretId;
39+
}
40+
41+
return {
42+
isIncoming: false,
43+
spanAttributes,
44+
spanKind,
45+
spanName,
46+
};
47+
}
48+
49+
responseHook(
50+
response: NormalizedResponse,
51+
span: Span,
52+
tracer: Tracer,
53+
config: AwsSdkInstrumentationConfig
54+
): void {
55+
const secretArn = response.data?.ARN;
56+
if (secretArn) {
57+
span.setAttribute(ATTR_AWS_SECRETSMANAGER_SECRET_ARN, secretArn);
58+
}
59+
}
60+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { getTestSpans } from '@opentelemetry/contrib-test-utils';
18+
import { SpanKind } from '@opentelemetry/api';
19+
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
20+
import { ATTR_AWS_SECRETSMANAGER_SECRET_ARN } from '@opentelemetry/semantic-conventions/incubating';
21+
22+
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
23+
24+
import { expect } from 'expect';
25+
import * as nock from 'nock';
26+
27+
const region = 'us-east-1';
28+
29+
describe('SecretsManager', () => {
30+
let secretsManager: SecretsManager;
31+
beforeEach(() => {
32+
secretsManager = new SecretsManager({
33+
region: region,
34+
credentials: {
35+
accessKeyId: 'abcde',
36+
secretAccessKey: 'abcde',
37+
},
38+
});
39+
});
40+
41+
describe('DescribeSecret', () => {
42+
const testParams = [
43+
'testId',
44+
'badarn:aws:secretsmanager:us-weast-1:123456789123:secret:testId123456',
45+
'arn:aws:secretsmanager:us-east-1:123456789123:secret:testId123456',
46+
];
47+
48+
testParams.forEach(secretId => {
49+
it('should generate secret arn attribute only if secretId is an valid ARN', async () => {
50+
nock(`https://secretsmanager.${region}.amazonaws.com/`)
51+
.post('/')
52+
.reply(200, 'null');
53+
54+
await secretsManager
55+
.describeSecret({
56+
SecretId: secretId,
57+
})
58+
.catch((err: any) => {});
59+
60+
const testSpans: ReadableSpan[] = getTestSpans();
61+
const getDescribeSecretSpans: ReadableSpan[] = testSpans.filter(
62+
(s: ReadableSpan) => {
63+
return s.name === 'SecretsManager.DescribeSecret';
64+
}
65+
);
66+
67+
expect(getDescribeSecretSpans.length).toBe(1);
68+
const describeSecretSpan = getDescribeSecretSpans[0];
69+
70+
if (secretId.startsWith('arn:aws:secretsmanager:')) {
71+
expect(
72+
describeSecretSpan.attributes[ATTR_AWS_SECRETSMANAGER_SECRET_ARN]
73+
).toBe(secretId);
74+
} else {
75+
expect(
76+
describeSecretSpan.attributes[ATTR_AWS_SECRETSMANAGER_SECRET_ARN]
77+
).toBeUndefined();
78+
}
79+
80+
expect(describeSecretSpan.kind).toBe(SpanKind.CLIENT);
81+
});
82+
});
83+
});
84+
85+
describe('GetSecretValue', () => {
86+
it('secret arn attribute should be populated from the response', async () => {
87+
const secretIdArn =
88+
'arn:aws:secretsmanager:us-east-1:123456789123:secret:testId123456';
89+
90+
nock(`https://secretsmanager.${region}.amazonaws.com/`)
91+
.post('/')
92+
.reply(200, {
93+
ARN: secretIdArn,
94+
Name: 'testId',
95+
});
96+
97+
await secretsManager
98+
.getSecretValue({
99+
SecretId: 'testSecret',
100+
})
101+
.catch((err: any) => {
102+
console.log(err);
103+
});
104+
105+
const testSpans: ReadableSpan[] = getTestSpans();
106+
const getSecretValueSpans: ReadableSpan[] = testSpans.filter(
107+
(s: ReadableSpan) => {
108+
return s.name === 'SecretsManager.GetSecretValue';
109+
}
110+
);
111+
112+
expect(getSecretValueSpans.length).toBe(1);
113+
114+
const secretValueSpan = getSecretValueSpans[0];
115+
expect(
116+
secretValueSpan.attributes[ATTR_AWS_SECRETSMANAGER_SECRET_ARN]
117+
).toBe(secretIdArn);
118+
expect(secretValueSpan.kind).toBe(SpanKind.CLIENT);
119+
});
120+
});
121+
});

0 commit comments

Comments
 (0)