Roles and trust¶
The module creates two IAM roles and grants each exactly one sensitive action. The split is load-bearing.
The counter-intuitive part¶
The obvious reading is that the reader role is the sensitive one — it decrypts the mail. That is half right, and the less important half.
| Role | Sensitive grant | What it can do |
|---|---|---|
| reader | kms:DeriveSharedSecret |
Read everything ever sent to this identity |
| certifier | kms:Sign on the primary |
Change which key future mail is encrypted to |
Signing with the certification primary can mint a new subkey binding signature, attaching a different encryption key to the same identity. Republish that certificate and every future message is encrypted to a key someone else holds — and the original encryption key is untouched, so nothing in KMS looks wrong.
That is a strictly larger capability than reading today's mail, and it does not
belong to whoever is on the reading rota this week. Hence
certifier_principal_arns should be a strict subset of
reader_principal_arns, or a dedicated break-glass principal.
Reader exposure is retroactive¶
Worth stating separately because it changes how you think about granting it.
A signing key compromise lets an attacker forge things going forward. An encryption key compromise exposes the entire history — every message ever encrypted to that key, including anything already archived. Rotating afterwards limits future exposure and does nothing about the past.
So reader access is not "can read new mail from today". It is "can read everything, ever". Grant it accordingly, and remove it promptly.
Why both roles can read public keys¶
Each key policy grants both roles kms:GetPublicKey and kms:DescribeKey,
while keeping the sensitive action to one role each.
This costs nothing — a public key is public by definition, and this one is published as part of the certificate — and it buys two things:
- The certifier needs it. Assembling a certificate means emitting a subkey
packet for the encryption key, which is impossible without its public half.
An earlier revision of this module granted each role read access only to its
own key, which produced a certifier that held
kms:Sign, looked correct, and could not perform the one operation it existed for. - The reader can verify the published certificate still matches the keys in KMS. That is how a rebind would be caught, and it should not require assuming the higher-privileged role to check.
No OIDC trust, anywhere¶
The sibling signing module grants its capability to a CI pipeline, because signing a release is something CI legitimately does. Receiving confidential mail is not.
A CI-assumable decrypt role would place the entire message history behind the
security of a build runner — and of every dependency that executes on one. The
module exposes no oidc_provider_arn input at all. Adding one should mean
re-opening the decision, not adding a variable.
MFA and federated sessions¶
The MFA condition lives in the roles' trust policies, never in a key policy. AWS documents the reason:
This condition key is not present for federated identities or requests made using access keys to sign AWS CLI, AWS API, or AWS SDK requests.
An IAM Identity Center session is a federated identity, so
aws:MultiFactorAuthPresent is absent rather than false. An Allow
carrying Bool: ... = true can never match it. In a key policy that would lock
out an SSO operator holding a perfectly good MFA'd session — at the exact
moment they are trying to read something urgent.
Setting reader_require_mfa = false for a federated principal is not a
weakening. Identity Center enforces MFA at sign-in; it simply is not
expressible as an IAM condition. The control moved upstream, it did not
disappear. (AWS suggests passing the authentication method as a session tag
instead — which needs an external IdP that supplies one. Identity Center's own
directory does not.)
Why the flags are per-role¶
The intended shape for an SSO estate is asymmetric:
| Role | Principal | Flag |
|---|---|---|
| reader | Identity Center permission set | reader_require_mfa = false |
| certifier | dedicated IAM role | certifier_require_mfa = true |
Routine decryption arrives through SSO, while the higher-privileged certification path keeps a hard IAM-level MFA gate. A single module-wide flag would have dragged the certifier down to whatever trust model the reader needed — exactly the wrong direction for the more dangerous capability.
No attached identity policies¶
Neither role has an attached IAM policy. The key policies are sufficient, and a second grant surface would only create the risk of role-policy / key-policy drift. Each role is single-purpose and needs exactly one KMS action, so a separate identity policy would be pure ceremony.
See also¶
- Key policies — the four principal classes.
- Use it with IAM Identity Center
- IAM permissions reference