🐛 hetzner: remodel the HTTP-redirect check the API cannot satisfy as written#2821
🐛 hetzner: remodel the HTTP-redirect check the API cannot satisfy as written#2821tas50 wants to merge 2 commits into
Conversation
1ca8a8c to
f8303ec
Compare
f8303ec to
a24ef82
Compare
a24ef82 to
a025c80
Compare
a025c80 to
c45a3e1
Compare
c45a3e1 to
e072347
Compare
e072347 to
2513d74
Compare
…written - The redirect check was modeled backwards: Hetzner's redirect_http is a property of the HTTPS service, so the console step, CLI flag, and terraform example were all rejected by the API, and the runtime mql read redirect_http where the provider emits redirectHttp, an assertion that was never true. All four variants now assert no plain HTTP service plus redirect enabled on the HTTPS service, and the remediations follow that model - hcloud firewall delete-rule matches rules with deep equality, so the single-CIDR commands errored on rules listing both 0.0.0.0/0 and ::/0 and never cleared IPv6 rules; all four firewall fixes now describe first, add replacements before deleting, and warn that removing a firewall's only inbound rule blocks all traffic - Hetzner does support in-place rebuilds; the image check's claim otherwise is corrected with data-loss warnings, and the migration path now deletes the old server the check still counts - Certificate rotation reordered to create, repoint, then delete; the HTTPS-service fix uses update-service instead of a port-conflicting add-service; blocked-IP checks gain in-list comments for the CLI/terraform methods Hetzner's abuse team alone controls Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iants Equivalent logic to the disjunctive form but reads as the intent: evaluate the redirect block only on https services. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2513d74 to
73b4d30
Compare
| hetzner.loadBalancer.services.none(protocol == "http") | ||
| hetzner.loadBalancer.services.where(protocol == "https").all(http["redirectHttp"] == true) |
There was a problem hiding this comment.
🟡 warning — The MQL query now uses two separate statements: none(protocol == "http") and .where(protocol == "https").all(...). If a load balancer has zero HTTPS services (e.g., only TCP services), the .all() on an empty collection will vacuously pass, meaning the check succeeds even though there's no redirect configured. Consider whether you need a guard like services.where(protocol == "https").length > 0 or whether vacuous truth is the intended behavior for non-HTTP/HTTPS load balancers.
| terraform.resources("hcloud_load_balancer_service").all(arguments['protocol'] != "http") | ||
| terraform.resources("hcloud_load_balancer_service").where(arguments['protocol'] == "https").all( | ||
| blocks.where(type == "http").all(arguments['redirect_http'] == true) | ||
| ) | ||
| - uid: mondoo-hetzner-security-lb-http-redirected-to-https-terraform-plan |
There was a problem hiding this comment.
🟡 warning — Same vacuous-truth concern in the Terraform HCL variant: if no hcloud_load_balancer_service resources have protocol == "https", the .where(...).all(...) passes trivially. This is consistent with the other variants, but verify this is the desired behavior — a project with only non-HTTP services would pass this check silently.
| filters: asset.platform == "hetzner-loadbalancer" | ||
| mql: | | ||
| hetzner.loadBalancer.services.where(protocol == "http").all(http["redirect_http"] == true) | ||
| hetzner.loadBalancer.services.none(protocol == "http") |
There was a problem hiding this comment.
🔵 suggestion — The Hetzner loadbalancer query uses http["redirectHttp"] (camelCase) while the Terraform queries use http['redirect_http'] (snake_case). This is likely correct since they target different APIs (Hetzner API vs Terraform schema), but worth double-checking that the Hetzner API field is indeed redirectHttp and not redirect_http.
Part of the series reviewing every remediation in
content/for correctness (#2775, #2780–#2820). This PR coversmondoo-hetzner-security.mql.yaml: all 12 checks were reviewed and all 12 needed fixes. Policy version bumped. Verified against the hetzner provider schema and implementation, the Hetzner OpenAPI spec, the hcloud CLI source, and the terraform provider docs.A check modeled backwards from the API
lb-http-redirected-to-https treated the redirect as a property of the HTTP service. In Hetzner's API,
redirect_httpbelongs to the HTTPS service ("Only available if protocol is https"), and when enabled the load balancer answers port 80 itself. Consequences: the console step pointed at a control that cannot exist, the CLI command was rejected by the API, the terraform example failed to apply — and independently, the runtime mql readhttp[\"redirect_http\"]while the provider emits the camelCaseredirectHttp, an assertion that was never true. All four variants now assert the corrected model (no plain HTTP service, redirect enabled on the HTTPS service), and the remediation and audit follow it.Firewall deletions that error or miss IPv6
hcloud firewall delete-rulematches the stored rule with deep equality. The four firewall checks' commands passed--source-ips 0.0.0.0/0alone, which errors with "rule was not found" on the common rule listing both0.0.0.0/0and::/0, and never cleared IPv6-only rules even though the checks flag them. All four now inspect the rule first, reuse its exact values, add scoped replacements before deleting, and the all-ports fix warns that deleting a firewall's only inbound rule default-denies everything to the attached servers.Other fixes
hcloud server rebuilddo exactly that (with the disk-erase warning now attached), and the migration path now deletes the old server, without which the check keeps failing.add-serviceagainst an existing service, a port conflict; nowupdate-service.blockedflag is controlled solely by Hetzner's abuse team, replacing comments that sat above the checks.Left for maintainers
hcloud_server_network,hcloud_firewall_attachment, orapply_toattachment forms, so compliant configs using them false-fail.Validation
cnspec policy lintpasses (compiles the remodeled runtime and terraform variant mql)validate_remediation_commands.py hetzner: 46 passed, 0 failed🤖 Generated with Claude Code