All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 17m1s
Ik sprong met tientallen (0.7.90 → 0.8.00 → … → 0.9.00 → 0.10.00) en liep daarmee de reeks uit: dit schema rolt over bij .90, dus na 0.9.90 hoort 1.0.00 te komen. "0.10.00" bestond niet, en schond ook het formaat v0.0.00 uit de projectafspraken. De vijftien uitgaven van deze sessie zijn hernummerd naar 0.7.81 t/m 0.7.95, aaneengesloten. In één doorloop met een tabel, want 0.8.81 wordt 0.7.90 en 0.7.90 wordt 0.7.81 — achtereenvolgende vervangingen zouden elkaar overschrijven. Meegenomen: verwijzingen in de documentatie, .env.example, install.sh en de tests. Verkorte vormen als "vanaf v0.10" zijn vervangen door het volledige nummer, want die waren niet automatisch te herleiden. De commit-onderwerpen in de geschiedenis dragen nog de oude nummers; VERSION en CHANGELOG zijn leidend. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q9eqpADJSRs49SoGGr4NAy
226 lines
9.1 KiB
Markdown
226 lines
9.1 KiB
Markdown
# 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.
|
|
|
|
## Onder welk account Server Up draait
|
|
|
|
Tot v0.7.92 draaide Server Up als root. Vanaf v0.7.93 kies je bij de installatie
|
|
een account — standaard een nieuw systeemaccount `serverup` zonder shell en
|
|
zonder wachtwoord — en zakt de container daarnaartoe af.
|
|
|
|
```bash
|
|
sh install.sh --user serverup # aanbevolen, wordt zo nodig aangemaakt
|
|
sh install.sh --user ramon # een bestaand account
|
|
sh install.sh --user root # de oude situatie
|
|
```
|
|
|
|
Zonder `--user` wordt ernaar gevraagd, met een lijst van bruikbare accounts. De
|
|
keuze belandt als `SU_UID`/`SU_GID` in je `.env`. Een bestaande installatie
|
|
bijwerken met `--update` vraagt het één keer; daarna blijft je keuze staan.
|
|
|
|
### Waarom de container tóch als root start
|
|
|
|
Docker maakt het `su-data`-volume als root aan. Een container die meteen als een
|
|
gewone gebruiker start, kan daar niet in schrijven en komt niet eens tot zijn
|
|
configuratie. `docker-entrypoint.sh` begint daarom als root, zet `/data` en de
|
|
mappen `stacks` en `backups` klaar, en zakt dan af met `setpriv`.
|
|
|
|
`appdata` blijft daarbij bewust ongemoeid. Die mappen zijn van de apps zelf —
|
|
postgres draait als 999, een linuxserver-image als de PUID die jij hebt
|
|
ingevuld. Er een `chown` overheen halen breekt precies die containers.
|
|
|
|
### Wat het wel en niet oplevert
|
|
|
|
Het afgezakte proces houdt drie capabilities: `CHOWN`, `DAC_OVERRIDE` en
|
|
`FOWNER`. Die heeft het nodig om appdata van andere gebruikers in te pakken voor
|
|
een backup, en om het eigenaarschap goed te zetten bij het verhuizen van mappen
|
|
en het terugzetten van een backup. Alle drie zitten al in de standaardset van
|
|
Docker: de container krijgt dus geen enkel recht bij, er gaan er alleen af.
|
|
|
|
**Wees niet gerust op meer dan dit.** Met `DAC_OVERRIDE` kan het proces nog
|
|
steeds elk bestand onder de gekoppelde mappen lezen en schrijven, en het heeft
|
|
nog steeds de docker-socket. Wat je wint:
|
|
|
|
- bestanden die Server Up aanmaakt zijn van jouw account, niet van root
|
|
- een echte uid om op te filteren in systeemlogs
|
|
- geen poorten onder 1024, geen kernelmodules, geen systeemtijd, geen mount
|
|
|
|
Wat je **niet** wint: bescherming tegen iemand die Server Up overneemt. Die heeft
|
|
de socket, en dat blijft root op de host. Zie hieronder.
|
|
|
|
### Zet je account niet in de `docker`-groep
|
|
|
|
Dat lijkt handig maar geeft dat account root op de machine: wie bij de socket kan,
|
|
start een container die `/` mount. Het is ook niet nodig — het entrypoint leest de
|
|
gid van de socket zelf uit en maakt de gebruiker daar in de container lid van.
|
|
|
|
### Terug naar root
|
|
|
|
Loop je ergens tegenaan, zet dan in je `.env`:
|
|
|
|
```
|
|
SU_UID=0
|
|
SU_GID=0
|
|
```
|
|
|
|
en draai `docker compose up -d`. Alles werkt dan zoals vóór v0.7.93.
|
|
|
|
## 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 hier niet: het dóél van deze app is containers met willekeurige
|
|
bind mounts aanmaken, dus een proxy die `POST /containers/create` doorlaat laat
|
|
de ontsnapping gewoon door. Voor een monitoringtool als Traefik is zo'n proxy
|
|
wél zinvol; voor een Docker-beheerder niet.
|
|
- **Niet als root draaien lost dit niet op.** Zie hierboven: het beschermt tegen
|
|
bugs en ongelukken, niet tegen misbruik van de interface.
|
|
- **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.
|