test(openbao): guard Retain on the platform PKI and transit CRs #756

Merged
binjovi-bot merged 1 commit from test/openbao-platform-retain-guard into trunk 2026-09-14 16:48:16 +00:00
Owner

What

Adds tests/openbao-platform-crs.sh, guarding the ten CRs that #750 moved out of Crossplane.

This has to land before platform-seanfarm empties the composition, so the invariant is never left unguarded for a window.

Why now

platform-seanfarm tests/check-binjovi-forgejo-key.sh asserts that exactly one binjovi-forgejo-jwt key exists, of type ecdsa-p256. It reads the composition. Once the composition is emptied that guard reads an empty file and fails -- which it correctly did when I made that change, and which is how this gap surfaced.

The obvious move is to delete the stale guard. That would silently drop a real invariant, so it is being ported here instead, to where the manifest now lives.

tests/binjovi.sh already checks the policy side in execution-platform.yaml (transit/sign/binjovi-forgejo-jwt, transit/keys/binjovi-forgejo-jwt). Nothing in this repo checked the key declaration. That was the gap.

Widened to the property that actually protects the keys

While porting it, the guard covers deletionPolicy, which matters more than the key names.

The transit backend holds cosign, cosign-ec and binjovi-forgejo-jwt. Unmounting it destroys every key in it, every existing signature stops verifying, and the material does not come back. spec.deletionPolicy: Retain is the only thing preventing that, and it is load bearing, not decorative -- libseanfarm-operator v0.4.13 (pinned by the deployed openbao-operator v0.12.32) reconcile/harness.go:401:

if policy != DeletionPolicyRetain && !dryRun {
    ... h.Delete(ctx, obj, c) ...
}

with the UnmountSecrets call inside that block (openbaotransitbackend_impl.go:152).

The CRD default is Delete. So an omitted line silently selects destruction. It renders green, dry-runs green, and only shows up when a CR is deleted -- which is exactly what the retirement is about to do.

What it checks

  1. Every numbered manifest is listed in kustomization.yaml -- an unlisted file is invisible to Flux while looking present in review
  2. All 9 openbao.sean.farm CRs set deletionPolicy: Retain explicitly (the OpenBaoConnection is skipped by API group, not by name, since it has no such field)
  3. The transit key set matches exactly, by name and type
  4. No key sets deletionAllowed, which gates per-key destruction ahead of the unmount

Red-green

Each check was verified against a mutation that should trip it:

mutation result
drop Retain from the transit backend caught (deletionPolicy <unset>)
flip a PKI role to Delete caught
change binjovi-forgejo-jwt to ed25519 caught
set deletionAllowed: true on cosign-ec caught
unlist a manifest from the kustomization caught

Tree restored after each; git status clean.

Tests

tests/check.sh exits 0 with the guard registered.

https://claude.ai/code/session_01GfkEuwuvGSqGyXVuxwT7PA

## What Adds `tests/openbao-platform-crs.sh`, guarding the ten CRs that #750 moved out of Crossplane. This has to land **before** platform-seanfarm empties the composition, so the invariant is never left unguarded for a window. ## Why now platform-seanfarm `tests/check-binjovi-forgejo-key.sh` asserts that exactly one `binjovi-forgejo-jwt` key exists, of type `ecdsa-p256`. It reads the composition. Once the composition is emptied that guard reads an empty file and fails -- which it correctly did when I made that change, and which is how this gap surfaced. The obvious move is to delete the stale guard. That would silently drop a real invariant, so it is being ported here instead, to where the manifest now lives. `tests/binjovi.sh` already checks the *policy* side in `execution-platform.yaml` (`transit/sign/binjovi-forgejo-jwt`, `transit/keys/binjovi-forgejo-jwt`). Nothing in this repo checked the key *declaration*. That was the gap. ## Widened to the property that actually protects the keys While porting it, the guard covers `deletionPolicy`, which matters more than the key names. The transit backend holds `cosign`, `cosign-ec` and `binjovi-forgejo-jwt`. Unmounting it destroys every key in it, every existing signature stops verifying, and the material does not come back. `spec.deletionPolicy: Retain` is the only thing preventing that, and it is load bearing, not decorative -- `libseanfarm-operator` v0.4.13 (pinned by the deployed `openbao-operator` v0.12.32) `reconcile/harness.go:401`: ```go if policy != DeletionPolicyRetain && !dryRun { ... h.Delete(ctx, obj, c) ... } ``` with the `UnmountSecrets` call inside that block (`openbaotransitbackend_impl.go:152`). **The CRD default is `Delete`.** So an omitted line silently selects destruction. It renders green, dry-runs green, and only shows up when a CR is deleted -- which is exactly what the retirement is about to do. ## What it checks 1. Every numbered manifest is listed in `kustomization.yaml` -- an unlisted file is invisible to Flux while looking present in review 2. All 9 `openbao.sean.farm` CRs set `deletionPolicy: Retain` explicitly (the `OpenBaoConnection` is skipped by API group, not by name, since it has no such field) 3. The transit key set matches exactly, by name and type 4. No key sets `deletionAllowed`, which gates per-key destruction ahead of the unmount ## Red-green Each check was verified against a mutation that should trip it: | mutation | result | |---|---| | drop `Retain` from the transit backend | caught (`deletionPolicy <unset>`) | | flip a PKI role to `Delete` | caught | | change `binjovi-forgejo-jwt` to `ed25519` | caught | | set `deletionAllowed: true` on `cosign-ec` | caught | | unlist a manifest from the kustomization | caught | Tree restored after each; `git status` clean. ## Tests `tests/check.sh` exits 0 with the guard registered. https://claude.ai/code/session_01GfkEuwuvGSqGyXVuxwT7PA
test(openbao): guard Retain on the platform PKI and transit CRs
All checks were successful
binjovi/ci Binjovi completed the frozen plan
d9c26b1167
Ports platform-seanfarm tests/check-binjovi-forgejo-key.sh to this repo, where
the manifests now live, and widens it.

That guard asserts one binjovi-forgejo-jwt key of type ecdsa-p256 by reading
apis/platform/composition.yaml. Emptying the composition makes it read an empty
file and fail, which is how this gap surfaced. Deleting it would drop a real
invariant, so it moves here instead. tests/binjovi.sh already covers the policy
side in execution-platform.yaml; nothing covered the key declaration.

The guard also pins deletionPolicy, which protects more than the key names. The
transit backend holds cosign, cosign-ec and binjovi-forgejo-jwt. Unmounting it
destroys every key in it and the material does not come back. Retain is the only
thing that prevents the unmount: libseanfarm-operator v0.4.13, pinned by the
deployed openbao-operator v0.12.32, skips the whole remote-delete block when the
policy is Retain, and the UnmountSecrets call is inside that block.

The CRD default is Delete, so an omitted line selects destruction silently. It
renders green and dry-runs green, and only appears when a CR is deleted, which
is what the retirement is about to do.

Checks: every numbered manifest is listed in the kustomization; all 9
openbao.sean.farm CRs set Retain explicitly, with the OpenBaoConnection skipped
by API group rather than by name; the transit key set matches exactly by name
and type; no key sets deletionAllowed.

Each check was proven against a mutation that should trip it, and all five were
caught.

Claude-Session: https://claude.ai/code/session_01GfkEuwuvGSqGyXVuxwT7PA
binjovi-bot deleted branch test/openbao-platform-retain-guard 2026-09-14 16:48:16 +00:00
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
sean/seanfarm!756
No description provided.