EbookFoundation / EbookFoundation/regluit-provisioning
certs.yml cleanup: no_log, state: absent, .my.cnf churn, force: yes
- Dominant language
- Shell
- Stars
- 6
- Forks
- 8
- Avg merge
- 1h 13m
- Merged PRs (30d)
- 2
Description
Four small, low-risk fixes in `roles/regluit_prod/tasks/certs.yml`, bundled as a single PR. Discovered during the 2026-04-23 prod deploy (per-env ADMINS override, provisioning#27).
## 1. Add `no_log: true` to the "Decrypt files" task (prevents secret leak)
`certs.yml:15-22` is a plain `copy:` that reads the ansible-vault-encrypted `private/letsencrypt_account.key` and writes it to `private/decrypted/`. Because Ansible's `copy:` auto-decrypts vault-encrypted sources, running the play with `--diff` prints the plaintext RSA account key in the diff output — to stdout, to any `tee`'d log, and to terminal scrollback.
This happened during the 2026-04-23 deploy: the Let's Encrypt ACME account key (account `50553940`) was dumped to `/tmp/setup-prod-2026-04-23.log`. The log was shredded immediately, but the same content lingers in terminal scrollback and any session transcript. Scope of that specific key is ACME order management (cert request/revoke under the account), **not** the server TLS key — so disruption risk, not site-takeover risk. But the class of bug is general: any `--diff` run on a vault-reading `copy:` leaks the vault content.
Fix: add `no_log: true` to the "Decrypt files" task. The `acme_account` and both `acme_certificate` tasks should also get `no_log: true` for belt-and-braces — their params reference the key file by path, but future refactors or module-version changes could surface contents.
## 2. "delete decrypted files" uses `state: file` (bug — doesn't delete)
`certs.yml:170-177` is named "delete decrypted files" but uses `state: file`, which only asserts the path exists as a regular file. It does **not** delete. After any real (non-check) run, `private/decrypted/letsencrypt_account.key` and `private/decrypted/{{ server_name }}.csr` remain on the operator's disk. `private/decrypted/*` is gitignored, so they don't get committed, but the plaintext account key sits on every operator's laptop indefinitely.
Side effect of this bug: it's why operator-local state diverges across contributors. An operator who has run the play in real mode will pass a subsequent `--check --diff` (because the decrypted key is still there from last time); an operator who hasn't yet, or who manually cleaned it up, will see the check-mode failure at the `acme_account` task.
Fix: `state: absent`.
## 3. `.my.cnf` mode churn between two tasks
In `tasks/main.yml` (or wherever sysadmin scripts are copied):
- The "Copy sysadmin scripts to home directory" loop applies mode `0755` to `.my.cnf`.
- The immediately-following "Copy mysql config file" loop applies mode `0600` to `.my.cnf`.
Both run on every play, so `.my.cnf` flips `0755 → 0600` every run. Idempotent on the final state but generates noise in `changed=` counts and fights itself.
Fix: exclude `.my.cnf` from the sysadmin-scripts `with_items` list. Only the 0600 copy should touch it.
## 4. Drop `force: yes` on the first `acme_certificate` call
`certs.yml:48-60` has `force: yes` on the first of two `acme_certificate` invocations. This forces fresh HTTP-01 challenge creation on every run, regardless of whether the current cert is fine. Harmless per-se (just writes tokens under `/var/www/static/.well-known/` and makes a few API calls) but wasteful and a gentle Let's Encrypt rate-limit nibble.
Actual cert renewal is already gated by the second `acme_certificate` call (`certs.yml:127-139`), which uses `force: no` + `remaining_days: 45`. That gate is what protects us today (cert valid through 2026-07-06, so the second call no-ops).
Fix: drop `force: yes` from the first call. The second call's `remaining_days: 45` is the correct renewal gate.
## Scope
One PR, four small changes, all in `roles/regluit_prod/tasks/certs.yml` plus one line in `roles/regluit_prod/tasks/main.yml` (item 3). No deploy-blocking urgency; current cert valid through 2026-07-06. Ship before July to avoid the `force: yes` issue becoming relevant at the next real renewal.
## Acceptance
- `--check --diff` run completes without printing any private-key contents
- After a real run, `private/decrypted/` is empty (no leftover plaintext key)
- `ansible-playbook setup-prod.yml` reports no `changed` on `.my.cnf` when nothing has changed
- First `acme_certificate` call only creates a challenge when the second call is actually going to renew
## Context
- Discovery session: dev-journal entry 2026-04-23 (see `~/dev-journal/projects/unglue-it-modernization.md`)
- Related: provisioning#27 (per-env ADMINS override, merged and deployed 2026-04-23)
- Related: #28 (SES reputation monitoring) — separate concern
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.