feat(security): reject secret last-applied annotations (CUST-WP-0073)

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e7fb-275d-7db0-b1df-39ed943427da
This commit is contained in:
codex 2026-09-28 14:51:25 +02:00
parent 7a12760160
commit 800cbfa870
5 changed files with 141 additions and 0 deletions

View file

@ -0,0 +1,23 @@
# Secret annotation guard
Implemented under the existing CUST-WP-0073-T03; no new platform workplan.
Native admission policy denies the last-applied annotation on Secret CREATE
and UPDATE, including an empty value. No new workload or controller.
Before the first manual sync, run `scripts/secret_annotation_maintenance.py`
on railiance01 through the supervised admin path, first without arguments,
then with `--clean`. It removes only the duplicate annotation; it never prints
kubectl output or Secret values. Concurrent Secret churn can stop cleanup;
inspect the receipt before retrying. Preserve only names, counts and booleans.
Declare the policy through the pinned `secret-annotation-guard` Argo Application;
automated sync and prune are off. Platform-addons AppProject must allow both
admission kinds. Sync policy first and inspect typeChecking; bind only after
cleanup. Test clean CREATE/UPDATE and denied annotated CREATE/UPDATE using
synthetic data in whitehat; verify client-side apply is denied, then delete
only the test fixture. Secret writers must use server-side apply or replace.
Rollback is a manual Argo sync of a reviewed revision without the binding
(with explicit resource-scoped pruning), or attended break-glass deletion of
the binding followed by Git reconciliation. Removing the binding reopens this
leak path. The policy does not rotate credentials or isolate agent accounts.

View file

@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- policy.yaml

View file

@ -0,0 +1,27 @@
# CUST-WP-0073-T03: Secret annotation guard; manual ArgoCD sync.
# Owner: railiance-platform. Clean existing annotations in an attended session
# before binding; otherwise subsequent updates to those Secrets are rejected.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: reject-secret-last-applied
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["secrets"]
scope: "*"
validations:
- expression: '!has(object.metadata.annotations) || !("kubectl.kubernetes.io/last-applied-configuration" in object.metadata.annotations)'
message: "Secret last-applied annotations are forbidden; use server-side apply or replace."
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: reject-secret-last-applied
spec:
policyName: reject-secret-last-applied
validationActions: [Deny]

View file

@ -29,6 +29,10 @@ spec:
kind: ClusterRoleBinding
- group: external-secrets.io
kind: ClusterSecretStore
- group: admissionregistration.k8s.io
kind: ValidatingAdmissionPolicy
- group: admissionregistration.k8s.io
kind: ValidatingAdmissionPolicyBinding
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap