Skip to content

Key policies

Both KMS keys carry a policy that is the single source of permission grants. Neither IAM role has an attached identity policy, so there is exactly one place to read to know who can do what.

Four principal classes

Each policy grants the same four classes, mirroring the sibling signing module:

Class Gets Explicitly does not get
account root kms:* whatever the Deny statements below withhold
key_administrator_arns key lifecycle: describe, enable/disable, tag, aliases kms:Sign, kms:DeriveSharedSecret, kms:PutKeyPolicy
automation_role_arn manage the key as an OpenTofu resource kms:Sign, kms:DeriveSharedSecret
the reader / certifier role exactly one crypto action each the other one

The separation that matters most is the third row. The apply pipeline can create, tag and re-policy these keys, and cannot use them directly. Because it can re-policy them, a compromise able to act as the apply role could grant itself either crypto action, so the runner holding that role needs protecting as if it held the keys. No other principal can change a key policy.

Why account root keeps kms:*

This looks like an over-broad grant and is deliberate. It is AWS's recommended break-glass provision, and it is what guarantees the key can never become unmanageable. A key whose policy has locked out every administrator is unrecoverable in a way that no amount of care prevents.

It is scoped to the root principal specifically, rather than the whole account, which is a narrower grant than the AWS default "Enable IAM policies" statement.

The Deny statements

Each key carries two:

  • the key's one crypto operation (kms:Sign on the primary, kms:DeriveSharedSecret and kms:Decrypt on the subkey) is denied to every principal but its role;
  • kms:PutKeyPolicy is denied to every principal but the automation role.

They hold whatever any IAM policy in the account allows, and they bind the account root too.

Why aws:PrincipalArn, and not NotPrincipal. An earlier version of this page rejected Deny statements altogether, and the reasoning still holds for the form it considered. Deny with NotPrincipal binds a principal's unique ID: if the excepted role is ever deleted and recreated, the new role is a different principal, everything is denied, and the key is bricked.

These statements use Principal: "*" with an ArnNotEquals condition on aws:PrincipalArn instead. That compares the role's ARN as a string, so a role recreated under the same name and path is excepted again. Revised 2026-10-15.

Break-glass

The account root keeps kms:*, so the key can never be orphaned. Recovery goes through the automation role:

  • Policy changes come only from the automation role, through a reviewed plan and apply. Applying this module must run as that role. KMS refuses a policy that would stop its caller changing it again, so an apply under any other role fails rather than locking anyone out.
  • If the automation role is deleted, recreate it with the same name and path, and it can apply again.
  • If the reader or certifier role is deleted, recreate it the same way to restore decryption or certification.
  • Administrators can still disable the key, cancel a deletion, and manage aliases and tags without the automation role.

Why resources = "*"

Every statement uses resources = ["*"], which security scanners flag. In a key policy that wildcard resolves to the single key the policy is attached to. Key policies are resource-based, not identity-based, so there is no "arbitrary resources" reading available.

The module carries checkov:skip annotations with that justification inline rather than suppressing the checks globally, so a reviewer sees the reasoning at the statement rather than in a config file somewhere else.

Administrator deletion rights

kms:ScheduleKeyDeletion is withheld from administrators unless allow_administrator_key_deletion is set. AWS's named control for accidental key deletion is the key policy, and deleting an asymmetric encryption key is the one mistake here that cannot be undone.

kms:DeleteAlias is granted regardless, because aliases are cheap to recreate. kms:CancelKeyDeletion is always granted because cancelling is protective rather than destructive.

[!NOTE] Administrator actions are enumerated explicitly rather than by wildcard, and kms:PutKeyPolicy is not among them, so an administrator cannot rewrite the policy to grant themselves deletion. The sibling signing module's kms:Put* would have permitted exactly that; a wildcard granting the power to rewrite the grant is not a guardrail.

The control is still not absolute: the account root holds kms:*, which covers deletion, and the automation role can change the policy through a reviewed plan/apply.

A caveat about testing

These policies are reviewed but, at the time of writing, unexercised against real AWS. Nothing in the module's gate can catch a policy that is too narrow: tofu validate checks shape, and checkov and trivy check for grants that are too broad. A role that cannot perform the operation it exists for passes all three.

One such defect was found by reading rather than by testing (see roles and trust). Expect the first real use to be the first genuine exercise of these policies, and treat a permissions error as a likely module bug rather than a KMS limitation.

See also