Skip to content

sec(iac/aws): deploy role grants account-wide kms:Decrypt and CreateGrant unconditioned #253

Description

@cristim

Summary

The KMS statement in the CI/CD deploy role's networking policy grants kms:CreateGrant, kms:Decrypt, kms:Encrypt, kms:GenerateDataKey and kms:DescribeKey on Resource = "*" with no Condition. Every other KMS grant in ci-cd-permissions/ is gated on an ARN prefix or on aws:ResourceTag/Project (KMSAliasMutate, KMSReadTaggedOnly, KMSTagOnCreate, KMSMutateTaggedOnly), and KMSReadTaggedOnly gates the far weaker kms:GetKeyPolicy explicitly to stop account-wide key reconnaissance. Because AWS CMKs carry the default key policy that delegates to IAM, this statement really does reach unrelated keys in the account. Blast radius is our own hosted AWS account, not customer accounts.

Location

terraform/environments/aws/ci-cd-permissions/policy_networking.tf:166 at 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (the KMS Sid spans lines 164-175)

Failure scenario

A leaked GitHub Actions OIDC token assumes cudly-terraform-deploy and calls kms:Decrypt against any CMK in the account, including keys belonging to unrelated workloads that share it. kms:CreateGrant lets it hand Decrypt on any CMK to a grantee principal it controls, and that grant survives the deploy role later being revoked.

Evidence

{
  Sid    = "KMS"
  Effect = "Allow"
  Action = [
    "kms:CreateGrant",
    "kms:Decrypt",
    "kms:DescribeKey",
    "kms:Encrypt",
    "kms:GenerateDataKey",
  ]
  Resource = "*"
}

Suggested fix

Add the same StringEqualsIgnoreCase condition on aws:ResourceTag/Project that KMSMutateTaggedOnly (policy_compute.tf:347-375) already uses, keeping only kms:DescribeKey unconditioned if a plan-time lookup needs it. Note the ACM (:139) and Route53 (:152) statements in the same file are also uncommented "*" grants, though those actions are read-mostly.


Found by the 2026-09-02 codebase audit, finding A13-004, reported by one reviewer and independently confirmed by a second. Full report: docs/audits/codebase-audit-2026-09-02.md.

Activity

  1. cristim commented on Sep 28, 2026

    @cristim
    MemberAuthor

    Verified against current main (7a541dae919249c6d360d432d1f1cdc6b34cef9e): fully fixed.

    The unconditioned Sid = "KMS" statement (kms:CreateGrant, kms:Decrypt, kms:Encrypt, kms:GenerateDataKey, kms:DescribeKey on Resource = "*") no longer exists in terraform/environments/aws/ci-cd-permissions/policy_networking.tf — the entire block was deleted rather than narrowed, since nothing on the deploy path consumes any of these actions (AWS-managed keys don't consult the caller's identity policy).

    Fixed by merged PR #4 (sec(iac/aws): drop unconditioned account-wide KMS data-plane grant from deploy role, merge commit 381d3c5cd6ac6a956dc541ad49ab007a8bbca2d0).

    A regression guard now exists: TestKMSDataPlaneActionsAreNotUnconditionallyGranted in terraform/environments/aws/ci-cd-permissions/policy_guard_test.go:1703, which asserts these actions are never granted without a aws:ResourceTag/Project condition (and kms:GrantIsForAWSResource for CreateGrant) across all five policy files.

    Closing as completed; no residual work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions