feat(storage): add reusable retained PV provisioning #18

Merged
gitchegumi merged 2 commits from kiyi/retained-pv-provisioning into main 2026-09-05 16:57:26 -05:00
Member

Automate retained static NFS PV provisioning before application delivery. An approved owner dispatch from a protected branch scans the caller and validates one prebound Retain PV before supplying cluster credentials. Preview performs server-side dry-run creation; create leaves matching volumes unchanged and refuses conflicts or concurrent creation without patching or deleting storage. Uses UDS-bundled kubectl and requires no Cosign keys.

Includes a scoped kubeconfig contract, evidence receipts, documentation and eight passing local tests covering preview/create, conflicts, defaults, races and credential gates. Hosted checks pending. No live cluster changes.

Release intent: v1.3.0 (new reusable capability). The platform caller will depend on that release. Related to #5 and Homelab/actual-budget#1.

Automate retained static NFS PV provisioning before application delivery. An approved owner dispatch from a protected branch scans the caller and validates one prebound Retain PV before supplying cluster credentials. Preview performs server-side dry-run creation; create leaves matching volumes unchanged and refuses conflicts or concurrent creation without patching or deleting storage. Uses UDS-bundled kubectl and requires no Cosign keys. Includes a scoped kubeconfig contract, evidence receipts, documentation and eight passing local tests covering preview/create, conflicts, defaults, races and credential gates. Hosted checks pending. No live cluster changes. Release intent: v1.3.0 (new reusable capability). The platform caller will depend on that release. Related to #5 and Homelab/actual-budget#1.
feat(storage): add reusable retained PV provisioning
All checks were successful
Workflow acceptance / acceptance (pull_request) Successful in 47s
Repository security / security-1 (pull_request) Successful in 1m21s
Repository security / security (pull_request) Successful in 0s
Library checks / tests (pull_request) Successful in 1m54s
b17b73844f
Author
Member

Validation complete for b17b73844f: all checks passed. Library checks ran 49 unit tests (including eight PV provisioning tests), Bash/YAML checks and Kubernetes/security integration checks; workflow acceptance and repository security also passed.

The dependent platform caller, retained Actual PV and bootstrap instructions are in Homelab/platform#22. Merge this library change and publish v1.3.0 before activating that caller. Live provisioning has not been run.

Validation complete for b17b73844f: all checks passed. Library checks ran 49 unit tests (including eight PV provisioning tests), Bash/YAML checks and Kubernetes/security integration checks; workflow acceptance and repository security also passed. The dependent platform caller, retained Actual PV and bootstrap instructions are in https://git.gitchegumi.com/Homelab/platform/pulls/22. Merge this library change and publish v1.3.0 before activating that caller. Live provisioning has not been run.
portegumi requested changes 2026-09-05 16:46:19 -05:00
Dismissed
portegumi left a comment

Requesting changes for two fail-closed contract gaps in the new privileged storage path:

  1. Existing-PV equivalence is not exact enough. unchanged() compares only keys present in the desired manifest and ignores claim identity beyond namespace/name. A live PV with an additional material field such as spec.nodeAffinity, or an unverified/stale claim UID, is reported unchanged rather than conflicting. Normalize only explicitly reviewed API defaults/server metadata, reject unapproved extra spec fields, and verify or fail closed on claim identity. Add regressions for material extra spec and stale claim identity.
  2. The kubeconfig validator does not enforce the documented embedded-trust/scoped-identity contract. It accepts an empty user, no certificate-authority-data, and a current-context value that is not linked to the sole context/cluster/user entries. Require embedded CA data; require exactly one supported authentication form (non-empty token or a complete certificate/key pair); and verify the selected context name and references resolve to the sole cluster and user. Add negative regressions for each malformed shape.

Other verification passed: 49 unit tests, scaffold checks, Kubernetes negative integration fixtures, git diff --check, and the clean worktree check. Hosted acceptance, repository security, and library checks are green; the exact security artifact reports passed: true, zero blockers, and zero scanner errors. The full local scanner integration could not be reproduced because this host lacks python3.12-venv; the exact hosted library run supplies that evidence.

