Skip to content

Commit 9fdc363

Browse files
authored
feat: Support credentials provider name for BedrockAgentCore Identity (#534)
*Description of changes:* Bedrock AgentCore data plane calls do not provide credential ARNs but instead provide credential provider names (see [GetResourceOauth2Token](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_GetResourceOauth2Token.html#API_GetResourceOauth2Token_RequestSyntax) and [GetResourceApiKey](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_GetResourceApiKey.html)). This PR adds logic to also handle names for credential providers. - Renamed `AWS_AUTH_CREDENTIAL_PROVIDER_ARN` to `AWS_AUTH_CREDENTIAL_PROVIDER` to support both ARN and name formats - Added mapping for `resourceCredentialProviderName` field in BedrockAgentCore patch - Enhanced credential provider type detection logic to handle name-only identifiers using RPC method context - Added tests for both ARN and name-based credential provider scenarios ``` { "name": "Bedrock AgentCore.GetResourceOauth2Token", "context": { "trace_id": "0x690c16ee2f248a4e86e5c13e2e626e20", "span_id": "0x1ed2e8bf53fa45fd", "trace_state": "[]" }, "kind": "SpanKind.CLIENT", "parent_id": "0x914729048ec3381b", "start_time": "2025-11-06T03:33:02.736876Z", "end_time": "2025-11-06T03:33:02.739796Z", "status": { "status_code": "OK", }, "attributes": { "rpc.system": "aws-api", "rpc.service": "Bedrock AgentCore", "rpc.method": "GetResourceOauth2Token", "aws.region": "us-east-1", "server.address": "bedrock-agentcore.us-east-1.amazonaws.com", "server.port": 443, "aws.auth.region": "us-east-1", "aws.auth.account.access_key": "[REDACTED]", "aws.auth.credential_provider": "test-oauth2-provider", "aws.local.operation": "GET /server_request", "aws.local.service": "UnknownService", "aws.remote.service": "AWS::BedrockAgentCore", "aws.remote.operation": "GetResourceOauth2Token", "aws.remote.resource.type": "AWS::BedrockAgentCore::OAuth2CredentialProvider", "aws.remote.resource.identifier": "test-oauth2-provider", "aws.remote.resource.cfn.primary.identifier": "test-oauth2-provider", "aws.remote.resource.account.access_key": "[REDACTED]", "aws.remote.resource.region": "us-east-1", "aws.span.kind": "CLIENT" }, "links": [], "resource": { "attributes": { "telemetry.sdk.language": "python", "telemetry.sdk.name": "opentelemetry", "telemetry.sdk.version": "1.37.0", "service.name": "unknown_service", "telemetry.auto.version": "0.13.0.dev0-aws", "aws.local.service": "UnknownService" }, "schema_url": "" } } ``` ``` { "name": "Bedrock AgentCore.GetResourceApiKey", "context": { "trace_id": "0x690c1776b6a22c4ac0d6cb9315a4b730", "span_id": "0x036b363af6af6695", "trace_state": "[]" }, "kind": "SpanKind.CLIENT", "parent_id": "0xb4c143957c6cb110", "start_time": "2025-11-06T03:35:18.490547Z", "end_time": "2025-11-06T03:35:18.628436Z", "status": { "status_code": "OK" }, "attributes": { "rpc.system": "aws-api", "rpc.service": "Bedrock AgentCore", "rpc.method": "GetResourceApiKey", "aws.region": "us-east-1", "server.address": "bedrock-agentcore.us-east-1.amazonaws.com", "server.port": 443, "aws.auth.region": "us-east-1", "aws.auth.account.access_key": "[REDACTED]", "aws.auth.credential_provider": "test-api-key-provider", "aws.local.operation": "GET /server_request", "aws.request_id": "5c0130a2-d29e-468a-abd4-86c73ff13f98", "retry_attempts": 0, "http.status_code": 200, "aws.local.service": "UnknownService", "aws.remote.service": "AWS::BedrockAgentCore", "aws.remote.operation": "GetResourceApiKey", "aws.remote.resource.type": "AWS::BedrockAgentCore::APIKeyCredentialProvider", "aws.remote.resource.identifier": "test-api-key-provider", "aws.remote.resource.cfn.primary.identifier": "test-api-key-provider", "aws.remote.resource.account.access_key": "[REDACTED]", "aws.remote.resource.region": "us-east-1", "aws.span.kind": "CLIENT" }, "links": [], "resource": { "attributes": { "telemetry.sdk.language": "python", "telemetry.sdk.name": "opentelemetry", "telemetry.sdk.version": "1.37.0", "service.name": "unknown_service", "telemetry.auto.version": "0.13.0.dev0-aws", "aws.local.service": "UnknownService" }, "schema_url": "" } } ``` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 050878f commit 9fdc363

File tree

9 files changed

+314
-43
lines changed

9 files changed

+314
-43
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ If your change does not need a CHANGELOG entry, add the "skip changelog" label t
2525
([#522](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/522))
2626
- Bump ADOT Python version to 0.13.0 and OTel dependencies to 1.37.0/0.58b0
2727
([#524](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/524))
28+
- Support credentials provider name for BedrockAgentCore Identity
29+
([#534](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/534))
2830

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_attribute_keys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# TODO:Move to Semantic Conventions when these attributes are added.
2121
AWS_AUTH_ACCESS_KEY: str = "aws.auth.account.access_key"
2222
AWS_AUTH_REGION: str = "aws.auth.region"
23-
AWS_AUTH_CREDENTIAL_PROVIDER_ARN: str = "aws.auth.credential_provider.arn"
23+
AWS_AUTH_CREDENTIAL_PROVIDER: str = "aws.auth.credential_provider"
2424
AWS_GATEWAY_TARGET_ID = "aws.gateway.target.id"
2525
AWS_SQS_QUEUE_URL: str = "aws.sqs.queue.url"
2626
AWS_SQS_QUEUE_NAME: str = "aws.sqs.queue.name"

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from amazon.opentelemetry.distro._aws_attribute_keys import (
1010
AWS_AUTH_ACCESS_KEY,
11-
AWS_AUTH_CREDENTIAL_PROVIDER_ARN,
11+
AWS_AUTH_CREDENTIAL_PROVIDER,
1212
AWS_AUTH_REGION,
1313
AWS_BEDROCK_AGENT_ID,
1414
AWS_BEDROCK_AGENTCORE_BROWSER_ARN,
@@ -741,7 +741,9 @@ def _handle_browser_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Opti
741741
if browser_id:
742742
agentcore_cfn_identifier = str(browser_id)
743743
elif browser_arn:
744-
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(browser_arn))
744+
agentcore_cfn_identifier = RegionalResourceArnParser.extract_bedrock_agentcore_resource_id_from_arn(
745+
str(browser_arn)
746+
)
745747

746748
# Uses browser ID as both resource identifier and CFN primary identifier.
747749
# aws.browser.v1 is a managed AWS resource, custom IDs are user-defined resources.
@@ -763,7 +765,9 @@ def _handle_gateway_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Opti
763765
if gateway_id:
764766
agentcore_cfn_identifier = str(gateway_id)
765767
elif gateway_arn:
766-
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(gateway_arn))
768+
agentcore_cfn_identifier = RegionalResourceArnParser.extract_bedrock_agentcore_resource_id_from_arn(
769+
str(gateway_arn)
770+
)
767771
else:
768772
agentcore_cfn_identifier = str(gateway_target_id)
769773

@@ -778,7 +782,9 @@ def _handle_gateway_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Opti
778782
if gateway_id:
779783
agentcore_cfn_identifier = str(gateway_id)
780784
elif gateway_arn:
781-
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(gateway_arn))
785+
agentcore_cfn_identifier = RegionalResourceArnParser.extract_bedrock_agentcore_resource_id_from_arn(
786+
str(gateway_arn)
787+
)
782788

783789
# Uses gateway ID as both resource identifier and CFN primary identifier.
784790
# If gateway ID is not available, extract it from the gateway ARN.
@@ -797,7 +803,9 @@ def _handle_runtime_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Opti
797803
runtime_endpoint_arn = attrs.get(AWS_BEDROCK_AGENTCORE_RUNTIME_ENDPOINT_ARN)
798804

799805
if runtime_endpoint_arn:
800-
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(runtime_endpoint_arn))
806+
agentcore_cfn_identifier = RegionalResourceArnParser.extract_bedrock_agentcore_resource_id_from_arn(
807+
str(runtime_endpoint_arn)
808+
)
801809

802810
# Uses extracted ID as resource identifier and full ARN as CFN primary identifier.
803811
return "RuntimeEndpoint", agentcore_cfn_identifier, str(runtime_endpoint_arn)
@@ -807,7 +815,9 @@ def _handle_runtime_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Opti
807815
if runtime_id:
808816
agentcore_cfn_identifier = str(runtime_id)
809817
elif runtime_arn:
810-
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(runtime_arn))
818+
agentcore_cfn_identifier = RegionalResourceArnParser.extract_bedrock_agentcore_resource_id_from_arn(
819+
str(runtime_arn)
820+
)
811821

812822
# Uses runtime ID as both resource identifier and CFN primary identifier.
813823
# If runtime ID is not available, extract it from the runtime ARN.
@@ -829,7 +839,9 @@ def _handle_code_interpreter_attrs(attrs: BoundedAttributes) -> tuple[Optional[s
829839
if code_interpreter_id:
830840
agentcore_cfn_identifier = str(code_interpreter_id)
831841
elif code_interpreter_arn:
832-
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(code_interpreter_arn))
842+
agentcore_cfn_identifier = RegionalResourceArnParser.extract_bedrock_agentcore_resource_id_from_arn(
843+
str(code_interpreter_arn)
844+
)
833845

834846
# Uses code interpreter ID as both resource identifier and CFN primary identifier.
835847
# aws.codeinterpreter.v1 is a managed AWS resource, custom IDs are user-defined resources.
@@ -844,22 +856,33 @@ def _handle_code_interpreter_attrs(attrs: BoundedAttributes) -> tuple[Optional[s
844856
def _handle_identity_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optional[str], Optional[str]]:
845857
"""
846858
Handler for BedrockAgentCore Identity resources.
847-
Returns (resource_type, resource_identifier, cfn_primary_identifier).
848859
"""
849-
credential_arn = attrs.get(AWS_AUTH_CREDENTIAL_PROVIDER_ARN)
860+
credentials_provider = attrs.get(AWS_AUTH_CREDENTIAL_PROVIDER)
861+
rpc_method = attrs.get(_RPC_METHOD)
850862

851-
if credential_arn:
852-
credential_arn_str = str(credential_arn)
863+
if credentials_provider and rpc_method:
864+
credentials_provider_str = str(credentials_provider)
865+
rpc_method_str = str(rpc_method).lower()
853866
resource_type = None
854-
if "apikeycredentialprovider" in credential_arn_str:
867+
868+
# Determine the credential provider type from the RPC method.
869+
# The credential provider can be either an ARN or a name. While ARNs contain
870+
# type information, names do not, so we infer the type from the API operation.
871+
if "apikey" in rpc_method_str:
855872
resource_type = "APIKeyCredentialProvider"
856-
elif "oauth2credentialprovider" in credential_arn_str:
873+
elif "oauth2" in rpc_method_str:
857874
resource_type = "OAuth2CredentialProvider"
858-
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(credential_arn_str)
859875

860-
# Uses extracted ID from credential ARN as both resource identifier and CFN primary identifier.
861-
# Resource type is determined by credential provider type in the ARN.
862-
return resource_type, agentcore_cfn_identifier, agentcore_cfn_identifier
876+
if resource_type:
877+
# CFN identifier is the credentials provider name.
878+
# If the credentials provider is an ARN, we extract the name from the ARN.
879+
agentcore_cfn_identifier = (
880+
RegionalResourceArnParser.extract_bedrock_agentcore_resource_id_from_arn(credentials_provider_str)
881+
or credentials_provider_str
882+
)
883+
884+
# Uses credential name as both resource identifier and CFN primary identifier.
885+
return resource_type, agentcore_cfn_identifier, agentcore_cfn_identifier
863886

864887
return None, None, None
865888

@@ -878,8 +901,9 @@ def _handle_memory_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optio
878901
if memory_id:
879902
agentcore_cfn_identifier = str(memory_id)
880903
if memory_arn:
881-
agentcore_cfn_identifier = agentcore_cfn_identifier or extract_bedrock_agentcore_resource_id_from_arn(
882-
str(memory_arn)
904+
agentcore_cfn_identifier = (
905+
agentcore_cfn_identifier
906+
or RegionalResourceArnParser.extract_bedrock_agentcore_resource_id_from_arn(str(memory_arn))
883907
)
884908
agentcore_cfn_primary_identifier = str(memory_arn)
885909

@@ -890,14 +914,6 @@ def _handle_memory_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optio
890914
return None, None, None
891915

892916

893-
def extract_bedrock_agentcore_resource_id_from_arn(arn: str) -> Optional[str]:
894-
"""Extract resource ID from ARN resource part."""
895-
resource_part = RegionalResourceArnParser.extract_resource_name_from_arn(arn)
896-
if not resource_part:
897-
return None
898-
return resource_part.split("/")[-1] if "/" in resource_part else resource_part
899-
900-
901917
def _log_unknown_attribute(attribute_key: str, span: ReadableSpan) -> None:
902918
message: str = "No valid %s value found for %s span %s"
903919
if _logger.isEnabledFor(DEBUG):

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_bedrock_agentcore_patches.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Any, Dict
44

55
from amazon.opentelemetry.distro._aws_attribute_keys import (
6-
AWS_AUTH_CREDENTIAL_PROVIDER_ARN,
6+
AWS_AUTH_CREDENTIAL_PROVIDER,
77
AWS_BEDROCK_AGENTCORE_BROWSER_ARN,
88
AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN,
99
AWS_BEDROCK_AGENTCORE_GATEWAY_ARN,
@@ -46,7 +46,8 @@
4646
"memory.arn": AWS_BEDROCK_AGENTCORE_MEMORY_ARN,
4747
"memory.id": GEN_AI_MEMORY_ID,
4848
"memoryId": GEN_AI_MEMORY_ID,
49-
"credentialProviderArn": AWS_AUTH_CREDENTIAL_PROVIDER_ARN,
49+
"credentialProviderArn": AWS_AUTH_CREDENTIAL_PROVIDER,
50+
"resourceCredentialProviderName": AWS_AUTH_CREDENTIAL_PROVIDER,
5051
"workloadIdentityArn": AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN,
5152
"workloadIdentityDetails.workloadIdentityArn": AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN,
5253
}

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/regional_resource_arn_parser.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,41 @@
88
class RegionalResourceArnParser:
99
@staticmethod
1010
def get_account_id(arn: str) -> Optional[str]:
11-
parts = _get_arn_parts(arn)
11+
parts = RegionalResourceArnParser._get_arn_parts(arn)
1212
return parts[4] if parts else None
1313

1414
@staticmethod
1515
def get_region(arn: str) -> Optional[str]:
16-
parts = _get_arn_parts(arn)
16+
parts = RegionalResourceArnParser._get_arn_parts(arn)
1717
return parts[3] if parts else None
1818

1919
@staticmethod
2020
def extract_dynamodb_table_name_from_arn(arn: str) -> Optional[str]:
21-
parts = _get_arn_parts(arn)
21+
parts = RegionalResourceArnParser._get_arn_parts(arn)
2222
return parts[-1].replace("table/", "") if parts else None
2323

2424
@staticmethod
2525
def extract_kinesis_stream_name_from_arn(arn: str) -> Optional[str]:
26-
parts = _get_arn_parts(arn)
26+
parts = RegionalResourceArnParser._get_arn_parts(arn)
2727
return parts[-1].replace("stream/", "") if parts else None
2828

2929
@staticmethod
30-
def extract_resource_name_from_arn(arn: str) -> Optional[str]:
31-
parts = _get_arn_parts(arn)
30+
def extract_bedrock_agentcore_resource_id_from_arn(arn: str) -> Optional[str]:
31+
"""Extract resource ID from ARN resource part."""
32+
resource_part = RegionalResourceArnParser.extract_resource_name_from_arn(arn)
33+
if resource_part is None:
34+
return None
35+
parts = resource_part.split("/")
3236
return parts[-1] if parts else None
3337

38+
@staticmethod
39+
def extract_resource_name_from_arn(arn: str) -> Optional[str]:
40+
parts = RegionalResourceArnParser._get_arn_parts(arn)
41+
return parts[-1] if parts else None
3442

35-
def _get_arn_parts(arn: str) -> Optional[list]:
36-
if not arn or not arn.startswith("arn"):
37-
return None
38-
parts = arn.split(":")
39-
return parts if len(parts) >= 6 and is_account_id(parts[4]) else None
43+
@staticmethod
44+
def _get_arn_parts(arn: str) -> Optional[list]:
45+
if not arn or not arn.startswith("arn"):
46+
return None
47+
parts = arn.split(":")
48+
return parts if len(parts) >= 6 and is_account_id(parts[4]) else None

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/patches/test_instrumentation_patch.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import opentelemetry.sdk.extension.aws.resource.ec2 as ec2_resource
99
import opentelemetry.sdk.extension.aws.resource.eks as eks_resource
1010
from amazon.opentelemetry.distro._aws_attribute_keys import (
11-
AWS_AUTH_CREDENTIAL_PROVIDER_ARN,
11+
AWS_AUTH_CREDENTIAL_PROVIDER,
1212
AWS_BEDROCK_AGENTCORE_BROWSER_ARN,
1313
AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN,
1414
AWS_BEDROCK_AGENTCORE_GATEWAY_ARN,
@@ -67,6 +67,7 @@
6767
_AGENTCORE_CREDENTIAL_PROVIDER_ARN: str = (
6868
"arn:aws:acps:us-east-1:123456789012:token-vault/test-vault/apikeycredentialprovider/test-provider"
6969
)
70+
_AGENTCORE_CREDENTIAL_PROVIDER_NAME: str = "test-oauth2-provider-123"
7071
_AGENTCORE_WORKLOAD_IDENTITY_ARN: str = "arn:aws:bedrock-agentcore:us-east-1:123456789012:workload-identity/test-wi"
7172

7273
# Patch names
@@ -266,14 +267,24 @@ def _test_patched_botocore_instrumentation(self):
266267
AWS_GATEWAY_TARGET_ID: _AGENTCORE_TARGET_ID,
267268
GEN_AI_MEMORY_ID: _AGENTCORE_MEMORY_ID,
268269
AWS_BEDROCK_AGENTCORE_MEMORY_ARN: _AGENTCORE_MEMORY_ARN,
269-
AWS_AUTH_CREDENTIAL_PROVIDER_ARN: _AGENTCORE_CREDENTIAL_PROVIDER_ARN,
270+
AWS_AUTH_CREDENTIAL_PROVIDER: _AGENTCORE_CREDENTIAL_PROVIDER_ARN,
270271
AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN: _AGENTCORE_WORKLOAD_IDENTITY_ARN,
271272
}
272273

273274
for attr_key, expected_value in expected_attrs.items():
274275
self.assertEqual(bedrock_agentcore_attributes[attr_key], expected_value)
275276
self.assertEqual(bedrock_agentcore_success_attributes[attr_key], expected_value)
276277

278+
# Test resourceCredentialProviderName
279+
name_attrs = _do_extract_attributes(
280+
"bedrock-agentcore", {"resourceCredentialProviderName": _AGENTCORE_CREDENTIAL_PROVIDER_NAME}
281+
)
282+
name_success_attrs = _do_on_success(
283+
"bedrock-agentcore", {"resourceCredentialProviderName": _AGENTCORE_CREDENTIAL_PROVIDER_NAME}
284+
)
285+
self.assertEqual(name_attrs[AWS_AUTH_CREDENTIAL_PROVIDER], _AGENTCORE_CREDENTIAL_PROVIDER_NAME)
286+
self.assertEqual(name_success_attrs[AWS_AUTH_CREDENTIAL_PROVIDER], _AGENTCORE_CREDENTIAL_PROVIDER_NAME)
287+
277288
# BedrockRuntime
278289
self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS)
279290

0 commit comments

Comments
 (0)