feat(backup): encrypt archives at rest (+ fix latent phpbu env-adapter bug) - #9
Conversation
Addresses the review finding that phpbu wrote the DB dump, files, and the config archive — which holds glpicrypt.key (the AES key for every encrypted DB field) — UNENCRYPTED to the backups volume, colocating the key with the ciphertext it decrypts. - backup.json: openssl crypt step (aes-256-cbc + pbkdf2) on all three backups, keyed by BACKUP_CRYPT_PASSWORD; the plaintext archive is deleted after encryption so the volume never holds cleartext. Also register the `env` adapter — it was missing, so every `adapter:env:*` reference (incl. the DB password) would have failed to resolve once the backup actually ran. - compose.yml: forward BACKUP_CRYPT_PASSWORD to the backup container; run it as root so cap_add DAC_OVERRIDE actually lets phpbu read GLPI's 0660 oauth keys (the config backup failed on them otherwise — caps are inert for a non-root uid). - init.sh generates a random BACKUP_CRYPT_PASSWORD; .env.example documents it with a loud 'store it separately' warning. - runbook-restore.md: every restore step now decrypts first; file table shows .enc. Verified end-to-end: OK (3 backups, 3 crypts), zero plaintext on the volume, and the documented restore decrypts to valid SQL + recovers glpicrypt.key. Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
There was a problem hiding this comment.
Automated approval for maintainer PR
All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.
There was a problem hiding this comment.
Code Review
This pull request introduces at-rest encryption for GLPI backups using phpbu's openssl adapter with a new BACKUP_CRYPT_PASSWORD environment variable. Feedback focuses on correcting the documented decryption commands by removing the incorrect -a (base64) flag, which would cause decryption to fail. Additionally, it is recommended to optimize the restore runbook by piping decryption output from the existing backup container rather than installing openssl at runtime in a temporary container, ensuring offline compatibility and better security.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Addresses review feedback: instead of `apk add openssl` in a throwaway alpine container (needs internet, fails air-gapped) with BACKUP_CRYPT_PASSWORD in its env, decrypt in the existing backup container (already has openssl + the passphrase) and pipe the plaintext tar into the extraction container. Keeps the '-a' flag — phpbu's openssl crypter base64-encodes its output (verified via phpbu --debug + round-trip), so decryption requires it. Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
There was a problem hiding this comment.
Automated approval for maintainer PR
All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.
|



Implements the one substantive deferred review finding ([7]) — now unblocked since
phpbu-dockerwas un-bricked (phpbu#162).Problem
phpbu wrote the DB dump, files, and the config archive — which contains
glpicrypt.key(the AES key for every encrypted DB field) — unencrypted to the backups volume, colocating the key with the ciphertext it decrypts. Anyone with the volume had both.Verifying the fix also surfaced two latent bugs that meant the backup never actually worked (it had never run — the image was broken and the backup is opt-in):
backup.jsonusedadapter:env:*with **noadaptersblock registered→ phpbu errorsno adapter registered with name: env` (would have broken even the DB-password lookup).tar: oauth.pem: Permission denied— phpbu (uid 1000) can't read GLPI's0660oauth keys (owned by www-data).Fix
opensslcrypt (aes-256-cbc + pbkdf2), keyed byBACKUP_CRYPT_PASSWORD; plaintext is deleted post-encryption.envadapter (fixes the latent resolve bug).cap_add: DAC_OVERRIDEactually applies and phpbu can read the protected config (caps are inert for a non-root uid). Opt-in + socket-less +:rosources bound the risk.make initgenerates a randomBACKUP_CRYPT_PASSWORD;.env.exampledocuments it with a store-it-separately warning..enc.Verified end-to-end
OK (3 backups, 3 crypts), zero plaintext on the volume, and the exact documented restore decrypts to valid SQL (DROP TABLE IF EXISTS glpi_agents…) and recoversglpicrypt.key+ oauth keys from the config archive. Default stack unaffected; hardening-check + yamllint + shellcheck + JSON-validate all clean.