AWS Bedrock Knowledge Base - Access Denied error when syncing with S3
Fix Access Denied in Bedrock Knowledge Base sync: verify IAM role trust, KMS key policy, S3 bucket policy and VPC endpoint policy.
This runbook covers resolving Access Denied errors during Bedrock Knowledge Base synchronisation with S3. For a full guide on RAG architecture on AWS, see RAG Architecture on AWS - S3, OpenSearch, Bedrock: infrastructure patterns and costs. For help designing RAG pipelines and configuring Knowledge Base, book a consulting session.
Symptoms
Bedrock console shows the sync status as “Failed”. The ingestion job ends with Access Denied without processing any documents:
# Typical error messages:
# "The knowledge base storage configuration is invalid... Access Denied"
# "Failed to access S3 bucket: Access Denied (Service: Amazon S3; Status Code: 403)"
# "The data source sync failed because Bedrock could not access objects in the S3 bucket"
# Check status of the latest ingestion job
aws bedrock-agent get-ingestion-job \
--knowledge-base-id KBXXXXXXXX \
--data-source-id DSXXXXXXXX \
--ingestion-job-id IJXXXXXXXX \
--query '{status:ingestionJob.status, failureReasons:ingestionJob.failureReasons, statistics:ingestionJob.statistics}'
# List recent ingestion jobs for the data source
aws bedrock-agent list-ingestion-jobs \
--knowledge-base-id KBXXXXXXXX \
--data-source-id DSXXXXXXXX \
--sort-by attribute=STARTED_AT,order=DESCENDING \
--max-results 5 \
--query 'ingestionJobSummaries[].{id:ingestionJobId, status:status, started:startedAt, docs:statistics.numberOfDocumentsScanned}'
# Check CloudTrail for Access Denied errors from Bedrock
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventSource,AttributeValue=s3.amazonaws.com \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--query 'Events[?contains(CloudTrailEvent, `AccessDenied`) && contains(CloudTrailEvent, `bedrock`)].{time:EventTime, event:EventName, user:Username}' \
-o table
# Detailed event from CloudTrail (find the specific errorCode)
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventSource,AttributeValue=s3.amazonaws.com \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--max-results 10 \
--query 'Events[?contains(CloudTrailEvent, `AccessDenied`)].[CloudTrailEvent]' \
--output text | jq -r '. | select(.errorCode == "AccessDenied") | {eventTime, eventName, errorCode, errorMessage, requestParameters, userIdentity}'
The ingestion job status is “FAILED” with failureReasons pointing to Access Denied. Statistics show 0 documents processed.
Cause
Bedrock Knowledge Base uses an IAM role to access the S3 bucket containing source data. Access Denied occurs when any authorisation layer blocks access:
-
Missing trust policy for bedrock.amazonaws.com: The IAM role assigned to the Knowledge Base does not include a trust policy entry allowing the
bedrock.amazonaws.comservice to performsts:AssumeRole. Bedrock cannot assume the role and every S3 operation results in access denied. -
KMS key policy does not include the Bedrock role: Objects in S3 are encrypted with a KMS key (customer-managed CMK), but the key policy does not contain a grant for
kms:Decryptandkms:GenerateDataKeyfor the Bedrock role. Even if the role’s IAM policy allows S3 GetObject, the lack of KMS permissions results in Access Denied. -
S3 bucket policy with explicit deny: The bucket policy contains a statement with
"Effect": "Deny"that matches requests from the Bedrock role - e.g. deny all except a specific VPC endpoint, deny all except a specific source IP, or deny requests without a particular condition. An explicit deny in the bucket policy overrides all Allow statements in the IAM policy. -
VPC endpoint policy restricts access: If Bedrock operates within a VPC with an S3 Gateway or Interface endpoint, the endpoint policy may restrict access to specific buckets or principals only. Requests to buckets not on the allow list are blocked at the endpoint level.
-
Objects encrypted with customer-managed KMS without proper grants: Even if the bucket default encryption is configured correctly, individual objects may be encrypted with a different KMS key (e.g. uploaded by another process using its own key). The Bedrock role does not have permissions for that specific key.
Fix
A) Repair the IAM role trust policy:
# Check the current trust policy of the Bedrock Knowledge Base role
aws iam get-role \
--role-name AmazonBedrockExecutionRoleForKnowledgeBase_kb123 \
--query 'Role.AssumeRolePolicyDocument' | jq .
# The trust policy MUST include bedrock.amazonaws.com as a trusted principal
# If missing - update the trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "bedrock.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "123456789012"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:bedrock:eu-west-1:123456789012:knowledge-base/KBXXXXXXXX"
}
}
}
]
}
# Save the JSON above to a file and update the trust policy
aws iam update-assume-role-policy \
--role-name AmazonBedrockExecutionRoleForKnowledgeBase_kb123 \
--policy-document file://trust-policy.json
# Also check the role's permissions policy - it must include s3:GetObject and s3:ListBucket
aws iam list-attached-role-policies \
--role-name AmazonBedrockExecutionRoleForKnowledgeBase_kb123
aws iam get-role-policy \
--role-name AmazonBedrockExecutionRoleForKnowledgeBase_kb123 \
--policy-name BedrockS3Access \
--query 'PolicyDocument' | jq .
B) Update the KMS key policy:
# Find which KMS key is used for bucket encryption
aws s3api get-bucket-encryption \
--bucket my-kb-documents \
--query 'ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.KMSMasterKeyID'
# Check the current key policy
aws kms get-key-policy \
--key-id arn:aws:kms:eu-west-1:123456789012:key/mrk-1234abcd \
--policy-name default \
--query 'Policy' --output text | jq .
Add a statement to the key policy allowing the Bedrock role to decrypt:
{
"Sid": "AllowBedrockKnowledgeBaseDecrypt",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForKnowledgeBase_kb123"
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey",
"kms:GenerateDataKey"
],
"Resource": "*"
}
# Update the key policy (fetch current, add statement, save)
aws kms get-key-policy \
--key-id mrk-1234abcd \
--policy-name default \
--query 'Policy' --output text > kms-policy.json
# Edit kms-policy.json - add the statement above to the Statement array
aws kms put-key-policy \
--key-id mrk-1234abcd \
--policy-name default \
--policy file://kms-policy.json
C) Repair the S3 bucket policy:
# Check the bucket policy
aws s3api get-bucket-policy \
--bucket my-kb-documents \
--query 'Policy' --output text | jq .
If the bucket policy contains an explicit deny - add a condition that excludes the Bedrock role from the deny:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowBedrockKnowledgeBase",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForKnowledgeBase_kb123"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-kb-documents",
"arn:aws:s3:::my-kb-documents/*"
]
},
{
"Sid": "DenyNonVpcAccess",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-kb-documents",
"arn:aws:s3:::my-kb-documents/*"
],
"Condition": {
"StringNotEquals": {
"aws:sourceVpce": "vpce-0abc1234def56789"
},
"ArnNotLike": {
"aws:PrincipalArn": "arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForKnowledgeBase_kb123"
}
}
}
]
}
# Apply the corrected bucket policy
aws s3api put-bucket-policy \
--bucket my-kb-documents \
--policy file://bucket-policy.json
D) Update the VPC endpoint policy:
# Find the S3 VPC endpoint
aws ec2 describe-vpc-endpoints \
--filters "Name=service-name,Values=com.amazonaws.eu-west-1.s3" \
--query 'VpcEndpoints[].{id:VpcEndpointId, policy:PolicyDocument, vpcId:VpcId}' | jq .
Add the bucket and the Bedrock role to the endpoint policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowBedrockS3Access",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForKnowledgeBase_kb123"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-kb-documents",
"arn:aws:s3:::my-kb-documents/*"
]
}
]
}
# Update the VPC endpoint policy
aws ec2 modify-vpc-endpoint \
--vpc-endpoint-id vpce-0abc1234def56789 \
--policy-document file://vpce-policy.json
E) Verify object-level encryption:
# Check encryption of individual objects in the bucket
aws s3api head-object \
--bucket my-kb-documents \
--key documents/handbook.pdf \
--query '{encryption:ServerSideEncryption, kmsKeyId:SSEKMSKeyId}'
# List objects with encryption different from bucket default
aws s3api list-objects-v2 \
--bucket my-kb-documents \
--prefix documents/ \
--max-items 20 \
--query 'Contents[].Key' --output text | \
xargs -I {} aws s3api head-object \
--bucket my-kb-documents \
--key {} \
--query '{key: "{}", kmsKey:SSEKMSKeyId}' 2>/dev/null
# If objects use a different KMS key - add a grant for the Bedrock role
aws kms create-grant \
--key-id arn:aws:kms:eu-west-1:123456789012:key/other-key-5678efgh \
--grantee-principal arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForKnowledgeBase_kb123 \
--operations Decrypt DescribeKey \
--name "BedrockKBAccessGrant"
# Alternatively - re-encrypt objects with the bucket default key
aws s3 cp \
s3://my-kb-documents/documents/handbook.pdf \
s3://my-kb-documents/documents/handbook.pdf \
--sse aws:kms \
--sse-kms-key-id arn:aws:kms:eu-west-1:123456789012:key/mrk-1234abcd
Verification
# 1. Start a new sync (ingestion job)
aws bedrock-agent start-ingestion-job \
--knowledge-base-id KBXXXXXXXX \
--data-source-id DSXXXXXXXX
# 2. Wait 1-2 minutes and check the status
aws bedrock-agent get-ingestion-job \
--knowledge-base-id KBXXXXXXXX \
--data-source-id DSXXXXXXXX \
--ingestion-job-id $(aws bedrock-agent list-ingestion-jobs \
--knowledge-base-id KBXXXXXXXX \
--data-source-id DSXXXXXXXX \
--sort-by attribute=STARTED_AT,order=DESCENDING \
--max-results 1 \
--query 'ingestionJobSummaries[0].ingestionJobId' --output text) \
--query '{status:ingestionJob.status, stats:ingestionJob.statistics}'
# Expected: status = "COMPLETE", numberOfDocumentsScanned > 0
# 3. Verify that documents are indexed in the vector store
aws bedrock-agent-runtime retrieve \
--knowledge-base-id KBXXXXXXXX \
--retrieval-query '{"text": "test query relevant to your documents"}' \
--retrieval-configuration '{"vectorSearchConfiguration": {"numberOfResults": 3}}' \
--query 'retrievalResults[].{score:score, uri:location.s3Location.uri}'
# Expected: results from S3 documents, score > 0
# 4. Check CloudTrail - no new Access Denied errors
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventSource,AttributeValue=s3.amazonaws.com \
--start-time $(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%S) \
--query 'Events[?contains(CloudTrailEvent, `AccessDenied`) && contains(CloudTrailEvent, `bedrock`)]' \
--output text
# Expected: no results (empty output)
If the ingestion job completed with status “COMPLETE”, documents are available via retrieve and there are no new Access Denied errors in CloudTrail - the problem is resolved. Run a full sync if previous partial attempts may have skipped documents.
Bedrock Knowledge Base not syncing with S3?
Book a free 30-minute call. We'll review your IAM, KMS and bucket policy configuration to unblock sync and restore up-to-date RAG responses.