diff --git a/access/.tflint.d/policies/security.rego b/access/.tflint.d/policies/security.rego index 6d39036..75aa626 100644 --- a/access/.tflint.d/policies/security.rego +++ b/access/.tflint.d/policies/security.rego @@ -159,8 +159,29 @@ deny_boundary_required contains issue if { ) } -# Warning, because the estate declares no security groups yet, so the rule has -# no live coverage to ratchet against. +# Warning, because the estate declares no security groups of its own, so the +# rule has no live coverage to ratchet against. The second clause reads the +# inline ingress blocks of an aws_security_group, which is the shape +# terraform plan -generate-config-out writes for an adopted group (lesson +# 15), so an adopted group with an open port gets the warning the rule was +# written for rather than slipping past it on shape alone. +warn_no_open_ingress contains issue if { + some r in terraform.resources("aws_security_group", {"ingress": {"cidr_blocks": "list(string)", "ipv6_cidr_blocks": "list(string)", "from_port": "number", "to_port": "number"}}, {"expand_mode": "none"}) + some ing in r.config.ingress + some key in ["cidr_blocks", "ipv6_cidr_blocks"] + attr := ing.config[key] + not attr.unknown + some cidr in attr.value + cidr in open_cidrs + issue := tflint.issue( + sprintf( + "aws_security_group.%s opens %v to %s in an inline ingress block. Name the source security group, and declare the rule as its own aws_vpc_security_group_ingress_rule so it has an address a review can point at.", + [r.name, ing.config.from_port.value, cidr], + ), + attr.range, + ) +} + warn_no_open_ingress contains issue if { some r in terraform.resources("aws_vpc_security_group_ingress_rule", {"cidr_ipv4": "string", "cidr_ipv6": "string"}, {"expand_mode": "none"}) some key in ["cidr_ipv4", "cidr_ipv6"] diff --git a/access/README.md b/access/README.md index f5e8ff1..5cc2fad 100644 --- a/access/README.md +++ b/access/README.md @@ -24,7 +24,7 @@ access/ modules/ persona/ the four archetypes a principal file instantiates backends/ the two backend files, one of which is copied into an env - scripts/ backend, check, and the lesson 6 to 11 scripts below + scripts/ backend, check, and the lesson 6 to 15 scripts below codeowners.map team name to GitHub handle, the one place the two meet .tflint.d/ policies/ the rule pack, as Rego @@ -149,7 +149,7 @@ are Rego like the rest rather than a side script. | `no-iam-user-or-group` | error | any IAM user, group, access key or attachment to one | | `tag-owner-required` | error | a role, policy, bucket, registry or permission set with no `owner` tag | | `boundary-required` | error | a role, or a principal file that makes one, with no `permissions_boundary` | -| `no-open-ingress` | warning | an ingress rule naming `0.0.0.0/0` or `::/0` | +| `no-open-ingress` | warning | an ingress rule, or an inline `ingress` block on a group, naming `0.0.0.0/0` or `::/0` | | `sg-reference-not-cidr` | warning | an ingress rule naming a raw CIDR instead of a source group | | `trust-subject-pinned` | error | a federated trust with no subject condition, a subject matched by pattern, or a subject carrying a wildcard | | `trust-audience-pinned` | error | a federated trust with no `StringEquals` audience, or an OIDC provider that lists no client id or is not https | @@ -931,3 +931,61 @@ declared because Identity Center is read only live, folds in the rotation check, and closes with what it did not see. `access-review.yml` runs it quarterly against a Floci the job filled itself, which proves the shape and not a real account, and uploads the artifact for four hundred days. + +## Lesson 15, adopt in place + +A resource that existed before the repo comes under management one file at +a time, and nothing about it changes except the estate's own tags. + +``` +access/ + envs/prod/ + provider.tf ec2 joins the endpoint overrides for the first security group + .