50ab46dbe1
App layer (R1): bind frps NewProxy to token-owned subdomain (anti-hijack), default-deny unknown webhook ops, HMAC-verify stripe-stub billing webhook, enforce bandwidth quota kill-switch (Ping op), least-privilege table grants (migrations 0002/0003), GOTRUE_PASSWORD_MIN_LENGTH=12. Infra/net (R2): unpublish internal host ports (kong/pooler/analytics/frps-dash), read-only docker-socket-proxy for vector (no host breakout), on-demand-TLS allow-list authorizer, edge-block machine-only webhooks, no-new-privileges on custom containers. Secrets (R3): rotate Postgres password (all roles) + frps dashboard; replace predictable supavisor defaults; secrets externalized to gitignored .env. Med/Low (R4): security response headers (HSTS/XCTO/XFO/Referrer/Permissions/COOP), restrict frp proxy_type to http (no open relay), disable destructive redis commands, tighten frps.toml perms. No secrets committed; rotated values live only in gitignored .env files.
71 lines
2.1 KiB
Caddyfile
71 lines
2.1 KiB
Caddyfile
{
|
|
email office@linumiq.com
|
|
on_demand_tls {
|
|
# SECURITY (R2/F3): closed allow-list authorizer. The edge function returns
|
|
# 200 only for reserved hosts (apex/app/api) and subdomains registered in
|
|
# the tunnels table; 403 otherwise. This prevents unbounded on-demand
|
|
# certificate issuance for arbitrary hostnames.
|
|
ask http://supabase-edge-functions:9000/check-subdomain
|
|
}
|
|
}
|
|
|
|
# SECURITY (R4/F10/W5): baseline response-hardening headers applied to the
|
|
# LinumIQ-controlled surfaces (apex/app/api). HSTS forces HTTPS for a year and
|
|
# is safe for first-party hostnames we fully control.
|
|
(security_headers) {
|
|
header {
|
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
|
X-Content-Type-Options "nosniff"
|
|
X-Frame-Options "SAMEORIGIN"
|
|
Referrer-Policy "no-referrer"
|
|
Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
|
Cross-Origin-Opener-Policy "same-origin"
|
|
-Server
|
|
-X-Powered-By
|
|
}
|
|
}
|
|
|
|
# Apex -> dashboard redirect
|
|
linumiq.net {
|
|
tls {
|
|
on_demand
|
|
}
|
|
import security_headers
|
|
redir https://app.linumiq.net{uri} permanent
|
|
}
|
|
|
|
# Reserved hostname: Next.js dashboard (upstream not yet running in Wave A)
|
|
app.linumiq.net {
|
|
tls {
|
|
on_demand
|
|
}
|
|
import security_headers
|
|
reverse_proxy web:3000
|
|
}
|
|
|
|
# Reserved hostname: Supabase API (Kong)
|
|
api.linumiq.net {
|
|
tls {
|
|
on_demand
|
|
}
|
|
import security_headers
|
|
# SECURITY (R2): block machine-only webhook functions from the public edge.
|
|
# auth-webhook and stripe-webhook are invoked internally over the docker
|
|
# network (supabase-edge-functions:9000) and must never be callable from the
|
|
# internet. get-node stays public for the Home Assistant add-on.
|
|
@blocked_webhooks path /functions/v1/auth-webhook* /functions/v1/stripe-webhook*
|
|
respond @blocked_webhooks 403
|
|
reverse_proxy supabase-kong:8000
|
|
}
|
|
|
|
# Wildcard tunnel subdomains -> frps vhost HTTP. Per-name HTTP-01 issued on first hit.
|
|
# NOTE: only HSTS is injected here; Home Assistant sets its own security headers
|
|
# (X-Frame-Options, etc.) and we must not override its CSP / framing behaviour.
|
|
*.linumiq.net {
|
|
tls {
|
|
on_demand
|
|
}
|
|
header Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
|
reverse_proxy frps:7080
|
|
}
|