---
cuppy_hub: aws
cuppy_path: reference/kms/key-policies-and-errors.md
cuppy_section: docs
cuppy_doc: ZYM8-8XFN
cuppy_doc_uuid: a89edbe5-d1f1-45c2-9742-900d958a3430
cuppy_permalink: https://cuppy.io/aws/d/ZYM8-8XFN
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/reference/kms/key-policies-and-errors
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: c8917925177f22d8f72a6ce8dd2e07f9542c6732386a657d0b3d7555cb74d13f
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# KMS key policies, grants and errors

**A KMS key's key policy is the root of all access to it.** Unlike other resource policies, it doesn't automatically trust the owning account. IAM policies only work if the key policy has the statement that gives the account's root principal access: the default "Enable IAM User Permissions" statement. Without it, IAM *allows* do nothing, while IAM *denies* still apply.

## How access is decided

- The key policy is in the key's **own account and Region**.
- Access needs an **Allow** in the key policy, in an IAM policy (when the key policy delegates to IAM), or in a **grant**, and no explicit **Deny**. SCPs and VPC endpoint policies also apply (`reference/iam/policy-evaluation.md`).
- **Cross-account:** the key policy must allow the other account (or a role in it) *and* that account's IAM policy must allow the caller. Both are required.
- The **default key policy** has these statements:
  - **Enable IAM User Permissions:** `kms:*` to `arn:aws:iam::<account>:root`, so IAM policies take effect.
  - **Key administrators:** manage the key (policy, grants, enable and disable, schedule deletion) but **can't use it** to encrypt or decrypt.
  - **Key users:** `Encrypt`, `Decrypt`, `GenerateDataKey*` and similar.
  - **Allow attachment of persistent resources:** key users can create grants only for AWS services integrated with KMS. This is how EBS, RDS and others use the key on your behalf.
- **Lockout:** a key policy naming one user, who is then deleted, makes the key unmanageable, and only AWS Support can help. Keep the account statement.

## What each service needs from the key

| Use | Caller | KMS permissions |
|---|---|---|
| S3 SSE-KMS read / write | The IAM principal reading or writing | `kms:Decrypt` / `kms:GenerateDataKey` (multipart upload needs both) (`reference/s3/encryption.md`) |
| CloudFront OAC serving SSE-KMS objects | `cloudfront.amazonaws.com` conditioned on `AWS:SourceArn` = the distribution | `kms:Decrypt`, `kms:Encrypt`, `kms:GenerateDataKey*` **in the key policy** |
| Lambda environment variables (customer key) | The execution role | `kms:Decrypt` (a disabled key fails invocations with `KMSDisabledException`) |
| ECS secrets from Secrets Manager or SSM with a customer key | The task **execution** role | `kms:Decrypt` |
| Auto Scaling launching encrypted EBS volumes | The Auto Scaling service-linked role | Key-policy access or a grant. Otherwise `Client.InternalError: Client error on launch.` |
| Cross-account anything | Caller in account B | Key policy allows B, *and* B's IAM policy allows the action |

## Errors

| Exception | HTTP | Cause → fix | Check |
|---|---|---|---|
| `AccessDeniedException` (message `… is not authorized to perform: kms:<Action> …`) | 400 | No Allow in the key policy (or IAM, when delegated), or an explicit Deny. Cross-account without both halves. A service principal missing from the key policy. Read the message's policy-type clause (`reference/iam/errors.md`) | `aws kms get-key-policy --key-id <k> --policy-name default`; the caller's identity policy; `aws kms list-grants --key-id <k>` |
| `DisabledException` | 400 | The key is disabled. Enable it (`aws kms enable-key --key-id <id>`), or use another key | `aws kms describe-key --key-id <k> --query KeyMetadata.KeyState` |
| `KMSInvalidStateException` | 400 | The key's state doesn't allow the operation, typically **pending deletion**. Cancel the deletion (`aws kms cancel-key-deletion`), then enable the key | Same call → `PendingDeletion`, `DeletionDate` |
| `NotFoundException` | 400 | Wrong key ID, alias or ARN, often the **wrong Region**: keys are Regional, and the ARN includes the Region | `aws kms describe-key --key-id <k> --region <r>` in the Region in the ARN |
| `IncorrectKeyException` | 400 | The key you named can't decrypt this ciphertext: it was encrypted under another key. Omit `KeyId` for symmetric ciphertext, or pass the right one | Decrypt without `KeyId`: the response's `KeyId` names the key that worked |
| `InvalidCiphertextException` | 400 | Ciphertext corrupted, missing or invalid, including a **different encryption context** from the one used to encrypt. The context must match exactly | Log the encryption context at encrypt and decrypt: they must be equal |
| `InvalidKeyUsageException` | 400 | The key's usage or spec doesn't fit the operation (for example, a signing key used to decrypt) | `describe-key` → `KeyUsage`, `KeySpec` |
| `InvalidGrantTokenException` | 400 | A bad grant token in the request | The token's source: `aws kms create-grant` output |
| `KeyUnavailableException`, `DependencyTimeoutException`, `KMSInternalException` | 500 | Transient. Retry with backoff | A retry succeeds |

Services wrap these. S3 reports `KMS.DisabledException`-style codes and Lambda `KMSAccessDeniedException` / `KMSInvalidStateException` (`reference/s3/bucket-and-object-errors.md`, `reference/lambda/errors.md`).

## Checks

```
aws kms describe-key --key-id <id-or-arn> --query 'KeyMetadata.[KeyState,KeyManager,Arn]'
aws kms get-key-policy --key-id <id> --policy-name default --output text
aws kms list-grants --key-id <id>
```

AWS managed keys (`aws/s3`, `aws/lambda`, …) have policies you **can't edit**. SQS's key-management guide states this for AWS managed keys, and the S3 and CloudFront sources are consistent with it. Anything that needs a custom grant, such as a cross-account principal or CloudFront, requires a customer managed key.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
