CluPilotCloud/docker/nginx/default.conf

111 lines
4.5 KiB
Plaintext

# Hostnames that are reachable from the public internet. The operator console
# (/admin) must NEVER be served on these — only the marketing site and the
# customer portal are public; Proxmox hosts and /admin stay on the private
# network / VPN.
#
# Deliberately a denylist, not an allowlist: a typo in an allowlist would lock
# the operators out of their own console. The app-level allowlist (ADMIN_HOSTS,
# see config/admin_access.php) is the strict layer — this one guarantees that a
# request arriving on a known-public hostname never even reaches PHP.
map $host $admin_host_is_public {
default 0;
www.dev.clupilot.com 1;
app.dev.clupilot.com 1;
api.dev.clupilot.com 1;
ws.dev.clupilot.com 1;
www.clupilot.com 1;
app.clupilot.com 1;
api.clupilot.com 1;
ws.clupilot.com 1;
}
server {
listen 80 default_server;
server_name _;
root /var/www/html/public;
index index.php;
charset utf-8;
client_max_body_size 64M;
# ^~ beats the regex locations, so this decides before PHP is reached. The
# try_files rewrite to /index.php re-runs location matching, so the console
# still executes normally on a permitted host.
#
# This only covers direct hits: Livewire posts component actions to
# /livewire/update, which nginx cannot attribute to /admin. That case is
# handled in the app (RestrictAdminHost is registered as Livewire-persistent
# middleware) — do not treat this block as the only control.
location ^~ /admin {
if ($admin_host_is_public) {
return 404;
}
try_files $uri $uri/ /index.php?$query_string;
}
# Der einzige Pfad zur Terminal-Bruecke. Bewusst eng: kein Praefix-Match
# auf /terminal, sondern genau dieser eine Ort.
#
# AN DER WURZEL, nicht unter AdminArea::prefix(): nginx kann die Konfiguration
# der Anwendung nicht lesen, dieser Ort muss also ein fester Text sein — und
# er ist ohnehin keine Konsolenseite, sondern eine Steckdose, die PHP nie
# sieht. resources/js/terminal.js verbindet aus demselben Grund fest hierher.
#
# ACHTUNG: dieser Pfad laeuft NICHT durch PHP, also greift
# RestrictConsoleNetwork hier nicht. Der Riegel ist allein das Ticket —
# einmalig, dreissig Sekunden, an einen Host und einen Betreiber gebunden.
# So steht es auch in der Spec. Das Ticket steht deshalb NICHT in der
# Adresszeile, sondern in `Sec-WebSocket-Protocol`: die Adresse eines
# Upgrade-Antrags landet im Zugriffsprotokoll, der Kopf nicht.
#
# `terminal` ist ein Netz-Alias von queue-provisioning (siehe
# docker-compose.yml): die Bruecke steht im Tunnel und teilt sich dessen
# Netz-Namensraum.
location = /terminal/ws {
# Dieselbe Regel wie bei /admin darueber: die Bruecke ist Werkzeug der
# Konsole und hat auf einem oeffentlichen Namen nichts zu suchen.
if ($admin_host_is_public) {
return 404;
}
# Aufgeloest zur Laufzeit, nicht beim Start. nginx loest einen festen
# Namen in `proxy_pass` beim Laden der Konfiguration auf und WEIGERT
# SICH ZU STARTEN, wenn er ihn nicht findet ("host not found in
# upstream"). Damit haengte die ganze Konsole daran, dass die
# Terminal-Bruecke schon laeuft — wegen eines Fensters, das gerade
# niemand offen hat. Ueber eine Variable mit `resolver` (Dockers
# eingebauter DNS) wird daraus ein 502 an genau dieser Stelle, waehrend
# alles andere weiterlaeuft.
resolver 127.0.0.11 valid=10s ipv6=off;
set $terminal_upstream terminal:8082;
proxy_pass http://$terminal_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 900s;
proxy_send_timeout 900s;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
# robots.txt is generated by the app (it changes with the site's visibility),
# so it must NOT be short-circuited to a file on disk.
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
location ~ /\.(?!well-known).* {
deny all;
}
}