4.3 KiB
4.3 KiB
Security Review - March 2026
Web-Facing Attack Surface
CRITICAL
1. Plaintext credentials committed to git
- Bitwarden admin token, SMTP passwords, DB passwords, Redis password, and system user password are all in plaintext
- Files:
k8s/core/bitwarden/configmaps.yml,k8s/nextcloud/values.yml,Ansible/roles/system/defaults/main.yml - Fix: Use Kubernetes Secrets with sealed-secrets or external-secrets-operator. Use Ansible Vault for Ansible vars.
2. Cloudflare Tunnel disables TLS verification
noTLSVerify: trueink8s/core/cloudflare-tunnel/cloudflared.yml— allows MITM between Cloudflare and your cluster- Internal traffic flows over plain HTTP (
http://192.168.178.55:80)
3. Bitwarden public signups enabled FIXED
SIGNUPS_ALLOWED: "true"— anyone on the internet can create an account on your password manager- Fixed 2026-03-14: Set
SIGNUPS_ALLOWED: "false"
4. No authentication middleware on any public ingress
- Jellyfin, Gitea, JNR-Web are accessible without any auth gate
- No OAuth2 proxy, no basic auth, no SSO configured
HIGH
5. Staging TLS certificates on public services FIXED
Bitwarden, Gitea, Jellyfin, JNR-Web all useletsencrypt-staging- Fixed 2026-03-14: All public services switched to
letsencrypt-prodwith DNS01 solver via Cloudflare - Also fixed typo
letsencrpyt-stagingin Gitea ingress - Cloudflare API token stored as K8s secret
cloudflare-api-tokenincert-managernamespace
6. No security headers FIXED
MissingStrict-Transport-Security,X-Frame-Options,X-Content-Type-Optionson all ingress routes- Fixed 2026-03-14: Traefik
security-headersmiddleware deployed indefaultnamespace - Headers: HSTS (7 days, no preload), X-Frame-Options SAMEORIGIN, X-Content-Type-Options nosniff, XSS filter, referrer policy, permissions policy, strips Server/X-Powered-By
7. No rate limiting FIXED
No brute-force protection on any login endpoint- Fixed 2026-03-14: Traefik
rate-limitmiddleware (100 req/min, 200 burst) applied to all public ingresses
Sonarr publicly accessible FIXED
- Fixed 2026-03-14: Removed
sonarr.schick-web.sitefrom ingress, now internal-only (sonarr.kimchi)
8. Privileged containers (LOW practical risk)
- Home Assistant:
privileged: true+hostNetwork: true— required for LAN device discovery (mDNS, Zigbee). Not exposed publicly (no.schick-web.siteingress), so exploitation requires an attacker already on the LAN. - Gitea Runner: privileged Docker-in-Docker — standard for CI runners that build containers. Risk limited to compromised Gitea accounts pushing malicious workflows; public fork CI triggers are disabled by default.
- Verdict: Acceptable tradeoffs for a home server. Both flags serve functional purposes, not oversight.
9. No network policies
- Any compromised pod can reach every other pod (lateral movement)
MEDIUM
10. No container hardening
- No
runAsNonRoot, noreadOnlyRootFilesystem, no capability drops on most workloads
11. Minimal RBAC
- Only Bitwarden has RBAC defined; other services use default service accounts
Action Plan
Done
- Set
SIGNUPS_ALLOWED: "false"in Bitwarden configmap - Switch all cert-manager issuers from
letsencrypt-stagingtoletsencrypt-prod(DNS01 via Cloudflare) - Fix issuer typo in Gitea ingress
- Add Traefik security headers middleware to all public ingresses
- Add rate-limiting middleware on all public ingresses
- Remove Sonarr from public access
Do now
- Rotate all credentials that are committed in plaintext, then move them to Kubernetes Secrets
Do soon (days)
- Set
noTLSVerify: falseon the Cloudflare tunnel (requires valid internal certs) - Put an auth proxy (e.g., Authelia or OAuth2-proxy) in front of services that lack built-in auth
Do when possible (weeks)
- Add NetworkPolicies to restrict pod-to-pod traffic
RemoveAccepted risk — both need privileged for functional reasons and are not publicly exposedprivileged: truefrom Home Assistant and Gitea runner- Add
securityContext(non-root, read-only root FS, drop all capabilities) across workloads - Adopt sealed-secrets or external-secrets-operator for proper secret management