infra/region-host: route53 grant narrowed to ACME challenge TXTs; validate zone ids
2 files changed,
+17
−0
infra/modules/region-host/main.tf+13 −0infra/modules/region-host/variables.tf+4 −0
diff --git a/infra/modules/region-host/main.tf b/infra/modules/region-host/main.tf| index 8d378e90a..21afdcbca 100644 |
| --- a/infra/modules/region-host/main.tf |
| +++ b/infra/modules/region-host/main.tf |
| @@ -113,6 +113,19 @@ resource "aws_iam_role_policy" "host_route53" { |
| Effect = "Allow" |
| Action = ["route53:ChangeResourceRecordSets"] |
| Resource = [for z in var.route53_zone_ids : "arn:aws:route53:::hostedzone/${z}"] |
| + # The box may touch ONLY _acme-challenge TXT records, and only via the |
| + # two operations the edge issues (UPSERT in present, DELETE in cleanup). |
| + # Without these conditions a zone grant is the whole zone — apex, MX, |
| + # NS — and this policy is the only bound on the box's DNS authority. |
| + Condition = { |
| + "ForAllValues:StringEquals" = { |
| + "route53:ChangeResourceRecordSetsRecordTypes" = ["TXT"] |
| + "route53:ChangeResourceRecordSetsActions" = ["UPSERT", "DELETE"] |
| + } |
| + "ForAllValues:StringLike" = { |
| + "route53:ChangeResourceRecordSetsNormalizedRecordNames" = ["_acme-challenge.*"] |
| + } |
| + } |
| }, |
| { |
| Effect = "Allow" |
diff --git a/infra/modules/region-host/variables.tf b/infra/modules/region-host/variables.tf| index e73e783be..5003e4d35 100644 |
| --- a/infra/modules/region-host/variables.tf |
| +++ b/infra/modules/region-host/variables.tf |
| @@ -71,4 +71,8 @@ variable "route53_zone_ids" { |
| description = "Hosted-zone IDs the host may write ACME DNS-01 challenge records into (wildcard certs, CARLOS_DNS_PROVIDER=route53). Empty = no Route53 access." |
| type = list(string) |
| default = [] |
| + validation { |
| + condition = alltrue([for z in var.route53_zone_ids : can(regex("^Z[A-Z0-9]+$", z))]) |
| + error_message = "route53_zone_ids entries must be bare hosted-zone ids (Z...), not /hostedzone/-prefixed paths or wildcards." |
| + } |
| } |