-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCaddyfile.prod
More file actions
136 lines (120 loc) · 5.52 KB
/
Caddyfile.prod
File metadata and controls
136 lines (120 loc) · 5.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# Primary hostname. Fronted by Cloudflare (free plan). Caddy issues a
# Let's Encrypt cert so origin pull happens over a properly-signed HTTPS
# connection. Caddy must NOT overwrite X-Forwarded-For or strip
# CF-Connecting-IP here -- both carry the real client IP that the app's
# rate-limiter keys on. Stripping them collapses every user behind a
# Cloudflare POP into one shared bucket key.
#
# Forgery-resistance: a client cannot bypass Cloudflare and reach this
# vhost directly because Cloudflare DNS is the only published A record
# for api.github-store.org. Direct-IP requests with a forged Host header
# would still be routed to this vhost, but Caddy's TLS handshake against
# api.github-store.org's cert pins the hostname and Cloudflare's edge is
# the only path that produces a CF-Connecting-IP / cf-ray. The app trusts
# CF-Connecting-IP only when the request actually traversed Cloudflare;
# the api-direct vhost below strips it as defence-in-depth.
api.github-store.org {
# Belt-and-suspenders against bodies that bypass the app's
# Content-Length-based pre-check (chunked transfer-encoding).
request_body {
max_size 1MB
}
reverse_proxy app:8080
header {
-Server
X-Content-Type-Options nosniff
X-Frame-Options DENY
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Content-Security-Policy "default-src 'none'"
Referrer-Policy "no-referrer"
Permissions-Policy "interest-cohort=()"
}
log {
output stdout
}
}
# Direct-to-origin fallback hostname. Bypasses any CDN — points straight at
# the Hetzner IP. Used as:
# (a) diagnostic when something's wrong with the CDN path,
# (b) client-side fallback when the CDN-fronted primary times out
# (valuable for users on networks where Cloudflare/Gcore IPs are
# throttled, e.g. some Chinese ISPs),
# (c) origin for the Gcore CDN itself, so Gcore can pull over HTTPS
# with a properly-issued cert instead of accepting a self-signed one.
api-direct.github-store.org {
# Overwrite any client-supplied X-Forwarded-For with the real TCP source.
# Without this a client who forges X-Forwarded-For can rotate past the
# app's rate-limiter (which keys on X-Forwarded-For's first IP).
request_header X-Forwarded-For {remote_host}
# Strip any client-supplied CF-Connecting-IP. See the rationale on the
# primary site above — same attack surface, same fix.
request_header -CF-Connecting-IP
# Belt-and-suspenders against bodies that bypass the app's
# Content-Length-based pre-check (chunked transfer-encoding).
request_body {
max_size 1MB
}
reverse_proxy app:8080
header {
-Server
X-Content-Type-Options nosniff
X-Frame-Options DENY
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Content-Security-Policy "default-src 'none'"
Referrer-Policy "no-referrer"
Permissions-Policy "interest-cohort=()"
}
log {
output stdout
}
}
# Komi Store paid backend. Hosted on the same VPS, separate Docker service
# (paid-app), separate Postgres database (komistore_paid). Will eventually
# absorb the github-store.org hostnames above once the free-tier migration
# lands; for now the two run side-by-side under distinct vhosts so DNS,
# Stripe webhook URLs, and Cloudflare KV bindings stay decoupled.
#
# DNS prereq: api.komistore.app must resolve to this VPS for Caddy's
# HTTP-01 challenge to issue a Let's Encrypt cert. Either:
# (a) DNS-only (grey-cloud) A record -> VPS IP, OR
# (b) Cloudflare proxied + SSL mode "Full (strict)" + Cloudflare origin
# cert OR DNS-01 challenge configured in Caddy.
# Start with (a) until traffic justifies fronting.
api.komistore.app {
# Direct-to-VPS path (grey-cloud DNS). Overwrite any client-supplied
# X-Forwarded-For with the real TCP source — without this a client who
# forges X-Forwarded-For can rotate past paid-app's rate limiter
# (which keys on X-Forwarded-For's first IP). Same defence as
# api-direct.github-store.org above.
request_header X-Forwarded-For {remote_host}
# Strip any client-supplied CF-Connecting-IP. No Cloudflare hop on this
# vhost yet (grey-cloud); accepting the header would let any client
# claim any source IP. Re-evaluate if/when this vhost goes orange-cloud.
request_header -CF-Connecting-IP
# Body cap. Stripe webhooks can be ~10 KB; checkout payloads are tiny.
# 1 MB matches the free-tier ceiling -- bumps need a written reason.
request_body {
max_size 1MB
}
reverse_proxy paid-app:8080
header {
-Server
X-Content-Type-Options nosniff
X-Frame-Options DENY
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# Looser than the API-only CSP above because the paid backend
# serves /legal HTML pages (terms, privacy) alongside JSON.
# default-src 'none' would block same-origin assets the legal
# pages load (linked stylesheets, images). Note that 'self' does
# NOT permit inline <style>/<script> blocks — those require an
# explicit 'unsafe-inline' in style-src/script-src or a nonce.
# The /legal templates are kept inline-asset-free for that reason.
# frame-ancestors stays locked to prevent clickjack.
Content-Security-Policy "default-src 'self'; frame-ancestors 'none'"
Referrer-Policy "no-referrer"
Permissions-Policy "interest-cohort=()"
}
log {
output stdout
}
}