From 98215682787607d1045b914a11cf26f4b3af120e Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Sun, 4 Oct 2026 17:15:34 -0600 Subject: [PATCH 1/4] feat(opencode): preflight response-header rules before scoped HSTS --- cf-opencode-hsts.tf | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 cf-opencode-hsts.tf diff --git a/cf-opencode-hsts.tf b/cf-opencode-hsts.tf new file mode 100644 index 0000000..dd86d2e --- /dev/null +++ b/cf-opencode-hsts.tf @@ -0,0 +1,26 @@ +data "cloudflare_rulesets" "opencode_hsts_preflight" { + zone_id = local.zone_id + max_items = 1000 + + lifecycle { + postcondition { + condition = length(self.rulesets) < 1000 + error_message = "Ruleset discovery reached its limit; stop and review the complete inventory before adding HSTS." + } + postcondition { + condition = length([ + for ruleset in self.rulesets : ruleset.id + if ruleset.kind == "zone" && ruleset.phase == "http_response_headers_transform" + ]) == 0 + error_message = "An existing response-header ruleset requires ownership review before adding OpenCode HSTS." + } + } +} + +output "opencode_hsts_existing_response_header_rulesets" { + description = "Preflight count of existing zone response-header rulesets; must be zero before HSTS creation." + value = length([ + for ruleset in data.cloudflare_rulesets.opencode_hsts_preflight.rulesets : ruleset.id + if ruleset.kind == "zone" && ruleset.phase == "http_response_headers_transform" + ]) +} From d49bfb594a83866ce0b166c9c890f9b6ec661748 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:16:44 +0000 Subject: [PATCH 2/4] chore: apply pre-commit fixes --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a75ab7a..e994f34 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ No modules. | [cloudflare_zone_setting.polish](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone_setting) | resource | | [cloudflare_zone_setting.rocket_loader](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone_setting) | resource | | [cloudflare_zone_setting.xnoto_dev_ssl](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone_setting) | resource | +| [cloudflare_rulesets.opencode_hsts_preflight](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/rulesets) | data source | | [cloudflare_zone.makeitwork_cloud](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/zone) | data source | | [sops_file.secret_vars](https://registry.terraform.io/providers/carlpett/sops/latest/docs/data-sources/file) | data source | @@ -73,6 +74,7 @@ No inputs. | [hero\_host\_config\_warp\_service\_token\_client\_secret](#output\_hero\_host\_config\_warp\_service\_token\_client\_secret) | Cloudflare Access service-token client secret for hero-host-config WARP enrollment | | [mcp\_gateway\_service\_token\_client\_id](#output\_mcp\_gateway\_service\_token\_client\_id) | CF-Access-Client-Id for MCP gateway clients | | [mcp\_gateway\_service\_token\_client\_secret](#output\_mcp\_gateway\_service\_token\_client\_secret) | CF-Access-Client-Secret for MCP gateway clients | +| [opencode\_hsts\_existing\_response\_header\_rulesets](#output\_opencode\_hsts\_existing\_response\_header\_rulesets) | Preflight count of existing zone response-header rulesets; must be zero before HSTS creation. | | [tunnel\_ids](#output\_tunnel\_ids) | Cloudflare Tunnel IDs for reference in kustomize-cluster ConfigMaps | From 01898d7896da98517bc89f58cc4e2f47954b1f5d Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Sun, 4 Oct 2026 17:53:17 -0600 Subject: [PATCH 3/4] feat(opencode): add hostname-only one-day HSTS response header --- README.md | 24 ++++++++++++++++++++++++ cf-opencode-hsts.tf | 45 ++++++++++++++++++++++----------------------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index e994f34..b61bf2a 100644 --- a/README.md +++ b/README.md @@ -103,3 +103,27 @@ This root owns only the bootstrap tunnel DNS required to reach the Kubernetes API before cluster workloads are available. `TunnelBinding` owns workload tunnel DNS, including the MCP gateway endpoints. Do not add a workload hostname to this OpenTofu root. + +## OpenCode HSTS + +`cf-opencode-hsts.tf` configures the zone response-header ruleset with a rule +matching only HTTPS requests to `opencode.makeitwork.cloud`. It sets +`Strict-Transport-Security: max-age=86400` (one day), without +`includeSubDomains` or `preload`. Native OpenCode authentication and existing +HTTPS redirection are unchanged. No Cloudflare Access, workload DNS/tunnel, +or AWX cleanup is included. + +This root owns the response-header entrypoint. Review and preserve its full +rule list before adding other header rules; do not create a competing +entrypoint or adopt an existing one without reviewing its rules. PR CI plans +validate the proposed changes; `main` runs a fresh environment-associated +apply, not the saved PR plan. Merge and apply authorization are separate. + +After approved apply, verify the HTTPS UI and unauthenticated API responses +carry the header, native phone login and session continuity still work, and +sibling hostnames are unaffected. A successful plan is not live verification. + +For rollback, first serve `max-age=0` over HTTPS through this rule, then remove +it after the rollback has been reviewed and applied. Simply removing the +header does not clear browser policies; they otherwise expire up to one day +after their last receipt. Preserve HTTPS throughout. diff --git a/cf-opencode-hsts.tf b/cf-opencode-hsts.tf index dd86d2e..b142597 100644 --- a/cf-opencode-hsts.tf +++ b/cf-opencode-hsts.tf @@ -1,26 +1,25 @@ -data "cloudflare_rulesets" "opencode_hsts_preflight" { - zone_id = local.zone_id - max_items = 1000 +resource "cloudflare_ruleset" "response_headers" { + zone_id = local.zone_id + name = "Response header transforms" + description = "Hostname-scoped response security headers" + kind = "zone" + phase = "http_response_headers_transform" - lifecycle { - postcondition { - condition = length(self.rulesets) < 1000 - error_message = "Ruleset discovery reached its limit; stop and review the complete inventory before adding HSTS." + rules = [ + { + ref = "opencode_hsts" + action = "rewrite" + action_parameters = { + headers = { + "strict-transport-security" = { + operation = "set" + value = "max-age=86400" + } + } + } + expression = "(http.host eq \"opencode.makeitwork.cloud\" and http.request.scheme eq \"https\")" + description = "One-day HSTS for the OpenCode HTTPS hostname only" + enabled = true } - postcondition { - condition = length([ - for ruleset in self.rulesets : ruleset.id - if ruleset.kind == "zone" && ruleset.phase == "http_response_headers_transform" - ]) == 0 - error_message = "An existing response-header ruleset requires ownership review before adding OpenCode HSTS." - } - } -} - -output "opencode_hsts_existing_response_header_rulesets" { - description = "Preflight count of existing zone response-header rulesets; must be zero before HSTS creation." - value = length([ - for ruleset in data.cloudflare_rulesets.opencode_hsts_preflight.rulesets : ruleset.id - if ruleset.kind == "zone" && ruleset.phase == "http_response_headers_transform" - ]) + ] } From 47a13e2fd8690f474e840f6b97172718b5c05eac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:53:50 +0000 Subject: [PATCH 4/4] chore: apply pre-commit fixes --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index b61bf2a..4413118 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ No modules. | [cloudflare_dns_record.xnoto_dev_root](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/dns_record) | resource | | [cloudflare_dns_record.xnoto_dev_www](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/dns_record) | resource | | [cloudflare_ruleset.cache_rules](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) | resource | +| [cloudflare_ruleset.response_headers](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) | resource | | [cloudflare_zero_trust_access_application.alertmanager](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_application) | resource | | [cloudflare_zero_trust_access_application.grafana_alerts](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_application) | resource | | [cloudflare_zero_trust_access_application.k3s](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_application) | resource | @@ -58,7 +59,6 @@ No modules. | [cloudflare_zone_setting.polish](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone_setting) | resource | | [cloudflare_zone_setting.rocket_loader](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone_setting) | resource | | [cloudflare_zone_setting.xnoto_dev_ssl](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone_setting) | resource | -| [cloudflare_rulesets.opencode_hsts_preflight](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/rulesets) | data source | | [cloudflare_zone.makeitwork_cloud](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/zone) | data source | | [sops_file.secret_vars](https://registry.terraform.io/providers/carlpett/sops/latest/docs/data-sources/file) | data source | @@ -74,7 +74,6 @@ No inputs. | [hero\_host\_config\_warp\_service\_token\_client\_secret](#output\_hero\_host\_config\_warp\_service\_token\_client\_secret) | Cloudflare Access service-token client secret for hero-host-config WARP enrollment | | [mcp\_gateway\_service\_token\_client\_id](#output\_mcp\_gateway\_service\_token\_client\_id) | CF-Access-Client-Id for MCP gateway clients | | [mcp\_gateway\_service\_token\_client\_secret](#output\_mcp\_gateway\_service\_token\_client\_secret) | CF-Access-Client-Secret for MCP gateway clients | -| [opencode\_hsts\_existing\_response\_header\_rulesets](#output\_opencode\_hsts\_existing\_response\_header\_rulesets) | Preflight count of existing zone response-header rulesets; must be zero before HSTS creation. | | [tunnel\_ids](#output\_tunnel\_ids) | Cloudflare Tunnel IDs for reference in kustomize-cluster ConfigMaps |