From 5565f4a0a5ce2fe2603319b7a5f46e82e6e34afa Mon Sep 17 00:00:00 2001 From: NetworkCats Date: Sun, 8 Mar 2026 05:52:42 +0800 Subject: [PATCH] feat: add HTTP port 80 support with Cloudflare-only firewall rules --- .../roles/app/templates/docker-compose.yml.j2 | 1 + .../roles/app/templates/haproxy.cfg.j2 | 40 +++++++++++++++++-- deploy/ansible/roles/base/tasks/main.yml | 7 ++++ deploy/terraform/firewall.tf | 12 ++++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/deploy/ansible/roles/app/templates/docker-compose.yml.j2 b/deploy/ansible/roles/app/templates/docker-compose.yml.j2 index ca8b997..23f50eb 100644 --- a/deploy/ansible/roles/app/templates/docker-compose.yml.j2 +++ b/deploy/ansible/roles/app/templates/docker-compose.yml.j2 @@ -3,6 +3,7 @@ services: image: haproxy:3.3.4-alpine3.23 container_name: rustyip-haproxy ports: + - "80:80" - "443:443" volumes: - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro diff --git a/deploy/ansible/roles/app/templates/haproxy.cfg.j2 b/deploy/ansible/roles/app/templates/haproxy.cfg.j2 index 73e83c0..b145774 100644 --- a/deploy/ansible/roles/app/templates/haproxy.cfg.j2 +++ b/deploy/ansible/roles/app/templates/haproxy.cfg.j2 @@ -26,14 +26,14 @@ defaults timeout http-keep-alive 60s timeout queue 30s -frontend https_in - bind *:443 ssl crt /etc/haproxy/certs/origin.pem alpn h2,http/1.1 strict-sni +frontend http_in + bind *:80 # Gzip compression compression algo gzip compression type text/html text/plain application/json text/css - # Access to port 443 is already restricted to Cloudflare IPs at the firewall level + # Access to port 80 is restricted to Cloudflare IPs at the firewall level # All requests reaching HAProxy come from Cloudflare; no additional ACL validation is required. # HAProxy's own forwardfor is not enabled; real IP is set explicitly below via CF-Connecting-IP @@ -62,6 +62,40 @@ frontend https_in default_backend app +frontend https_in + bind *:443 ssl crt /etc/haproxy/certs/origin.pem alpn h2,http/1.1 strict-sni + + # Gzip compression + compression algo gzip + compression type text/html text/plain application/json text/css + + # Access to port 443 is restricted to Cloudflare IPs at the firewall level + # All requests reaching HAProxy come from Cloudflare; no additional ACL validation is required. + + # HAProxy's own forwardfor is not enabled; real IP is set explicitly below via CF-Connecting-IP + + # Overwrite standard headers directly with the real IP forcibly added by Cloudflare + http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)] + http-request set-header X-Real-IP %[req.hdr(CF-Connecting-IP)] + + # Rate limiting uses the shared stick-table from http_in frontend + http-request track-sc0 req.hdr(CF-Connecting-IP) table http_in + # Track global request rate (per second) + http-request track-sc1 str(global) table global_rate_tracking + + # Tier 3: global >= 200 RPS -> limit each IP to 5 RPM + http-request deny deny_status 429 if { sc_http_req_rate(1,global_rate_tracking) ge 200 } { sc_http_req_rate(0,http_in) gt 5 } + # Tier 2: global >= 100 RPS -> limit each IP to 10 RPM + http-request deny deny_status 429 if { sc_http_req_rate(1,global_rate_tracking) ge 100 } { sc_http_req_rate(0,http_in) gt 10 } + # Tier 1: default -> limit each IP to 30 RPM + http-request deny deny_status 429 if { sc_http_req_rate(0,http_in) gt 30 } + + # Block /health from external access + acl is_health path /health + http-request deny deny_status 403 if is_health + + default_backend app + backend global_rate_tracking stick-table type string len 6 size 1 expire 10m store http_req_rate(1s) diff --git a/deploy/ansible/roles/base/tasks/main.yml b/deploy/ansible/roles/base/tasks/main.yml index 1c1b818..d778984 100644 --- a/deploy/ansible/roles/base/tasks/main.yml +++ b/deploy/ansible/roles/base/tasks/main.yml @@ -45,6 +45,13 @@ proto: tcp comment: "SSH" +- name: Allow HTTP (port 80) through UFW + community.general.ufw: + rule: allow + port: "80" + proto: tcp + comment: "HTTP" + - name: Allow HTTPS (port 443) through UFW community.general.ufw: rule: allow diff --git a/deploy/terraform/firewall.tf b/deploy/terraform/firewall.tf index 8c5d026..703870f 100644 --- a/deploy/terraform/firewall.tf +++ b/deploy/terraform/firewall.tf @@ -13,6 +13,18 @@ resource "vultr_firewall_rule" "ssh_v4" { notes = "Allow SSH from anywhere" } +# Allow HTTP from Cloudflare only (IPv4) +resource "vultr_firewall_rule" "http_cloudflare_v4" { + firewall_group_id = vultr_firewall_group.rustyip.id + protocol = "tcp" + ip_type = "v4" + subnet = "0.0.0.0" + subnet_size = 0 + port = "80" + source = "cloudflare" + notes = "Allow HTTP from Cloudflare IPv4" +} + # Allow HTTPS from Cloudflare only (IPv4) resource "vultr_firewall_rule" "https_cloudflare_v4" { firewall_group_id = vultr_firewall_group.rustyip.id