Files
HomeServer/SECURITY.md
2026-03-14 11:33:56 +00:00

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: true in k8s/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 use letsencrypt-staging
  • Fixed 2026-03-14: All public services switched to letsencrypt-prod with DNS01 solver via Cloudflare
  • Also fixed typo letsencrpyt-staging in Gitea ingress
  • Cloudflare API token stored as K8s secret cloudflare-api-token in cert-manager namespace

6. No security headers FIXED

  • Missing Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options on all ingress routes
  • Fixed 2026-03-14: Traefik security-headers middleware deployed in default namespace
  • 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-limit middleware (100 req/min, 200 burst) applied to all public ingresses

Sonarr publicly accessible FIXED

  • Fixed 2026-03-14: Removed sonarr.schick-web.site from 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.site ingress), 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, no readOnlyRootFilesystem, 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-staging to letsencrypt-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: false on 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
  • Remove privileged: true from Home Assistant and Gitea runner Accepted risk — both need privileged for functional reasons and are not publicly exposed
  • Add securityContext (non-root, read-only root FS, drop all capabilities) across workloads
  • Adopt sealed-secrets or external-secrets-operator for proper secret management