1
0
mirror of synced 2026-05-22 22:53:35 +00:00

Fix cdk nag warnings (#1542)

* fix: resolve cfn-nag failures in CloudFormation templates

- customer_support_lambda.yaml: add cfn_nag suppression metadata
  (F3, F38, W11) to GatewayAgentCoreRole; wildcard policy is
  intentional for this tutorial sample
- bearer-token-injection/cognito.yaml: MfaConfiguration OFF -> OPTIONAL
- strands-agents/cognito.yaml: MfaConfiguration OFF -> OPTIONAL
- typescript_mastra/github-source.yaml: fix YAML indentation on
  ImageScanningConfiguration (was at col 0, causing parse error)
- java_adk/github-source.yaml: same YAML indentation fix

Fixes 6 cfn-nag failures (F3, F38 x1, F78 x2, FATAL x2) across 5 templates.

* fix: correct agentcore-map.png image path in 01-features README

* fix: resolve cdk-nag warnings across features and workshops

* chore: add cfn-nag suppression metadata to 12 CloudFormation templates

Adds cfn_nag rules_to_suppress Metadata blocks to suppress expected
warnings in tutorial/demo templates:

- W60 (VPC flow logs): 10 templates — demo VPCs don't require flow logs
- W33 (MapPublicIpOnLaunch): 8 templates — public subnets need auto-IP
  for tutorial accessibility
- W40/W5 (SG open egress/ingress): 8 templates — intentional open
  egress in AgentCore SGs and browser-firewall SGs (filtered by Network
  Firewall); broad rules in demo ALB/web server SGs
- W2/W9 (SG SSH from 0.0.0.0/0): 2 templates — development EC2
  instances use SSM but SSH open for tutorial convenience
- W56 (ALB HTTP listener): cluster.yaml — demo uses HTTP; HTTPS
  requires ACM certificate
- W59 (API Gateway no auth): infrastructure_all.yaml — AgentCore
  Gateway handles authentication upstream

Files modified (12):
  01-features/.../01-claude-code-with-s3-files/cfn-vpc.yaml
  01-features/.../02-claude-code-with-efs/cfn-vpc.yaml
  01-features/.../05-domain-filtering/agentcore-browser-firewall.yaml
  03-integrations/.../common/01-network.yaml
  03-integrations/.../ecs/cluster.yaml
  06-workshops/.../01-claude-code-with-s3-files/cfn-vpc.yaml
  06-workshops/.../02-claude-code-with-efs/cfn-vpc.yaml
  06-workshops/.../07-bearer-token-injection/.../infrastructure_all.yaml
  06-workshops/.../07-connecting-public-browser-.../cfn-browser.yaml
  06-workshops/.../08-Interacting-with-vpc-.../cfn-vpc-browser.yaml
  06-workshops/.../09-browser-with-domain-filtering/agentcore-browser-firewall.yaml
  06-workshops/.../11-browser-with-proxy/agentcore-browser-proxy.yaml

* fix: move suppression comments after Python syntax tokens

  Misplaced # pragma: allowlist secret comments were placed before
  trailing commas and closing braces, causing those tokens to be treated
  as comment text rather than Python syntax.  Moved all commas and
  closing brace/comma sequences before the comment in 21 files, resolving
  all ruff invalid-syntax errors reported by CI.

* fix: restore missing Python files in 04-entra-obo-mcp-runtime

* style: apply ruff formatting to all PR-touched Python files

* style: apply ruff formatting to pre-existing unformatted Python files

12 files with pre-existing formatting issues were surfaced by the
CI ruff format check because they appear in this PR's changed-files
list. Applied ruff format to bring them into compliance.
This commit is contained in:
Bharathi Srinivasan
2026-05-22 10:52:01 -07:00
committed by GitHub
parent 87c999ba43
commit 02471ab710
55 changed files with 1859 additions and 5721 deletions
+966 -4524
View File
File diff suppressed because it is too large Load Diff
@@ -47,7 +47,7 @@ def authenticate_user(username: str, account_number: str) -> str:
"member_since": "2020-01-15",
"account_type": "Premium Checking",
},
"session_token": "tok_demo_abc123xyz789",
"session_token": "tok_demo_abc123xyz789", # pragma: allowlist secret
"message": f"Welcome back, {username}! Authentication successful.",
}
@@ -151,9 +151,7 @@ async def main():
"""Run the MCP server"""
logger.info("Starting Authentication Tools MCP Server")
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream, write_stream, server.create_initialization_options()
)
await server.run(read_stream, write_stream, server.create_initialization_options())
if __name__ == "__main__":
@@ -110,7 +110,7 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username=username,
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
@@ -118,15 +118,13 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username=username,
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
# Authenticate User and get Access Token
auth_response = cognito_client.initiate_auth(
@@ -134,7 +132,7 @@ def get_or_create_cognito_pool(refresh_token=False):
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -171,16 +169,14 @@ def reauthenticate_user(client_id, client_secret):
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -207,11 +203,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": account_id},
"ArnLike": {
"aws:SourceArn": (
f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
)
},
"ArnLike": {"aws:SourceArn": (f"arn:aws:bedrock-agentcore:{region}:{account_id}:*")},
},
}
],
@@ -230,10 +222,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:"
"/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -244,8 +233,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:"
"/aws/bedrock-agentcore/runtimes/*:log-stream:*"
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
],
},
{
@@ -268,9 +256,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -281,8 +267,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"bedrock-agentcore:GetWorkloadAccessTokenForUserId",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{region}:{account_id}:"
f"workload-identity-directory/default",
f"arn:aws:bedrock-agentcore:{region}:{account_id}:workload-identity-directory/default",
f"arn:aws:bedrock-agentcore:{region}:{account_id}:"
"workload-identity-directory/default/workload-identity/*",
],
@@ -333,9 +318,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Sid": "GetSecrets",
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": [
f"arn:aws:secretsmanager:{region}:{account_id}:secret:{sm_name}*"
],
"Resource": [f"arn:aws:secretsmanager:{region}:{account_id}:secret:{sm_name}*"],
},
],
}
@@ -354,9 +337,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
role_response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=json.dumps(trust_policy),
Description=(
"IAM role for Amazon Bedrock AgentCore with required permissions"
),
Description=("IAM role for Amazon Bedrock AgentCore with required permissions"),
)
print(f"✅ Created IAM role: {role_name}")
@@ -440,26 +421,18 @@ def cleanup_cognito_resources(pool_id: str) -> bool:
if pool_id:
try:
# List and delete all app clients
clients_response = cognito_client.list_user_pool_clients(
UserPoolId=pool_id, MaxResults=60
)
clients_response = cognito_client.list_user_pool_clients(UserPoolId=pool_id, MaxResults=60)
for client in clients_response["UserPoolClients"]:
print(f"Deleting app client: {client['ClientName']}")
cognito_client.delete_user_pool_client(
UserPoolId=pool_id, ClientId=client["ClientId"]
)
cognito_client.delete_user_pool_client(UserPoolId=pool_id, ClientId=client["ClientId"])
# List and delete all users
users_response = cognito_client.list_users(
UserPoolId=pool_id, AttributesToGet=["email"]
)
users_response = cognito_client.list_users(UserPoolId=pool_id, AttributesToGet=["email"])
for user in users_response.get("Users", []):
print(f"Deleting user: {user['Username']}")
cognito_client.admin_delete_user(
UserPoolId=pool_id, Username=user["Username"]
)
cognito_client.admin_delete_user(UserPoolId=pool_id, Username=user["Username"])
# Delete the user pool
print(f"Deleting user pool: {pool_id}")
@@ -469,9 +442,7 @@ def cleanup_cognito_resources(pool_id: str) -> bool:
return True
except cognito_client.exceptions.ResourceNotFoundException:
print(
f"User pool {pool_id} not found. It may have already been deleted."
)
print(f"User pool {pool_id} not found. It may have already been deleted.")
return True
except cognito_client.exceptions.ClientError as e:
@@ -530,7 +501,4 @@ def local_file_cleanup() -> None:
if deleted_files:
print(f"\n📁 Successfully deleted {len(deleted_files)} files")
if missing_files:
print(
f"{len(missing_files)} files were already missing: "
f"{', '.join(missing_files)}"
)
print(f"{len(missing_files)} files were already missing: {', '.join(missing_files)}")
@@ -63,6 +63,12 @@ Resources:
VPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
@@ -88,6 +94,12 @@ Resources:
PublicSubnet1:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
@@ -99,6 +111,12 @@ Resources:
PublicSubnet2:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.2.0/24"
@@ -209,6 +227,15 @@ Resources:
AgentCoreSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W40
reason: >-
All outbound traffic required for AgentCore runtime to reach AWS services; restrict in production
- id: W5
reason: >-
Sample security group uses broad egress rules for tutorial simplicity; restrict in production
Properties:
GroupDescription: Security group for AgentCore runtimes
VpcId: !Ref VPC
@@ -57,6 +57,12 @@ Resources:
VPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
@@ -82,6 +88,12 @@ Resources:
PublicSubnet1:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
@@ -93,6 +105,12 @@ Resources:
PublicSubnet2:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.2.0/24"
@@ -203,6 +221,15 @@ Resources:
AgentCoreSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W40
reason: >-
All outbound traffic required for AgentCore runtime to reach AWS services; restrict in production
- id: W5
reason: >-
Sample security group uses broad egress rules for tutorial simplicity; restrict in production
Properties:
GroupDescription: Security group for AgentCore runtimes
VpcId: !Ref VPC
@@ -73,6 +73,12 @@ Resources:
VPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsHostnames: true
@@ -160,6 +166,15 @@ Resources:
BrowserSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W40
reason: >-
All outbound traffic required for browser; actual filtering is enforced by AWS Network Firewall
- id: W5
reason: >-
Open egress is intentional; domain-level filtering is handled by Network Firewall rules
Properties:
GroupName: !Sub '${AWS::StackName}-browser-sg'
GroupDescription: Security group for AgentCore Browser instances
@@ -26,14 +26,14 @@ def setup_cognito_user_pool(region):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser1",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 1
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser1",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -41,14 +41,14 @@ def setup_cognito_user_pool(region):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser2",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 2
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser2",
Password="MyPassword456!",
Password="MyPassword456!", # pragma: allowlist secret
Permanent=True,
)
@@ -56,7 +56,7 @@ def setup_cognito_user_pool(region):
auth_response1 = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token1 = auth_response1["AuthenticationResult"]["AccessToken"]
@@ -70,9 +70,7 @@ def setup_cognito_user_pool(region):
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"User 1 Bearer Token: {bearer_token1}")
print(f"User 2 Bearer Token: {bearer_token2}")
@@ -161,9 +159,7 @@ def create_agentcore_role(agent_name):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -197,9 +193,7 @@ def create_agentcore_role(agent_name):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -226,9 +220,7 @@ def create_agentcore_role(agent_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -247,14 +239,10 @@ def create_agentcore_role(agent_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -29,14 +29,14 @@ def setup_cognito_user_pool(region):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser1",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 1
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser1",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -44,14 +44,14 @@ def setup_cognito_user_pool(region):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser2",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 2
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser2",
Password="MyPassword456!",
Password="MyPassword456!", # pragma: allowlist secret
Permanent=True,
)
@@ -59,7 +59,7 @@ def setup_cognito_user_pool(region):
auth_response1 = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token1 = auth_response1["AuthenticationResult"]["AccessToken"]
@@ -73,9 +73,7 @@ def setup_cognito_user_pool(region):
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"User 1 Bearer Token: {bearer_token1}")
print(f"User 2 Bearer Token: {bearer_token2}")
@@ -191,9 +189,7 @@ def create_agentcore_role(agent_name, region):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -227,9 +223,7 @@ def create_agentcore_role(agent_name, region):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -256,9 +250,7 @@ def create_agentcore_role(agent_name, region):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -277,14 +269,10 @@ def create_agentcore_role(agent_name, region):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -28,14 +28,14 @@ def setup_cognito_user_pool(region, memory_id):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser1",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 1
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser1",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -43,14 +43,14 @@ def setup_cognito_user_pool(region, memory_id):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser2",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 2
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser2",
Password="MyPassword456!",
Password="MyPassword456!", # pragma: allowlist secret
Permanent=True,
)
@@ -58,7 +58,7 @@ def setup_cognito_user_pool(region, memory_id):
auth_response1 = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token1 = auth_response1["AuthenticationResult"]["AccessToken"]
id_token1 = auth_response1["AuthenticationResult"]["IdToken"]
@@ -73,15 +73,11 @@ def setup_cognito_user_pool(region, memory_id):
id_token2 = auth_response2["AuthenticationResult"]["IdToken"]
# Create Identity Pool federated with User Pool
identity_pool_info = create_cognito_identity_pool(
pool_id, client_id, region, memory_id
)
identity_pool_info = create_cognito_identity_pool(pool_id, client_id, region, memory_id)
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"Identity Pool ID: {identity_pool_info['identity_pool_id']}")
print(f"User 1 Bearer Token: {bearer_token1}")
@@ -134,12 +130,8 @@ def reauthenticate_users(client_id, region, users=None):
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": username, "PASSWORD": password},
)
result["access_tokens"][username] = auth_response["AuthenticationResult"][
"AccessToken"
]
result["id_tokens"][username] = auth_response["AuthenticationResult"][
"IdToken"
]
result["access_tokens"][username] = auth_response["AuthenticationResult"]["AccessToken"]
result["id_tokens"][username] = auth_response["AuthenticationResult"]["IdToken"]
print(f"Successfully authenticated {username}")
except Exception as e:
print(f"Error authenticating {username}: {str(e)}")
@@ -215,9 +207,7 @@ def create_agentcore_role(agent_name, region):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -251,9 +241,7 @@ def create_agentcore_role(agent_name, region):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -295,9 +283,7 @@ def create_agentcore_role(agent_name, region):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -316,14 +302,10 @@ def create_agentcore_role(agent_name, region):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -378,9 +360,7 @@ def create_cognito_identity_pool(user_pool_id, client_id, region, memory_id="*")
# Create a shorter, unique role name using just the last part of the identity pool ID
# This ensures we stay under the 64 character limit
short_id = identity_pool_id.split(":")[-1][-12:].replace(
"-", ""
) # Last 12 chars without dashes
short_id = identity_pool_id.split(":")[-1][-12:].replace("-", "") # Last 12 chars without dashes
authenticated_role_name = f"cognito_auth_{short_id}"
# Create roles for authenticated users
@@ -404,14 +384,8 @@ def create_cognito_identity_pool(user_pool_id, client_id, region, memory_id="*")
"bedrock-agentcore:DeleteMemoryRecord",
"bedrock-agentcore:RetrieveMemoryRecords",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{region}:{account_id}:memory/{memory_id}"
],
"Condition": {
"StringEquals": {
"bedrock-agentcore:actorId": "${cognito-identity.amazonaws.com:sub}"
}
},
"Resource": [f"arn:aws:bedrock-agentcore:{region}:{account_id}:memory/{memory_id}"],
"Condition": {"StringEquals": {"bedrock-agentcore:actorId": "${cognito-identity.amazonaws.com:sub}"}},
},
{
"Effect": "Allow",
@@ -429,12 +403,8 @@ def create_cognito_identity_pool(user_pool_id, client_id, region, memory_id="*")
"Principal": {"Federated": "cognito-identity.amazonaws.com"},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": identity_pool_id
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
},
"StringEquals": {"cognito-identity.amazonaws.com:aud": identity_pool_id},
"ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "authenticated"},
},
}
],
@@ -455,9 +425,7 @@ def create_cognito_identity_pool(user_pool_id, client_id, region, memory_id="*")
PolicyDocument=json.dumps(authenticated_policy_document),
)
iam_client.attach_role_policy(
RoleName=authenticated_role_name, PolicyArn=auth_policy["Policy"]["Arn"]
)
iam_client.attach_role_policy(RoleName=authenticated_role_name, PolicyArn=auth_policy["Policy"]["Arn"])
except iam_client.exceptions.EntityAlreadyExistsException:
# Role already exists, get its ARN
response = iam_client.get_role(RoleName=authenticated_role_name)
@@ -159,7 +159,7 @@ def setup_cognito_user_pool(pool_name: str = None) -> dict:
# Create test user
username = "testuser"
password = "MyPassword123!"
password = "MyPassword123!" # pragma: allowlist secret
email = "testuser@example.com"
cognito.admin_create_user(
@@ -175,14 +175,9 @@ def setup_cognito_user_pool(pool_name: str = None) -> dict:
Password=password,
Permanent=True,
)
print(
f" Test user created: {username} / {password}"
) # codeql[py/clear-text-logging-sensitive-data]
print(f" Test user created: {username} / {password}") # codeql[py/clear-text-logging-sensitive-data]
discovery_url = (
f"https://cognito-idp.{REGION}.amazonaws.com/{user_pool_id}"
"/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{REGION}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration"
return {
"user_pool_id": user_pool_id,
@@ -234,9 +229,7 @@ def create_execution_role() -> str:
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -259,9 +252,7 @@ def create_execution_role() -> str:
"Effect": "Allow",
"Action": ["cloudwatch:PutMetricData"],
"Resource": "*",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Effect": "Allow",
@@ -364,11 +355,7 @@ def build_and_upload_zip() -> str:
try:
s3.create_bucket(
Bucket=S3_BUCKET,
**(
{"CreateBucketConfiguration": {"LocationConstraint": REGION}}
if REGION != "us-east-1"
else {}
),
**({"CreateBucketConfiguration": {"LocationConstraint": REGION}} if REGION != "us-east-1" else {}),
)
except (
s3.exceptions.BucketAlreadyOwnedByYou,
@@ -389,9 +376,7 @@ def build_and_upload_zip() -> str:
# ── Step 4: Create AgentCore Runtime with inbound auth ─────────────────────────
def create_runtime_with_inbound_auth(
role_arn: str, s3_uri: str, cognito_config: dict
) -> dict:
def create_runtime_with_inbound_auth(role_arn: str, s3_uri: str, cognito_config: dict) -> dict:
"""Create AgentCore Runtime configured for Cognito JWT inbound auth."""
control = boto3.client("bedrock-agentcore-control", region_name=REGION)
@@ -497,9 +482,7 @@ def cleanup(state: dict):
pool_info = cognito.describe_user_pool(UserPoolId=state["user_pool_id"])
domain = pool_info["UserPool"].get("Domain", "")
if domain:
cognito.delete_user_pool_domain(
UserPoolId=state["user_pool_id"], Domain=domain
)
cognito.delete_user_pool_domain(UserPoolId=state["user_pool_id"], Domain=domain)
cognito.delete_user_pool(UserPoolId=state["user_pool_id"])
print(f" Deleted Cognito pool: {state['user_pool_id']}")
except Exception as e:
@@ -565,9 +548,7 @@ def main():
# ── 4. Create runtime with inbound auth ──────────────────────────────
print("\n=== Step 4: Creating AgentCore Runtime with Inbound Auth ===")
runtime_info = create_runtime_with_inbound_auth(
role_arn, s3_uri, cognito_config
)
runtime_info = create_runtime_with_inbound_auth(role_arn, s3_uri, cognito_config)
runtime_arn = runtime_info["runtime_arn"]
state = {
@@ -19,7 +19,7 @@ logger.setLevel(logging.INFO)
# PingFederate configuration constants
CLIENT_ID = "agentcore-client"
CLIENT_SECRET = "agentcore-test-secret-12345"
CLIENT_SECRET = os.environ.get("PINGFED_CLIENT_SECRET", "agentcore-test-secret-12345") # pragma: allowlist secret
ATM_ID = "agentcoreJwtAtm"
OIDC_POLICY_ID = "agentcoreOidcPolicy"
SIGNING_KEY_ID = "agentcore-signing-key"
@@ -44,11 +44,7 @@ def handler(event, context):
# VPC ENIs can take a few seconds to initialize on cold start, causing
# "[Errno 16] Device or resource busy" errors on the first network call.
sm = boto3.client("secretsmanager")
secret_value = json.loads(
_retry_on_eni_busy(lambda: sm.get_secret_value(SecretId=secret_id))[
"SecretString"
]
)
secret_value = json.loads(_retry_on_eni_busy(lambda: sm.get_secret_value(SecretId=secret_id))["SecretString"])
admin_password = secret_value["adminPassword"]
try:
@@ -69,9 +65,7 @@ def handler(event, context):
)
else:
# Delete — nothing to tear down
send_response(
response_url, "SUCCESS", stack_id, request_id, logical_id, physical_id
)
send_response(response_url, "SUCCESS", stack_id, request_id, logical_id, physical_id)
except Exception as e:
logger.exception("Configuration failed")
send_response(
@@ -98,9 +92,7 @@ def _retry_on_eni_busy(fn, max_attempts=6, delay=5):
except OSError as e:
if attempt == max_attempts - 1:
raise
logger.warning(
f"VPC ENI not ready (attempt {attempt + 1}/{max_attempts}): {e}"
)
logger.warning(f"VPC ENI not ready (attempt {attempt + 1}/{max_attempts}): {e}")
time.sleep(delay)
@@ -122,9 +114,7 @@ def configure_pingfederate(admin_url, admin_user, admin_password, base_url):
break
except Exception:
if i == max_attempts - 1:
raise TimeoutError(
f"PingFederate not ready after {max_attempts} attempts"
)
raise TimeoutError(f"PingFederate not ready after {max_attempts} attempts")
time.sleep(5)
# 1. Generate signing key pair
@@ -86,21 +86,18 @@ def setup_cognito():
cognito.admin_create_user(
UserPoolId=user_pool_id,
Username="testuser",
TemporaryPassword="MyPassword123!",
TemporaryPassword="MyPassword123!", # pragma: allowlist secret
UserAttributes=[{"Name": "email", "Value": "testuser@example.com"}],
MessageAction="SUPPRESS",
)
cognito.admin_set_user_password(
UserPoolId=user_pool_id,
Username="testuser",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
discovery_url = (
f"https://cognito-idp.{REGION}.amazonaws.com/{user_pool_id}"
"/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{REGION}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration"
config = {
"user_pool_id": user_pool_id,
@@ -118,7 +115,7 @@ def reauthenticate_user(client_id: str) -> str:
cognito = boto3.client("cognito-idp", region_name=REGION)
auth_resp = cognito.initiate_auth(
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
ClientId=client_id,
)
return auth_resp["AuthenticationResult"]["AccessToken"]
@@ -221,18 +218,14 @@ def invoke_agent_with_google_calendar(runtime, bearer_token: str):
wait_for_oauth2_server_to_be_ready,
)
oauth2_callback_server_process = subprocess.Popen(
[sys.executable, "oauth2_callback_server.py", "--region", REGION]
)
oauth2_callback_server_process = subprocess.Popen([sys.executable, "oauth2_callback_server.py", "--region", REGION])
try:
if wait_for_oauth2_server_to_be_ready():
store_token_in_oauth2_callback_server(bearer_token)
print(" Invoking agent (this will trigger the Google OAuth2 flow)...")
invoke_response = runtime.invoke(
{
"prompt": "What is in my agenda for today? Highlight the main events!"
},
{"prompt": "What is in my agenda for today? Highlight the main events!"},
bearer_token=bearer_token,
)
print(f" Response: {invoke_response}")
@@ -260,9 +253,7 @@ def main():
print("\n=== Step 3: Agent Code (strands_claude_google_3lo.py) ===")
print(" The agent code is in strands_claude_google_3lo.py.")
print(" Key pattern: @requires_access_token(provider_name='google-cal-provider',")
print(
" scopes=['https://www.googleapis.com/auth/calendar.readonly'],"
)
print(" scopes=['https://www.googleapis.com/auth/calendar.readonly'],")
print(" auth_flow='USER_FEDERATION',")
print(" callback_url=CALLBACK_URL)")
print(" This triggers the 3LO OAuth flow when the tool is first called.")
@@ -283,9 +274,7 @@ def main():
# ── 5. Streamlit app ───────────────────────────────────────────────────────
print("\n=== Step 5: Running the Streamlit Chat App ===")
print(" For an interactive experience, run:")
print(
f" python oauth2_callback_server.py --region {REGION} & streamlit run chatbot_app_cognito.py"
)
print(f" python oauth2_callback_server.py --region {REGION} & streamlit run chatbot_app_cognito.py")
print(" Login: testuser / MyPassword123!")
print(" Try: 'What is in my agenda for today?'")
@@ -294,9 +283,7 @@ def main():
print(f" Provider ARN: {provider_info['provider_arn']}")
print(f" Callback URL: {provider_info['callback_url']}")
print(f" Cognito User Pool: {cognito_config['user_pool_id']}")
print(
"\n Register the callback URL in Google Developer Console to enable the OAuth flow."
)
print("\n Register the callback URL in Google Developer Console to enable the OAuth flow.")
if __name__ == "__main__":
@@ -77,21 +77,18 @@ def setup_cognito() -> dict:
cognito.admin_create_user(
UserPoolId=user_pool_id,
Username="testuser",
TemporaryPassword="MyPassword123!",
TemporaryPassword="MyPassword123!", # pragma: allowlist secret
UserAttributes=[{"Name": "email", "Value": "testuser@example.com"}],
MessageAction="SUPPRESS",
)
cognito.admin_set_user_password(
UserPoolId=user_pool_id,
Username="testuser",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
discovery_url = (
f"https://cognito-idp.{REGION}.amazonaws.com/{user_pool_id}"
"/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{REGION}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration"
config = {
"user_pool_id": user_pool_id,
"client_id": client_id,
@@ -198,9 +195,7 @@ def main():
# ── 4. Running the app ────────────────────────────────────────────────────
print("\n=== Step 4: Running the Interactive App ===")
print(" The github_agent.py file is the agent code deployed to AgentCore Runtime.")
print(
" It lists private GitHub repositories using the GithubOauth2 credential provider."
)
print(" It lists private GitHub repositories using the GithubOauth2 credential provider.")
print("")
print(" For interactive testing, run the Streamlit app:")
print(f" python oauth2_callback_server.py --region {REGION} &")
@@ -16,8 +16,8 @@ from boto3.session import Session
POOL_NAME = "M2MAuthCodeDemoPool"
USERNAME = "testuser"
PASSWORD = "AgentCoreTest1!"
TEMP_PASSWORD = "TempPass123!"
PASSWORD = "AgentCoreTest1!" # pragma: allowlist secret
TEMP_PASSWORD = "TempPass123!" # pragma: allowlist secret
RESOURCE_SERVER_ID = "https://api.m2m-demo.internal"
@@ -38,9 +38,7 @@ def setup_cognito():
domain_prefix = "m2m-demo-" + re.sub(r"[^a-z0-9]", "-", pool_id.lower())[:18]
print(f"Creating Cognito domain '{domain_prefix}'...")
cognito.create_user_pool_domain(Domain=domain_prefix, UserPoolId=pool_id)
token_endpoint = (
f"https://{domain_prefix}.auth.{region}.amazoncognito.com/oauth2/token"
)
token_endpoint = f"https://{domain_prefix}.auth.{region}.amazoncognito.com/oauth2/token"
print(f" Token endpoint: {token_endpoint}")
# Resource server defines the scopes the machine client can request
@@ -97,10 +95,7 @@ def setup_cognito():
AuthParameters={"USERNAME": USERNAME, "PASSWORD": PASSWORD},
)
discovery_url = (
f"https://cognito-idp.{region}.amazonaws.com/{pool_id}"
"/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
config = {
"pool_id": pool_id,
@@ -0,0 +1,101 @@
"""Agent that performs Entra ID OBO token exchange to call Microsoft Graph via an MCP tool."""
import os
import boto3
from strands import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient
from mcp.client.streamable_http import streamablehttp_client
from bedrock_agentcore.runtime import BedrockAgentCoreApp, BedrockAgentCoreContext
from bedrock_agentcore.identity.auth import requires_access_token
app = BedrockAgentCoreApp()
MCP_URL = os.environ["MCP_URL"]
MCP_CLIENT_ID = os.environ["ENTRA_MCP_CLIENT_ID"]
CREDENTIAL_PROVIDER_NAME = os.environ.get("CREDENTIAL_PROVIDER_NAME", "entra-agent-provider")
REGION = os.environ.get("AWS_REGION", "us-west-2")
# Must match the header name allowlisted on the MCP runtime (Step 4).
# AgentCore Runtime only forwards headers whose names start with
# 'X-Amzn-Bedrock-AgentCore-Runtime-Custom-' AND are in the runtime's requestHeaderAllowlist.
GRAPH_TOKEN_HEADER = "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Graph-Token"
# Graph scopes we request in the OBO exchange. These must be delegated permissions
# granted to the Agent app in Entra (and admin-consented).
GRAPH_SCOPES = ["User.Read"]
bedrock_model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
temperature=0.1,
)
def exchange_for_graph_token() -> str:
"""Exchange the inbound user JWT for a Graph-scoped OBO delegation token.
Uses the workload access token populated on BedrockAgentCoreContext by the runtime,
then calls GetResourceOauth2Token with oauth2Flow=ON_BEHALF_OF_TOKEN_EXCHANGE.
The returned token has aud=Microsoft Graph and carries sub=user. Microsoft records the
acting middle-tier service in the xms_act.sub claim.
See: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/on-behalf-of-token-exchange.html
"""
workload_token = BedrockAgentCoreContext.get_workload_access_token()
if not workload_token:
raise RuntimeError("No workload access token on context; did Runtime deliver it?")
client = boto3.client("bedrock-agentcore", region_name=REGION)
response = client.get_resource_oauth2_token(
workloadIdentityToken=workload_token,
resourceCredentialProviderName=CREDENTIAL_PROVIDER_NAME,
scopes=GRAPH_SCOPES,
oauth2Flow="ON_BEHALF_OF_TOKEN_EXCHANGE",
)
return response["accessToken"]
# The @requires_access_token decorator fetches an M2M token (client_credentials grant)
# for the MCP transport. Same credential provider is used for both flows.
@requires_access_token(
provider_name="entra-agent-provider",
scopes=[f"api://{MCP_CLIENT_ID}/.default"],
auth_flow="M2M",
into="m2m_token",
)
def invoke_agent(prompt: str, *, m2m_token: str) -> str:
"""invoke_agent with two tokens:
- M2M token in the Authorization header (identifies the agent, authorizes transport).
- Graph OBO token in a custom request header (used by MCP tools as the Bearer credential
when calling Microsoft Graph).
The LLM never sees either token. Tool signatures expose no auth parameters.
"""
# 1. OBO exchange: user JWT -> Graph-scoped delegation token.
graph_token = exchange_for_graph_token()
# 2. Both tokens travel as HTTP headers. Neither reaches the LLM context.
headers = {
"authorization": f"Bearer {m2m_token}",
GRAPH_TOKEN_HEADER: graph_token,
}
mcp_client = MCPClient(lambda: streamablehttp_client(MCP_URL, headers))
with mcp_client:
tools = mcp_client.list_tools_sync()
agent = Agent(
model=bedrock_model,
tools=tools,
system_prompt="You are a helpful assistant. Use the available tools to answer user questions.",
)
return str(agent(prompt))
@app.entrypoint
def handler(payload, context):
prompt = payload.get("prompt", "hello")
return invoke_agent(prompt)
if __name__ == "__main__":
app.run()
@@ -0,0 +1,62 @@
"""MCP Server that exposes Microsoft Graph tools.
Auth model:
- MCP transport: authorized via AgentCore customJWTAuthorizer on the MCP Server app's M2M tokens.
- Graph calls: the agent sends the Graph-scoped OBO token in a custom request header
(X-Amzn-Bedrock-AgentCore-Runtime-Custom-Graph-Token). The MCP server reads
it from the request context and uses it as the Bearer credential to Microsoft Graph.
Why the Graph token travels via a header, not a tool argument:
- The LLM must never see or handle credentials (RFC 9700 §4.9; OWASP LLM06).
- Tool signatures therefore contain only business-layer parameters.
- The header is TLS-protected in transit and visible only to the agent and the MCP server, both
within the same trust boundary as the user's inbound JWT.
"""
import httpx
from mcp.server.fastmcp import FastMCP
from typing import Dict, Any
mcp = FastMCP(host="0.0.0.0", stateless_http=True)
GRAPH_BASE = "https://graph.microsoft.com/v1.0"
# AgentCore only forwards request headers that are explicitly allowlisted on the runtime and
# either named 'Authorization' or prefixed with 'X-Amzn-Bedrock-AgentCore-Runtime-Custom-'.
# The allowlist is set on the MCP runtime in Step 4. See:
# https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-header-allowlist.html
GRAPH_TOKEN_HEADER = "x-amzn-bedrock-agentcore-runtime-custom-graph-token"
def _get_graph_token() -> str:
"""Read the Graph OBO token the agent attached to this request."""
ctx = mcp.get_context()
headers = dict(ctx.request_context.request.headers)
token = headers.get(GRAPH_TOKEN_HEADER, "").strip()
if not token:
raise RuntimeError(
f"Missing Graph OBO token. Expected request header '{GRAPH_TOKEN_HEADER}' "
"to carry the delegation token. Check that the header is allowlisted on the "
"MCP runtime's requestHeaderConfiguration."
)
return token
@mcp.tool()
async def get_my_profile() -> Dict[str, Any]:
"""Return the signed-in user's Microsoft Graph profile."""
access_token = _get_graph_token()
async with httpx.AsyncClient() as client:
r = await client.get(f"{GRAPH_BASE}/me", headers={"Authorization": f"Bearer {access_token}"})
if r.status_code != 200:
return {"error": f"Graph returned {r.status_code}", "body": r.text}
p = r.json()
return {
"displayName": p.get("displayName"),
"email": p.get("mail") or p.get("userPrincipalName"),
"jobTitle": p.get("jobTitle"),
"id": p.get("id"),
}
if __name__ == "__main__":
mcp.run(transport="streamable-http")
@@ -53,8 +53,8 @@ RESTAURANT_LAMBDA_NAME = "restaurant_lambda_gateway"
COGNITO_POOL_NAME = "MCPServerPool"
COGNITO_CLIENT_NAME = "MCPServerPoolClient"
COGNITO_USERNAME = "testuser"
COGNITO_TEMP_PASSWORD = "Temp123!"
COGNITO_PASSWORD = "MyPassword123!"
COGNITO_TEMP_PASSWORD = "Temp123!" # pragma: allowlist secret
COGNITO_PASSWORD = "MyPassword123!" # pragma: allowlist secret
LAMBDA_RUNTIME = "python3.12"
LAMBDA_HANDLER = "lambda_function_code.lambda_handler"
@@ -30,13 +30,9 @@ from boto3.session import Session
boto_session = Session()
AWS_REGION = boto_session.region_name
registry_client = boto_session.client(
"bedrock-agentcore-control", region_name=AWS_REGION
)
registry_client = boto_session.client("bedrock-agentcore-control", region_name=AWS_REGION)
REGISTRY_SEARCH_ENDPOINT = (
f"https://bedrock-agentcore.{AWS_REGION}.amazonaws.com/registry-records/search"
)
REGISTRY_SEARCH_ENDPOINT = f"https://bedrock-agentcore.{AWS_REGION}.amazonaws.com/registry-records/search"
print(f"Session ready | Region: {AWS_REGION}")
@@ -57,9 +53,7 @@ class C:
def wait_for_record_draft(registry_id, record_id, interval=3):
while True:
resp = registry_client.get_registry_record(
registryId=registry_id, recordId=record_id
)
resp = registry_client.get_registry_record(registryId=registry_id, recordId=record_id)
status = resp["status"]
if status == "DRAFT":
return resp
@@ -111,10 +105,7 @@ else:
)
print(f" {C.GREEN}✅ User pool created{C.RESET}")
discovery_url = (
f"https://cognito-idp.{AWS_REGION}.amazonaws.com/{user_pool_id}"
"/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{AWS_REGION}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration"
print(f" {C.BOLD}Pool ID:{C.RESET} {C.CYAN}{user_pool_id}{C.RESET}")
print(f" {C.BOLD}Discovery URL:{C.RESET} {C.CYAN}{discovery_url}{C.RESET}")
@@ -140,7 +131,7 @@ print(f" {C.BOLD}Client ID:{C.RESET} {C.CYAN}{client_id}{C.RESET}")
# 1.3 Create test user
TEST_USERNAME = "testuser"
TEST_PASSWORD = "TempPass123!"
TEST_PASSWORD = "TempPass123!" # pragma: allowlist secret
try:
cognito.admin_create_user(
@@ -223,9 +214,7 @@ mcp_tool_schema = json.dumps(
"description": "Get current weather for a city",
"inputSchema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"],
},
},
@@ -279,21 +268,13 @@ print(f"\n{C.BOLD}=== Registry Records ==={C.RESET}")
print(f"Found {len(records_response['registryRecords'])} record(s):\n")
for rec in records_response["registryRecords"]:
status = rec["status"]
sc = (
C.GREEN
if status == "APPROVED"
else C.YELLOW
if status in ("DRAFT", "PENDING_APPROVAL")
else C.RED
)
sc = C.GREEN if status == "APPROVED" else C.YELLOW if status in ("DRAFT", "PENDING_APPROVAL") else C.RED
print(
f" {sc}[{status}]{C.RESET} {rec['name']} | {C.CYAN}{rec['descriptorType']}{C.RESET} | {C.DIM}{rec['recordId']}{C.RESET}"
)
# Approve record
registry_client.submit_registry_record_for_approval(
registryId=REGISTRY_ID, recordId=MCP_RECORD_ID
)
registry_client.submit_registry_record_for_approval(registryId=REGISTRY_ID, recordId=MCP_RECORD_ID)
print(f" {C.YELLOW}⏳ MCP record → PENDING_APPROVAL{C.RESET}")
registry_client.update_registry_record_status(
@@ -305,26 +286,14 @@ registry_client.update_registry_record_status(
print(f" {C.GREEN}✅ MCP record → APPROVED{C.RESET}")
# Verify record status
record_response = registry_client.get_registry_record(
registryId=REGISTRY_ID, recordId=MCP_RECORD_ID
)
record_response = registry_client.get_registry_record(registryId=REGISTRY_ID, recordId=MCP_RECORD_ID)
status = record_response["status"]
sc = (
C.GREEN
if status == "APPROVED"
else C.YELLOW
if status in ("DRAFT", "PENDING_APPROVAL")
else C.RED
)
sc = C.GREEN if status == "APPROVED" else C.YELLOW if status in ("DRAFT", "PENDING_APPROVAL") else C.RED
print(f"\n{C.BOLD}=== Record Details ==={C.RESET}")
print(f" {C.BOLD}Name:{C.RESET} {C.CYAN}{record_response['name']}{C.RESET}")
print(
f" {C.BOLD}Protocol:{C.RESET} {C.CYAN}{record_response['descriptorType']}{C.RESET}"
)
print(f" {C.BOLD}Protocol:{C.RESET} {C.CYAN}{record_response['descriptorType']}{C.RESET}")
print(f" {C.BOLD}Status:{C.RESET} {sc}{status}{C.RESET}")
print(
f" {C.BOLD}Version:{C.RESET} {C.CYAN}{record_response['recordVersion']}{C.RESET}"
)
print(f" {C.BOLD}Version:{C.RESET} {C.CYAN}{record_response['recordVersion']}{C.RESET}")
# ── 4. Authenticate and obtain access token ───────────────────────────────────
print(f"\n{C.BOLD}=== 4. Authenticate and Obtain Access Token ==={C.RESET}")
@@ -349,9 +318,7 @@ except Exception as e:
print(f"\n{C.BOLD}=== 5. Perform Authenticated Search ==={C.RESET}")
def search_registry_records(
access_token, search_query, registry_identifiers, max_results=10
):
def search_registry_records(access_token, search_query, registry_identifiers, max_results=10):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
@@ -361,9 +328,7 @@ def search_registry_records(
"registryIds": registry_identifiers,
"maxResults": max_results,
}
response = requests.post(
REGISTRY_SEARCH_ENDPOINT, headers=headers, json=payload, timeout=30
)
response = requests.post(REGISTRY_SEARCH_ENDPOINT, headers=headers, json=payload, timeout=30)
return response.json()
@@ -420,9 +385,7 @@ print(f"\n{C.BOLD}=== 6. Cleanup ==={C.RESET}")
# Delete registry records
records = registry_client.list_registry_records(registryId=REGISTRY_ID)
for rec in records.get("registryRecords", []):
registry_client.delete_registry_record(
registryId=REGISTRY_ID, recordId=rec["recordId"]
)
registry_client.delete_registry_record(registryId=REGISTRY_ID, recordId=rec["recordId"])
print(f" {C.GREEN}✅ Deleted record: {C.DIM}{rec['recordId']}{C.RESET}")
registry_client.delete_registry(registryId=REGISTRY_ID)
+1 -1
View File
@@ -23,7 +23,7 @@ AgentCore services work together or independently with any open-source framework
| **browser** | Managed cloud browser for agents to interact with web applications, navigate sites, fill forms, and extract information. |
| **harness** | Serverless agent orchestration layer — model, tools, system prompt, and context management in a single API call, without managing a runtime. |
![AgentCore map](01-getting-started/images/agentcore-map.png)
![AgentCore map](../00-getting-started/images/agentcore-map.png)
### What Can You Build?
@@ -106,8 +106,8 @@ Resources:
Type: AWS::ECR::Repository
DeletionPolicy: Delete
Properties:
ImageTagMutability: IMMUTABLE
ImageScanningConfiguration:
ImageTagMutability: IMMUTABLE
ImageScanningConfiguration:
ScanOnPush: true
LifecyclePolicy:
LifecyclePolicyText: |
@@ -104,8 +104,8 @@ Resources:
Type: AWS::ECR::Repository
DeletionPolicy: Delete
Properties:
ImageTagMutability: IMMUTABLE
ImageScanningConfiguration:
ImageTagMutability: IMMUTABLE
ImageScanningConfiguration:
ScanOnPush: true
LifecyclePolicy:
LifecyclePolicyText: |
+3 -3
View File
@@ -36,7 +36,7 @@ def setup_cognito_user_pool():
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
@@ -44,7 +44,7 @@ def setup_cognito_user_pool():
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -52,7 +52,7 @@ def setup_cognito_user_pool():
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token = auth_response["AuthenticationResult"]["AccessToken"]
@@ -9,6 +9,12 @@ Parameters:
Resources:
VPC: # cdk-nag: disable=AwsSolutions-VPC7:Demo application does not require VPC Flow Logs
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
@@ -32,6 +38,12 @@ Resources:
PublicSubnet1:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
@@ -44,6 +56,12 @@ Resources:
PublicSubnet2:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
@@ -37,6 +37,18 @@ Resources:
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W2
reason: >-
Demo ALB requires public HTTP access from internet; restrict to specific CIDRs in production
- id: W5
reason: >-
Demo ALB security group allows ingress from 0.0.0.0/0 for public accessibility; restrict in production
- id: W40
reason: >-
ALB requires broad outbound access for health checks and ECS task routing; restrict in production
Properties:
GroupName: !Sub ${ProjectName}-alb-sg
GroupDescription: ALB security group
@@ -136,6 +148,12 @@ Resources:
# See: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html
Listener: # checkov:skip=CKV_AWS_103,CKV_AWS_2:Demo application uses HTTP for simplicity
Type: AWS::ElasticLoadBalancingV2::Listener
Metadata:
cfn_nag:
rules_to_suppress:
- id: W56
reason: >-
Demo application uses HTTP for simplicity; use HTTPS with ACM certificate in production
Properties:
LoadBalancerArn: !Ref ALB
Port: 80
@@ -35,7 +35,7 @@ def setup_cognito_user_pool():
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
@@ -43,7 +43,7 @@ def setup_cognito_user_pool():
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -51,15 +51,13 @@ def setup_cognito_user_pool():
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token = auth_response["AuthenticationResult"]["AccessToken"]
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"Bearer Token: {bearer_token}")
@@ -92,9 +90,7 @@ def get_or_create_user_pool(cognito, USER_POOL_NAME):
if domain:
region = user_pool_id.split("_")[0] if "_" in user_pool_id else region
domain_url = f"https://{domain}.auth.{region}.amazoncognito.com"
print(
f"Found domain for user pool {user_pool_id}: {domain} ({domain_url})"
)
print(f"Found domain for user pool {user_pool_id}: {domain} ({domain_url})")
else:
print(f"No domains found for user pool {user_pool_id}")
return pool["Id"]
@@ -102,20 +98,14 @@ def get_or_create_user_pool(cognito, USER_POOL_NAME):
created = cognito.create_user_pool(PoolName=USER_POOL_NAME)
user_pool_id = created["UserPool"]["Id"]
user_pool_id_without_underscore_lc = user_pool_id.replace("_", "").lower()
cognito.create_user_pool_domain(
Domain=user_pool_id_without_underscore_lc, UserPoolId=user_pool_id
)
cognito.create_user_pool_domain(Domain=user_pool_id_without_underscore_lc, UserPoolId=user_pool_id)
print("Domain created as well")
return created["UserPool"]["Id"]
def get_or_create_resource_server(
cognito, user_pool_id, RESOURCE_SERVER_ID, RESOURCE_SERVER_NAME, SCOPES
):
def get_or_create_resource_server(cognito, user_pool_id, RESOURCE_SERVER_ID, RESOURCE_SERVER_NAME, SCOPES):
try:
cognito.describe_resource_server(
UserPoolId=user_pool_id, Identifier=RESOURCE_SERVER_ID
)
cognito.describe_resource_server(UserPoolId=user_pool_id, Identifier=RESOURCE_SERVER_ID)
return RESOURCE_SERVER_ID
except cognito.exceptions.ResourceNotFoundException:
print("creating new resource server")
@@ -128,15 +118,11 @@ def get_or_create_resource_server(
return RESOURCE_SERVER_ID
def get_or_create_m2m_client(
cognito, user_pool_id, CLIENT_NAME, RESOURCE_SERVER_ID, SCOPES=None
):
def get_or_create_m2m_client(cognito, user_pool_id, CLIENT_NAME, RESOURCE_SERVER_ID, SCOPES=None):
response = cognito.list_user_pool_clients(UserPoolId=user_pool_id, MaxResults=60)
for client in response["UserPoolClients"]:
if client["ClientName"] == CLIENT_NAME:
describe = cognito.describe_user_pool_client(
UserPoolId=user_pool_id, ClientId=client["ClientId"]
)
describe = cognito.describe_user_pool_client(UserPoolId=user_pool_id, ClientId=client["ClientId"])
return client["ClientId"], describe["UserPoolClient"]["ClientSecret"]
print("creating new m2m client")
@@ -157,9 +143,7 @@ def get_or_create_m2m_client(
SupportedIdentityProviders=["COGNITO"],
ExplicitAuthFlows=["ALLOW_REFRESH_TOKEN_AUTH"],
)
return created["UserPoolClient"]["ClientId"], created["UserPoolClient"][
"ClientSecret"
]
return created["UserPoolClient"]["ClientId"], created["UserPoolClient"]["ClientSecret"]
def get_token(
@@ -209,9 +193,7 @@ def create_agentcore_role(agent_name):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -239,9 +221,7 @@ def create_agentcore_role(agent_name):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Effect": "Allow",
@@ -279,9 +259,7 @@ def create_agentcore_role(agent_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -300,14 +278,10 @@ def create_agentcore_role(agent_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -365,9 +339,7 @@ def create_agentcore_gateway_role(gateway_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -387,14 +359,10 @@ def create_agentcore_gateway_role(gateway_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_gateway_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_gateway_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_gateway_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_gateway_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_gateway_role_name}")
iam_client.delete_role(RoleName=agentcore_gateway_role_name)
print(f"recreating {agentcore_gateway_role_name}")
@@ -453,9 +421,7 @@ def create_agentcore_gateway_role_s3_smithy(gateway_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -475,14 +441,10 @@ def create_agentcore_gateway_role_s3_smithy(gateway_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_gateway_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_gateway_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_gateway_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_gateway_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_gateway_role_name}")
iam_client.delete_role(RoleName=agentcore_gateway_role_name)
print(f"recreating {agentcore_gateway_role_name}")
@@ -505,9 +467,7 @@ def create_agentcore_gateway_role_s3_smithy(gateway_name):
return agentcore_iam_role
def create_gateway_lambda(
lambda_function_code_path, environment_variables
) -> dict[str, int]:
def create_gateway_lambda(lambda_function_code_path, environment_variables) -> dict[str, int]:
boto_session = Session()
region = boto_session.region_name
@@ -562,11 +522,7 @@ def create_gateway_lambda(
role_arn = response["Role"]["Arn"]
print(f"IAM role {role_name} already exists. Using the same ARN {role_arn}")
else:
error_message = (
error.response["Error"]["Code"]
+ "-"
+ error.response["Error"]["Message"]
)
error_message = error.response["Error"]["Code"] + "-" + error.response["Error"]["Message"]
print(f"Error creating role: {error_message}")
return_resp["lambda_function_arn"] = error_message
@@ -584,15 +540,9 @@ def create_gateway_lambda(
PackageType="Zip",
Environment={
"Variables": {
"ELASTIC_ENDPOINT_URL_ENV": environment_variables[
"ELASTIC_ENDPOINT_URL_ENV"
],
"ELASTIC_API_KEY_ENV": environment_variables[
"ELASTIC_API_KEY_ENV"
],
"ELASTIC_INDEX_NAME_ENV": environment_variables[
"ELASTIC_INDEX_NAME_ENV"
],
"ELASTIC_ENDPOINT_URL_ENV": environment_variables["ELASTIC_ENDPOINT_URL_ENV"],
"ELASTIC_API_KEY_ENV": environment_variables["ELASTIC_API_KEY_ENV"],
"ELASTIC_INDEX_NAME_ENV": environment_variables["ELASTIC_INDEX_NAME_ENV"],
}
},
)
@@ -603,16 +553,10 @@ def create_gateway_lambda(
if error.response["Error"]["Code"] == "ResourceConflictException":
response = lambda_client.get_function(FunctionName=lambda_function_name)
lambda_arn = response["Configuration"]["FunctionArn"]
print(
f"AWS Lambda function {lambda_function_name} already exists. Using the same ARN {lambda_arn}"
)
print(f"AWS Lambda function {lambda_function_name} already exists. Using the same ARN {lambda_arn}")
return_resp["lambda_function_arn"] = lambda_arn
else:
error_message = (
error.response["Error"]["Code"]
+ "-"
+ error.response["Error"]["Message"]
)
error_message = error.response["Error"]["Code"] + "-" + error.response["Error"]["Message"]
print(f"Error creating lambda function: {error_message}")
return_resp["lambda_function_arn"] = error_message
@@ -621,15 +565,11 @@ def create_gateway_lambda(
def delete_gateway(gateway_client, gatewayId):
print("Deleting all targets for gateway", gatewayId)
list_response = gateway_client.list_gateway_targets(
gatewayIdentifier=gatewayId, maxResults=100
)
list_response = gateway_client.list_gateway_targets(gatewayIdentifier=gatewayId, maxResults=100)
for item in list_response["items"]:
targetId = item["targetId"]
print("Deleting target ", targetId)
gateway_client.delete_gateway_target(
gatewayIdentifier=gatewayId, targetId=targetId
)
gateway_client.delete_gateway_target(gatewayIdentifier=gatewayId, targetId=targetId)
time.sleep(5)
print("Deleting gateway ", gatewayId)
gateway_client.delete_gateway(gatewayIdentifier=gatewayId)
@@ -706,12 +646,8 @@ def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn):
time.sleep(3)
except iam_client.exceptions.EntityAlreadyExistsException:
print(f"Role '{role_name}' already exists — updating trust and inline policy.")
iam_client.update_assume_role_policy(
RoleName=role_name, PolicyDocument=assume_role_policy_json
)
for policy_name in iam_client.list_role_policies(RoleName=role_name).get(
"PolicyNames", []
):
iam_client.update_assume_role_policy(RoleName=role_name, PolicyDocument=assume_role_policy_json)
for policy_name in iam_client.list_role_policies(RoleName=role_name).get("PolicyNames", []):
iam_client.delete_role_policy(RoleName=role_name, PolicyName=policy_name)
agentcoregw_iam_role = iam_client.get_role(RoleName=role_name)
@@ -730,9 +666,7 @@ def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn):
assume_policy = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "sts:AssumeRole", "Resource": role_arn}
],
"Statement": [{"Effect": "Allow", "Action": "sts:AssumeRole", "Resource": role_arn}],
}
# Attach assume-role policy if user/role
@@ -751,9 +685,7 @@ def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn):
)
except ClientError as e:
print(f"Unable to attach assume-role policy: {e}")
print(
"Make sure the caller has iam:PutUserPolicy or iam:PutRolePolicy permission."
)
print("Make sure the caller has iam:PutUserPolicy or iam:PutRolePolicy permission.")
# Retry loop for eventual consistency
max_retries = 5
@@ -769,11 +701,7 @@ def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn):
else:
raise
else:
raise RuntimeError(
f"Failed to assume role {role_name} after {max_retries} retries"
)
raise RuntimeError(f"Failed to assume role {role_name} after {max_retries} retries")
print(
f" Role '{role_name}' is ready and {current_arn} can invoke the Bedrock Agent Gateway."
)
print(f" Role '{role_name}' is ready and {current_arn} can invoke the Bedrock Agent Gateway.")
return agentcoregw_iam_role
@@ -29,9 +29,7 @@ def get_ssm_parameter(name: str, with_decryption: bool = True) -> str:
return response["Parameter"]["Value"]
def put_ssm_parameter(
name: str, value: str, parameter_type: str = "String", with_encryption: bool = False
) -> None:
def put_ssm_parameter(name: str, value: str, parameter_type: str = "String", with_encryption: bool = False) -> None:
ssm = boto3.client("ssm")
put_params = {
@@ -124,8 +122,7 @@ def read_config(file_path: str) -> Dict[str, Any]:
return yaml.safe_load(content)
except yaml.YAMLError:
raise ValueError(
f"Unsupported configuration file format: {ext}. "
f"Supported formats: .json, .yaml, .yml"
f"Unsupported configuration file format: {ext}. Supported formats: .json, .yaml, .yml"
)
except json.JSONDecodeError as e:
@@ -150,9 +147,7 @@ def save_customer_support_secret(secret_value):
)
print("✅ Created secret")
except secrets_client.exceptions.ResourceExistsException:
secrets_client.update_secret(
SecretId=cognito_config_name, SecretString=secret_value
)
secrets_client.update_secret(SecretId=cognito_config_name, SecretString=secret_value)
print("✅ Updated existing secret")
except Exception as e:
print(f"❌ Error saving secret: {str(e)}")
@@ -179,9 +174,7 @@ def delete_customer_support_secret():
region = boto_session.region_name
secrets_client = boto3.client("secretsmanager", region_name=region)
try:
secrets_client.delete_secret(
SecretId=cognito_config_name, ForceDeleteWithoutRecovery=True
)
secrets_client.delete_secret(SecretId=cognito_config_name, ForceDeleteWithoutRecovery=True)
print("✅ Deleted secret")
return True
except Exception as e:
@@ -231,7 +224,7 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username=username,
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
@@ -239,15 +232,13 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username=username,
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
# Authenticate User and get Access Token
auth_response = cognito_client.initiate_auth(
@@ -255,7 +246,7 @@ def get_or_create_cognito_pool(refresh_token=False):
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -277,9 +268,7 @@ def get_or_create_cognito_pool(refresh_token=False):
}
put_ssm_parameter("/app/customersupport/agentcore/client_id", client_id)
put_ssm_parameter("/app/customersupport/agentcore/pool_id", pool_id)
put_ssm_parameter(
"/app/customersupport/agentcore/cognito_discovery_url", discovery_url
)
put_ssm_parameter("/app/customersupport/agentcore/cognito_discovery_url", discovery_url)
put_ssm_parameter("/app/customersupport/agentcore/client_secret", client_secret)
save_customer_support_secret(json.dumps(cognito_config))
@@ -303,26 +292,18 @@ def cleanup_cognito_resources(pool_id):
if pool_id:
try:
# List and delete all app clients
clients_response = cognito_client.list_user_pool_clients(
UserPoolId=pool_id, MaxResults=60
)
clients_response = cognito_client.list_user_pool_clients(UserPoolId=pool_id, MaxResults=60)
for client in clients_response["UserPoolClients"]:
print(f"Deleting app client: {client['ClientName']}")
cognito_client.delete_user_pool_client(
UserPoolId=pool_id, ClientId=client["ClientId"]
)
cognito_client.delete_user_pool_client(UserPoolId=pool_id, ClientId=client["ClientId"])
# List and delete all users
users_response = cognito_client.list_users(
UserPoolId=pool_id, AttributesToGet=["email"]
)
users_response = cognito_client.list_users(UserPoolId=pool_id, AttributesToGet=["email"])
for user in users_response.get("Users", []):
print(f"Deleting user: {user['Username']}")
cognito_client.admin_delete_user(
UserPoolId=pool_id, Username=user["Username"]
)
cognito_client.admin_delete_user(UserPoolId=pool_id, Username=user["Username"])
# Delete the user pool
print(f"Deleting user pool: {pool_id}")
@@ -332,9 +313,7 @@ def cleanup_cognito_resources(pool_id):
return True
except cognito_client.exceptions.ResourceNotFoundException:
print(
f"User pool {pool_id} not found. It may have already been deleted."
)
print(f"User pool {pool_id} not found. It may have already been deleted.")
return True
except Exception as e:
@@ -358,16 +337,14 @@ def reauthenticate_user(client_id, client_secret):
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -392,9 +369,7 @@ def create_agentcore_runtime_execution_role():
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": account_id},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -413,9 +388,7 @@ def create_agentcore_runtime_execution_role():
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -449,9 +422,7 @@ def create_agentcore_runtime_execution_role():
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -505,9 +476,7 @@ def create_agentcore_runtime_execution_role():
"bedrock-agentcore:GetGateway",
"bedrock-agentcore:InvokeGateway",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*"
],
"Resource": [f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*"],
},
],
}
@@ -599,9 +568,7 @@ def delete_agentcore_runtime_execution_role():
except Exception:
pass
delete_ssm_parameter(
"/app/customersupport/agentcore/runtime_execution_role_arn"
)
delete_ssm_parameter("/app/customersupport/agentcore/runtime_execution_role_arn")
except Exception as e:
print(f"❌ Error during cleanup: {str(e)}")
@@ -660,16 +627,12 @@ def gateway_target_cleanup(gateway_id: str = None):
print(f"🗑️ Deleting all targets for gateway: {gateway_id}")
# List and delete all targets
list_response = gateway_client.list_gateway_targets(
gatewayIdentifier=gateway_id, maxResults=100
)
list_response = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id, maxResults=100)
for item in list_response["items"]:
target_id = item["targetId"]
print(f" Deleting target: {target_id}")
gateway_client.delete_gateway_target(
gatewayIdentifier=gateway_id, targetId=target_id
)
gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=target_id)
print(f" ✅ Target {target_id} deleted")
# Delete the gateway
@@ -681,14 +644,10 @@ def gateway_target_cleanup(gateway_id: str = None):
def runtime_resource_cleanup(runtime_arn: str = None):
try:
# Initialize AWS clients
agentcore_control_client = boto3.client(
"bedrock-agentcore-control", region_name=REGION
)
agentcore_control_client = boto3.client("bedrock-agentcore-control", region_name=REGION)
if runtime_arn:
runtime_id = runtime_arn.split(":")[-1].split("/")[-1]
response = agentcore_control_client.delete_agent_runtime(
agentRuntimeId=runtime_id
)
response = agentcore_control_client.delete_agent_runtime(agentRuntimeId=runtime_id)
print(f" ✅ Agent runtime deleted: {response['status']}")
else:
ecr_client = boto3.client("ecr", region_name=REGION)
@@ -697,9 +656,7 @@ def runtime_resource_cleanup(runtime_arn: str = None):
# print(" 🗑️ Deleting AgentCore Runtime...")
runtimes = agentcore_control_client.list_agent_runtimes()
for runtime in runtimes["agentRuntimes"]:
response = agentcore_control_client.delete_agent_runtime(
agentRuntimeId=runtime["agentRuntimeId"]
)
response = agentcore_control_client.delete_agent_runtime(agentRuntimeId=runtime["agentRuntimeId"])
print(f" ✅ Agent runtime deleted: {response['status']}")
# Delete the ECR repository
@@ -707,9 +664,7 @@ def runtime_resource_cleanup(runtime_arn: str = None):
repositories = ecr_client.describe_repositories()
for repo in repositories["repositories"]:
if "bedrock-agentcore-customer_support_agent" in repo["repositoryName"]:
ecr_client.delete_repository(
repositoryName=repo["repositoryName"], force=True
)
ecr_client.delete_repository(repositoryName=repo["repositoryName"], force=True)
print(f" ✅ ECR repository deleted: {repo['repositoryName']}")
except Exception as e:
@@ -726,9 +681,7 @@ def delete_observability_resources():
# Delete log stream first (must be done before deleting log group)
try:
print(f" 🗑️ Deleting log stream '{log_stream_name}'...")
logs_client.delete_log_stream(
logGroupName=log_group_name, logStreamName=log_stream_name
)
logs_client.delete_log_stream(logGroupName=log_group_name, logStreamName=log_stream_name)
print(f" ✅ Log stream '{log_stream_name}' deleted successfully")
except Exception as e:
if e.response["Error"]["Code"] == "ResourceNotFoundException":
@@ -775,6 +728,4 @@ def local_file_cleanup():
if deleted_files:
print(f"\n📁 Successfully deleted {len(deleted_files)} files")
if missing_files:
print(
f"{len(missing_files)} files were already missing: {', '.join(missing_files)}"
)
print(f"{len(missing_files)} files were already missing: {', '.join(missing_files)}")
@@ -35,7 +35,7 @@ def setup_cognito_user_pool():
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
@@ -43,7 +43,7 @@ def setup_cognito_user_pool():
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -51,15 +51,13 @@ def setup_cognito_user_pool():
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token = auth_response["AuthenticationResult"]["AccessToken"]
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"Bearer Token: {bearer_token}")
@@ -108,9 +106,7 @@ def create_agentcore_role(agent_name, region="us-east-1"):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -144,9 +140,7 @@ def create_agentcore_role(agent_name, region="us-east-1"):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -179,9 +173,7 @@ def create_agentcore_role(agent_name, region="us-east-1"):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -200,14 +192,10 @@ def create_agentcore_role(agent_name, region="us-east-1"):
time.sleep(sleep_time_10())
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -54,9 +54,7 @@ def get_ssm_parameter(name: str, with_decryption: bool = True) -> str:
return response["Parameter"]["Value"]
def put_ssm_parameter(
name: str, value: str, parameter_type: str = "String", with_encryption: bool = False
) -> None:
def put_ssm_parameter(name: str, value: str, parameter_type: str = "String", with_encryption: bool = False) -> None:
"""Put a parameter value into AWS Systems Manager Parameter Store."""
ssm = boto3.client("ssm")
put_params = {
@@ -90,9 +88,7 @@ def save_secret(secret_value: str) -> bool:
secrets_client.create_secret(
Name=SECRET_NAME,
SecretString=secret_value,
Description=(
"Secret containing the Cognito Configuration for the AWS Docs Agent"
),
Description=("Secret containing the Cognito Configuration for the AWS Docs Agent"),
)
print("✅ Created secret")
except secrets_client.exceptions.ResourceExistsException:
@@ -123,9 +119,7 @@ def delete_cognito_secret() -> bool:
region = boto_session.region_name
secrets_client = boto3.client("secretsmanager", region_name=region)
try:
secrets_client.delete_secret(
SecretId=SECRET_NAME, ForceDeleteWithoutRecovery=True
)
secrets_client.delete_secret(SecretId=SECRET_NAME, ForceDeleteWithoutRecovery=True)
print("✅ Secret Deleted")
return True
except secrets_client.exceptions.ClientError as e:
@@ -144,16 +138,14 @@ def reauthenticate_user(client_id: str, client_secret: str) -> str:
message = bytes(USERNAME + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": USERNAME,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -194,40 +186,35 @@ def setup_cognito_user_pool() -> Optional[Dict[str, str]]:
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username=USERNAME,
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username=USERNAME,
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
# Generate secret hash and authenticate
message = bytes(USERNAME + client_id, "utf-8")
key_bytes = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key_bytes, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key_bytes, message, digestmod=hashlib.sha256).digest()).decode()
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": USERNAME,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
bearer_token = auth_response["AuthenticationResult"]["AccessToken"]
# Create configuration object
discovery_url = (
f"https://cognito-idp.{region}.amazonaws.com/"
f"{pool_id}/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
cognito_config = {
"pool_id": pool_id,
@@ -263,26 +250,18 @@ def cleanup_cognito_resources(pool_id: str) -> bool:
if pool_id:
try:
# List and delete all app clients
clients_response = cognito_client.list_user_pool_clients(
UserPoolId=pool_id, MaxResults=60
)
clients_response = cognito_client.list_user_pool_clients(UserPoolId=pool_id, MaxResults=60)
for client in clients_response["UserPoolClients"]:
print(f"Deleting app client: {client['ClientName']}")
cognito_client.delete_user_pool_client(
UserPoolId=pool_id, ClientId=client["ClientId"]
)
cognito_client.delete_user_pool_client(UserPoolId=pool_id, ClientId=client["ClientId"])
# List and delete all users
users_response = cognito_client.list_users(
UserPoolId=pool_id, AttributesToGet=["email"]
)
users_response = cognito_client.list_users(UserPoolId=pool_id, AttributesToGet=["email"])
for user in users_response.get("Users", []):
print(f"Deleting user: {user['Username']}")
cognito_client.admin_delete_user(
UserPoolId=pool_id, Username=user["Username"]
)
cognito_client.admin_delete_user(UserPoolId=pool_id, Username=user["Username"])
# Delete the user pool
print(f"Deleting user pool: {pool_id}")
@@ -292,9 +271,7 @@ def cleanup_cognito_resources(pool_id: str) -> bool:
return True
except cognito_client.exceptions.ResourceNotFoundException:
print(
f"User pool {pool_id} not found. It may have already been deleted."
)
print(f"User pool {pool_id} not found. It may have already been deleted.")
return True
except cognito_client.exceptions.ClientError as e:
@@ -328,11 +305,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": account_id},
"ArnLike": {
"aws:SourceArn": (
f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
)
},
"ArnLike": {"aws:SourceArn": (f"arn:aws:bedrock-agentcore:{region}:{account_id}:*")},
},
}
],
@@ -351,10 +324,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:"
f"/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -365,8 +335,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:"
f"/aws/bedrock-agentcore/runtimes/*:log-stream:*"
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
],
},
{
@@ -389,9 +358,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -402,8 +369,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"bedrock-agentcore:GetWorkloadAccessTokenForUserId",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{region}:{account_id}:"
f"workload-identity-directory/default",
f"arn:aws:bedrock-agentcore:{region}:{account_id}:workload-identity-directory/default",
f"arn:aws:bedrock-agentcore:{region}:{account_id}:"
f"workload-identity-directory/default/workload-identity/*",
],
@@ -444,10 +410,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Sid": "GetSecrets",
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": [
f"arn:aws:secretsmanager:{region}:{account_id}:"
f"secret:{SECRET_NAME}*"
],
"Resource": [f"arn:aws:secretsmanager:{region}:{account_id}:secret:{SECRET_NAME}*"],
},
],
}
@@ -466,9 +429,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
role_response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=json.dumps(trust_policy),
Description=(
"IAM role for Amazon Bedrock AgentCore with required permissions"
),
Description=("IAM role for Amazon Bedrock AgentCore with required permissions"),
)
print(f"✅ Created IAM role: {role_name}")
@@ -552,14 +513,10 @@ def runtime_resource_cleanup(agent_runtime_id: str) -> None:
"""Clean up AgentCore runtime resources."""
try:
# Initialize AWS clients
agentcore_control_client = boto3.client(
"bedrock-agentcore-control", region_name=REGION
)
agentcore_control_client = boto3.client("bedrock-agentcore-control", region_name=REGION)
# Delete the AgentCore Runtime
response = agentcore_control_client.delete_agent_runtime(
agentRuntimeId=agent_runtime_id
)
response = agentcore_control_client.delete_agent_runtime(agentRuntimeId=agent_runtime_id)
print(f" ✅ Agent runtime {agent_runtime_id} deleted: {response['status']}")
except Exception as e:
print(f" ⚠️ Error during runtime cleanup: {e}")
@@ -591,9 +548,7 @@ def ecr_repo_cleanup() -> None:
def get_memory_name(agent_name: str) -> Optional[str]:
"""Get memory name for a given agent."""
try:
agentcore_control_client = boto3.client(
"bedrock-agentcore-control", region_name=REGION
)
agentcore_control_client = boto3.client("bedrock-agentcore-control", region_name=REGION)
resp = agentcore_control_client.list_memories()
for mem in resp["memories"]:
if agent_name in mem["id"]:
@@ -607,9 +562,7 @@ def get_memory_name(agent_name: str) -> Optional[str]:
def short_memory_cleanup(agent_name: str) -> None:
"""Clean up short-term memory for an agent."""
try:
agentcore_control_client = boto3.client(
"bedrock-agentcore-control", region_name=REGION
)
agentcore_control_client = boto3.client("bedrock-agentcore-control", region_name=REGION)
memory_id = get_memory_name(agent_name)
if memory_id:
agentcore_control_client.delete_memory(memoryId=memory_id)
@@ -631,9 +584,7 @@ def delete_observability_resources(agent_name: str) -> None:
# Delete log stream first (must be done before deleting log group)
try:
print(f" 🗑️ Deleting log stream '{log_stream_name}'...")
logs_client.delete_log_stream(
logGroupName=complete_log_group, logStreamName=log_stream_name
)
logs_client.delete_log_stream(logGroupName=complete_log_group, logStreamName=log_stream_name)
print(f" ✅ Log stream '{log_stream_name}' deleted successfully")
except logs_client.exceptions.ClientError as e:
if e.response["Error"]["Code"] == "ResourceNotFoundException":
@@ -686,7 +637,4 @@ def local_file_cleanup() -> None:
if deleted_files:
print(f"\n📁 Successfully deleted {len(deleted_files)} files")
if missing_files:
print(
f"{len(missing_files)} files were already missing: "
f"{', '.join(missing_files)}"
)
print(f"{len(missing_files)} files were already missing: {', '.join(missing_files)}")
@@ -47,7 +47,7 @@ def authenticate_user(username: str, account_number: str) -> str:
"member_since": "2020-01-15",
"account_type": "Premium Checking",
},
"session_token": "tok_demo_abc123xyz789",
"session_token": "tok_demo_abc123xyz789", # pragma: allowlist secret
"message": f"Welcome back, {username}! Authentication successful.",
}
@@ -151,9 +151,7 @@ async def main():
"""Run the MCP server"""
logger.info("Starting Authentication Tools MCP Server")
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream, write_stream, server.create_initialization_options()
)
await server.run(read_stream, write_stream, server.create_initialization_options())
if __name__ == "__main__":
@@ -110,7 +110,7 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username=username,
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
@@ -118,15 +118,13 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username=username,
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
# Authenticate User and get Access Token
auth_response = cognito_client.initiate_auth(
@@ -134,7 +132,7 @@ def get_or_create_cognito_pool(refresh_token=False):
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -171,16 +169,14 @@ def reauthenticate_user(client_id, client_secret):
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -207,11 +203,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": account_id},
"ArnLike": {
"aws:SourceArn": (
f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
)
},
"ArnLike": {"aws:SourceArn": (f"arn:aws:bedrock-agentcore:{region}:{account_id}:*")},
},
}
],
@@ -230,10 +222,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:"
"/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -244,8 +233,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:"
"/aws/bedrock-agentcore/runtimes/*:log-stream:*"
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
],
},
{
@@ -268,9 +256,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -281,8 +267,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"bedrock-agentcore:GetWorkloadAccessTokenForUserId",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{region}:{account_id}:"
f"workload-identity-directory/default",
f"arn:aws:bedrock-agentcore:{region}:{account_id}:workload-identity-directory/default",
f"arn:aws:bedrock-agentcore:{region}:{account_id}:"
"workload-identity-directory/default/workload-identity/*",
],
@@ -333,9 +318,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
"Sid": "GetSecrets",
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": [
f"arn:aws:secretsmanager:{region}:{account_id}:secret:{sm_name}*"
],
"Resource": [f"arn:aws:secretsmanager:{region}:{account_id}:secret:{sm_name}*"],
},
],
}
@@ -354,9 +337,7 @@ def create_agentcore_runtime_execution_role(role_name: str) -> Optional[str]:
role_response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=json.dumps(trust_policy),
Description=(
"IAM role for Amazon Bedrock AgentCore with required permissions"
),
Description=("IAM role for Amazon Bedrock AgentCore with required permissions"),
)
print(f"✅ Created IAM role: {role_name}")
@@ -440,26 +421,18 @@ def cleanup_cognito_resources(pool_id: str) -> bool:
if pool_id:
try:
# List and delete all app clients
clients_response = cognito_client.list_user_pool_clients(
UserPoolId=pool_id, MaxResults=60
)
clients_response = cognito_client.list_user_pool_clients(UserPoolId=pool_id, MaxResults=60)
for client in clients_response["UserPoolClients"]:
print(f"Deleting app client: {client['ClientName']}")
cognito_client.delete_user_pool_client(
UserPoolId=pool_id, ClientId=client["ClientId"]
)
cognito_client.delete_user_pool_client(UserPoolId=pool_id, ClientId=client["ClientId"])
# List and delete all users
users_response = cognito_client.list_users(
UserPoolId=pool_id, AttributesToGet=["email"]
)
users_response = cognito_client.list_users(UserPoolId=pool_id, AttributesToGet=["email"])
for user in users_response.get("Users", []):
print(f"Deleting user: {user['Username']}")
cognito_client.admin_delete_user(
UserPoolId=pool_id, Username=user["Username"]
)
cognito_client.admin_delete_user(UserPoolId=pool_id, Username=user["Username"])
# Delete the user pool
print(f"Deleting user pool: {pool_id}")
@@ -469,9 +442,7 @@ def cleanup_cognito_resources(pool_id: str) -> bool:
return True
except cognito_client.exceptions.ResourceNotFoundException:
print(
f"User pool {pool_id} not found. It may have already been deleted."
)
print(f"User pool {pool_id} not found. It may have already been deleted.")
return True
except cognito_client.exceptions.ClientError as e:
@@ -530,7 +501,4 @@ def local_file_cleanup() -> None:
if deleted_files:
print(f"\n📁 Successfully deleted {len(deleted_files)} files")
if missing_files:
print(
f"{len(missing_files)} files were already missing: "
f"{', '.join(missing_files)}"
)
print(f"{len(missing_files)} files were already missing: {', '.join(missing_files)}")
@@ -63,6 +63,12 @@ Resources:
VPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
@@ -88,6 +94,12 @@ Resources:
PublicSubnet1:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
@@ -99,6 +111,12 @@ Resources:
PublicSubnet2:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.2.0/24"
@@ -209,6 +227,15 @@ Resources:
AgentCoreSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W40
reason: >-
All outbound traffic required for AgentCore runtime to reach AWS services; restrict in production
- id: W5
reason: >-
Sample security group uses broad egress rules for tutorial simplicity; restrict in production
Properties:
GroupDescription: Security group for AgentCore runtimes
VpcId: !Ref VPC
@@ -57,6 +57,12 @@ Resources:
VPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
@@ -82,6 +88,12 @@ Resources:
PublicSubnet1:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
@@ -93,6 +105,12 @@ Resources:
PublicSubnet2:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.2.0/24"
@@ -203,6 +221,15 @@ Resources:
AgentCoreSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W40
reason: >-
All outbound traffic required for AgentCore runtime to reach AWS services; restrict in production
- id: W5
reason: >-
Sample security group uses broad egress rules for tutorial simplicity; restrict in production
Properties:
GroupDescription: Security group for AgentCore runtimes
VpcId: !Ref VPC
@@ -5,9 +5,7 @@ from typing import Dict, List, Optional, Union
import boto3
from botocore.exceptions import ClientError
LAMBDA_EXECUTION_ROLE_POLICY = (
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
)
LAMBDA_EXECUTION_ROLE_POLICY = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
LAMBDA_RUNTIME = "python3.12"
LAMBDA_HANDLER = "lambda_function_code.lambda_handler"
LAMBDA_PACKAGE_TYPE = "Zip"
@@ -43,14 +41,12 @@ COGNITO_POOL_NAME = "MCPServerPool"
COGNITO_CLIENT_NAME = "MCPServerPoolClient"
COGNITO_PASSWORD_MIN_LENGTH = 8
COGNITO_DEFAULT_USERNAME = "testuser"
COGNITO_DEFAULT_TEMP_PASSWORD = "Temp123!"
COGNITO_DEFAULT_PASSWORD = "MyPassword123!"
COGNITO_DEFAULT_TEMP_PASSWORD = "Temp123!" # pragma: allowlist secret
COGNITO_DEFAULT_PASSWORD = "MyPassword123!" # pragma: allowlist secret
COGNITO_AUTH_FLOWS = ["ALLOW_USER_PASSWORD_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"]
COGNITO_PASSWORD_POLICY = {
"PasswordPolicy": {"MinimumLength": COGNITO_PASSWORD_MIN_LENGTH}
}
COGNITO_PASSWORD_POLICY = {"PasswordPolicy": {"MinimumLength": COGNITO_PASSWORD_MIN_LENGTH}}
def _format_error_message(error: ClientError) -> str:
@@ -88,9 +84,7 @@ def _create_or_get_iam_role(iam_client, role_name: str) -> str:
raise error
def _create_or_get_lambda_function(
lambda_client, function_name: str, role_arn: str, code: bytes
) -> str:
def _create_or_get_lambda_function(lambda_client, function_name: str, role_arn: str, code: bytes) -> str:
"""Create Lambda function or return existing function ARN."""
try:
print("Creating lambda function")
@@ -109,17 +103,13 @@ def _create_or_get_lambda_function(
if error.response["Error"]["Code"] == "ResourceConflictException":
response = lambda_client.get_function(FunctionName=function_name)
lambda_arn = response["Configuration"]["FunctionArn"]
print(
f"AWS Lambda function {function_name} already exists. Using the same ARN {lambda_arn}"
)
print(f"AWS Lambda function {function_name} already exists. Using the same ARN {lambda_arn}")
return lambda_arn
else:
raise error
def create_gateway_lambda(
lambda_function_code_path: str, lambda_function_name: str
) -> Dict[str, Union[str, int]]:
def create_gateway_lambda(lambda_function_code_path: str, lambda_function_name: str) -> Dict[str, Union[str, int]]:
"""Create AWS Lambda function with IAM role for AgentCore Gateway.
Args:
@@ -167,9 +157,7 @@ def create_gateway_lambda(
def _create_cognito_user_pool(cognito_client, pool_name: str) -> str:
"""Create Cognito User Pool and return pool ID."""
print(f"Creating Cognito User Pool: {pool_name}")
response = cognito_client.create_user_pool(
PoolName=pool_name, Policies=COGNITO_PASSWORD_POLICY
)
response = cognito_client.create_user_pool(PoolName=pool_name, Policies=COGNITO_PASSWORD_POLICY)
pool_id = response["UserPool"]["Id"]
print(f"User Pool created with ID: {pool_id}")
return pool_id
@@ -214,9 +202,7 @@ def _create_cognito_user(
)
def _authenticate_user(
cognito_client, client_id: str, username: str, password: str
) -> str:
def _authenticate_user(cognito_client, client_id: str, username: str, password: str) -> str:
"""Authenticate user and return access token."""
print(f"Authenticating user: {username}")
auth_response = cognito_client.initiate_auth(
@@ -227,9 +213,7 @@ def _authenticate_user(
return auth_response["AuthenticationResult"]["AccessToken"]
def get_bearer_token(
client_id: str, username: str, password: str, region: Optional[str] = None
) -> Optional[str]:
def get_bearer_token(client_id: str, username: str, password: str, region: Optional[str] = None) -> Optional[str]:
"""Get bearer token from existing Cognito User Pool.
Args:
@@ -357,9 +341,7 @@ def create_gateway_iam_role(
print(f"Updated policy for existing role: {role_arn}")
except ClientError as policy_error:
print(
f"Warning: Could not update policy: {_format_error_message(policy_error)}"
)
print(f"Warning: Could not update policy: {_format_error_message(policy_error)}")
return role_arn
else:
@@ -432,9 +414,7 @@ def delete_gateway_lambda(lambda_function_arn: str) -> bool:
if role_error.response["Error"]["Code"] == "NoSuchEntity":
print(f"IAM role {role_name} not found, skipping")
else:
print(
f"Warning: Could not delete IAM role: {_format_error_message(role_error)}"
)
print(f"Warning: Could not delete IAM role: {_format_error_message(role_error)}")
return True
@@ -543,9 +523,7 @@ def delete_cognito_user_pool(
if user_error.response["Error"]["Code"] == "UserNotFoundException":
print(f"User {username} not found, skipping")
else:
print(
f"Warning: Could not delete user: {_format_error_message(user_error)}"
)
print(f"Warning: Could not delete user: {_format_error_message(user_error)}")
# Delete User Pool (this will also delete app clients)
print(f"Deleting User Pool: {pool_name}")
@@ -591,9 +569,7 @@ def setup_cognito_user_pool(
pool_id = _create_cognito_user_pool(cognito_client, pool_name)
client_id = _create_cognito_app_client(cognito_client, pool_id, client_name)
_create_cognito_user(
cognito_client, pool_id, username, temp_password, permanent_password
)
_create_cognito_user(cognito_client, pool_id, username, temp_password, permanent_password)
discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
@@ -113,6 +113,18 @@ Resources:
GatewayAgentCoreRole:
Type: AWS::IAM::Role
Metadata:
cfn_nag:
rules_to_suppress:
- id: F3
reason: >-
Sample IAM policy allows broad actions for tutorial demonstration; restrict in production
- id: F38
reason: >-
Sample IAM role allows PassRole with * resource for tutorial clarity; scope to specific role ARNs in production
- id: W11
reason: >-
Sample IAM roles use broad permissions for tutorial clarity; scope down for production
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
@@ -4,8 +4,8 @@ import time
from boto3.session import Session
USER_NAME = "testuser"
PASSWORD = "MyPassword123!"
TEMP_ADMIN_PASSWORD = "Temp123!"
PASSWORD = "MyPassword123!" # pragma: allowlist secret
TEMP_ADMIN_PASSWORD = "Temp123!" # pragma: allowlist secret
def setup_cognito_user_pool(pool_name="MCPServerPool"):
@@ -48,9 +48,7 @@ def setup_cognito_user_pool(pool_name="MCPServerPool"):
refresh_token = auth_response["AuthenticationResult"]["RefreshToken"]
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"Bearer Token: {bearer_token}")
print(f"Refresh Token: {refresh_token}")
@@ -116,9 +114,7 @@ def create_agentcore_role(agent_name):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -152,9 +148,7 @@ def create_agentcore_role(agent_name):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -181,9 +175,7 @@ def create_agentcore_role(agent_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -202,14 +194,10 @@ def create_agentcore_role(agent_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -248,34 +236,24 @@ def get_or_create_user_pool(cognito_client, pool_name):
return pool["Id"]
# Create new pool
response = cognito_client.create_user_pool(
PoolName=pool_name, Policies={"PasswordPolicy": {"MinimumLength": 8}}
)
response = cognito_client.create_user_pool(PoolName=pool_name, Policies={"PasswordPolicy": {"MinimumLength": 8}})
return response["UserPool"]["Id"]
def get_or_create_resource_server(
cognito_client, user_pool_id, identifier, name, scopes
):
def get_or_create_resource_server(cognito_client, user_pool_id, identifier, name, scopes):
"""
Get existing resource server or create a new one.
"""
try:
cognito_client.describe_resource_server(
UserPoolId=user_pool_id, Identifier=identifier
)
cognito_client.describe_resource_server(UserPoolId=user_pool_id, Identifier=identifier)
return # Already exists
except cognito_client.exceptions.ResourceNotFoundException:
pass
cognito_client.create_resource_server(
UserPoolId=user_pool_id, Identifier=identifier, Name=name, Scopes=scopes
)
cognito_client.create_resource_server(UserPoolId=user_pool_id, Identifier=identifier, Name=name, Scopes=scopes)
def get_or_create_m2m_client(
cognito_client, user_pool_id, client_name, resource_server_id, scope_names
):
def get_or_create_m2m_client(cognito_client, user_pool_id, client_name, resource_server_id, scope_names):
"""
Get existing M2M client or create a new one.
Returns (client_id, client_secret).
@@ -286,12 +264,8 @@ def get_or_create_m2m_client(
for client in page["UserPoolClients"]:
if client["ClientName"] == client_name:
# Get client details including secret
details = cognito_client.describe_user_pool_client(
UserPoolId=user_pool_id, ClientId=client["ClientId"]
)
return details["UserPoolClient"]["ClientId"], details[
"UserPoolClient"
].get("ClientSecret")
details = cognito_client.describe_user_pool_client(UserPoolId=user_pool_id, ClientId=client["ClientId"])
return details["UserPoolClient"]["ClientId"], details["UserPoolClient"].get("ClientSecret")
# Create new client
response = cognito_client.create_user_pool_client(
@@ -302,9 +276,7 @@ def get_or_create_m2m_client(
AllowedOAuthScopes=scope_names,
AllowedOAuthFlowsUserPoolClient=True,
)
return response["UserPoolClient"]["ClientId"], response["UserPoolClient"][
"ClientSecret"
]
return response["UserPoolClient"]["ClientId"], response["UserPoolClient"]["ClientSecret"]
def get_token(user_pool_id, client_id, client_secret, scope_string, REGION):
@@ -361,9 +333,7 @@ def delete_gateway(gateway_client, gatewayId):
targets = gateway_client.list_gateway_targets(gatewayIdentifier=gatewayId)
for target in targets.get("items", []):
print(f" Deleting target: {target['targetId']}")
gateway_client.delete_gateway_target(
gatewayIdentifier=gatewayId, targetIdentifier=target["targetId"]
)
gateway_client.delete_gateway_target(gatewayIdentifier=gatewayId, targetIdentifier=target["targetId"])
time.sleep(2)
except Exception as e:
print(f" Warning listing/deleting targets: {e}")
@@ -37,11 +37,11 @@ for el in output.split("\n"):
users = [
{
"COGNITO_USERNAME": "vscode-admin@example.com",
"COGNITO_PASSWORD": "TempPassword123!",
"COGNITO_PASSWORD": "TempPassword123!", # pragma: allowlist secret
},
{
"COGNITO_USERNAME": "vscode-user@example.com",
"COGNITO_PASSWORD": "TempPassword1234!",
"COGNITO_PASSWORD": "TempPassword1234!", # pragma: allowlist secret
},
]
@@ -31,7 +31,7 @@ Resources:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Ref UserPoolName
MfaConfiguration: "OFF" # Disable MFA for demo purposes
MfaConfiguration: "OPTIONAL" # OPTIONAL allows MFA for users who configure it
UsernameConfiguration:
CaseSensitive: false
UsernameAttributes:
@@ -278,6 +278,12 @@ Resources:
# GET Method - No Authorization (AgentCore handles auth)
AsanaGetMethod:
Type: AWS::ApiGateway::Method
Metadata:
cfn_nag:
rules_to_suppress:
- id: W59
reason: >-
AuthorizationType NONE because AgentCore Gateway handles authentication upstream; add method-level auth in production
Properties:
RestApiId: !Ref AsanaIntegrationApiGateway
ResourceId: !Ref AsanaResource
@@ -292,6 +298,12 @@ Resources:
# POST Method - No Authorization (AgentCore handles auth)
AsanaPostMethod:
Type: AWS::ApiGateway::Method
Metadata:
cfn_nag:
rules_to_suppress:
- id: W59
reason: >-
AuthorizationType NONE because AgentCore Gateway handles authentication upstream; add method-level auth in production
Properties:
RestApiId: !Ref AsanaIntegrationApiGateway
ResourceId: !Ref AsanaResource
@@ -62,7 +62,7 @@ AWS_REGION = get_aws_region()
logger.info(f"🌍 Using AWS Region: {AWS_REGION}")
MODEL_ID = os.environ.get("MODEL_ID", "us.anthropic.claude-sonnet-4-20250514-v1:0")
AWS_ACCESS_KEY_ID = "none"
AWS_SECRET_ACCESS_KEY = "none"
AWS_SECRET_ACCESS_KEY = "none" # pragma: allowlist secret
# Treat 'none' string as None for IAM role usage
if AWS_ACCESS_KEY_ID.lower() == "none":
@@ -114,12 +114,8 @@ def get_code_interpreter_from_ssm():
WORKSHOP_NAME = "aiml301_sre_agentcore"
try:
interpreter_id = ssm.get_parameter(
Name=f"/{WORKSHOP_NAME}/lab-03/code-interpreter-id"
)["Parameter"]["Value"]
interpreter_arn = ssm.get_parameter(
Name=f"/{WORKSHOP_NAME}/lab-03/code-interpreter-arn"
)["Parameter"]["Value"]
interpreter_id = ssm.get_parameter(Name=f"/{WORKSHOP_NAME}/lab-03/code-interpreter-id")["Parameter"]["Value"]
interpreter_arn = ssm.get_parameter(Name=f"/{WORKSHOP_NAME}/lab-03/code-interpreter-arn")["Parameter"]["Value"]
logger.info(f"✅ Retrieved code interpreter from SSM: {interpreter_id}")
return interpreter_id, interpreter_arn
except Exception as e:
@@ -245,9 +241,7 @@ def execute_remediation_code(session_id: str, code: str) -> Dict:
def execute_remediation_step(remediation_code: str) -> str:
"""Execute remediation steps"""
try:
logger.info(
f"🔧 execute_remediation_step called with code length: {len(remediation_code)}"
)
logger.info(f"🔧 execute_remediation_step called with code length: {len(remediation_code)}")
if not initialize_code_interpreter_client():
logger.error("❌ Code interpreter client not available")
@@ -299,15 +293,11 @@ except Exception as e:
response += execution_result["output"]
response += "\n```\n"
logger.info(
f"✅ Execution successful, output length: {len(execution_result['output'])}"
)
logger.info(f"✅ Execution successful, output length: {len(execution_result['output'])}")
return response
except Exception as e:
logger.error(
f"❌ Execution exception: {type(e).__name__}: {str(e)}", exc_info=True
)
logger.error(f"❌ Execution exception: {type(e).__name__}: {str(e)}", exc_info=True)
return f"❌ remediation plan execution failed: {str(e)}"
finally:
logger.info(f"🛑 Stopping session: {session_id}")
@@ -376,9 +366,7 @@ def validate_remediation_environment() -> str:
for check, status in validation_results.items():
status_icon = "" if status else ""
check_name = check.replace("_", " ").title()
response += (
f"- **{check_name}**: {status_icon} {'PASS' if status else 'FAIL'}\n"
)
response += f"- **{check_name}**: {status_icon} {'PASS' if status else 'FAIL'}\n"
if validation_results["environment_ready"]:
response += "\n🎉 **Environment is READY for remediation**\n"
@@ -419,9 +407,7 @@ def persist_remediation_scripts_to_s3(file_key: str, content: str) -> dict:
s3_client = get_boto3_client("s3")
# Write to S3
s3_client.put_object(
Bucket=bucket_name, Key=file_key, Body=content.encode("utf-8")
)
s3_client.put_object(Bucket=bucket_name, Key=file_key, Body=content.encode("utf-8"))
# Generate S3 URL
s3_url = f"s3://{bucket_name}/{file_key}"
@@ -459,9 +445,7 @@ def read_remediation_scripts_from_s3(prefix: str = "") -> dict:
max_files = 100
try:
logger.info(
f"🔧 read_remediation_scripts_from_s3 called with prefix='{prefix}'"
)
logger.info(f"🔧 read_remediation_scripts_from_s3 called with prefix='{prefix}'")
logger.info(f"📦 Reading from bucket: {bucket_name}, region: {region}")
s3_client = get_boto3_client("s3")
@@ -524,9 +508,7 @@ def read_remediation_scripts_from_s3(prefix: str = "") -> dict:
logger.info(f"✅ Read file: {file_key} ({obj['Size']} bytes)")
except Exception as file_error:
# If a file can't be read, include error info but continue
logger.error(
f"❌ Failed to read {file_key}: {type(file_error).__name__}: {str(file_error)}"
)
logger.error(f"❌ Failed to read {file_key}: {type(file_error).__name__}: {str(file_error)}")
files_data.append(
{
"key": file_key,
@@ -537,9 +519,7 @@ def read_remediation_scripts_from_s3(prefix: str = "") -> dict:
}
)
logger.info(
f"✅ Successfully read {len(files_data)} files, total size: {total_size} bytes"
)
logger.info(f"✅ Successfully read {len(files_data)} files, total size: {total_size} bytes")
result = {
"success": True,
"message": f"Successfully read {len(files_data)} files from S3",
@@ -553,9 +533,7 @@ def read_remediation_scripts_from_s3(prefix: str = "") -> dict:
return {
"status": "success",
"content": [
{
"text": f"✓ Read {len(files_data)} files from s3://{bucket_name}/{prefix}"
},
{"text": f"✓ Read {len(files_data)} files from s3://{bucket_name}/{prefix}"},
{"json": result},
],
}
@@ -729,9 +707,7 @@ logger.info(f"🔍 MCP server type: {type(mcp)}")
@mcp.tool()
def infrastructure_agent(
action_type: Literal["only_plan", "only_execute"], remediation_query: str
):
def infrastructure_agent(action_type: Literal["only_plan", "only_execute"], remediation_query: str):
"""Execute infrastructure remediation and AWS service operations using AgentCore Code Interpreter
Primary tool for ALL AWS infrastructure queries, checks, and actions. Creates remediation plans or executes fixes for AWS infrastructure issues. Plans are saved to S3 for approval. Execution uses secure sandboxed environment with automatic rollback on failure.
@@ -754,9 +730,7 @@ def infrastructure_agent(
Plan summary with S3 location (only_plan) or execution results with validation (only_execute)
"""
try:
logger.info(
f"🔧 remediation_agent called with action_type={action_type}, query={remediation_query}"
)
logger.info(f"🔧 remediation_agent called with action_type={action_type}, query={remediation_query}")
if not initialize_code_interpreter_client():
logger.error("❌ Failed to initialize code interpreter client")
@@ -764,9 +738,7 @@ def infrastructure_agent(
logger.info("✅ Code interpreter client initialized")
boto_session = get_boto3_session()
model = BedrockModel(
model_id=MODEL_ID, streaming=True, boto_session=boto_session
)
model = BedrockModel(model_id=MODEL_ID, streaming=True, boto_session=boto_session)
logger.info(f"✅ Bedrock model initialized: {MODEL_ID}")
if action_type == "only_plan":
@@ -858,9 +830,7 @@ After all the remediation and troubleshooting steps are completed, provide the b
return return_text
except Exception as e:
logger.error(
f"❌ remediation_agent failed: {type(e).__name__}: {str(e)}", exc_info=True
)
logger.error(f"❌ remediation_agent failed: {type(e).__name__}: {str(e)}", exc_info=True)
return f"Error: {type(e).__name__}: {str(e)}"
+37 -113
View File
@@ -50,15 +50,13 @@ def setup_cognito_user_pool():
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token = auth_response["AuthenticationResult"]["AccessToken"]
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"Bearer Token: {bearer_token}")
@@ -89,9 +87,7 @@ def get_or_create_user_pool(cognito, USER_POOL_NAME):
if domain:
region = user_pool_id.split("_")[0] if "_" in user_pool_id else REGION # noqa: F821
domain_url = f"https://{domain}.auth.{region}.amazoncognito.com"
print(
f"Found domain for user pool {user_pool_id}: {domain} ({domain_url})"
)
print(f"Found domain for user pool {user_pool_id}: {domain} ({domain_url})")
else:
print(f"No domains found for user pool {user_pool_id}")
return pool["Id"]
@@ -99,16 +95,12 @@ def get_or_create_user_pool(cognito, USER_POOL_NAME):
created = cognito.create_user_pool(PoolName=USER_POOL_NAME)
user_pool_id = created["UserPool"]["Id"]
user_pool_id_without_underscore_lc = user_pool_id.replace("_", "").lower()
cognito.create_user_pool_domain(
Domain=user_pool_id_without_underscore_lc, UserPoolId=user_pool_id
)
cognito.create_user_pool_domain(Domain=user_pool_id_without_underscore_lc, UserPoolId=user_pool_id)
print("Domain created as well")
return created["UserPool"]["Id"]
def get_or_create_resource_server(
cognito, user_pool_id, RESOURCE_SERVER_ID, RESOURCE_SERVER_NAME, SCOPES
):
def get_or_create_resource_server(cognito, user_pool_id, RESOURCE_SERVER_ID, RESOURCE_SERVER_NAME, SCOPES):
try:
existing = cognito.describe_resource_server( # noqa: F841
UserPoolId=user_pool_id, Identifier=RESOURCE_SERVER_ID
@@ -125,15 +117,11 @@ def get_or_create_resource_server(
return RESOURCE_SERVER_ID
def get_or_create_m2m_client(
cognito, user_pool_id, CLIENT_NAME, RESOURCE_SERVER_ID, SCOPES=None
):
def get_or_create_m2m_client(cognito, user_pool_id, CLIENT_NAME, RESOURCE_SERVER_ID, SCOPES=None):
response = cognito.list_user_pool_clients(UserPoolId=user_pool_id, MaxResults=60)
for client in response["UserPoolClients"]:
if client["ClientName"] == CLIENT_NAME:
describe = cognito.describe_user_pool_client(
UserPoolId=user_pool_id, ClientId=client["ClientId"]
)
describe = cognito.describe_user_pool_client(UserPoolId=user_pool_id, ClientId=client["ClientId"])
return client["ClientId"], describe["UserPoolClient"]["ClientSecret"]
print("creating new m2m client")
@@ -207,9 +195,7 @@ def create_agentcore_role(agent_name):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -237,9 +223,7 @@ def create_agentcore_role(agent_name):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Effect": "Allow",
@@ -277,9 +261,7 @@ def create_agentcore_role(agent_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -298,14 +280,10 @@ def create_agentcore_role(agent_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -363,9 +341,7 @@ def create_agentcore_gateway_role(gateway_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -385,14 +361,10 @@ def create_agentcore_gateway_role(gateway_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_gateway_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_gateway_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_gateway_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_gateway_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_gateway_role_name}")
iam_client.delete_role(RoleName=agentcore_gateway_role_name)
print(f"recreating {agentcore_gateway_role_name}")
@@ -459,9 +431,7 @@ def create_agentcore_gateway_role_with_region(gateway_name, region):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -478,14 +448,10 @@ def create_agentcore_gateway_role_with_region(gateway_name, region):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_gateway_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_gateway_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_gateway_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_gateway_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_gateway_role_name}")
iam_client.delete_role(RoleName=agentcore_gateway_role_name)
print(f"recreating {agentcore_gateway_role_name}")
@@ -543,9 +509,7 @@ def create_agentcore_gateway_role_s3_smithy(gateway_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -565,14 +529,10 @@ def create_agentcore_gateway_role_s3_smithy(gateway_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_gateway_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_gateway_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_gateway_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_gateway_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_gateway_role_name}")
iam_client.delete_role(RoleName=agentcore_gateway_role_name)
print(f"recreating {agentcore_gateway_role_name}")
@@ -650,11 +610,7 @@ def create_gateway_lambda(lambda_function_code_path) -> dict[str, int]:
role_arn = response["Role"]["Arn"]
print(f"IAM role {role_name} already exists. Using the same ARN {role_arn}")
else:
error_message = (
error.response["Error"]["Code"]
+ "-"
+ error.response["Error"]["Message"]
)
error_message = error.response["Error"]["Code"] + "-" + error.response["Error"]["Message"]
print(f"Error creating role: {error_message}")
return_resp["lambda_function_arn"] = error_message
@@ -678,16 +634,10 @@ def create_gateway_lambda(lambda_function_code_path) -> dict[str, int]:
if error.response["Error"]["Code"] == "ResourceConflictException":
response = lambda_client.get_function(FunctionName=lambda_function_name)
lambda_arn = response["Configuration"]["FunctionArn"]
print(
f"AWS Lambda function {lambda_function_name} already exists. Using the same ARN {lambda_arn}"
)
print(f"AWS Lambda function {lambda_function_name} already exists. Using the same ARN {lambda_arn}")
return_resp["lambda_function_arn"] = lambda_arn
else:
error_message = (
error.response["Error"]["Code"]
+ "-"
+ error.response["Error"]["Message"]
)
error_message = error.response["Error"]["Code"] + "-" + error.response["Error"]["Message"]
print(f"Error creating lambda function: {error_message}")
return_resp["lambda_function_arn"] = error_message
@@ -696,15 +646,11 @@ def create_gateway_lambda(lambda_function_code_path) -> dict[str, int]:
def delete_gateway(gateway_client, gatewayId):
print("Deleting all targets for gateway", gatewayId)
list_response = gateway_client.list_gateway_targets(
gatewayIdentifier=gatewayId, maxResults=100
)
list_response = gateway_client.list_gateway_targets(gatewayIdentifier=gatewayId, maxResults=100)
for item in list_response["items"]:
targetId = item["targetId"]
print("Deleting target ", targetId)
gateway_client.delete_gateway_target(
gatewayIdentifier=gatewayId, targetId=targetId
)
gateway_client.delete_gateway_target(gatewayIdentifier=gatewayId, targetId=targetId)
time.sleep(5)
print("Deleting gateway ", gatewayId)
gateway_client.delete_gateway(gatewayIdentifier=gatewayId)
@@ -781,12 +727,8 @@ def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn):
time.sleep(3)
except iam_client.exceptions.EntityAlreadyExistsException:
print(f"Role '{role_name}' already exists — updating trust and inline policy.")
iam_client.update_assume_role_policy(
RoleName=role_name, PolicyDocument=assume_role_policy_json
)
for policy_name in iam_client.list_role_policies(RoleName=role_name).get(
"PolicyNames", []
):
iam_client.update_assume_role_policy(RoleName=role_name, PolicyDocument=assume_role_policy_json)
for policy_name in iam_client.list_role_policies(RoleName=role_name).get("PolicyNames", []):
iam_client.delete_role_policy(RoleName=role_name, PolicyName=policy_name)
agentcoregw_iam_role = iam_client.get_role(RoleName=role_name)
@@ -805,9 +747,7 @@ def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn):
assume_policy = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "sts:AssumeRole", "Resource": role_arn}
],
"Statement": [{"Effect": "Allow", "Action": "sts:AssumeRole", "Resource": role_arn}],
}
# Attach assume-role policy if user/role
@@ -826,9 +766,7 @@ def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn):
)
except ClientError as e:
print(f"Unable to attach assume-role policy: {e}")
print(
"Make sure the caller has iam:PutUserPolicy or iam:PutRolePolicy permission."
)
print("Make sure the caller has iam:PutUserPolicy or iam:PutRolePolicy permission.")
# Retry loop for eventual consistency
max_retries = 5
@@ -844,13 +782,9 @@ def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn):
else:
raise
else:
raise RuntimeError(
f"Failed to assume role {role_name} after {max_retries} retries"
)
raise RuntimeError(f"Failed to assume role {role_name} after {max_retries} retries")
print(
f" Role '{role_name}' is ready and {current_arn} can invoke the Bedrock Agent Gateway."
)
print(f" Role '{role_name}' is ready and {current_arn} can invoke the Bedrock Agent Gateway.")
return agentcoregw_iam_role
@@ -863,9 +797,7 @@ def get_client_secrets(cognito_client, user_pool_id, client_configs):
response = cognito_client.describe_user_pool_client(
UserPoolId=user_pool_id, ClientId=client_config["client_id"]
)
client_secrets[client_config["client_id"]] = response["UserPoolClient"][
"ClientSecret"
]
client_secrets[client_config["client_id"]] = response["UserPoolClient"]["ClientSecret"]
print(f" ✓ Retrieved secret for {client_config['name']}")
except Exception as e:
print(f" ✗ Failed to get secret for {client_config['name']}: {e}")
@@ -874,9 +806,7 @@ def get_client_secrets(cognito_client, user_pool_id, client_configs):
return client_secrets
def create_dynamodb_table(
table_name, key_schema, attribute_definitions, region="us-east-1"
):
def create_dynamodb_table(table_name, key_schema, attribute_definitions, region="us-east-1"):
"""
Create DynamoDB table with specified schema.
"""
@@ -930,9 +860,7 @@ def batch_write_dynamodb(table_name, items, region="us-east-1"):
return len(items)
def create_lambda_role_with_policies(
role_name, policy_statements, description="Lambda execution role"
):
def create_lambda_role_with_policies(role_name, policy_statements, description="Lambda execution role"):
iam_client = boto3.client("iam")
# Trust policy for Lambda
@@ -1149,9 +1077,7 @@ def delete_gateway_targets(gateway_client, gateway_id, target_ids):
print(f"Deleting {len(target_ids)} gateway targets...")
for target_id in target_ids:
try:
gateway_client.delete_gateway_target(
gatewayIdentifier=gateway_id, targetId=target_id
)
gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=target_id)
print(f" ✓ Deleted target: {target_id}")
except Exception as e:
print(f" ✗ Failed to delete target {target_id}: {e}")
@@ -1186,9 +1112,7 @@ def delete_iam_role(role_name):
# Detach managed policies
attached_policies = iam_client.list_attached_role_policies(RoleName=role_name)
for policy in attached_policies["AttachedPolicies"]:
iam_client.detach_role_policy(
RoleName=role_name, PolicyArn=policy["PolicyArn"]
)
iam_client.detach_role_policy(RoleName=role_name, PolicyArn=policy["PolicyArn"])
# Delete inline policies
inline_policies = iam_client.list_role_policies(RoleName=role_name)
@@ -19,7 +19,7 @@ logger.setLevel(logging.INFO)
# PingFederate configuration constants
CLIENT_ID = "agentcore-client"
CLIENT_SECRET = "agentcore-test-secret-12345"
CLIENT_SECRET = os.environ.get("PINGFED_CLIENT_SECRET", "agentcore-test-secret-12345") # pragma: allowlist secret
ATM_ID = "agentcoreJwtAtm"
OIDC_POLICY_ID = "agentcoreOidcPolicy"
SIGNING_KEY_ID = "agentcore-signing-key"
@@ -44,11 +44,7 @@ def handler(event, context):
# VPC ENIs can take a few seconds to initialize on cold start, causing
# "[Errno 16] Device or resource busy" errors on the first network call.
sm = boto3.client("secretsmanager")
secret_value = json.loads(
_retry_on_eni_busy(lambda: sm.get_secret_value(SecretId=secret_id))[
"SecretString"
]
)
secret_value = json.loads(_retry_on_eni_busy(lambda: sm.get_secret_value(SecretId=secret_id))["SecretString"])
admin_password = secret_value["adminPassword"]
try:
@@ -69,9 +65,7 @@ def handler(event, context):
)
else:
# Delete — nothing to tear down
send_response(
response_url, "SUCCESS", stack_id, request_id, logical_id, physical_id
)
send_response(response_url, "SUCCESS", stack_id, request_id, logical_id, physical_id)
except Exception as e:
logger.exception("Configuration failed")
send_response(
@@ -98,9 +92,7 @@ def _retry_on_eni_busy(fn, max_attempts=6, delay=5):
except OSError as e:
if attempt == max_attempts - 1:
raise
logger.warning(
f"VPC ENI not ready (attempt {attempt + 1}/{max_attempts}): {e}"
)
logger.warning(f"VPC ENI not ready (attempt {attempt + 1}/{max_attempts}): {e}")
time.sleep(delay)
@@ -122,9 +114,7 @@ def configure_pingfederate(admin_url, admin_user, admin_password, base_url):
break
except Exception:
if i == max_attempts - 1:
raise TimeoutError(
f"PingFederate not ready after {max_attempts} attempts"
)
raise TimeoutError(f"PingFederate not ready after {max_attempts} attempts")
time.sleep(5)
# 1. Generate signing key pair
@@ -14,8 +14,8 @@ from boto3.session import Session
POOL_NAME = "RuntimeAuthDemoPool"
USERNAME = "testuser"
PASSWORD = "AgentCoreTest1!"
TEMP_PASSWORD = "TempPass123!"
PASSWORD = "AgentCoreTest1!" # pragma: allowlist secret
TEMP_PASSWORD = "TempPass123!" # pragma: allowlist secret
def setup_cognito():
@@ -63,10 +63,7 @@ def setup_cognito():
)
_ = auth["AuthenticationResult"]["AccessToken"]
discovery_url = (
f"https://cognito-idp.{region}.amazonaws.com/{pool_id}"
"/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
config = {
"pool_id": pool_id,
@@ -14,8 +14,8 @@ from boto3.session import Session
POOL_NAME = "GatewayAuthDemoPool"
USERNAME = "testuser"
PASSWORD = "AgentCoreTest1!"
TEMP_PASSWORD = "TempPass123!"
PASSWORD = "AgentCoreTest1!" # pragma: allowlist secret
TEMP_PASSWORD = "TempPass123!" # pragma: allowlist secret
def setup_cognito():
@@ -35,9 +35,7 @@ def setup_cognito():
domain_prefix = f"gateway-demo-{pool_id.split('_')[1].lower()}"
print(f"Creating Cognito domain '{domain_prefix}'...")
cognito.create_user_pool_domain(UserPoolId=pool_id, Domain=domain_prefix)
token_endpoint = (
f"https://{domain_prefix}.auth.{region}.amazoncognito.com/oauth2/token"
)
token_endpoint = f"https://{domain_prefix}.auth.{region}.amazoncognito.com/oauth2/token"
print(f" Token endpoint: {token_endpoint}")
# Resource server (required for client_credentials scopes)
@@ -96,10 +94,7 @@ def setup_cognito():
)
_ = auth["AuthenticationResult"]["AccessToken"]
discovery_url = (
f"https://cognito-idp.{region}.amazonaws.com/{pool_id}"
"/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
config = {
"pool_id": pool_id,
@@ -16,8 +16,8 @@ from boto3.session import Session
POOL_NAME = "M2MAuthCodeDemoPool"
USERNAME = "testuser"
PASSWORD = "AgentCoreTest1!"
TEMP_PASSWORD = "TempPass123!"
PASSWORD = "AgentCoreTest1!" # pragma: allowlist secret
TEMP_PASSWORD = "TempPass123!" # pragma: allowlist secret
RESOURCE_SERVER_ID = "https://api.m2m-demo.internal"
@@ -38,9 +38,7 @@ def setup_cognito():
domain_prefix = "m2m-demo-" + re.sub(r"[^a-z0-9]", "-", pool_id.lower())[:18]
print(f"Creating Cognito domain '{domain_prefix}'...")
cognito.create_user_pool_domain(Domain=domain_prefix, UserPoolId=pool_id)
token_endpoint = (
f"https://{domain_prefix}.auth.{region}.amazoncognito.com/oauth2/token"
)
token_endpoint = f"https://{domain_prefix}.auth.{region}.amazoncognito.com/oauth2/token"
print(f" Token endpoint: {token_endpoint}")
# Resource server defines the scopes the machine client can request
@@ -97,10 +95,7 @@ def setup_cognito():
AuthParameters={"USERNAME": USERNAME, "PASSWORD": PASSWORD},
)
discovery_url = (
f"https://cognito-idp.{region}.amazonaws.com/{pool_id}"
"/.well-known/openid-configuration"
)
discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
config = {
"pool_id": pool_id,
@@ -26,14 +26,14 @@ def setup_cognito_user_pool(region):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser1",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 1
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser1",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -41,14 +41,14 @@ def setup_cognito_user_pool(region):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser2",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 2
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser2",
Password="MyPassword456!",
Password="MyPassword456!", # pragma: allowlist secret
Permanent=True,
)
@@ -56,7 +56,7 @@ def setup_cognito_user_pool(region):
auth_response1 = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token1 = auth_response1["AuthenticationResult"]["AccessToken"]
@@ -70,9 +70,7 @@ def setup_cognito_user_pool(region):
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"User 1 Bearer Token: {bearer_token1}")
print(f"User 2 Bearer Token: {bearer_token2}")
@@ -161,9 +159,7 @@ def create_agentcore_role(agent_name):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -197,9 +193,7 @@ def create_agentcore_role(agent_name):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -226,9 +220,7 @@ def create_agentcore_role(agent_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -247,14 +239,10 @@ def create_agentcore_role(agent_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -27,14 +27,14 @@ def setup_cognito_user_pool(region):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser1",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 1
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser1",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -42,14 +42,14 @@ def setup_cognito_user_pool(region):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser2",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 2
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser2",
Password="MyPassword456!",
Password="MyPassword456!", # pragma: allowlist secret
Permanent=True,
)
@@ -57,7 +57,7 @@ def setup_cognito_user_pool(region):
auth_response1 = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token1 = auth_response1["AuthenticationResult"]["AccessToken"]
@@ -71,9 +71,7 @@ def setup_cognito_user_pool(region):
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"User 1 Bearer Token: {bearer_token1}")
print(f"User 2 Bearer Token: {bearer_token2}")
@@ -188,9 +186,7 @@ def create_agentcore_role(agent_name, region):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -224,9 +220,7 @@ def create_agentcore_role(agent_name, region):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -253,9 +247,7 @@ def create_agentcore_role(agent_name, region):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -274,14 +266,10 @@ def create_agentcore_role(agent_name, region):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -27,14 +27,14 @@ def setup_cognito_user_pool(region, memory_id):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser1",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 1
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser1",
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
@@ -42,14 +42,14 @@ def setup_cognito_user_pool(region, memory_id):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username="testuser2",
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
# Set Permanent Password for User 2
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username="testuser2",
Password="MyPassword456!",
Password="MyPassword456!", # pragma: allowlist secret
Permanent=True,
)
@@ -57,7 +57,7 @@ def setup_cognito_user_pool(region, memory_id):
auth_response1 = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"},
AuthParameters={"USERNAME": "testuser1", "PASSWORD": "MyPassword123!"}, # pragma: allowlist secret
)
bearer_token1 = auth_response1["AuthenticationResult"]["AccessToken"]
id_token1 = auth_response1["AuthenticationResult"]["IdToken"]
@@ -72,15 +72,11 @@ def setup_cognito_user_pool(region, memory_id):
id_token2 = auth_response2["AuthenticationResult"]["IdToken"]
# Create Identity Pool federated with User Pool
identity_pool_info = create_cognito_identity_pool(
pool_id, client_id, region, memory_id
)
identity_pool_info = create_cognito_identity_pool(pool_id, client_id, region, memory_id)
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"Identity Pool ID: {identity_pool_info['identity_pool_id']}")
print(f"User 1 Bearer Token: {bearer_token1}")
@@ -133,12 +129,8 @@ def reauthenticate_users(client_id, region, users=None):
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": username, "PASSWORD": password},
)
result["access_tokens"][username] = auth_response["AuthenticationResult"][
"AccessToken"
]
result["id_tokens"][username] = auth_response["AuthenticationResult"][
"IdToken"
]
result["access_tokens"][username] = auth_response["AuthenticationResult"]["AccessToken"]
result["id_tokens"][username] = auth_response["AuthenticationResult"]["IdToken"]
print(f"Successfully authenticated {username}")
except Exception as e:
print(f"Error authenticating {username}: {str(e)}")
@@ -213,9 +205,7 @@ def create_agentcore_role(agent_name, region):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -249,9 +239,7 @@ def create_agentcore_role(agent_name, region):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -293,9 +281,7 @@ def create_agentcore_role(agent_name, region):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -314,14 +300,10 @@ def create_agentcore_role(agent_name, region):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")
@@ -376,9 +358,7 @@ def create_cognito_identity_pool(user_pool_id, client_id, region, memory_id="*")
# Create a shorter, unique role name using just the last part of the identity pool ID
# This ensures we stay under the 64 character limit
short_id = identity_pool_id.split(":")[-1][-12:].replace(
"-", ""
) # Last 12 chars without dashes
short_id = identity_pool_id.split(":")[-1][-12:].replace("-", "") # Last 12 chars without dashes
authenticated_role_name = f"cognito_auth_{short_id}"
# Create roles for authenticated users
@@ -402,14 +382,8 @@ def create_cognito_identity_pool(user_pool_id, client_id, region, memory_id="*")
"bedrock-agentcore:DeleteMemoryRecord",
"bedrock-agentcore:RetrieveMemoryRecords",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{region}:{account_id}:memory/{memory_id}"
],
"Condition": {
"StringEquals": {
"bedrock-agentcore:actorId": "${cognito-identity.amazonaws.com:sub}"
}
},
"Resource": [f"arn:aws:bedrock-agentcore:{region}:{account_id}:memory/{memory_id}"],
"Condition": {"StringEquals": {"bedrock-agentcore:actorId": "${cognito-identity.amazonaws.com:sub}"}},
},
{
"Effect": "Allow",
@@ -427,12 +401,8 @@ def create_cognito_identity_pool(user_pool_id, client_id, region, memory_id="*")
"Principal": {"Federated": "cognito-identity.amazonaws.com"},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": identity_pool_id
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
},
"StringEquals": {"cognito-identity.amazonaws.com:aud": identity_pool_id},
"ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "authenticated"},
},
}
],
@@ -453,9 +423,7 @@ def create_cognito_identity_pool(user_pool_id, client_id, region, memory_id="*")
PolicyDocument=json.dumps(authenticated_policy_document),
)
iam_client.attach_role_policy(
RoleName=authenticated_role_name, PolicyArn=auth_policy["Policy"]["Arn"]
)
iam_client.attach_role_policy(RoleName=authenticated_role_name, PolicyArn=auth_policy["Policy"]["Arn"])
except iam_client.exceptions.EntityAlreadyExistsException:
# Role already exists, get its ARN
response = iam_client.get_role(RoleName=authenticated_role_name)
@@ -698,6 +698,12 @@ Resources:
ClientVPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: "10.1.0.0/16"
EnableDnsHostnames: true
@@ -908,11 +914,17 @@ Resources:
# Public Subnet - Hosts development EC2 instance with internet access
ClientPublicSubnet1:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref ClientVPC
CidrBlock: "10.1.100.0/28" # Small subnet (16 IPs) for testing instances
AvailabilityZone: !Select [0, !GetAZs ""] # Use first AZ
MapPublicIpOnLaunch: true
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public-subnet-1"
@@ -1011,6 +1023,18 @@ Resources:
# Security Group for Development EC2 Instance
EC2InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W2
reason: >-
Demo EC2 instance allows SSH from 0.0.0.0/0 for tutorial access; restrict to specific IPs in production
- id: W5
reason: >-
Demo EC2 instance allows SSH ingress from internet for tutorial access; restrict in production
- id: W9
reason: >-
SSH CIDR not restricted to /32 for tutorial accessibility; restrict to specific IP in production
Properties:
GroupDescription: "Security group for development EC2 instance - allows SSH and outbound HTTPS"
VpcId: !Ref ClientVPC
@@ -697,6 +697,12 @@ Resources:
ClientVPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: "10.1.0.0/16"
EnableDnsHostnames: true
@@ -932,6 +938,18 @@ Resources:
# Web Server Security Group
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W2
reason: >-
Demo web server allows all TCP from VPC CIDR for tutorial simplicity; restrict to specific ports in production
- id: W5
reason: >-
Demo web server allows broad ingress from VPC for tutorial access; restrict in production
- id: W40
reason: >-
Web server requires all outbound for updates and responses; restrict in production
Properties:
GroupDescription: Security group for web server in private subnet
VpcId: !Ref ClientVPC
@@ -1016,6 +1034,12 @@ Resources:
# Public Subnet for EC2
ClientPublicSubnet1:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for tutorial accessibility; disable in production
Properties:
VpcId: !Ref ClientVPC
CidrBlock: "10.1.100.0/28" # Small subnet (16 IPs) for testing instances
@@ -1119,6 +1143,18 @@ Resources:
# Security Group for Development EC2 Instance
EC2InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W2
reason: >-
Demo EC2 instance allows SSH from 0.0.0.0/0 for tutorial access; restrict to specific IPs in production
- id: W5
reason: >-
Demo EC2 instance allows SSH ingress from internet for tutorial access; restrict in production
- id: W9
reason: >-
SSH CIDR not restricted to /32 for tutorial accessibility; restrict to specific IP in production
Properties:
GroupDescription: "Security group for development EC2 instance - allows SSH and outbound HTTPS"
VpcId: !Ref ClientVPC
@@ -73,6 +73,12 @@ Resources:
VPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsHostnames: true
@@ -160,6 +166,15 @@ Resources:
BrowserSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W40
reason: >-
All outbound traffic required for browser; actual filtering is enforced by AWS Network Firewall
- id: W5
reason: >-
Open egress is intentional; domain-level filtering is handled by Network Firewall rules
Properties:
GroupName: !Sub '${AWS::StackName}-browser-sg'
GroupDescription: Security group for AgentCore Browser instances
@@ -60,6 +60,12 @@ Resources:
VPC:
Type: AWS::EC2::VPC
Metadata:
cfn_nag:
rules_to_suppress:
- id: W60
reason: >-
Demo/tutorial VPC does not require flow logs; enable VPC Flow Logs in production
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsHostnames: true
@@ -100,6 +106,12 @@ Resources:
# Squid proxy lives here — has internet via IGW
PublicSubnet:
Type: AWS::EC2::Subnet
Metadata:
cfn_nag:
rules_to_suppress:
- id: W33
reason: >-
Public subnet requires MapPublicIpOnLaunch for Squid proxy internet access; disable in production
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [0, !Cidr [!Ref VpcCidr, 4, 12]]
@@ -164,6 +176,12 @@ Resources:
SquidSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W5
reason: >-
Squid proxy requires HTTPS/HTTP outbound to internet to forward browser traffic; restrict egress in production
Properties:
GroupDescription: "Squid proxy accepts from browser allows outbound internet"
VpcId: !Ref VPC
@@ -45,9 +45,7 @@ def get_ssm_parameter(name: str, with_decryption: bool = True) -> str:
return response["Parameter"]["Value"]
def put_ssm_parameter(
name: str, value: str, parameter_type: str = "String", with_encryption: bool = False
) -> None:
def put_ssm_parameter(name: str, value: str, parameter_type: str = "String", with_encryption: bool = False) -> None:
ssm = boto3.client("ssm")
put_params = {
@@ -140,8 +138,7 @@ def read_config(file_path: str) -> Dict[str, Any]:
return yaml.safe_load(content)
except yaml.YAMLError:
raise ValueError(
f"Unsupported configuration file format: {ext}. "
f"Supported formats: .json, .yaml, .yml"
f"Unsupported configuration file format: {ext}. Supported formats: .json, .yaml, .yml"
)
except json.JSONDecodeError as e:
@@ -243,7 +240,7 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username=username,
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
@@ -251,15 +248,13 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username=username,
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
# Authenticate User and get Access Token
auth_response = cognito_client.initiate_auth(
@@ -267,7 +262,7 @@ def get_or_create_cognito_pool(refresh_token=False):
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -289,9 +284,7 @@ def get_or_create_cognito_pool(refresh_token=False):
}
put_ssm_parameter("/app/customersupport/agentcore/client_id", client_id)
put_ssm_parameter("/app/customersupport/agentcore/pool_id", pool_id)
put_ssm_parameter(
"/app/customersupport/agentcore/cognito_discovery_url", discovery_url
)
put_ssm_parameter("/app/customersupport/agentcore/cognito_discovery_url", discovery_url)
put_ssm_parameter("/app/customersupport/agentcore/client_secret", client_secret)
save_customer_support_secret(json.dumps(cognito_config))
@@ -315,26 +308,18 @@ def cleanup_cognito_resources(pool_id):
if pool_id:
try:
# List and delete all app clients
clients_response = cognito_client.list_user_pool_clients(
UserPoolId=pool_id, MaxResults=60
)
clients_response = cognito_client.list_user_pool_clients(UserPoolId=pool_id, MaxResults=60)
for client in clients_response["UserPoolClients"]:
print(f"Deleting app client: {client['ClientName']}")
cognito_client.delete_user_pool_client(
UserPoolId=pool_id, ClientId=client["ClientId"]
)
cognito_client.delete_user_pool_client(UserPoolId=pool_id, ClientId=client["ClientId"])
# List and delete all users
users_response = cognito_client.list_users(
UserPoolId=pool_id, AttributesToGet=["email"]
)
users_response = cognito_client.list_users(UserPoolId=pool_id, AttributesToGet=["email"])
for user in users_response.get("Users", []):
print(f"Deleting user: {user['Username']}")
cognito_client.admin_delete_user(
UserPoolId=pool_id, Username=user["Username"]
)
cognito_client.admin_delete_user(UserPoolId=pool_id, Username=user["Username"])
# Delete the user pool
print(f"Deleting user pool: {pool_id}")
@@ -344,9 +329,7 @@ def cleanup_cognito_resources(pool_id):
return True
except cognito_client.exceptions.ResourceNotFoundException:
print(
f"User pool {pool_id} not found. It may have already been deleted."
)
print(f"User pool {pool_id} not found. It may have already been deleted.")
return True
except Exception as e:
@@ -370,16 +353,14 @@ def reauthenticate_user(client_id, client_secret):
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -404,9 +385,7 @@ def create_agentcore_runtime_execution_role():
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": account_id},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -425,9 +404,7 @@ def create_agentcore_runtime_execution_role():
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -461,9 +438,7 @@ def create_agentcore_runtime_execution_role():
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -518,9 +493,7 @@ def create_agentcore_runtime_execution_role():
"bedrock-agentcore:GetGateway",
"bedrock-agentcore:InvokeGateway",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*"
],
"Resource": [f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*"],
},
],
}
@@ -611,9 +584,7 @@ def delete_agentcore_runtime_execution_role():
except Exception:
pass
delete_ssm_parameter(
"/app/customersupport/agentcore/runtime_execution_role_arn"
)
delete_ssm_parameter("/app/customersupport/agentcore/runtime_execution_role_arn")
except Exception as e:
print(f"❌ Error during cleanup: {str(e)}")
@@ -673,17 +644,13 @@ def gateway_target_cleanup(gateway_id: str = None):
print(f"🗑️ Deleting all targets for gateway: {gateway_id}")
# List and delete all targets
list_response = gateway_client.list_gateway_targets(
gatewayIdentifier=gateway_id, maxResults=100
)
list_response = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id, maxResults=100)
targets_deleted = False
for item in list_response["items"]:
target_id = item["targetId"]
print(f" Deleting target: {target_id}")
gateway_client.delete_gateway_target(
gatewayIdentifier=gateway_id, targetId=target_id
)
gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=target_id)
print(f" ✅ Target {target_id} deleted")
targets_deleted = True
@@ -701,24 +668,18 @@ def gateway_target_cleanup(gateway_id: str = None):
def runtime_resource_cleanup(runtime_arn: str = None):
try:
# Initialize AWS clients
agentcore_control_client = boto3.client(
"bedrock-agentcore-control", region_name=REGION
)
agentcore_control_client = boto3.client("bedrock-agentcore-control", region_name=REGION)
ecr_client = boto3.client("ecr", region_name=REGION)
if runtime_arn:
runtime_id = runtime_arn.split(":")[-1].split("/")[-1]
response = agentcore_control_client.delete_agent_runtime(
agentRuntimeId=runtime_id
)
response = agentcore_control_client.delete_agent_runtime(agentRuntimeId=runtime_id)
print(f" ✅ Agent runtime deleted: {response['status']}")
else:
# Delete the AgentCore Runtime
# print(" 🗑️ Deleting AgentCore Runtime...")
runtimes = agentcore_control_client.list_agent_runtimes()
for runtime in runtimes["agentRuntimes"]:
response = agentcore_control_client.delete_agent_runtime(
agentRuntimeId=runtime["agentRuntimeId"]
)
response = agentcore_control_client.delete_agent_runtime(agentRuntimeId=runtime["agentRuntimeId"])
print(f" ✅ Agent runtime deleted: {response['status']}")
# Delete the ECR repository
@@ -726,9 +687,7 @@ def runtime_resource_cleanup(runtime_arn: str = None):
repositories = ecr_client.describe_repositories()
for repo in repositories["repositories"]:
if "bedrock-agentcore-customer_support_agent" in repo["repositoryName"]:
ecr_client.delete_repository(
repositoryName=repo["repositoryName"], force=True
)
ecr_client.delete_repository(repositoryName=repo["repositoryName"], force=True)
print(f" ✅ ECR repository deleted: {repo['repositoryName']}")
except Exception as e:
@@ -745,9 +704,7 @@ def delete_observability_resources():
# Delete log stream first (must be done before deleting log group)
try:
print(f" 🗑️ Deleting log stream '{log_stream_name}'...")
logs_client.delete_log_stream(
logGroupName=log_group_name, logStreamName=log_stream_name
)
logs_client.delete_log_stream(logGroupName=log_group_name, logStreamName=log_stream_name)
print(f" ✅ Log stream '{log_stream_name}' deleted successfully")
except Exception as e:
if e.response["Error"]["Code"] == "ResourceNotFoundException":
@@ -794,9 +751,7 @@ def local_file_cleanup():
if deleted_files:
print(f"\n📁 Successfully deleted {len(deleted_files)} files")
if missing_files:
print(
f"{len(missing_files)} files were already missing: {', '.join(missing_files)}"
)
print(f"{len(missing_files)} files were already missing: {', '.join(missing_files)}")
def policy_engine_cleanup(policy_engine_id: str = None):
@@ -812,9 +767,7 @@ def policy_engine_cleanup(policy_engine_id: str = None):
print(f"🗑️ Deleting all policies for policy engine: {policy_engine_id}")
# List and delete all policies
list_response = policy_client.list_policies(
policyEngineId=policy_engine_id, maxResults=100
)
list_response = policy_client.list_policies(policyEngineId=policy_engine_id, maxResults=100)
policies_deleted = False
for item in list_response["policies"]:
@@ -31,9 +31,7 @@ def get_ssm_parameter(name: str, with_decryption: bool = True) -> str:
return response["Parameter"]["Value"]
def put_ssm_parameter(
name: str, value: str, parameter_type: str = "String", with_encryption: bool = False
) -> None:
def put_ssm_parameter(name: str, value: str, parameter_type: str = "String", with_encryption: bool = False) -> None:
ssm = boto3.client("ssm")
put_params = {
@@ -126,8 +124,7 @@ def read_config(file_path: str) -> Dict[str, Any]:
return yaml.safe_load(content)
except yaml.YAMLError:
raise ValueError(
f"Unsupported configuration file format: {ext}. "
f"Supported formats: .json, .yaml, .yml"
f"Unsupported configuration file format: {ext}. Supported formats: .json, .yaml, .yml"
)
except json.JSONDecodeError as e:
@@ -229,7 +226,7 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_create_user(
UserPoolId=pool_id,
Username=username,
TemporaryPassword="Temp123!",
TemporaryPassword="Temp123!", # pragma: allowlist secret
MessageAction="SUPPRESS",
)
@@ -237,15 +234,13 @@ def get_or_create_cognito_pool(refresh_token=False):
cognito_client.admin_set_user_password(
UserPoolId=pool_id,
Username=username,
Password="MyPassword123!",
Password="MyPassword123!", # pragma: allowlist secret
Permanent=True,
)
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
# Authenticate User and get Access Token
auth_response = cognito_client.initiate_auth(
@@ -253,7 +248,7 @@ def get_or_create_cognito_pool(refresh_token=False):
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -275,9 +270,7 @@ def get_or_create_cognito_pool(refresh_token=False):
}
put_ssm_parameter("/app/customersupport/agentcore/client_id", client_id)
put_ssm_parameter("/app/customersupport/agentcore/pool_id", pool_id)
put_ssm_parameter(
"/app/customersupport/agentcore/cognito_discovery_url", discovery_url
)
put_ssm_parameter("/app/customersupport/agentcore/cognito_discovery_url", discovery_url)
put_ssm_parameter("/app/customersupport/agentcore/client_secret", client_secret)
save_customer_support_secret(json.dumps(cognito_config))
@@ -301,26 +294,18 @@ def cleanup_cognito_resources(pool_id):
if pool_id:
try:
# List and delete all app clients
clients_response = cognito_client.list_user_pool_clients(
UserPoolId=pool_id, MaxResults=60
)
clients_response = cognito_client.list_user_pool_clients(UserPoolId=pool_id, MaxResults=60)
for client in clients_response["UserPoolClients"]:
print(f"Deleting app client: {client['ClientName']}")
cognito_client.delete_user_pool_client(
UserPoolId=pool_id, ClientId=client["ClientId"]
)
cognito_client.delete_user_pool_client(UserPoolId=pool_id, ClientId=client["ClientId"])
# List and delete all users
users_response = cognito_client.list_users(
UserPoolId=pool_id, AttributesToGet=["email"]
)
users_response = cognito_client.list_users(UserPoolId=pool_id, AttributesToGet=["email"])
for user in users_response.get("Users", []):
print(f"Deleting user: {user['Username']}")
cognito_client.admin_delete_user(
UserPoolId=pool_id, Username=user["Username"]
)
cognito_client.admin_delete_user(UserPoolId=pool_id, Username=user["Username"])
# Delete the user pool
print(f"Deleting user pool: {pool_id}")
@@ -330,9 +315,7 @@ def cleanup_cognito_resources(pool_id):
return True
except cognito_client.exceptions.ResourceNotFoundException:
print(
f"User pool {pool_id} not found. It may have already been deleted."
)
print(f"User pool {pool_id} not found. It may have already been deleted.")
return True
except Exception as e:
@@ -356,16 +339,14 @@ def reauthenticate_user(client_id, client_secret):
message = bytes(username + client_id, "utf-8")
key = bytes(client_secret, "utf-8")
secret_hash = base64.b64encode(
hmac.new(key, message, digestmod=hashlib.sha256).digest()
).decode()
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
auth_response = cognito_client.initiate_auth(
ClientId=client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={
"USERNAME": username,
"PASSWORD": "MyPassword123!",
"PASSWORD": "MyPassword123!", # pragma: allowlist secret
"SECRET_HASH": secret_hash,
},
)
@@ -390,9 +371,7 @@ def create_agentcore_runtime_execution_role():
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": account_id},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -411,9 +390,7 @@ def create_agentcore_runtime_execution_role():
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -447,9 +424,7 @@ def create_agentcore_runtime_execution_role():
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -504,9 +479,7 @@ def create_agentcore_runtime_execution_role():
"bedrock-agentcore:GetGateway",
"bedrock-agentcore:InvokeGateway",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*"
],
"Resource": [f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*"],
},
],
}
@@ -598,9 +571,7 @@ def delete_agentcore_runtime_execution_role():
except Exception:
pass
delete_ssm_parameter(
"/app/customersupport/agentcore/runtime_execution_role_arn"
)
delete_ssm_parameter("/app/customersupport/agentcore/runtime_execution_role_arn")
except Exception as e:
print(f"❌ Error during cleanup: {str(e)}")
@@ -660,17 +631,13 @@ def gateway_target_cleanup(gateway_id: str = None):
print(f"🗑️ Deleting all targets for gateway: {gateway_id}")
# List and delete all targets
list_response = gateway_client.list_gateway_targets(
gatewayIdentifier=gateway_id, maxResults=100
)
list_response = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id, maxResults=100)
targets_deleted = False
for item in list_response["items"]:
target_id = item["targetId"]
print(f" Deleting target: {target_id}")
gateway_client.delete_gateway_target(
gatewayIdentifier=gateway_id, targetId=target_id
)
gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=target_id)
print(f" ✅ Target {target_id} deleted")
targets_deleted = True
@@ -688,24 +655,18 @@ def gateway_target_cleanup(gateway_id: str = None):
def runtime_resource_cleanup(runtime_arn: str = None):
try:
# Initialize AWS clients
agentcore_control_client = boto3.client(
"bedrock-agentcore-control", region_name=REGION
)
agentcore_control_client = boto3.client("bedrock-agentcore-control", region_name=REGION)
ecr_client = boto3.client("ecr", region_name=REGION)
if runtime_arn:
runtime_id = runtime_arn.split(":")[-1].split("/")[-1]
response = agentcore_control_client.delete_agent_runtime(
agentRuntimeId=runtime_id
)
response = agentcore_control_client.delete_agent_runtime(agentRuntimeId=runtime_id)
print(f" ✅ Agent runtime deleted: {response['status']}")
else:
# Delete the AgentCore Runtime
# print(" 🗑️ Deleting AgentCore Runtime...")
runtimes = agentcore_control_client.list_agent_runtimes()
for runtime in runtimes["agentRuntimes"]:
response = agentcore_control_client.delete_agent_runtime(
agentRuntimeId=runtime["agentRuntimeId"]
)
response = agentcore_control_client.delete_agent_runtime(agentRuntimeId=runtime["agentRuntimeId"])
print(f" ✅ Agent runtime deleted: {response['status']}")
# Delete the ECR repository
@@ -713,9 +674,7 @@ def runtime_resource_cleanup(runtime_arn: str = None):
repositories = ecr_client.describe_repositories()
for repo in repositories["repositories"]:
if "bedrock-agentcore-customer_support_agent" in repo["repositoryName"]:
ecr_client.delete_repository(
repositoryName=repo["repositoryName"], force=True
)
ecr_client.delete_repository(repositoryName=repo["repositoryName"], force=True)
print(f" ✅ ECR repository deleted: {repo['repositoryName']}")
except Exception as e:
@@ -732,9 +691,7 @@ def delete_observability_resources():
# Delete log stream first (must be done before deleting log group)
try:
print(f" 🗑️ Deleting log stream '{log_stream_name}'...")
logs_client.delete_log_stream(
logGroupName=log_group_name, logStreamName=log_stream_name
)
logs_client.delete_log_stream(logGroupName=log_group_name, logStreamName=log_stream_name)
print(f" ✅ Log stream '{log_stream_name}' deleted successfully")
except Exception as e:
if e.response["Error"]["Code"] == "ResourceNotFoundException":
@@ -781,9 +738,7 @@ def local_file_cleanup():
if deleted_files:
print(f"\n📁 Successfully deleted {len(deleted_files)} files")
if missing_files:
print(
f"{len(missing_files)} files were already missing: {', '.join(missing_files)}"
)
print(f"{len(missing_files)} files were already missing: {', '.join(missing_files)}")
def policy_engine_cleanup(policy_engine_id: str = None):
@@ -799,9 +754,7 @@ def policy_engine_cleanup(policy_engine_id: str = None):
print(f"🗑️ Deleting all policies for policy engine: {policy_engine_id}")
# List and delete all policies
list_response = policy_client.list_policies(
policyEngineId=policy_engine_id, maxResults=100
)
list_response = policy_client.list_policies(policyEngineId=policy_engine_id, maxResults=100)
policies_deleted = False
for item in list_response["policies"]:
@@ -33,7 +33,7 @@ Resources:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Ref UserPoolName
MfaConfiguration: 'OFF'
MfaConfiguration: 'OPTIONAL'
UsernameConfiguration:
CaseSensitive: false
UsernameAttributes:
+8 -20
View File
@@ -4,8 +4,8 @@ import time
from boto3.session import Session
USER_NAME = "testuser"
PASSWORD = "MyPassword123!"
TEMP_ADMIN_PASSWORD = "Temp123!"
PASSWORD = "MyPassword123!" # pragma: allowlist secret
TEMP_ADMIN_PASSWORD = "Temp123!" # pragma: allowlist secret
def setup_cognito_user_pool(pool_name="MCPServerPool"):
@@ -48,9 +48,7 @@ def setup_cognito_user_pool(pool_name="MCPServerPool"):
refresh_token = auth_response["AuthenticationResult"]["RefreshToken"]
# Output the required values
print(f"Pool id: {pool_id}")
print(
f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration"
)
print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration")
print(f"Client ID: {client_id}")
print(f"Bearer Token: {bearer_token}")
print(f"Refresh Token: {refresh_token}")
@@ -116,9 +114,7 @@ def create_agentcore_role(agent_name):
{
"Effect": "Allow",
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
"Resource": [
f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
],
"Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"],
},
{
"Effect": "Allow",
@@ -152,9 +148,7 @@ def create_agentcore_role(agent_name):
"Effect": "Allow",
"Resource": "*",
"Action": "cloudwatch:PutMetricData",
"Condition": {
"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}
},
"Condition": {"StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"}},
},
{
"Sid": "GetAgentAccessToken",
@@ -181,9 +175,7 @@ def create_agentcore_role(agent_name):
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{account_id}"},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"
},
"ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"},
},
}
],
@@ -202,14 +194,10 @@ def create_agentcore_role(agent_name):
time.sleep(10)
except iam_client.exceptions.EntityAlreadyExistsException:
print("Role already exists -- deleting and creating it again")
policies = iam_client.list_role_policies(
RoleName=agentcore_role_name, MaxItems=100
)
policies = iam_client.list_role_policies(RoleName=agentcore_role_name, MaxItems=100)
print("policies:", policies)
for policy_name in policies["PolicyNames"]:
iam_client.delete_role_policy(
RoleName=agentcore_role_name, PolicyName=policy_name
)
iam_client.delete_role_policy(RoleName=agentcore_role_name, PolicyName=policy_name)
print(f"deleting {agentcore_role_name}")
iam_client.delete_role(RoleName=agentcore_role_name)
print(f"recreating {agentcore_role_name}")