An IAM Permissions Boundary Fails to Block Decrypt Access Granted by a KMS Key Policy
Overview
For cross-account KMS decrypt, AWS requires both the key policy and the role's identity-based policies, as limited by its permissions boundary, to allow the call. A boundary fails to block decrypt only when it is not actually account-scoped.
At a glance
- Symptom
- A workload assumes an IAM role carrying an explicit permissions boundary intended to cap the role to a single AWS account's resources.
- Likely cause
- An IAM permissions boundary is a managed or inline policy that limits the maximum permissions an identity-based policy can grant to the IAM entity…
- Impact
- The immediate impact is that a role believed to be contained to one account can read encrypted data protected by a key in another account, undermining the isolation…
- Verification signal
- Validation must confirm the specific unwanted grant is gone without breaking legitimate same-account decrypt operations that depend on the key.
- Safe correction
- The fix is not to modify the permissions boundary, which is already working exactly as designed; it targets the key policy that grants the unintended access.
- Rollback or recovery
- If removing the cross-account principal breaks a dependency that was not fully mapped, restore the exact prior key policy captured during diagnosis rather than improvising a new one.
Symptom
A workload assumes an IAM role carrying an explicit permissions boundary intended to cap the role to a single AWS account’s resources. Despite the boundary, the role successfully calls kms:Decrypt against a customer-managed KMS key that lives in a second, unrelated AWS account. CloudTrail records the call as successful, with no errorCode, and no access-denied event appears for the role. Nothing about the role’s own identity-based policies was changed before the access appeared.
False Assumption
The team that attached the permissions boundary assumed it would cap every permission the role could ever exercise, from any source, because that is how boundaries are commonly described in informal terms: as a hard ceiling on the role. On that assumption, the boundary was treated as limiting the role to one account’s resources, and nobody audited what its KMS statement actually allowed or which resource-based policies in other accounts named the role.
Root Cause
An IAM permissions boundary is a managed policy that sets the maximum permissions an identity-based policy can grant to the IAM entity it is attached to. For a cross-account request such as this one, AWS runs two evaluations and allows the request only if both allow it: the role’s own account evaluates its identity-based policies together with any permissions boundary and SCPs, and the key’s account evaluates the KMS key policy. A boundary that genuinely restricts kms:Decrypt to the home account’s key ARNs therefore does block a grant in another account’s key policy. In this trap it did not: the boundary’s KMS statement, copied from a template, allowed kms:Decrypt on "Resource": "*", the role’s identity-based policy did the same, and the key policy in the other account named the role’s ARN as a principal, so both evaluations returned Allow. The boundary was working as written; it was never account-scoped for KMS. The case where a resource-based policy really does bypass a boundary is narrower: within a single account, a policy that names an IAM role session ARN (rather than the role ARN) grants permissions directly to that session, and an implicit deny in the boundary does not limit them.
Impact
The immediate impact is that a role believed to be contained to one account can read encrypted data protected by a key in another account, undermining the isolation the boundary was meant to enforce. Because the access is technically authorised by two independent, correctly evaluated policies, no policy-evaluation error or explicit deny is generated, so the exposure is unlikely to surface through routine access-denied alerting. The blast radius depends on what the key protects; where the key wraps sensitive data such as customer records or credentials, this qualifies as a high-severity identity boundary failure even though every individual policy statement is syntactically valid.
Diagnosis
Confirm the mechanism before changing anything. First, retrieve the role’s attached policies and boundary to confirm what the boundary actually restricts.
Correction
Fix both sides: scope the boundary’s KMS statements to the key ARNs in the role’s own account, so it really caps cross-account use, and remove the role from the other account’s key policy if that access was never intended.
Validation
Validation must confirm the specific unwanted grant is gone without breaking legitimate same-account decrypt operations that depend on the key.
Rollback
If removing the cross-account principal breaks a dependency that was not fully mapped, restore the exact prior key policy, or the prior boundary policy version, captured during diagnosis rather than improvising a new one.
Prevention
Treat permissions boundaries and resource-based policies as two independent control planes that must both be reviewed, not one substituting for the other. Maintain an inventory of resource-based policies (KMS key policies, S3 bucket policies, SNS/SQS policies, Secrets Manager resource policies) that name cross-account or wildcard principals, and review it whenever a boundary is introduced or a role’s scope changes. Where an organisation-wide backstop is needed regardless of individual resource policies, use an AWS Organizations service control policy, which does apply across accounts, rather than relying solely on a per-role permissions boundary to achieve that effect.
Related reading
- Account Root Principal in IAM Trust Policy Grants Every Identity, Not Just Root
- S3 Block Public Access Silently Overrides an Explicit Bucket Policy Grant
- A Bucket-Level Public Access Block Only Masks a Permissive ACL Until It Is Loosened
- AWS Cross-Account AssumeRole Is Denied by a Mismatched Trust Policy
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
Fix commands and configuration
kms:DecrypterrorCode"Resource": "*"Verify, roll back or escalate
Verify
Validation must confirm the specific unwanted grant is gone without breaking legitimate same-account decrypt operations that depend on the key.
Rollback
If removing the cross-account principal breaks a dependency that was not fully mapped, restore the exact prior key policy captured during diagnosis rather than improvising a new one.
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.
Fixed? Get the next one before it hits you: The KBY Brief
Practical fixes for the systems you run, by email about twice a week. Unsubscribe at any time.
About this page
Dates
Published
Last updated
Corrections
No corrections are listed for this page. Some review sweeps corrected pages without naming each one; those are in the corrections log.
Was this useful?
Reports go to the site owner and are used to correct pages. Corrections are listed at /corrections.