Skip to content

Commit 5565f4a

Browse files
committed
feat: add HTTP port 80 support with Cloudflare-only firewall rules
1 parent 801ed1f commit 5565f4a

4 files changed

Lines changed: 57 additions & 3 deletions

File tree

deploy/ansible/roles/app/templates/docker-compose.yml.j2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ services:
33
image: haproxy:3.3.4-alpine3.23
44
container_name: rustyip-haproxy
55
ports:
6+
- "80:80"
67
- "443:443"
78
volumes:
89
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro

deploy/ansible/roles/app/templates/haproxy.cfg.j2

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ defaults
2626
timeout http-keep-alive 60s
2727
timeout queue 30s
2828

29-
frontend https_in
30-
bind *:443 ssl crt /etc/haproxy/certs/origin.pem alpn h2,http/1.1 strict-sni
29+
frontend http_in
30+
bind *:80
3131

3232
# Gzip compression
3333
compression algo gzip
3434
compression type text/html text/plain application/json text/css
3535

36-
# Access to port 443 is already restricted to Cloudflare IPs at the firewall level
36+
# Access to port 80 is restricted to Cloudflare IPs at the firewall level
3737
# All requests reaching HAProxy come from Cloudflare; no additional ACL validation is required.
3838

3939
# HAProxy's own forwardfor is not enabled; real IP is set explicitly below via CF-Connecting-IP
@@ -62,6 +62,40 @@ frontend https_in
6262

6363
default_backend app
6464

65+
frontend https_in
66+
bind *:443 ssl crt /etc/haproxy/certs/origin.pem alpn h2,http/1.1 strict-sni
67+
68+
# Gzip compression
69+
compression algo gzip
70+
compression type text/html text/plain application/json text/css
71+
72+
# Access to port 443 is restricted to Cloudflare IPs at the firewall level
73+
# All requests reaching HAProxy come from Cloudflare; no additional ACL validation is required.
74+
75+
# HAProxy's own forwardfor is not enabled; real IP is set explicitly below via CF-Connecting-IP
76+
77+
# Overwrite standard headers directly with the real IP forcibly added by Cloudflare
78+
http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)]
79+
http-request set-header X-Real-IP %[req.hdr(CF-Connecting-IP)]
80+
81+
# Rate limiting uses the shared stick-table from http_in frontend
82+
http-request track-sc0 req.hdr(CF-Connecting-IP) table http_in
83+
# Track global request rate (per second)
84+
http-request track-sc1 str(global) table global_rate_tracking
85+
86+
# Tier 3: global >= 200 RPS -> limit each IP to 5 RPM
87+
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 }
88+
# Tier 2: global >= 100 RPS -> limit each IP to 10 RPM
89+
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 }
90+
# Tier 1: default -> limit each IP to 30 RPM
91+
http-request deny deny_status 429 if { sc_http_req_rate(0,http_in) gt 30 }
92+
93+
# Block /health from external access
94+
acl is_health path /health
95+
http-request deny deny_status 403 if is_health
96+
97+
default_backend app
98+
6599
backend global_rate_tracking
66100
stick-table type string len 6 size 1 expire 10m store http_req_rate(1s)
67101

deploy/ansible/roles/base/tasks/main.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@
4545
proto: tcp
4646
comment: "SSH"
4747

48+
- name: Allow HTTP (port 80) through UFW
49+
community.general.ufw:
50+
rule: allow
51+
port: "80"
52+
proto: tcp
53+
comment: "HTTP"
54+
4855
- name: Allow HTTPS (port 443) through UFW
4956
community.general.ufw:
5057
rule: allow

deploy/terraform/firewall.tf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ resource "vultr_firewall_rule" "ssh_v4" {
1313
notes = "Allow SSH from anywhere"
1414
}
1515

16+
# Allow HTTP from Cloudflare only (IPv4)
17+
resource "vultr_firewall_rule" "http_cloudflare_v4" {
18+
firewall_group_id = vultr_firewall_group.rustyip.id
19+
protocol = "tcp"
20+
ip_type = "v4"
21+
subnet = "0.0.0.0"
22+
subnet_size = 0
23+
port = "80"
24+
source = "cloudflare"
25+
notes = "Allow HTTP from Cloudflare IPv4"
26+
}
27+
1628
# Allow HTTPS from Cloudflare only (IPv4)
1729
resource "vultr_firewall_rule" "https_cloudflare_v4" {
1830
firewall_group_id = vultr_firewall_group.rustyip.id

0 commit comments

Comments
 (0)