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:
committed by
GitHub
parent
87c999ba43
commit
02471ab710
+966
-4524
File diff suppressed because it is too large
Load Diff
+2
-4
@@ -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)}")
|
||||
|
||||
+27
@@ -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
|
||||
|
||||
+27
@@ -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
|
||||
|
||||
+15
@@ -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
|
||||
|
||||
+11
-23
@@ -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}")
|
||||
|
||||
+11
-23
@@ -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}")
|
||||
|
||||
+20
-52
@@ -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)
|
||||
|
||||
+9
-28
@@ -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 = {
|
||||
|
||||
+5
-15
@@ -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
|
||||
|
||||
+9
-22
@@ -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__":
|
||||
|
||||
+4
-9
@@ -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,
|
||||
|
||||
+101
@@ -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()
|
||||
+62
@@ -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")
|
||||
+2
-2
@@ -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"
|
||||
|
||||
+15
-52
@@ -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)
|
||||
|
||||
@@ -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. |
|
||||
|
||||

|
||||

|
||||
|
||||
### 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: |
|
||||
|
||||
+2
-2
@@ -104,8 +104,8 @@ Resources:
|
||||
Type: AWS::ECR::Repository
|
||||
DeletionPolicy: Delete
|
||||
Properties:
|
||||
ImageTagMutability: IMMUTABLE
|
||||
ImageScanningConfiguration:
|
||||
ImageTagMutability: IMMUTABLE
|
||||
ImageScanningConfiguration:
|
||||
ScanOnPush: true
|
||||
LifecyclePolicy:
|
||||
LifecyclePolicyText: |
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
+18
@@ -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
|
||||
|
||||
+18
@@ -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)}")
|
||||
|
||||
+9
-21
@@ -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}")
|
||||
|
||||
+28
-80
@@ -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)}")
|
||||
|
||||
+2
-4
@@ -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)}")
|
||||
|
||||
+27
@@ -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
|
||||
|
||||
+27
@@ -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"
|
||||
|
||||
|
||||
+12
@@ -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}")
|
||||
|
||||
+2
-2
@@ -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
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
+12
@@ -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
|
||||
|
||||
+16
-46
@@ -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)}"
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+5
-15
@@ -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
|
||||
|
||||
+3
-6
@@ -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,
|
||||
|
||||
+4
-9
@@ -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,
|
||||
|
||||
+11
-23
@@ -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}")
|
||||
|
||||
+11
-23
@@ -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}")
|
||||
|
||||
+20
-52
@@ -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)
|
||||
|
||||
+25
-1
@@ -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
|
||||
|
||||
+36
@@ -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
|
||||
|
||||
+15
@@ -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
|
||||
|
||||
+18
@@ -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
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user