# aws kms AccessDeniedException on decrypt: key policy vs IAM policy

## TL;DR

KMS authorization needs both the key policy and the IAM policy to allow the call; either one denying (or not allowing) fails it. Check the key policy first (it is the usual culprit), then the IAM policy, and remember explicit denies win everywhere. KMS has two gates, not one.

```text
AccessDeniedException on kms:Decrypt
```

## Steps

1. Read the key policy: does it allow the principal to call Decrypt?
   Expected: The key policy's stance is known.

2. Read the IAM policy attached to the caller: does it allow kms:Decrypt on this key?
   Expected: The IAM stance is known.

3. Check for explicit denies in either policy or in SCPs; denies override allows.
   Expected: No hidden deny exists.

4. Fix the missing allow (usually the key policy; cross-account calls fail here most).
   Expected: Both policies allow.

5. Retest the decrypt call.
   Expected: It succeeds.

## When to use

- KMS decrypt fails with AccessDeniedException
- cross-account KMS use
- after a policy change

## When not to use

- the error is on encrypt (same debugging, different action)
- the key is disabled or pending deletion (check the key state)
- the failure is intermittent (policies do not flake; check the network)

## Tool compatibility

- AWS KMS
- IAM + key policies

## Variant phrasings

- "kms AccessDeniedException decrypt"
- "kms key policy vs IAM policy"
- "kms decrypt access denied"

## Why it happens

KMS evaluates two policies: the key policy (attached to the key) and the IAM policy (attached to the caller). Both must allow, and any explicit deny anywhere wins. Most KMS permission bugs are the key policy, because people set the IAM side and forget the key has its own gate. Cross-account makes it mandatory to get both right.

## Edge cases

- the default key policy allows the account root; narrowing it is where bugs start
- grants are a third mechanism; check them when policies look right
- CloudTrail shows the evaluated context; use it when the policies look correct but calls fail

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst__1TMUpTAUXJPkyFCNpapoQ
