Skip to content
PtahOperator

Exact-plan approvals

An approval is an independent authorization object, not a Boolean field on the schema. The approver must explicitly select all three stable identifiers:

  • schema name and UID;
  • plan name and UID;
  • plan fingerprint.

The admission webhook reads the current plan directly from the API server and fills the remaining derived bindings when they are omitted. It never silently corrects a conflicting value. The validating webhook then checks the complete post-mutation object against the current schema, plan storage commit, observed database state, artifact digest, policy bytes, and execution images.

Approvals are required where spec.policy.apply asks for them. That field is on the desired-state resource, so whoever may edit a PtahSchema or a PtahMigration may also select Always and have non-destructive plans applied without any approval. If this guide’s gate is meant to be one a person cannot step around, install the administrator-owned policy in examples/approval-policy-guard.yaml as well; the security model says why RBAC alone does not do it.

Start from the minimal approval example in examples/approval.yaml. Obtain the values only after reviewing the plan:

Terminal window
kubectl -n application get ptahschema application \
-o jsonpath='{.metadata.uid}{"\n"}{.status.plan.name}{"\n"}{.status.plan.uid}{"\n"}{.status.plan.fingerprint}{"\n"}'
kubectl -n application get ptahschemaplan <plan-name> -o yaml

The plan resource contains immutable chunk names and digests; exact SQL is in those controller-owned ConfigMaps. The built-in approver ClusterRole does not grant cluster-wide ConfigMap access. Before review, a namespace administrator must grant get on every current chunk name to the approver. Start from the least-privilege Role template in examples/approver-plan-reader-role.yaml, copy all .spec.chunks[*].name values into resourceNames, and bind that Role only to the reviewer. Replace the Role for the next plan. A broader Role that can read every ConfigMap in an application namespace is easier to operate but also exposes unrelated configuration.

Once the access is granted, read the plan with kubectl ptah, which is a plugin the reviewer installs once:

Terminal window
kubectl ptah plan application --current -n application

It reads every chunk, checks each against the digest and size the plan records, joins them in index order and checks the whole document against spec.contentDigest before printing a line. An approval of hashes without reading the SQL they refer to is not an independent review, and neither is reading one chunk of a plan that has several.

Fill those values in the approval and use server-side dry run to inspect the object after authenticated identity and derived bindings are stamped:

Terminal window
kubectl apply --server-side --dry-run=server -f examples/approval.yaml -o yaml
kubectl apply -f examples/approval.yaml

Creation is rejected if the plan is already stale, its immutable storage is not committed, the verification-policy ConfigMap changed, or a supplied derived field conflicts. Creation is also rejected unless the schema is currently waiting for exactly one approval and no operation or recorded approval already owns that decision. Concurrent duplicates are retired, and the accepted approval is consumed only at the persisted Apply dispatch boundary. Updates cannot change spec; create a new approval for a new plan.

The chart’s optional approver ClusterRole grants read access to schemas and plan metadata plus create access to approvals, but it has no binding and no ConfigMap permission. Bind approval permission only to authenticated identities that are independent from routine desired-state writers, and grant plan-chunk access separately in each application namespace.