137 lines
7 KiB
Django/Jinja
137 lines
7 KiB
Django/Jinja
# Managed by Ansible — BunkerWeb configuration.
|
|
# Reference: https://docs.bunkerweb.io/latest/settings/
|
|
|
|
# ─── Server identity ────────────────────────────────────────────
|
|
SERVER_NAME={{ morphit_domain }}
|
|
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/{{ morphit_domain }}/fullchain.pem
|
|
CUSTOM_SSL_KEY=/etc/letsencrypt/live/{{ morphit_domain }}/privkey.pem
|
|
|
|
# Redirect plain HTTP to HTTPS.
|
|
AUTO_REDIRECT_HTTP_TO_HTTPS=yes
|
|
|
|
# ─── Scheduler <-> instance API ─────────────────────────────────
|
|
# The scheduler runs in a SEPARATE container and must be told the
|
|
# instance by name; without BUNKERWEB_INSTANCES it falls back to
|
|
# Docker-socket autoconf. The instance's API defaults to trusting
|
|
# only 127.0.0.0/8, but the scheduler reaches it from another
|
|
# container on this network's bridge (172.20.0.0/16 per
|
|
# docker-compose.yml), so that CIDR MUST be in API_WHITELIST_IP or the
|
|
# instance resets every config push ("Sending nginx configs failed").
|
|
# Both were missing on the first real install (cp663 #9/#11).
|
|
BUNKERWEB_INSTANCES=bunkerweb
|
|
API_WHITELIST_IP=127.0.0.0/8 172.20.0.0/16
|
|
|
|
# ─── Upstream — everything goes to the morphit frontend container ─
|
|
# NOTE: validate this reverse-proxy wiring on a real host (it
|
|
# can't run in the project's CI sandbox) — see OPERATIONS.md §32.
|
|
# BunkerWeb proxies EVERY path to the `frontend` nginx container,
|
|
# which serves the SvelteKit static build AND reverse-proxies the
|
|
# API paths (/v1/, /relay/, /rss/, SSE) to the relay + indexer on the
|
|
# host. See ops/bunkerweb/frontend/nginx.conf (mirrors web.conf);
|
|
# the relay/indexer ports live there, 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
|
|
MODSECURITY_CRS_PARANOIA={{ bunkerweb_crs_paranoia }}
|
|
|
|
# ─── Rate limiting at the WAF layer — COARSE ceiling only; the
|
|
# indexer/relay enforce finer per-endpoint per-IP limits ───────
|
|
# Must sit ABOVE the indexer's own limits (120 r/m list / 600 r/m
|
|
# single-record) so the app's limiter binds first. The old 60 r/m
|
|
# (1 r/s) was tighter than a normal SvelteKit page-load burst of
|
|
# /v1/* calls, producing 429s that bad-behavior counted into IP bans
|
|
# (cp231). BAD_BEHAVIOR_STATUS_CODES below excludes 429 so a 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) ───────
|
|
# Count only genuinely-anomalous codes. The default set bans real
|
|
# users because it counts 429 (rate-limit burst), 403 (which is also
|
|
# the ban's OWN response → self-perpetuating ban), and 404 (normal
|
|
# PWA/SPA asset/manifest/icon probing). See OPERATIONS.md §38.6.
|
|
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
|
|
# (§38.6 item d) ────────────────────────────────────────────
|
|
USE_BLOCK_REFERRER_NONE=yes
|
|
BLOCK_REFERRER_NONE_URL=/relay/v1/account/invite
|
|
|
|
# ─── Country block (§38.6 item b) ───────────────────────────────
|
|
{% if bunkerweb_block_countries | length > 0 %}
|
|
BLACKLIST_COUNTRY={{ bunkerweb_block_countries | join(' ') }}
|
|
{% else %}
|
|
# No country blocks configured. Add ISO-3166-1 alpha-2 codes to
|
|
# bunkerweb_block_countries in group_vars/all.yml to enable.
|
|
{% endif %}
|
|
|
|
# ─── ASN block (§38.6 item c) ───────────────────────────────────
|
|
{% if bunkerweb_block_asns | length > 0 %}
|
|
BLACKLIST_ASN={{ bunkerweb_block_asns | join(' ') }}
|
|
{% else %}
|
|
# No ASN blocks configured.
|
|
{% endif %}
|
|
|
|
# ─── Real-IP forwarding ─────────────────────────────────────────
|
|
# BunkerWeb sets the real client as the leftmost X-Forwarded-For
|
|
# entry; requests pass BunkerWeb -> frontend -> relay and the
|
|
# frontend appends to the chain, so the relay reads the client from
|
|
# XFF[0]. The relay sees the FRONTEND container as its immediate
|
|
# peer, so its trusted-proxy IPs setting must include this network's
|
|
# CIDR (172.20.0.0/16 per docker-compose.yml) — one CIDR covers both
|
|
# 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
|
|
# NO global antibot (cp663 #12). BunkerWeb's USE_ANTIBOT is SITE-WIDE:
|
|
# it challenges EVERY visitor to the whole marketplace, and ANTIBOT_URI
|
|
# only sets where the challenge renders — pointing it at a live API path
|
|
# (/relay/v1/account/invite) both gated the entire site behind a captcha
|
|
# AND shadowed that real relay endpoint. The invite endpoint is already
|
|
# protected, correctly scoped, by USE_BLOCK_REFERRER_NONE above plus the
|
|
# relay's own ALTCHA challenge. If a deliberate site-wide gate is ever
|
|
# wanted, set ANTIBOT_URI to a DEDICATED path (e.g. /__antibot), never a
|
|
# live endpoint.
|
|
USE_ANTIBOT=no
|
|
{% if morphit_instance_tor_address | default('') | length > 0 %}
|
|
|
|
# ─── Onion-Location (cp700) ─────────────────────────────────────
|
|
# Advertise this instance's Tor onion to Tor-capable browsers via the
|
|
# Onion-Location HTTP HEADER. Brave reads ONLY the header — it ignores the
|
|
# <meta http-equiv="onion-location"> tag the frontend also emits — so without
|
|
# this the ".onion available" pill never appears in Brave. (Tor Browser honours
|
|
# the meta tag, so it already shows there; this adds Brave + anything else that
|
|
# is header-only.) Non-Tor browsers (Chrome/Firefox/Safari/Opera) have no Tor
|
|
# and cannot reach a .onion, so they simply ignore it — the footer pill / link
|
|
# is their discovery path.
|
|
#
|
|
# Clearnet-only BY CONSTRUCTION: the onion bypasses BunkerWeb entirely
|
|
# (Tor → frontend:8090, cp695), so BunkerWeb only ever serves clearnet — the
|
|
# spec's "must not be emitted on the onion itself / must be HTTPS clearnet"
|
|
# rules are satisfied without any host gating. $request_uri keeps the visitor
|
|
# on the same page once redirected onto the onion.
|
|
CUSTOM_HEADER=Onion-Location: http://{{ morphit_instance_tor_address }}$request_uri
|
|
{% endif %}
|