177 lines
9.4 KiB
Text
177 lines
9.4 KiB
Text
# Morphit — canonical BunkerWeb environment file.
|
|
#
|
|
# Copy to /etc/bunkerweb/bunkerweb.env and edit the DUMMY-VALUE
|
|
# entries before bringing the compose up.
|
|
#
|
|
# Reference: https://docs.bunkerweb.io/latest/settings/
|
|
|
|
# ─── Server identity ────────────────────────────────────────────
|
|
|
|
# DUMMY-VALUE: your instance's public domain (the one with the
|
|
# Let's Encrypt cert).
|
|
SERVER_NAME=morphit.example.com
|
|
|
|
SERVER_TYPE=http
|
|
MULTISITE=no
|
|
|
|
# ─── TLS (host-mounted Let's Encrypt cert) ──────────────────────
|
|
USE_LETS_ENCRYPT=no
|
|
USE_CUSTOM_SSL=yes
|
|
CUSTOM_SSL_CERT=/etc/letsencrypt/live/${SERVER_NAME}/fullchain.pem
|
|
CUSTOM_SSL_KEY=/etc/letsencrypt/live/${SERVER_NAME}/privkey.pem
|
|
|
|
# Redirect plain HTTP to HTTPS.
|
|
AUTO_REDIRECT_HTTP_TO_HTTPS=yes
|
|
|
|
# ─── Upstream — everything goes to the morphit frontend container ─
|
|
# NOTE: BunkerWeb can't be exercised in the project's CI sandbox,
|
|
# so validate this reverse-proxy wiring on a real host (hit the
|
|
# site root, then /relay/v1/health, /v1/instance,
|
|
# /rss/orderbook.xml) before relying on it. See OPERATIONS.md §32.
|
|
#
|
|
# BunkerWeb terminates TLS + runs the WAF, then proxies EVERY path
|
|
# to the `frontend` nginx container (docker-compose.yml). That
|
|
# container serves the SvelteKit static build for page routes AND
|
|
# reverse-proxies the API paths (/v1/, /relay/, /rss/, and the
|
|
# SSE .../stream paths) to the relay + indexer on the host — see
|
|
# ops/bunkerweb/frontend/nginx.conf, which mirrors ops/nginx/web.conf.
|
|
# Routing static-serving + SPA fallback + per-path proxy + SSE in one
|
|
# nginx is far easier to get right (and matches the bare-metal
|
|
# topology) than expressing all of it in BunkerWeb env vars, so the
|
|
# only reverse-proxy setting BunkerWeb needs is the single upstream.
|
|
# The relay/indexer ports live in frontend/nginx.conf (relay 8080,
|
|
# indexer 8081 — the morphit defaults), not here.
|
|
USE_REVERSE_PROXY=yes
|
|
REVERSE_PROXY_HOST=http://frontend:80
|
|
|
|
# ─── WAF: OWASP CRS ─────────────────────────────────────────────
|
|
USE_MODSECURITY=yes
|
|
USE_MODSECURITY_CRS=yes
|
|
MODSECURITY_CRS_VERSION=4
|
|
|
|
# Paranoia 3 is the sweet spot for a public API. Drop to 2 if
|
|
# you see real-user false positives in rejection logs. Raise
|
|
# to 4 only if you verify it doesn't break legitimate traffic.
|
|
MODSECURITY_CRS_PARANOIA=3
|
|
|
|
# ─── Rate limiting at the WAF layer ─────────────────────────────
|
|
# COARSE edge ceiling ONLY — the indexer and relay enforce their own
|
|
# finer per-endpoint, per-IP limits (the indexer documents 120 r/m for
|
|
# list endpoints and 600 r/m for single-record lookups; both operator-
|
|
# tunable). The WAF limit must sit ABOVE those so the app's limiter is
|
|
# the binding one and the WAF only catches egregious abuse.
|
|
#
|
|
# WHY THIS IS GENEROUS (cp231): a single SvelteKit page load fires many
|
|
# /v1/* calls near-simultaneously (/v1/instance, /v1/orderbook,
|
|
# /v1/orderbook/featured, /v1/listing-fee, /v1/chain-fee, /v1/instances,
|
|
# /v1/release, plus the SSE stream). A tight limit here (the old 60 r/m
|
|
# = 1 r/s) made that normal burst overflow into 429s, which BunkerWeb's
|
|
# bad-behavior counted toward an IP ban — so ordinary browsing banned
|
|
# real users. Keep /v1/ well above the indexer's own ceiling, and note
|
|
# the BAD_BEHAVIOR_STATUS_CODES below deliberately exclude 429 so a
|
|
# transient burst can never escalate to a ban.
|
|
USE_LIMIT_REQ=yes
|
|
LIMIT_REQ_URL_1=/v1/
|
|
LIMIT_REQ_RATE_1=1800r/m
|
|
LIMIT_REQ_URL_2=/relay/
|
|
LIMIT_REQ_RATE_2=120r/m
|
|
|
|
# ─── Bad-behavior bans: SPA/PWA + public-API safe (cp231) ───────
|
|
# BunkerWeb's bad-behavior counts "bad" response codes per IP and bans
|
|
# the IP when the count crosses the threshold inside the window. Its
|
|
# DEFAULT counted set (400 401 403 404 405 429 444) is wrong for a site
|
|
# that is a SPA + PWA + public read API + RSS + SSE:
|
|
# • 429 — the rate limiter's own response; counting it turns a normal
|
|
# asset/API burst into a ban (the cp231 incident).
|
|
# • 403 — also the response BunkerWeb returns to an already-banned IP,
|
|
# so counting it makes a ban SELF-PERPETUATE: every blocked request
|
|
# re-arms the counter and the IP never recovers until it goes fully
|
|
# silent for the whole window.
|
|
# • 404 — a PWA/SPA legitimately probes for assets, the manifest,
|
|
# icons, service-worker scopes, etc.; transient 404s are normal.
|
|
# So we count only the genuinely-anomalous codes and leave a generous
|
|
# threshold. This keeps bad-behavior useful against real abuse without
|
|
# banning ordinary visitors or trapping them in a self-feeding ban.
|
|
USE_BAD_BEHAVIOR=yes
|
|
BAD_BEHAVIOR_STATUS_CODES=400 401 405 444
|
|
BAD_BEHAVIOR_THRESHOLD=50
|
|
BAD_BEHAVIOR_COUNT_TIME=60
|
|
BAD_BEHAVIOR_BAN_TIME=3600
|
|
|
|
# ─── Anti-bot: referer-none rule on the invite endpoint ─────────
|
|
# Per OPERATIONS.md §38.6 item d. Browsers send a Referer
|
|
# header; bot scripts often don't. This filters the lazy
|
|
# curl-based squatter tier (real attackers bypass with `-H
|
|
# 'Referer: https://x.example/'`, but lazy ones don't).
|
|
USE_BLOCK_REFERRER_NONE=yes
|
|
BLOCK_REFERRER_NONE_URL=/relay/v1/account/invite
|
|
|
|
# ─── Country block (§38.6 item b) ───────────────────────────────
|
|
# Empty by default. Populate ONLY under active attack — Morphit
|
|
# serves global users and country-blocking is ethically fraught.
|
|
# ISO-3166-1 alpha-2 codes, space-separated.
|
|
# BLACKLIST_COUNTRY=
|
|
|
|
# ─── ASN block (§38.6 item c) ───────────────────────────────────
|
|
# Empty by default. Uncomment + populate based on YOUR rejection
|
|
# logs (§38.2). Cheap-VPS provider ASNs are over-represented in
|
|
# squatter traffic because they're cheap and don't scrutinize
|
|
# signups. Real users almost never connect from these ASNs.
|
|
# Verify against your own logs before enabling — false positives
|
|
# DO exist (rare, but they do).
|
|
# BLACKLIST_ASN=AS14061 AS24940 AS16276
|
|
# AS14061 — DigitalOcean
|
|
# AS24940 — Hetzner
|
|
# AS16276 — OVH
|
|
|
|
# ─── Real-IP forwarding ─────────────────────────────────────────
|
|
# BunkerWeb terminates TLS and sets the real client as the leftmost
|
|
# X-Forwarded-For entry. Requests then pass BunkerWeb -> frontend
|
|
# -> relay, and the frontend APPENDS to X-Forwarded-For (keeping the
|
|
# client leftmost), so the relay reads the client from XFF[0]. The
|
|
# relay sees the FRONTEND container as its immediate peer, so the
|
|
# relay's MORPHIT_RELAY_TRUSTED_PROXY_IPS must include this Docker
|
|
# network's CIDR (172.20.0.0/16 per docker-compose.yml) — that one
|
|
# CIDR covers BOTH the BunkerWeb and the frontend containers.
|
|
USE_REAL_IP=yes
|
|
REAL_IP_FROM=0.0.0.0/0
|
|
REAL_IP_HEADER=X-Forwarded-For
|
|
|
|
# ─── Generic hardening ──────────────────────────────────────────
|
|
DISABLE_DEFAULT_SERVER=yes
|
|
SERVER_TOKENS=off
|
|
USE_GZIP=yes
|
|
USE_BROTLI=yes
|
|
|
|
# Active-bot challenge on the invite endpoint. CAPTCHA blocks
|
|
# headless bots without WebGL/canvas; real users see it once
|
|
# per session.
|
|
USE_ANTIBOT=captcha
|
|
ANTIBOT_URI=/relay/v1/account/invite
|
|
|
|
# ─── Security headers ───────────────────────────────────────────
|
|
# In this topology BunkerWeb OWNS the response security headers (the
|
|
# frontend nginx.conf sets none). These mirror ops/nginx/web.conf so
|
|
# both deploy paths deliver the identical policy — keep them in sync.
|
|
#
|
|
# CONTENT_SECURITY_POLICY is the one you MUST set: BunkerWeb's default
|
|
# is `default-src 'self'`, which blocks the in-browser WASM crypto
|
|
# (argon2 KDF / signing) and the inline bootstrap — the site loads but
|
|
# login / keystore-unlock silently breaks. See docs/OPERATIONS.md §15
|
|
# for the directive-by-directive rationale. connect-src lists the six
|
|
# default Blurt RPC nodes (apps/web/src/lib/net/config.ts); edit to
|
|
# match if you run a different RPC set. NO price API is listed — the
|
|
# browser never calls one (price is server-side), so adding it would
|
|
# only leak visitor IPs.
|
|
CONTENT_SECURITY_POLICY=default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self' https://rpc.drakernoise.com https://blurtrpc.dagobert.uk https://rpc.blurt.blog https://rpc.beblurt.com https://rpc.blurt.one https://blurt-rpc.saboin.com; media-src 'none'; object-src 'none'; child-src 'none'; frame-src 'none'; worker-src 'self' blob:; manifest-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'self'
|
|
# no-referrer matches Morphit's privacy posture; BunkerWeb's default
|
|
# (strict-origin-when-cross-origin) leaks the origin cross-site.
|
|
REFERRER_POLICY=no-referrer
|
|
# DENY mirrors web.conf. The CSP's frame-ancestors 'none' enforces the
|
|
# same thing; this is the legacy belt-and-suspenders header.
|
|
X_FRAME_OPTIONS=DENY
|
|
# Microphone/geolocation/FLoC off; camera=(self) so the same-origin
|
|
# QR-login scanner (getUserMedia) keeps working — camera=() would
|
|
# break it. Delivered as a real header (the app.html <meta> form,
|
|
# now removed, was ignored by browsers).
|
|
PERMISSIONS_POLICY=camera=(self), microphone=(), geolocation=(), interest-cohort=()
|