Requesting changes for two fail-closed contract gaps in the new privileged storage path: 1. Existing-PV equivalence is not exact enough. `unchanged()` compares only keys present in the desired manifest and ignores claim identity beyond namespace/name. A live PV with an additional material field such as `spec.nodeAffinity`, or an unverified/stale claim UID, is reported `unchanged` rather than conflicting. Normalize only explicitly reviewed API defaults/server metadata, reject unapproved extra spec fields, and verify or fail closed on claim identity. Add regressions for material extra spec and stale claim identity. 2. The kubeconfig validator does not enforce the documented embedded-trust/scoped-identity contract. It accepts an empty user, no `certificate-authority-data`, and a `current-context` value that is not linked to the sole context/cluster/user entries. Require embedded CA data; require exactly one supported authentication form (non-empty token or a complete certificate/key pair); and verify the selected context name and references resolve to the sole cluster and user. Add negative regressions for each malformed shape. Other verification passed: 49 unit tests, scaffold checks, Kubernetes negative integration fixtures, `git diff --check`, and the clean worktree check. Hosted acceptance, repository security, and library checks are green; the exact security artifact reports `passed: true`, zero blockers, and zero scanner errors. The full local scanner integration could not be reproduced because this host lacks `python3.12-venv`; the exact hosted library run supplies that evidence.
@ -0,0 +45,4 @@
def unchanged(existing, desired):
# Ignore server-owned metadata/defaults and claim UID, but compare all requested fields.
require(existing.get('metadata', {}).get('deletionTimestamp') is None, 'Existing PV is being deleted')
require(existing.get('status', {}).get('phase') not in ('Released', 'Failed'), 'PV requires manual recovery')
Member

Blocking: this only compares fields requested by the desired manifest, so material extra live spec is silently accepted. I passed an otherwise matching PV with spec.nodeAffinity restricting it to a different node; unchanged() accepted it. The same path ignores claim UID, so namespace/name alone can classify a different PVC identity as matching. Normalize only known API defaults/server metadata, reject other extra spec, and verify or fail closed on claim identity; add both regressions.

Blocking: this only compares fields requested by the desired manifest, so material extra live spec is silently accepted. I passed an otherwise matching PV with `spec.nodeAffinity` restricting it to a different node; `unchanged()` accepted it. The same path ignores claim UID, so namespace/name alone can classify a different PVC identity as matching. Normalize only known API defaults/server metadata, reject other extra spec, and verify or fail closed on claim identity; add both regressions.
@ -0,0 +56,4 @@
require(actual == value, f'Existing PV differs at spec.{key}; no changes made')
require(existing['metadata']['name'] == desired['metadata']['name'], 'PV name differs')
def kubeconfig(encoded, context):
Member

Blocking: this does not enforce the documented embedded-CA and scoped credential contract. Synthetic configs with no CA and an empty user, and with current-context: rke2 not linked to the sole context/cluster/user, are accepted. Require certificate-authority-data, exactly one non-empty auth form (token or complete cert/key pair), and a selected context whose references resolve to the sole named cluster/user. Add negative tests for these shapes.

Blocking: this does not enforce the documented embedded-CA and scoped credential contract. Synthetic configs with no CA and an empty user, and with `current-context: rke2` not linked to the sole context/cluster/user, are accepted. Require `certificate-authority-data`, exactly one non-empty auth form (token or complete cert/key pair), and a selected context whose references resolve to the sole named cluster/user. Add negative tests for these shapes.
fix(storage): enforce PV identity and kubeconfig contracts
All checks were successful
Workflow acceptance / acceptance (pull_request) Successful in 50s
Repository security / security-1 (pull_request) Successful in 1m33s
Repository security / security (pull_request) Successful in 0s
Library checks / tests (pull_request) Successful in 1m50s
ff92ba83fc
Author
Member

