skip to content

A role in the same AWS account has an IAM policy allowing kms:Decrypt on a customer managed KMS key, but its calls still fail with AccessDenied. Why does KMS behave differently from most AWS resource policies here, and how do you fix it?

level: seniorimportance: must knowfreq 58%

answer

  1. the resource policy is not optional here
  2. every key carries a key policy
  3. look for the account-root statement
  4. that statement is what delegates to IAM

basics

~20 s

Every KMS key has a mandatory key policy, and it is the root of authority for that key. An IAM policy grants nothing unless the key policy also allows the principal, either directly or through its default statement that delegates to IAM.

solid answer

~50 s

KMS is the exception to the usual pattern where an identity policy alone is enough inside one account. Every KMS key carries a key policy, it cannot be removed, and access must be allowed there. The default key policy created with the key contains a statement whose `Principal` is the account root ARN with `kms:*` — that statement is what delegates authority to IAM, and it is why IAM policies normally appear to be sufficient. If someone replaced the default policy with a list of named principals, IAM allows for anyone else become dead letters. The fix is either to restore that delegation statement or to add the role to the key policy explicitly with the actions it needs, typically `kms:Decrypt` and `kms:GenerateDataKey`. Grants are the third mechanism, useful when you want temporary, programmatic access without editing the policy at all.

code

json · 19 lines
json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Enable IAM User Permissions",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "AllowAppRuntimeUse",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:role/app-runtime" },
      "Action": ["kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"],
      "Resource": "*"
    }
  ]
}</br>

go deeper

for a junior

Remember that a KMS key has its own policy attached to it, and that permissions can be denied there even when your IAM policy looks correct. Knowing where to go and read the key policy is the expected answer.

for a middle

Explain the default statement with the account-root principal and kms:*, and why its presence is what makes IAM policies work for that key at all. Name the actions an application role usually needs.

for a senior

Diagnose it end to end: read the key policy, check for a ViaService or similar condition, distinguish a key-policy denial from a boundary or session-policy ceiling, and choose between restoring delegation and naming principals explicitly.

for a principal

Own the governance question. Delegating to IAM centralises management but means any IAM admin can self-grant key access; naming principals in key policies is stricter but does not scale and risks lockout. Decide which keys deserve which model.

## The rule that makes KMS different For most AWS resources, a resource-based policy is optional and additive: an S3 bucket with no bucket policy is still reachable by a principal in the same account whose IAM policy allows `s3:GetObject`. KMS inverts that default. **Every KMS key has a key policy, you cannot delete it, and it is the primary authority for the key.** A principal gets access only if the key policy allows it — directly, or by explicitly handing authority to IAM. That is what the default key policy does. When you create a customer managed key without specifying a policy, KMS attaches one containing a single statement: ```json { "Sid": "Enable IAM User Permissions", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:root" }, "Action": "kms:*", "Resource": "*" } ``` The `root` principal here does not mean the root user. It means *the account*, and the statement's real function is to say "IAM policies in this account may grant access to this key." With it in place, KMS behaves the way engineers expect and an IAM allow is enough. Take it out — which happens the moment someone hardens the key by replacing the policy with an explicit list of principals — and IAM policies referencing that key stop having any effect, silently, for every principal not named in the new document. ## Diagnosing it The symptom is an `AccessDeniedException` on `kms:Decrypt` or `kms:GenerateDataKey` from a role whose IAM policy plainly allows the action. The message text is the tell: KMS distinguishes a denial where no resource-based policy allows the action from an ordinary identity-policy denial. The confirming step is to read the key policy itself (`aws kms get-key-policy --key-id <id> --policy-name default`) and look for the account-root delegation statement. Its absence, or a `Condition` on it that the caller does not satisfy, is the answer nine times out of ten. Two related traps produce the same symptom. First, a `Condition` such as `kms:ViaService` on the allow statement restricts use to requests that a named AWS service makes on your behalf — a direct SDK call from the role then fails even though the same role succeeds through that service. Second, the caller may be a *session* whose permissions were narrowed by a session policy or a permissions boundary, in which case the key policy is fine and the ceiling is elsewhere. ## Fixing it Two shapes are legitimate: - **Restore the delegation** and keep per-principal detail in IAM. This scales — you manage access in one place — but it means anyone with IAM-editing power in the account can grant themselves the key. - **Name principals in the key policy.** Stricter, and required for cross-account access, where both sides must allow: the key policy must name the external principal *and* that principal's own IAM policy must allow the action. A scoped statement looks like: ```json { "Sid": "AllowAppRuntimeUse", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:role/app-runtime" }, "Action": ["kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"], "Resource": "*" } ``` `Resource` is `*` because a key policy is attached to exactly one key — it can mean nothing else. ## Grants, and the lockout guard Grants are KMS's third authorization mechanism, and the one people forget in this discussion. A grant is a separate, temporary allowance created with `CreateGrant`, naming a `GranteePrincipal`, a list of `Operations`, an optional `RetiringPrincipal`, and optional constraints. AWS services use grants heavily to obtain scoped, revocable use of your key on your behalf. They are additive to the key policy, they can be retired or revoked individually, and they avoid rewriting a shared policy document for a short-lived need — but the key policy must still permit `kms:CreateGrant` for whoever creates one. Because the key policy is the root of authority, it is possible to write one that leaves nobody able to manage the key. KMS runs a lockout safety check on `CreateKey` and `PutKeyPolicy` that rejects such a policy, which you can override with `BypassPolicyLockoutSafetyCheck`. Overriding it on a production key is how a key becomes permanently unmanageable — there is no support path back.

  • What changes when the caller is a role in a different AWS account?
    Both sides must allow it. The key policy has to name the external principal (or its account) and the external principal's own IAM policy has to allow the KMS action on the key ARN. Neither side alone is sufficient, and the delegation-to-IAM statement only covers principals in the key's own account — it never reaches outward.
  • When would you use a grant rather than editing the key policy?
    When the access is temporary, programmatic, or per-workload: a grant is created with `CreateGrant`, names a grantee and a specific list of operations, can carry constraints, and can be retired or revoked on its own. That avoids serialising short-lived needs into a shared policy document that many teams edit and that has a size limit.
  • Can you lock yourself out of a KMS key with a bad key policy?
    Yes. KMS runs a lockout safety check on CreateKey and PutKeyPolicy that rejects a policy leaving nobody able to manage the key, but `BypassPolicyLockoutSafetyCheck` disables it. Bypassing it on a real key can leave the key permanently unmanageable — there is no recovery path, only scheduling the key for deletion if some principal can still do that.

saying these in an interview costs you the question

  • Assumes an IAM allow is sufficient inside one account
  • Thinks the key policy is optional like a bucket policy
  • Reads the root principal as the root user only
  • Deletes the default statement while 'tightening' the policy
  • Forgets cross-account access needs both sides to allow

context