server-up/docs/beveiliging.md
Ramon bc55252267
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m34s
v0.7.70-beta - invulmenu bij installeren, geheimen naar .env
Invulmenu:
- Vaste stappen (Basis, Verbinden, Instellingen, Toegang, Controleren) in
  plaats van één lange lijst. Eén stap per groep werkt niet: de mediane app
  heeft één groep, arr-stack eenentwintig. Lege stappen worden overgeslagen.
- Volgende controleert de verplichte velden van die stap; die melding kwam
  eerder pas bij het installeren.
- Geavanceerde opties achter één schakelaar die op elke stap zichtbaar is,
  plus 'Alles op één pagina' voor het oude gedrag. Beide onthouden.

Geheimen naar .env:
- compose_transform.geheimen_naar_env() vervangt geheime waarden door
  ${NAAM} en schrijft ze naar .env met rechten 0600. Werkt ook midden in
  een database-URL. Poorten en paden blijven leesbaar in compose.
- .serverup.json bevat geen geheimen meer en krijgt ook 0600; daar stonden
  ze wereldleesbaar in. Het herconfiguratieformulier leest ze terug uit
  .env via een bewaarde veld-naar-sleutel-koppeling.
- docs/beveiliging.md legt uit wat dit niet oplost: docker inspect toont de
  waarde nog steeds.

Bewerken:
- Laatste stap toont het gerenderde resultaat met jouw waarden en is
  bewerkbaar; compose_override/env_override gaan mee bij het installeren.
  Ongeldige YAML wordt geweigerd voordat er een map bestaat.
- De bestaande editor heeft tabbladen (compose/.env) en biedt herstarten na
  opslaan. De .env-PUT weigert een regel zonder '=' en logt in audit.

Inloggegevens:
- /api/stacks/<n>/credentials plus paneel op de stackkaart: adres,
  gebruikersnaam, wachtwoord achter een oog-knop, kopieerknop. Alleen voor
  beheerders, elk bekijken komt in het auditlog.
- Nieuw 'credential'-kenmerk in template.json met naamherkenning als
  terugval, zodat de bestaande 96 sjablonen meteen werken.

Herstel van een regressie uit v0.7.60: acht inlogwachtwoorden hadden hun
genereerknop verloren toen die aan 'generate' werd gehangen. Terug, behalve
waar een verzonnen waarde fout is (WireGuard-sleutel, externe tokens).

De menulogica wordt getest door de echte component uit index.html in Node uit
te voeren; slaat zichzelf over waar node ontbreekt. 2295 tests groen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-28 21:31:51 +02:00

6.2 KiB

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:

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:

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

  • SessiesHttpOnly, 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:

    {"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:

# docker-compose.yml — te delen, te committen
environment:
  - TZ=Europe/Amsterdam
  - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
# .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.