Bedrock Core Tools Inventory
Deployment Runbook
Architecture Overview
The inventory Lambda runs on a 15-minute schedule inside the target AWS account. It reads from five AWS service APIs, writes seven JSON artifacts to an SSE-KMS encrypted S3 bucket, and promotes them to a latest/ prefix. The OpenICF connector in PingOne IDM reads those artifacts (plus live Bedrock Agent APIs) to surface AI governance data during reconciliation.
Shell Variables
All commands reference shell variables. Set these in your terminal before running anything. Later steps capture additional ARNs as resources are created.
export ACCOUNT_ID=<your-account-id> export AWS_REGION=us-east-1 export INVENTORY_BUCKET=bedrock-core-inventory export DEPLOY_BUCKET=bedrock-core-tools-inventory-deploy export ROLE_NAME=bedrock-core-inventory-lambda-role export FUNCTION_NAME=bedrock-core-tools-inventory export LOG_GROUP=/aws/lambda/${FUNCTION_NAME} export CMK_ALIAS=alias/bedrock-core-inventory export EVENTBRIDGE_RULE=bedrock-core-inventory-schedule
<< EOF, not << 'EOF') so shell variables expand. None of the policies use IAM policy variables of the form ${aws:...}. If you add such variables later, switch the specific block to a quoted heredoc.
Prerequisites
Before starting, ensure you have:
- AWS CLI installed, configured with permissions to create IAM roles, Lambda functions, S3 buckets, KMS keys, CloudWatch log groups, and EventBridge rules
- Python 3.11+,
pip, andzipinstalled locally - The inventory Lambda package (contact your Customer Success Outcome Manager (CSOM) for the inventory package)
The four prerequisite steps (P1βP4) must all complete before Step 1.
P1 Create the KMS CMK
The CMK encrypts both inventory artifacts in S3 (SSE-KMS) and the Lambda's CloudWatch log group. A single key is used for both. Created with a minimal root-only policy; the Lambda execution role grant is added in Step 2e after the role exists.
cat > /tmp/cmk-bootstrap-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RootAccountFullControl",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::${ACCOUNT_ID}:root" },
"Action": "kms:*",
"Resource": "*"
}
]
}
EOF
export CMK_ARN=$(aws kms create-key \
--description "Encryption key for bedrock-core-tools-inventory artifacts and log group" \
--key-usage ENCRYPT_DECRYPT \
--key-spec SYMMETRIC_DEFAULT \
--policy file:///tmp/cmk-bootstrap-policy.json \
--region ${AWS_REGION} \
--query KeyMetadata.Arn \
--output text)
echo "CMK_ARN=${CMK_ARN}"
Create a friendly alias
aws kms create-alias \
--alias-name ${CMK_ALIAS} \
--target-key-id ${CMK_ARN} \
--region ${AWS_REGION}
P2 Create the Inventory Bucket
aws s3api create-bucket \
--bucket ${INVENTORY_BUCKET} \
--region ${AWS_REGION} \
--create-bucket-configuration LocationConstraint=${AWS_REGION}
aws s3api put-public-access-block \
--bucket ${INVENTORY_BUCKET} \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
${INVENTORY_BUCKET} is taken, use an account-suffixed value:export INVENTORY_BUCKET=bedrock-core-inventory-${ACCOUNT_ID}
P3 Apply the Inventory Bucket Policy
Enforces three invariants: all access must use TLS, all PutObject requests must use SSE-KMS, and SSE-KMS puts must specify the correct CMK ARN from P1.
cat > /tmp/inventory-bucket-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonTLS",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::${INVENTORY_BUCKET}",
"arn:aws:s3:::${INVENTORY_BUCKET}/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "false" }
}
},
{
"Sid": "DenyUnencryptedPuts",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::${INVENTORY_BUCKET}/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "DenyWrongKMSKey",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::${INVENTORY_BUCKET}/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": "${CMK_ARN}"
}
}
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket ${INVENTORY_BUCKET} \
--policy file:///tmp/inventory-bucket-policy.json \
--region ${AWS_REGION}
P4 Create the Lambda Log Group
Pre-created so retention and CMK encryption can be set explicitly. Without this, Lambda auto-creates the log group on first invocation with default settings (never expire, AWS-managed encryption).
aws logs create-log-group \
--log-group-name ${LOG_GROUP} \
--region ${AWS_REGION}
aws logs put-retention-policy \
--log-group-name ${LOG_GROUP} \
--retention-in-days 90 \
--region ${AWS_REGION}
aws logs associate-kms-key \
--log-group-name ${LOG_GROUP} \
--kms-key-id ${CMK_ARN} \
--region ${AWS_REGION}
The CloudWatch Logs service principal grant on the CMK is added in Step 2e alongside the Lambda role grant.
Step 1 Create the Deployment Bucket
This bucket stages the Lambda ZIP during deployment. It is separate from the inventory output bucket.
aws s3api create-bucket \
--bucket ${DEPLOY_BUCKET} \
--region ${AWS_REGION} \
--create-bucket-configuration LocationConstraint=${AWS_REGION}
aws s3api put-public-access-block \
--bucket ${DEPLOY_BUCKET} \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
${DEPLOY_BUCKET} is taken, use: export DEPLOY_BUCKET=bedrock-core-tools-inventory-deploy-${ACCOUNT_ID}
Step 2 Create the IAM Execution Role
2a β Trust policy document
cat > /tmp/lambda-trust-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
2b β Create the role
export ROLE_ARN=$(aws iam create-role \ --role-name ${ROLE_NAME} \ --assume-role-policy-document file:///tmp/lambda-trust-policy.json \ --description "Execution role for bedrock-core-tools-inventory Lambda" \ --query Role.Arn \ --output text) echo "ROLE_ARN=${ROLE_ARN}"
2c β Attach CloudWatch Logs managed policy
aws iam attach-role-policy \
--role-name ${ROLE_NAME} \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
2d β Custom inventory policy
The KmsArtifactEncryption statement grants the role permissions on the CMK. Other statements are unchanged.
cat > /tmp/inventory-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "IAMReadForBindings",
"Effect": "Allow",
"Action": [
"iam:ListRoles",
"iam:ListRolePolicies",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:ListUsers",
"iam:ListUserPolicies",
"iam:GetUserPolicy",
"iam:ListAttachedUserPolicies",
"iam:ListGroupsForUser",
"iam:ListGroupPolicies",
"iam:GetGroupPolicy",
"iam:ListAttachedGroupPolicies",
"iam:GetPolicy",
"iam:GetPolicyVersion"
],
"Resource": "*"
},
{
"Sid": "BedrockRead",
"Effect": "Allow",
"Action": [
"bedrock:ListFoundationModels",
"bedrock:ListAgents",
"bedrock:GetAgent",
"bedrock:ListAgentActionGroups",
"bedrock:GetAgentActionGroup"
],
"Resource": "*"
},
{
"Sid": "LambdaReadForToolCredentials",
"Effect": "Allow",
"Action": "lambda:GetFunction",
"Resource": "*"
},
{
"Sid": "CloudWatchLogsQuery",
"Effect": "Allow",
"Action": [
"logs:StartQuery",
"logs:GetQueryResults"
],
"Resource": "*"
},
{
"Sid": "CloudTrailLookup",
"Effect": "Allow",
"Action": "cloudtrail:LookupEvents",
"Resource": "*"
},
{
"Sid": "S3WriteInventory",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:CopyObject"
],
"Resource": "arn:aws:s3:::${INVENTORY_BUCKET}/*"
},
{
"Sid": "KmsArtifactEncryption",
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey",
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "${CMK_ARN}"
},
{
"Sid": "STSCallerIdentity",
"Effect": "Allow",
"Action": "sts:GetCallerIdentity",
"Resource": "*"
}
]
}
EOF
aws iam put-role-policy \
--role-name ${ROLE_NAME} \
--policy-name BedrockCoreInventoryPolicy \
--policy-document file:///tmp/inventory-policy.json
2e β Finalize the KMS key policy
Now that the execution role exists, install the full policy granting the Lambda role SSE-KMS operations, the CloudWatch Logs service principal log-group encryption, and root account full control.
cat > /tmp/cmk-full-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RootAccountFullControl",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::${ACCOUNT_ID}:root" },
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "LambdaExecutionRoleArtifactEncryption",
"Effect": "Allow",
"Principal": { "AWS": "${ROLE_ARN}" },
"Action": [
"kms:GenerateDataKey",
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "CloudWatchLogsLogGroupEncryption",
"Effect": "Allow",
"Principal": { "Service": "logs.${AWS_REGION}.amazonaws.com" },
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:Describe*"
],
"Resource": "*",
"Condition": {
"ArnEquals": {
"kms:EncryptionContext:aws:logs:arn":
"arn:aws:logs:${AWS_REGION}:${ACCOUNT_ID}:log-group:${LOG_GROUP}"
}
}
}
]
}
EOF
aws kms put-key-policy \
--key-id ${CMK_ARN} \
--policy-name default \
--policy file:///tmp/cmk-full-policy.json \
--region ${AWS_REGION}
Step 3 Package the Lambda
# Extract the inventory package provided by your CSOM
tar xzf bedrock-core-tools-inventory.tar.gz
cd bedrock-core-tools-inventory
pip install -r requirements.txt -t package/
cp src/*.py package/
cd package
zip -r ../bedrock-core-tools-inventory.zip .
cd ..
Verify the ZIP contains the handler at the root (no subdirectory prefix):
unzip -l bedrock-core-tools-inventory.zip | grep handler.py
Step 4 Upload ZIP to Deployment Bucket
aws s3 cp bedrock-core-tools-inventory.zip \
s3://${DEPLOY_BUCKET}/bedrock-core-tools-inventory.zip \
--region ${AWS_REGION}
Step 5 Create the Lambda Function
The INVENTORY_KMS_KEY_ID env var is required. The Lambda fails at startup with RuntimeError if missing.
export FUNCTION_ARN=$(aws lambda create-function \ --function-name ${FUNCTION_NAME} \ --runtime python3.11 \ --handler handler.handler \ --role ${ROLE_ARN} \ --code S3Bucket=${DEPLOY_BUCKET},S3Key=bedrock-core-tools-inventory.zip \ --timeout 900 \ --memory-size 256 \ --environment "Variables={REGION=${AWS_REGION},CORE_INVENTORY_BUCKET=${INVENTORY_BUCKET},OUTPUT_PREFIX=bedrock-core-inventory/,ACCOUNT_ID=${ACCOUNT_ID},INVENTORY_KMS_KEY_ID=${CMK_ARN}}" \ --region ${AWS_REGION} \ --query FunctionArn \ --output text) echo "FUNCTION_ARN=${FUNCTION_ARN}"
Optional: Supplemental tool log groups
If any tool Lambdas use a custom loggingConfig (log group other than /aws/lambda/{function-name}), add TOOL_LOG_GROUPS. Comma-separated log group names, merged with auto-discovered groups.
aws lambda update-function-configuration \
--function-name ${FUNCTION_NAME} \
--environment "Variables={REGION=${AWS_REGION},CORE_INVENTORY_BUCKET=${INVENTORY_BUCKET},OUTPUT_PREFIX=bedrock-core-inventory/,ACCOUNT_ID=${ACCOUNT_ID},INVENTORY_KMS_KEY_ID=${CMK_ARN},TOOL_LOG_GROUPS=/custom/log/group1,/custom/log/group2}" \
--region ${AWS_REGION}
Step 6 Create the EventBridge Schedule
6a β Create the rule
export RULE_ARN=$(aws events put-rule \ --name ${EVENTBRIDGE_RULE} \ --schedule-expression "rate(15 minutes)" \ --state ENABLED \ --description "Triggers bedrock-core-tools-inventory every 15 minutes" \ --region ${AWS_REGION} \ --query RuleArn \ --output text) echo "RULE_ARN=${RULE_ARN}"
6b β Add the Lambda as the target
aws events put-targets \
--rule ${EVENTBRIDGE_RULE} \
--targets "Id=LambdaTarget,Arn=${FUNCTION_ARN}" \
--region ${AWS_REGION}
6c β Grant EventBridge permission to invoke
aws lambda add-permission \
--function-name ${FUNCTION_NAME} \
--statement-id EventBridgeInvoke \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn ${RULE_ARN} \
--region ${AWS_REGION}
Step 7 Invoke Manually & Verify
7a β Trigger a test invocation
aws lambda invoke \
--function-name ${FUNCTION_NAME} \
--payload '{}' \
--region ${AWS_REGION} \
/tmp/response.json && cat /tmp/response.json
INVENTORY_KMS_KEY_ID not set (RuntimeError), KMS AccessDenied (re-check Step 2e key policy), bucket policy denial (re-check P3 ARN matches ${CMK_ARN}).
7b β Check CloudWatch Logs
aws logs tail ${LOG_GROUP} \
--follow \
--region ${AWS_REGION}
Look for uploaded agent-bindings.json and uploaded activity-logs.json. Final log line should read "statusCode": 200.
7c β Verify S3 artifacts
aws s3 ls s3://${INVENTORY_BUCKET}/latest/ --region ${AWS_REGION}
Expected β seven files:
7d β Inspect agent-bindings.json
aws s3 cp s3://${INVENTORY_BUCKET}/latest/agent-bindings.json - \
--region ${AWS_REGION} | python3 -m json.tool
accountId matches ${ACCOUNT_ID}, region matches ${AWS_REGION}, and bindings array is non-empty (assuming IAM roles with bedrock:InvokeAgent exist).
7e β Inspect agent-tool-credentials.json
aws s3 cp s3://${INVENTORY_BUCKET}/latest/agent-tool-credentials.json - \
--region ${AWS_REGION} | python3 -m json.tool
Confirm records with credentialType values: LAMBDA_EXECUTION_ROLE, CONFLUENCE_SECRET, S3_READ, or NONE.
7f β Inspect manifest.json
aws s3 cp s3://${INVENTORY_BUCKET}/latest/manifest.json - \
--region ${AWS_REGION} | python3 -m json.tool
Confirm agentBindingCount and agentToolCredentialCount are non-zero, generatedAt reflects current timestamp.
7g β Inspect activity-logs.json
aws s3 cp s3://${INVENTORY_BUCKET}/latest/activity-logs.json - \
--region ${AWS_REGION} | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'{len(d)} records'); print(set(r['event_type'] for r in d))"
Confirm non-zero record count and expected event types (tool_invocation, model_invocation, etc.).
7h β Verify SSE-KMS on a written artifact
aws s3api head-object \
--bucket ${INVENTORY_BUCKET} \
--key latest/manifest.json \
--region ${AWS_REGION} \
--query '[ServerSideEncryption, SSEKMSKeyId]' \
--output text
Confirms encryption is enforced and the correct CMK is in use.
Updating the Lambda Code
After any change to src/:
# Re-package rm -rf package bedrock-core-tools-inventory.zip pip install -r requirements.txt -t package/ cp src/*.py package/ cd package && zip -r ../bedrock-core-tools-inventory.zip . && cd .. # Upload aws s3 cp bedrock-core-tools-inventory.zip \ s3://${DEPLOY_BUCKET}/bedrock-core-tools-inventory.zip \ --region ${AWS_REGION} # Deploy aws lambda update-function-code \ --function-name ${FUNCTION_NAME} \ --s3-bucket ${DEPLOY_BUCKET} \ --s3-key bedrock-core-tools-inventory.zip \ --region ${AWS_REGION}
Resource Summary
| Resource | Variable / Name |
|---|---|
| Lambda function | ${FUNCTION_NAME} β bedrock-core-tools-inventory |
| Lambda execution role | ${ROLE_NAME} β bedrock-core-inventory-lambda-role |
| KMS CMK alias | ${CMK_ALIAS} β alias/bedrock-core-inventory |
| KMS CMK ARN | ${CMK_ARN} β captured in P1 |
| Deployment bucket | ${DEPLOY_BUCKET} β bedrock-core-tools-inventory-deploy |
| Inventory bucket | ${INVENTORY_BUCKET} β bedrock-core-inventory |
| Lambda log group | ${LOG_GROUP} β /aws/lambda/bedrock-core-tools-inventory |
| Log retention | 90 days |
| EventBridge rule | ${EVENTBRIDGE_RULE} β bedrock-core-inventory-schedule |
| Schedule | Every 15 minutes |
| Handler | handler.handler |
| Runtime | Python 3.11 |
| Timeout | 900s |
| Memory | 256 MB |