feat(secrets): monthly expiry report workflow and the manifest facts the repo knows (P8, 3/3; stacked on #99) #100

Merged
jfraeys merged 1 commit from jfraeys/feat/secrets-expiry-workflow into main 2026-10-13 13:03:31 -04:00
Owner

Merge #98, then #99, then this (stacked; until then the diff also shows their commits).

Delivery for the P8 expiry report.

  • .forgejo/workflows/secrets-expiry.yml: runs on the 1st of each month and on workflow_dispatch. Reads only the manifest (no values, no decryption), keeps ONE issue titled Secrets expiry report YYYY-MM up to date, and fails the run when a secret is expired or overdue. Approaching and unknown entries go in the issue but never fail the run. Uses the existing CI_FORGEJO_COMMENT_TOKEN (the manifest already describes it as able to open issues); without the token it warns and still evaluates the exit code.
  • scripts/forgejo-upsert-issue.py (stdlib only): creates or updates the open issue with an exact title; the token comes from the environment and is never printed. 5 tests against a local mock of the Forgejo issues API (create then update the same issue, a different month is a new issue, only an exact title matches, the token is sent but never printed, a missing token fails before any API call), added to the lint workflow with the expiry tests (17 pass).
  • Classifier detail: an entry with a recorded rotation date but no schedule now says rotated YYYY-MM-DD; no expiry and no rotation schedule recorded (still unknown).
  • Manifest, only facts the repo can verify: HEADSCALE_API_KEY expiry 2027-01-09 and rotated_at 2026-10-11 (read from headscale apikeys list on services: prefixes and dates only); AUTHELIA_OIDC_PRIVATE_KEY_PEM and CI_EPHEMERAL_ANSIBLE_SSH_KEY rotated_at 2026-10-10 (the rotation commit in #52) and their exposure text updated. Nothing invented: the report now reads expired=0 overdue=0 approaching=0 unknown=48 documented=1 ok=13.
  • Docs: how the monthly issue works and how to turn 'unknown' into real dates.

What 48 unknown means: most manifest entries have no real expiry or rotation date because nobody has recorded one (for tokens that is a provider-console lookup). That is a data gap for you, not something to guess; the report keeps listing it until it is closed.

After merge, run it once by hand (Actions > Secrets Expiry > Run workflow) to confirm the issue is created; it has not run on a real runner yet.

🤖 Generated with Claude Code

**Merge #98, then #99, then this** (stacked; until then the diff also shows their commits). Delivery for the P8 expiry report. - `.forgejo/workflows/secrets-expiry.yml`: runs on the 1st of each month and on workflow_dispatch. Reads only the manifest (no values, no decryption), keeps ONE issue titled `Secrets expiry report YYYY-MM` up to date, and fails the run when a secret is expired or overdue. Approaching and unknown entries go in the issue but never fail the run. Uses the existing CI_FORGEJO_COMMENT_TOKEN (the manifest already describes it as able to open issues); without the token it warns and still evaluates the exit code. - `scripts/forgejo-upsert-issue.py` (stdlib only): creates or updates the open issue with an exact title; the token comes from the environment and is never printed. 5 tests against a local mock of the Forgejo issues API (create then update the same issue, a different month is a new issue, only an exact title matches, the token is sent but never printed, a missing token fails before any API call), added to the lint workflow with the expiry tests (17 pass). - Classifier detail: an entry with a recorded rotation date but no schedule now says `rotated YYYY-MM-DD; no expiry and no rotation schedule recorded` (still unknown). - Manifest, only facts the repo can verify: `HEADSCALE_API_KEY` expiry 2027-01-09 and rotated_at 2026-10-11 (read from `headscale apikeys list` on services: prefixes and dates only); `AUTHELIA_OIDC_PRIVATE_KEY_PEM` and `CI_EPHEMERAL_ANSIBLE_SSH_KEY` rotated_at 2026-10-10 (the rotation commit in #52) and their exposure text updated. Nothing invented: the report now reads expired=0 overdue=0 approaching=0 unknown=48 documented=1 ok=13. - Docs: how the monthly issue works and how to turn 'unknown' into real dates. What 48 unknown means: most manifest entries have no real expiry or rotation date because nobody has recorded one (for tokens that is a provider-console lookup). That is a data gap for you, not something to guess; the report keeps listing it until it is closed. After merge, run it once by hand (Actions > Secrets Expiry > Run workflow) to confirm the issue is created; it has not run on a real runner yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(secrets): one expiry classifier and a manifest-only expiry report
All checks were successful
Lint / lint (pull_request) Successful in 1m13s
Secret scan / scan (pull_request) Successful in 14s
44ba182f43
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
feat(secrets): rotation lock for vault read-modify-write cycles
All checks were successful
Lint / lint (pull_request) Successful in 1m58s
Secret scan / scan (pull_request) Successful in 12s
b35100b7dc
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
feat(secrets): monthly expiry report workflow and the manifest facts the repo knows
All checks were successful
Lint / lint (pull_request) Successful in 1m42s
Secret scan / scan (pull_request) Successful in 24s
d05d8e0b96
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jfraeys/infra!100
No description provided.