# Beveiliging Server Up beheert de Docker-daemon. Wie toegang heeft tot de webinterface, kan containers starten met willekeurige volumes — en daarmee in de praktijk alles op de host. Behandel toegang tot Server Up dus als root-toegang tot de server. Vanaf v0.5.00 is de interface standaard afgeschermd met een login. --- ## Eerste start Bij de eerste start is er nog geen account. Open de webinterface en maak er meteen een aan: **zolang dat niet gebeurd is, kan iedereen die de pagina bereikt het beheerdersaccount claimen.** De container laat daarom bij het opstarten een waarschuwing zien. Standaard bindt `docker-compose.yml` de poort op `127.0.0.1`, dus alleen vanaf de server zelf bereikbaar. Wil je er van buitenaf bij, zet dan een reverse proxy met TLS ervoor (zie onder) en pas `BIND` aan in je `.env`: ```bash cd /opt/docker/server-up # of /opt/server-up op prod cp .env.example .env # alleen de eerste keer sed -i 's/^BIND=.*/BIND=0.0.0.0/' .env docker compose up -d ``` `.env` staat in `.gitignore` en wordt door de deploy-workflow overgeslagen, dus je instellingen blijven staan bij een update. Alle beschikbare sleutels staan met uitleg in `.env.example`. ## Authenticatiemodi Instelbaar via `PUT /api/auth/mode`: | Modus | Betekenis | |-------|-----------| | `local` (standaard) | Gebruikersnaam + wachtwoord in Server Up zelf | | `proxy` | Identiteit komt uit een header van je reverse proxy; geen lokale login meer | | `both` | Beide; handig om SSO te testen zonder jezelf buiten te sluiten | Wachtwoorden worden opgeslagen als scrypt-hash (n=2¹⁴) met een willekeurige salt. Na vijf mislukte pogingen is het account vijf minuten geblokkeerd. ### SSO via een reverse proxy Draai je Authelia, Authentik of Cloudflare Access, dan kan die de identiteit in een header zetten (meestal `Remote-User`). Belangrijk: die header wordt **alleen** vertrouwd als het bron-IP in `trusted_proxies` staat. Zonder die lijst kan iedereen de header zelf meesturen en zich voordoen als beheerder. Server Up weigert daarom modus `proxy`/`both` als `trusted_proxies` leeg is. Zorg er ook voor dat je proxy de header van binnenkomende requests **wist** en zelf opnieuw zet. ## Reverse proxy met TLS Server Up spreekt gewoon HTTP. Zet er een proxy voor die TLS afhandelt: ```nginx location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Remote-User ""; # wis wat de client stuurt } ``` Zet `SU_HTTPS=1` in de omgeving zodra alles via TLS loopt; de sessiecookie krijgt dan de `Secure`-vlag. ## Wat er beveiligd is - **Sessies** — `HttpOnly`, `SameSite=Strict`, 12 uur geldig. - **CSRF** — elke POST/PUT/DELETE vereist de `X-CSRF-Token`-header. Routes die de toestand wijzigen accepteren geen GET meer. - **Padvalidatie** — stack-, instantie- en repo-namen worden gecontroleerd, zodat ze nooit buiten `LIBRARY_DIR` of de git-cache kunnen wijzen. - **Git-URL's** — alleen `http(s)://`, `ssh://` en `git@host:pad`. Git's `ext::`-transport voert een shell-commando uit en wordt geweigerd. - **Templates** — boilerplates renderen in een Jinja2-sandbox. - **Geheimen** — git-tokens worden nooit teruggegeven door de API (alleen een `has_token`-vlag); `config.json` en `secret.key` staan op 0600. - **Headers** — CSP op `'self'`, `X-Frame-Options: DENY`, `nosniff`, `Referrer-Policy: no-referrer`. Alle front-end libraries worden meegeleverd, dus er gaat op runtime niets naar een CDN. - **SSH** — host-keys worden geverifieerd (`accept-new`, opgeslagen in `/data/known_hosts`). ## Externe repo's en modules Een module is Python-code die Server Up **uitvoert**. Voeg alleen repo's toe die je vertrouwt. Twee knoppen om aan te draaien: - `AUTO_SYNC_ON_BOOT` staat standaard **uit**. Repo's worden dus niet vanzelf bijgewerkt; je synchroniseert zelf wanneer je dat wilt. - Zet per repo een `commit` in de config om hem op een specifieke commit vast te zetten: ```json {"id": "mijn-apps", "url": "https://…", "branch": "main", "commit": "a1b2c3d"} ``` ## Waar de wachtwoorden van je apps staan Vul je bij het installeren een wachtwoord of sleutel in, dan komt die waarde **niet** in het compose-bestand terecht maar in `.env` naast de stack: ```yaml # docker-compose.yml — te delen, te committen environment: - TZ=Europe/Amsterdam - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} ``` ```bash # .env — rechten 0600, alleen leesbaar voor de eigenaar POSTGRES_PASSWORD=aB3dE6gH9jK2mN5p ``` Ook `.serverup.json`, waarin de gemaakte keuzes staan zodat je ze later kunt wijzigen, bevat geen geheimen meer en krijgt `0600`. Tot v0.7.60 stonden wachtwoorden op allebei die plekken wereldleesbaar. **Wat dit wél oplost:** het compose-bestand is deelbaar en te committen, alle geheimen staan op één plek, en die plek is afgeschermd. **Wat dit níét oplost:** compose vult `${...}` in bij het inlezen, dus de container krijgt gewoon de letterlijke waarde en `docker inspect` toont hem nog steeds. Wie de docker-socket kan bereiken, kan de wachtwoorden van je apps lezen — maar die kan sowieso al elke container overnemen. Wil je een geheim écht buiten `docker inspect` houden, dan is `secrets:` van Compose de weg: de waarde staat dan in een bestand dat de container zelf inleest. Dat vereist wel dat het image een `*_FILE`-variant kent. Postgres, MariaDB en MySQL kunnen het; de meeste apps niet. Een externe wachtwoordkluis (Vault, Infisical) raden we hier af: Server Up zou dan afhangen van een app die het zelf beheert, en als die niet draait start er niets meer. ## Wat níét is afgedekt - **De docker-socket zelf.** Server Up heeft volledige toegang tot de daemon; dat is inherent aan wat het doet. Een socket-proxy die alleen bepaalde endpoints toelaat werkt niet, omdat compose vrijwel alles nodig heeft. - **Rate limiting op de API** buiten de login-lockout om. Zet er zo nodig een proxy met rate limiting voor. ## Een probleem melden Vind je een beveiligingsprobleem, meld het dan via de repository-issues met zo veel mogelijk details over de reproductie.