Skip to content

Commit 6ded401

Browse files
authored
fix(iac/aws): grant kms:TagResource for KMS CreateKey tag-on-create (#1671)
CreateKey's Tags parameter requires kms:TagResource per the AWS API reference, but the only existing grant (KMSMutateTaggedOnly) is gated on aws:ResourceTag/Project, which can never be satisfied at create time since the key doesn't exist yet. Every AWS deploy that creates the OIDC signing KMS key was failing with AccessDeniedException. Add a separate, aws:RequestTag-gated statement, restricted via a Null condition on aws:ResourceTag/Project to resources with no Project tag yet, so it only ever performs the initial tag-on-create and can't be used to self-tag and hijack an existing, unrelated key through KMSMutateTaggedOnly. Closes #1670
1 parent 1dbff53 commit 6ded401

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

‎terraform/environments/aws/ci-cd-permissions/policy_compute_b.tf‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,44 @@
2828
# SA from enumerating key policies of unrelated workloads sharing the
2929
# account (account-wide key-policy reconnaissance). Split from the main
3030
# policy because the 6144-char limit was reached.
31+
#
32+
# - KMSTagOnCreate: kms:TagResource against a key that does NOT exist
33+
# yet (the CreateKey Tags parameter, per the AWS KMS API reference's
34+
# "Required permissions" for CreateKey) can never satisfy
35+
# aws:ResourceTag/Project: there is no resource, so no resource tag,
36+
# at authorization time. KMSMutateTaggedOnly's tag-on-EXISTING-key
37+
# grant (policy_compute.tf) therefore denies kms:TagResource during
38+
# aws_kms_key creation with Terraform's `tags` argument, breaking
39+
# every `terraform apply` that creates a new CMK (e.g. the OIDC
40+
# signing key in modules/compute/aws/lambda/signing-key.tf). AWS
41+
# authorizes tag-on-create via the aws:RequestTag/aws:TagKeys
42+
# condition keys instead, which inspect the tags being attached in
43+
# the request rather than tags already on the resource; see
44+
# "Controlling access during AWS requests" in the IAM user guide.
45+
# This MUST stay a separate statement: folding aws:RequestTag into
46+
# KMSMutateTaggedOnly would AND it with that statement's
47+
# aws:ResourceTag condition, which is exactly the unsatisfiable
48+
# combination this statement exists to avoid. Kept resource-unscoped
49+
# (Resource = "*") like KMSCreateAndRead/KMSReadTaggedOnly, since the
50+
# key ARN isn't known until CreateKey returns; the condition is what
51+
# limits it to CUDly-tagged create calls. StringEqualsIgnoreCase
52+
# matches the case-insensitive convention documented on
53+
# KMSMutateTaggedOnly (policy_compute.tf) for the same Project tag.
54+
# Added here (rather than policy_compute.tf) because the main policy
55+
# is within ~60 characters of the 6144-char limit; this statement
56+
# would not reliably fit.
57+
# aws:RequestTag alone would be an unintended privilege escalation:
58+
# it says nothing about the resource's CURRENT tags, so without more
59+
# it would let a leaked deploy token call kms:TagResource on ANY
60+
# existing key in the account (not just ones it creates), self-attach
61+
# Project=CUDly, and then use KMSMutateTaggedOnly to disable, delete,
62+
# or rewrite the key policy of that now-mistagged key, i.e. hijack a
63+
# key it never created. The added Null condition on
64+
# aws:ResourceTag/Project restricts the statement to resources with
65+
# NO Project tag yet (true for a brand-new key mid-CreateKey, false
66+
# for anything already tagged, ours or not), so it can only ever
67+
# perform the initial tag-on-create and can't be used to claim an
68+
# existing key.
3169

3270
resource "aws_iam_policy" "compute_b" {
3371
name = "cudly-deploy-compute-b"
@@ -97,6 +135,27 @@ resource "aws_iam_policy" "compute_b" {
97135
}
98136
}
99137
},
138+
{
139+
# kms:TagResource for the CreateKey Tags parameter. See the
140+
# KMSTagOnCreate note in the file header comment above for why
141+
# this must be a separate, aws:RequestTag-gated statement rather
142+
# than folded into KMSMutateTaggedOnly's aws:ResourceTag gate,
143+
# and why the Null condition on aws:ResourceTag/Project is
144+
# required to stop this from being usable to hijack an existing,
145+
# unrelated key by self-tagging it.
146+
Sid = "KMSTagOnCreate"
147+
Effect = "Allow"
148+
Action = ["kms:TagResource"]
149+
Resource = "*"
150+
Condition = {
151+
StringEqualsIgnoreCase = {
152+
"aws:RequestTag/Project" = "CUDly"
153+
}
154+
Null = {
155+
"aws:ResourceTag/Project" = "true"
156+
}
157+
}
158+
},
100159
]
101160
})
102161

0 commit comments

Comments
 (0)