Files
leerdoelen_tracker/SECURITY.md
Sam 07bcfede75
Some checks failed
Build, Push & Deploy / Build & Push image (push) Failing after 56s
Build, Push & Deploy / Deploy naar VPS (push) Has been skipped
Build & Push / Build & Push image (push) Successful in 1m2s
Add more security and audit
2026-02-28 14:47:33 +01:00

90 lines
3.4 KiB
Markdown

# Security beleid
## Kwetsbaarheden melden
Gevonden een beveiligingsprobleem? Stuur een e-mail naar de systeembeheerder van jouw scholengroep.
Voeg zo veel mogelijk detail toe: stappen om te reproduceren, impact, en eventueel een proof-of-concept.
Publiceer kwetsbaarheden **niet** publiek voordat ze zijn opgelost.
---
## Beveiligingsmaatregelen in deze applicatie
### Authenticatie
- Primaire login via Microsoft Entra ID (Azure AD) — geen wachtwoorden opgeslagen voor gewone gebruikers
- Superadmin wachtwoord gehasht met **scrypt** (sterk geheugenintensief algoritme)
- OAuth2 state parameter validatie — beschermt tegen CSRF in OAuth flow
- `?next=` redirect parameter gevalideerd — beschermt tegen open redirect aanvallen
- Session cookies: `HttpOnly`, `Secure` (HTTPS), `SameSite=Lax`
### Rate limiting
| Endpoint | Limiet |
|----------|--------|
| Alle `/auth/*` routes | 10 per minuut per IP |
| Superadmin login | 10/min + 30/uur per IP |
| API endpoints | 120 per minuut per IP |
| Doelen upload | 5 per minuut per IP |
| Setup endpoint | 5 per minuut per IP |
Rate limiting via **Redis** (persistent over meerdere workers).
Nginx voegt een extra laag rate limiting toe vóór Flask.
### HTTP Security headers
Via Flask-Talisman + Nginx:
- `Content-Security-Policy` — nonce-based, geen unsafe-inline scripts
- `Strict-Transport-Security` — HSTS 1 jaar, incl. subdomains
- `X-Frame-Options: DENY` — clickjacking preventie
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `Permissions-Policy` — geen toegang tot camera/microfoon/locatie
- `form-action: 'self'` — voorkomt form hijacking
- `base-uri: 'self'` — voorkomt base tag injection
- `object-src: 'none'` — geen Flash of plugins
### Autorisatie
- Rolgebaseerde toegangscontrole (superadmin → scholengroep_ict → school_ict → director → teacher)
- Elke API route heeft expliciete rolauthenticatie decorator
- School-isolatie: gebruikers kunnen enkel data van hun eigen school zien
- Auditlog van alle beheerhandelingen
### Database
- Parameterized queries via SQLAlchemy ORM — geen raw SQL met gebruikersinput
- Non-root database gebruiker
- PostgreSQL container niet publiek blootgesteld (intern Docker netwerk)
### Infrastructuur
- Flask draait als non-root gebruiker (`appuser`) in Docker container
- Read-only volume mount voor doelen JSON bestanden
- Redis beveiligd met wachtwoord
- Backend enkel bereikbaar via `127.0.0.1` (niet publiek)
- Nginx als reverse proxy met request size limiting en timeouts (Slowloris bescherming)
---
## Dependency updates
Controleer regelmatig op kwetsbaarheden in dependencies:
```bash
pip install pip-audit
pip-audit -r backend/requirements.txt
```
Python base image: pin op specifieke patch versie in `Dockerfile`.
Controleer updates op: https://hub.docker.com/_/python
---
## Checklist voor nieuwe deployment
- [ ] `SECRET_KEY` gegenereerd met `python3 -c "import secrets; print(secrets.token_hex(32))"`
- [ ] `POSTGRES_PASSWORD` sterk en uniek
- [ ] `REDIS_PASSWORD` ingesteld
- [ ] `BASE_URL` correct ingesteld op HTTPS URL
- [ ] SSL/TLS certificaat aanwezig (Let's Encrypt via Certbot)
- [ ] Microsoft Entra ID app registratie correct geconfigureerd
- [ ] Superadmin wachtwoord ingesteld via `/auth/setup` (min. 12 tekens)
- [ ] `/auth/setup` endpoint niet meer toegankelijk na setup (wordt automatisch geblokkeerd)
- [ ] Firewall: enkel poorten 80 en 443 publiek open