diff --git a/BEHEER.md b/BEHEER.md index e1516c3..8b27398 100644 --- a/BEHEER.md +++ b/BEHEER.md @@ -335,6 +335,40 @@ dan is het antwoord `no configuration file provided: not found`. Met Pullen zelf werkt anoniem zolang de organisatie public is — zie [Pullen op een deploy-machine](#pullen-op-een-deploy-machine). +### De scan meldt 46 CVE's op postgres:18-alpine + +Dat klopt, en het blijft zo. Ze zitten allemaal in één bestand: + +```bash +# Alle bevindingen komen uit /usr/local/bin/gosu, niets uit de Alpine-pakketten +docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + aquasec/trivy image --scanners vuln postgres:18-alpine +``` + +`gosu` is een klein Go-programma dat het postgres-entrypoint gebruikt om van +root naar de `postgres`-gebruiker te schakelen. Upstream compileert het met Go +1.24.6, en alle meldingen zijn CVE's in die Go-standaardbibliotheek. Je kunt er +niets aan verhelpen met een `pull`: dit ís de nieuwste versie. + +Wat wél is gedaan: beide compose-bestanden starten de container met +`user: "postgres"`. Het entrypoint roept gosu alleen aan als het als root start +(`if [ "$(id -u)" = '0' ]`), dus die tak wordt overgeslagen en de binary draait +nooit. De CVE's zijn daarmee onbereikbaar — de scanner kijkt naar wat aanwezig +is, niet naar wat uitgevoerd wordt. + +> Dit werkt ook op een volume dat ooit als root is aangemaakt: de +> data-directory is dan al eigendom van postgres. Getest met een bestaande +> database; die start en de data blijft intact. + +**Waarom geen eigen image zonder gosu.** Dat kan (`FROM postgres:18-alpine` + +`RUN rm -f /usr/local/bin/gosu`) en zet de scan op nul, maar je ruilt een +onbereikbaar risico voor een echt risico: je moet dat image dan zelf herbouwen +bij elke postgres-patch. Precies die achterstand was het probleem toen het image +vier maanden op 18.4 stond met 143 bevindingen. Met het officiële image haalt +`docker compose pull` de patches vanzelf op, en zodra upstream gosu met een +nieuwere Go herbouwt is het gratis opgelost. `test.ps1` waarschuwt daarom bij +stap 4 als `postgres:18-alpine` in de registry nieuwer is dan wat je lokaal hebt. + ### PostgreSQL versie-upgrade Twee soorten, met een belangrijk verschil. Kijk eerst welke je voor je hebt: diff --git a/Changelog.md b/Changelog.md index caa0dcc..f3248a6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,27 @@ ## [Niet vrijgegeven] +### Beveiliging +- **De postgres-container draait niet meer als root.** Beide compose-bestanden + starten hem met `user: "postgres"`. Alle 46 bevindingen op `postgres:18-alpine` + zitten in één bestand — `/usr/local/bin/gosu`, dat upstream met Go 1.24.6 + compileert — en het entrypoint roept gosu alleen aan als het als root start. + Non-root starten slaat die tak over, dus de binary draait nooit meer. De + Alpine-pakketten in dat image zijn zelf schoon +- Getest op een bestaande database die ooit als root was aangemaakt: die start + als non-root en de data blijft intact. Ook een verse database initialiseert + gewoon +- Bewust géén eigen postgres-image gebouwd om die binary te verwijderen. Dat zou + de scan op nul zetten maar je verplichten het image zelf te herbouwen bij elke + postgres-patch — precies de achterstand die het image vier maanden op 18.4 met + 143 bevindingen hield. De afweging staat in `BEHEER.md`, zodat de volgende + scan niet opnieuw tot dezelfde vraag leidt +- `test.ps1` waarschuwt bij stap 4 wanneer `postgres:18-alpine` in de registry + nieuwer is dan wat er lokaal staat. Die achterstand is het werkelijke risico; + het getal in de scan is dat niet + +## [1.0.11] - 2026-09-18 + ### Beveiliging - **PostgreSQL 18.4 → 18.6.** Het `postgres:18-alpine` image dat in gebruik was dateerde van vier maanden geleden. Dat is een patch binnen dezelfde major, dus diff --git a/README.md b/README.md index 4d4445e..197c0ac 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Eigen vermogen · Schulden · Voortgang · Gebruikersbeheer ![Node.js](https://img.shields.io/badge/Node.js_22-339933?style=flat&logo=node.js&logoColor=white) ![PostgreSQL](https://img.shields.io/badge/PostgreSQL_18-4169E1?style=flat&logo=postgresql&logoColor=white) ![Docker](https://img.shields.io/badge/Docker_Compose-2496ED?style=flat&logo=docker&logoColor=white) -![Version](https://img.shields.io/badge/versie-1.0.10-8b5cf6?style=flat) +![Version](https://img.shields.io/badge/versie-1.0.11-8b5cf6?style=flat) ![Built with Claude](https://img.shields.io/badge/Gebouwd_met-Claude.ai-8b5cf6?style=flat&logo=anthropic&logoColor=white) VibeFinance dashboard diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9c8cefc..16548fc 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -15,6 +15,9 @@ services: postgres: image: postgres:18-alpine container_name: vibefinance-postgres-dev + # Zie docker-compose.yml: non-root starten omzeilt gosu, waar alle CVE's + # van dit image in zitten. + user: "postgres" environment: POSTGRES_DB: ${POSTGRES_DB:-vibefinance} POSTGRES_USER: ${POSTGRES_USER:-vibefinance} diff --git a/docker-compose.yml b/docker-compose.yml index 2dbd546..2433d4a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,13 @@ services: image: postgres:18-alpine container_name: vibefinance_postgres restart: unless-stopped + # Meteen als postgres draaien in plaats van als root. Het entrypoint schakelt + # anders zelf terug met `gosu`, en juist die binary bevat alle 46 CVE's van + # dit image (Go stdlib 1.24.6, meegecompileerd door upstream). Start de + # container al als postgres, dan slaat het entrypoint die tak over en wordt + # gosu nooit uitgevoerd. Werkt ook op een volume dat ooit als root is + # aangemaakt — de data-directory is dan al van postgres. + user: "postgres" environment: POSTGRES_DB: ${POSTGRES_DB:-vibefinance} POSTGRES_USER: ${POSTGRES_USER:-vibefinance} diff --git a/frontend/package.json b/frontend/package.json index 1ac9ac4..1efd3e7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance-frontend", - "version": "1.0.11-dev", + "version": "1.0.12-dev", "private": true, "type": "module", "scripts": { diff --git a/package.json b/package.json index 63e1177..f819aa8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance", - "version": "1.0.11", + "version": "1.0.12", "description": "VibeFinance — persoonlijk vermogensbeheer", "private": true, "repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance", diff --git a/test.ps1 b/test.ps1 index 39776ac..e277cb0 100644 --- a/test.ps1 +++ b/test.ps1 @@ -124,6 +124,23 @@ if (-not $SkipScan) { exit 1 } Write-Host "Imagescan schoon ($ernst)" -ForegroundColor Green + + # Het postgres-image scannen we niet als gate: alle bevindingen daarin zitten + # in /usr/local/bin/gosu, en die binary wordt nooit uitgevoerd omdat de + # compose-bestanden de container al als `postgres` starten (zie BEHEER.md). + # Wat wél misgaat is achterstand: het image stond vier maanden op 18.4 met + # 143 bevindingen omdat niemand pullde. Daar waarschuwen we voor — falen zou + # onterecht zijn, want een release van de app hangt er niet van af. + $pgTag = "postgres:18-alpine" + $lokaal = (docker image inspect $pgTag --format '{{index .RepoDigests 0}}' 2>$null) + if ($lokaal) { + $remote = (docker buildx imagetools inspect $pgTag --format '{{.Manifest.Digest}}' 2>$null) + if ($remote -and $lokaal -notlike "*$remote*") { + Write-Host "Let op: $pgTag is nieuwer in de registry — draai 'docker compose pull postgres'." -ForegroundColor Yellow + } else { + Write-Host "$pgTag is actueel" -ForegroundColor Green + } + } } else { Write-Step "[4/5] Imagescan overgeslagen (-SkipScan)" }