Two-layer structure: Sources (raw notes) + Wiki (compile output) Four domains: Dev (40), Venture (3), Homelab (23), Reference (0) Includes CLAUDE.md spec, index pages at all levels, compile log Co-Authored-By: Lovebug <lovebug@herbylab.dev>
5.2 KiB
| created | path | project | status | tags | type | updated | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-04-19 | Sources/Homelab | env-file-hardening | active |
|
project-plan | 2026-04-19 |
Env File Hardening
Goal
Remove the easy path to PBS secrets. Right now .env files in
/opt/docker are readable by anyone with filesystem access — including
pbsdeploy. If the deploy key is compromised, an attacker can cat .env
and grab every API key and password instantly.
Goal: pbsdeploy can deploy containers without ever being able to read the
secrets they use.
Threat Model
- Primary risk:
pbsdeploySSH key leaks → attacker SSHes in → reads.envfiles → owns every service. - Mitigation goal: eliminate the trivial read path. Raise the bar so an
attacker would need to escalate to root (no sudo on
pbsdeploy), pivot accounts, or break out of a container. - Accepted residual risk: Determined attackers with deep access can still cause damage. This is about removing low-hanging fruit, not achieving zero risk.
Two-Phase Approach
Phase 1: Filesystem Permissions (80/20 win)
Lock .env files down to root:root 0600. The Docker daemon runs as root,
so docker compose still reads them fine when processing compose files.
pbsdeploy runs docker compose up, which talks to the daemon via socket
— pbsdeploy itself never reads .env directly.
Net effect: the trivial cat .env path is gone. No sidecar needed, no
app code changes, no runtime complexity.
Phase 2: Encryption at Rest on the Server (defense in depth, deferred)
Currently secrets are encrypted in the wp-i repo via ansible-vault, but
land as plaintext on the server filesystem. Phase 2 keeps them encrypted on
disk and decrypts only at container start (via sidecar or similar).
Protects against: backup leaks, Linode snapshot exfil, accidental exposure, offline decrypt of historical filesystem copies.
Not urgent because: secrets are already encrypted in the repo, Linode account is behind 2FA, no compliance driver. This is "nice to have when refactoring," per earlier conversation on the topic.
Design Decisions
- Phase 1 is just filesystem perms — no sidecar, no Docker secrets, no
app changes. Docker daemon reads
.envas root;pbsdeploydoesn't need to. - Phase 2 uses sidecar pattern when we get there — tiny init container
decrypts at runtime, injects env vars into main container, zero app code
changes. Compose secrets ruled out because it requires refactoring every
app to read from
/run/secrets/. - External secret manager (Vault, etc.) ruled out — overkill for self-hosted prod scale. Revisit only if moving to Kubernetes.
- Ansible stays the deploy tool throughout — writes
.envfiles as root with tight perms.pbsdeployhas no sudoers entry, so it can't read them back. - Staging first for both phases.
How the Permissions Work (Phase 1)
- Ansible SSHes in as
pbsdeploy→ usesbecome: yesto elevate for file writes → files created asroot:root 0600. pbsdeploycan'tcatthem (no read perms). Can'tsudo catthem either (no sudoers entry).- Docker daemon runs as root → reads
.envwhen processing compose files → injects vars into containers. - Main container sees env vars, doesn't know or care about file perms.
Next Steps (Phase 1)
- Audit current state — which services have
.envfiles in/opt/docker, current ownership and perms - Update Ansible role(s) to write
.envasroot:root 0600(notdeployergroup) - Pick a non-critical pilot service on staging
- Deploy change to pilot, verify container still works, verify
pbsdeploycan't read the file - Roll out to all staging services
- Verify nothing breaks over a few deploys
- Roll out to production
- Remove
deployergroup read access to.envfiles (group can still exist for other things if needed)
Next Steps (Phase 2 — later)
- Decide on encryption approach: custom sidecar vs. SOPS + age vs. something else
- Design sidecar container (Dockerfile, entrypoint, decryption strategy)
- Decide where the decryption key lives on the server and how it's protected
- Pilot on one staging service
- Roll out across stack
Open Questions
Phase 1
- Does anything in
/opt/dockercurrently rely ondeployergroup read access to.envthat we'd break by tightening perms? - Are all services using
env_file:in compose, or do some hardcode secrets in the compose file itself (which would need separate handling)?
Phase 2 (deferred)
- Sidecar implementation: custom shell-based init container vs. off-the-shelf tool like SOPS?
- Where does the decryption key live on the server? (If it's in another root-owned file, we're just moving the problem — though one layer deeper is still an improvement.)
Depends On / Related
- SSH Login Alerting — higher priority sibling project. Early warning system for the threat this project mitigates.
- PBSII CI/CD — same
pbsdeploy/deployersetup on staging; phase 1 changes apply there too. - Previous conversation on secrets managers — concluded that for single-server self-hosted prod, encryption-at-rest on the server is "nice to have," not urgent. Phase 2 is the realization of that future work.
...sent from Jenny & Travis