Symptoms
- A role in account B gets
AccessDeniedonGetObjectfrom account A's bucket, even though the bucket policy allows it. - An AMI shared from account A launches in account B, then terminates immediately with state reason
Client.InternalError: Client error on launch. - Account B can see a shared RDS snapshot but can't restore or copy it.
- CloudTrail in account A shows
Decrypt,GenerateDataKeyorCreateGrantwitherrorCode: AccessDenied.
Encryption adds a second permission check. Access to the resource (bucket policy, snapshot share) is not enough; the caller also needs access to the KMS key.
Rule 1: AWS managed keys can't be shared
Keys like aws/s3, aws/ebs and aws/rds are AWS managed keys. Their key policy can't be changed, so no other account can use them. Anything encrypted with them can't be used cross-account.
aws kms describe-key --key-id <key-arn-or-alias> \
--query "KeyMetadata.{manager:KeyManager,state:KeyState,arn:Arn}"
KeyManager: AWS means you must re-encrypt with a customer managed key (CMK) first:
- S3: copy objects onto themselves with the new key (
aws s3 cp s3://b/p s3://b/p --recursive --sse aws:kms --sse-kms-key-id <cmk>), or use S3 Batch Operations for large buckets. - EBS snapshot / AMI:
aws ec2 copy-snapshot --encrypted --kms-key-id <cmk>oraws ec2 copy-image --encrypted --kms-key-id <cmk>. - RDS snapshot:
aws rds copy-db-snapshot --kms-key-id <cmk>(orcopy-db-cluster-snapshot).
Rule 2: both sides must allow it
| Account | Needs |
|---|---|
| A (key owner) | Key policy statement allowing account B's principals (or account B root) the needed KMS actions |
| B (consumer) | IAM policy on the role allowing those KMS actions on account A's key ARN |
Key policy statement in account A:
{
"Sid": "AllowAccountBUse",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::222233334444:root" },
"Action": ["kms:Decrypt", "kms:DescribeKey", "kms:GenerateDataKey*", "kms:ReEncrypt*", "kms:CreateGrant"],
"Resource": "*",
"Condition": { "Bool": { "kms:GrantIsForAWSResource": "true" } }
}
Put kms:CreateGrant with the GrantIsForAWSResource condition in its own statement if you also need Decrypt for direct calls (S3 reads don't use grants; EBS and RDS do).
IAM policy in account B:
{
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:DescribeKey", "kms:GenerateDataKey*", "kms:ReEncrypt*", "kms:CreateGrant"],
"Resource": "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
}
Tip
Granting the key to account B's
rootdelegates the decision to account B's IAM. Nobody in B gets access until B's IAM also allows it. That is the normal, safe pattern.
Per-service details
S3
- Bucket policy (A) allows B's role
s3:GetObject; key policy (A) allowskms:Decrypt; B's role allows both. - Writing from B into A's bucket: B needs
kms:GenerateDataKey, and object ownership should be "bucket owner enforced" so A owns the objects. - If A uses S3 Bucket Keys, the encryption context is the bucket ARN, not the object ARN; key policy conditions on
kms:EncryptionContext:aws:s3:arnmust match.
EBS snapshots and AMIs
Launching from a shared encrypted AMI needs B's role to use A's key through EC2, which creates a grant. Missing kms:CreateGrant is the classic cause of "terminated at launch".
Best practice: B copies the shared AMI or snapshot and re-encrypts it with B's own key:
aws ec2 copy-image --source-region eu-west-1 --source-image-id ami-0shared \
--name app-golden-copy --encrypted --kms-key-id arn:aws:kms:eu-west-1:222233334444:key/<b-key>
Then B no longer depends on A's key staying enabled.
RDS and Aurora snapshots
- Share the snapshot:
aws rds modify-db-snapshot-attribute --db-snapshot-identifier snap1 --attribute-name restore --values-to-add 222233334444. - The snapshot must be encrypted with a CMK whose policy allows B.
- B copies it with its own key, then restores from the copy.
Step-by-step diagnosis
- Which key?
aws s3api head-object --bucket b --key k --query SSEKMSKeyId,aws ec2 describe-snapshots --snapshot-ids snap-x --query "Snapshots[0].KmsKeyId". - Is it AWS managed? (
KeyManager) - In account A's CloudTrail, find the failing call:
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventSource,AttributeValue=kms.amazonaws.com \
--max-results 50 --query "Events[?contains(CloudTrailEvent, 'AccessDenied')].CloudTrailEvent" --output text | head -c 3000
- Read
userIdentity(who),eventName(which action) anderrorMessage(which policy type denied). - Check for SCPs or permission boundaries in B, and for key policy conditions (
kms:ViaService,aws:PrincipalOrgID) that don't match.
Prevent it next time
- Never use AWS managed keys for anything that may be shared: backups, AMIs, data exchange buckets.
- Create a "sharing" CMK per data domain with a key policy scoped to your organisation (
aws:PrincipalOrgID). - Consumers copy and re-encrypt shared artefacts with their own keys.
- Keep key policies in code and alert on
ScheduleKeyDeletionandDisableKeyfor shared keys.