EbookFoundation / EbookFoundation/regluit-provisioning

certs.yml cleanup: no_log, state: absent, .my.cnf churn, force: yes

Open
#30 1 comment 0 reactions 0 assignees View on GitHub
theme:infra
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.