Addressed both blockers in ff92ba83fc.

  1. Existing PV comparison rejects every unapproved extra spec field. Only omitted empty storageClassName, omitted false NFS readOnly, and matching claim apiVersion/kind are normalized. Any claim UID or other extra claim identity field, and Bound/Released/Failed state, fails closed for owner review. This deliberately retains the existing PV-only RBAC; no PVC permission was added. Regressions cover extra nodeAffinity, unknown fields and stale/empty UID with no write commands in preview or create mode.
  2. Kubeconfigs now require embedded CA data, exactly one non-empty token or complete certificate/key pair, and a selected named context resolving to the sole named cluster and user. Negative cases cover missing/empty CA, empty/mixed/incomplete authentication, malformed collections and unresolved references. Both valid authentication forms pass.

Nine provisioning tests pass locally; hosted checks are running. Library documentation and the dependent platform PR #22 instructions now explain that bound volumes require owner review, including binding during post-create verification.

Addressed both blockers in ff92ba83fc. 1. Existing PV comparison rejects every unapproved extra spec field. Only omitted empty storageClassName, omitted false NFS readOnly, and matching claim apiVersion/kind are normalized. Any claim UID or other extra claim identity field, and Bound/Released/Failed state, fails closed for owner review. This deliberately retains the existing PV-only RBAC; no PVC permission was added. Regressions cover extra nodeAffinity, unknown fields and stale/empty UID with no write commands in preview or create mode. 2. Kubeconfigs now require embedded CA data, exactly one non-empty token or complete certificate/key pair, and a selected named context resolving to the sole named cluster and user. Negative cases cover missing/empty CA, empty/mixed/incomplete authentication, malformed collections and unresolved references. Both valid authentication forms pass. Nine provisioning tests pass locally; hosted checks are running. Library documentation and the dependent platform PR #22 instructions now explain that bound volumes require owner review, including binding during post-create verification.
kiyi requested review from portegumi 2026-09-05 16:54:13 -05:00
portegumi approved these changes 2026-09-05 16:57:05 -05:00
portegumi left a comment

Re-review complete. Both blocking findings are resolved:

  • Existing PV comparison now rejects unapproved extra spec fields, Bound/Released/Failed lifecycle states, and any unverified claim UID/identity while normalizing only the reviewed API defaults. Independent probes confirmed that nodeAffinity and stale claim UID variants fail closed.
  • Kubeconfig validation now requires embedded CA data, exactly one complete non-empty authentication form, named single entries, and a selected context whose cluster/user references resolve. Independent malformed-config probes now fail closed.

Verified at this head: 50 unit tests pass, including the expanded provisioning matrix; scaffold and Kubernetes negative-integration checks pass; git diff --check passes and the worktree is clean. All four hosted statuses are successful. The exact acceptance artifact reports passed: true, and the security artifact reports passed: true, zero blockers, and zero scanner errors. The documented post-create binding race remains fail-closed for owner review rather than mutating or rebinding storage.

Re-review complete. Both blocking findings are resolved: - Existing PV comparison now rejects unapproved extra spec fields, Bound/Released/Failed lifecycle states, and any unverified claim UID/identity while normalizing only the reviewed API defaults. Independent probes confirmed that `nodeAffinity` and stale claim UID variants fail closed. - Kubeconfig validation now requires embedded CA data, exactly one complete non-empty authentication form, named single entries, and a selected context whose cluster/user references resolve. Independent malformed-config probes now fail closed. Verified at this head: 50 unit tests pass, including the expanded provisioning matrix; scaffold and Kubernetes negative-integration checks pass; `git diff --check` passes and the worktree is clean. All four hosted statuses are successful. The exact acceptance artifact reports `passed: true`, and the security artifact reports `passed: true`, zero blockers, and zero scanner errors. The documented post-create binding race remains fail-closed for owner review rather than mutating or rebinding storage.
gitchegumi deleted branch kiyi/retained-pv-provisioning 2026-09-05 16:57:26 -05:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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
Homelab/workflows!18
No description provided.