A security scanner flagged a finding in my Terraform, and the correct response, the one I had to talk myself into, was to leave it exactly as it was. Not because the finding was wrong about what the code did. It was right. It’s that doing what it asked would have quietly bricked the account.
A finding that looks open and shut
I run checkov over the infrastructure as part of CI, and on the KMS key that protects the Terraform state bucket it raised CKV_AWS_111: a key policy that grants kms:* is overly permissive. On the face of it, unarguable. The policy says the account root can perform any KMS action on the key, with a resource of *. A wildcard action and a wildcard resource is the exact shape a scanner is built to shout about, and ninety-nine times in a hundred it’d be right to.
This was the hundredth time.
Why narrowing it bricks the key
Here’s the bit that turns the finding on its head. That kms:*-for-root statement isn’t an over-broad grant I left lying around. It’s the default key policy AWS itself applies, and it’s load-bearing in a way that’s easy to miss.
A KMS key is administered through its own key policy, and that policy is the only way in. Unlike most resources, IAM permissions elsewhere can’t grant access to a key whose policy doesn’t allow it. So if you “tighten” the key by removing root’s full control, and you don’t perfectly replace it with some other administrative principal, you can end up with a key that nobody can administer. Not you, not root, not a future you with a very good reason. KMS will not let you recover it. The key, and anything it encrypts, is stranded.
The kms:*-for-root statement is what keeps the account’s own root able to manage the key as a last resort. It’s not the vulnerability. It’s the escape hatch, and the scanner was asking me to weld it shut.
So the finding gets suppressed, out loud
The answer isn’t to silence the scanner globally, and it isn’t to obey it. It’s to suppress this finding, on this resource, with the reasoning written right there next to it, from modules/state-backend/main.tf:
data "aws_iam_policy_document" "kms" {
# checkov:skip=CKV_AWS_111:kms:* on the CMK for the account root is the
# AWS-documented pattern; narrowing it risks an unrecoverable lockout from
# the key. See https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html
statement {
sid = "AllowAccountRootAdmin"
effect = "Allow"
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${var.account_id}:root"]
}
actions = ["kms:*"]
resources = ["*"]
}
}
The skip carries a reason and a link to AWS’s own documentation of why this is the recommended default. That matters more than it looks. A bare # checkov:skip with no explanation is indistinguishable from laziness, and the next person to read it (quite possibly me, a year on) has no way to tell whether it was a considered decision or someone making a red mark go away. A skip with a documented reason is a decision you can audit. The finding is still visible in the sense that the suppression is right there in the code, attached to the thing it’s about, defensible out loud.
A scanner is an argument, not an order
The wider lesson is the one worth keeping, because it generalises well past this one key. A static-analysis finding is a prompt to think, not an instruction to comply with. Most of the time thinking leads you straight to “yes, fix it”, and you should. But a scanner encodes a general rule, and general rules meet specific contexts where they’re wrong, or merely irrelevant, or, in the rare and dangerous case, actively harmful to obey. kms:* for root on a customer-managed key is that last kind: the tool’s general rule (“wildcards are bad”) collides with a hard AWS-specific fact (“root must retain control of the key or it’s gone”).
The discipline that keeps this honest is the one in the code above. You don’t get to ignore a finding. You get to suppress it, scoped to the exact resource, with a reason a reviewer can weigh. Cheap enough that you’ll do it properly, costly enough that you won’t paper over a real finding by reflex.
The bottom line
checkov was right that the state-bucket key grants the account root kms:* on *. It was wrong that I should narrow it, because that statement is AWS’s documented default and the thing that stops the key becoming permanently unadministrable. The fix was a scoped checkov:skip carrying its reasoning and a link to the AWS docs, so the decision lives next to the code and can be defended rather than merely trusted.
Treat your scanner as a sharp, tireless colleague who’s usually right and occasionally, confidently, about to lock you out of your own key. Read every finding. Obey most of them. And write down, in the open, the rare one you mustn’t.
