Skip to content

Lock ​

Scope: namespaced · Guardrail

Lock protects another resource from being deleted by mistake — the same idea as an Azure CanNotDelete resource lock. While a Lock referencing a resource exists, any kubectl delete (or API delete) of that resource is rejected at admission time; you must delete the Lock first.

yaml
apiVersion: core.navique.com/v1alpha1
kind: Lock
metadata:
  name: protect-forge-stack
  namespace: forge
spec:
  targetRef:
    apiVersion: core.navique.com/v1alpha1
    kind: Stack
    name: forge
  reason: "Production environment — remove this Lock deliberately before deleting."
console
$ kubectl delete stack forge -n forge
Error from server (Forbidden): admission webhook ... denied the request:
resource is protected by Lock(s) [protect-forge-stack]; delete the Lock(s) before deleting this resource

How it works ​

Kubernetes has no built-in resource lock, and a controller alone cannot stop a delete — by the time a controller sees it, the deletion is already irreversible. So the Lock is enforced where deletes can actually be rejected: the API server's admission stage. Two cooperating pieces:

  1. The Lock controller stamps the target with a core.navique.com/locked annotation listing the Locks that protect it. When the last Lock is removed, the annotation is cleared.
  2. A cluster ValidatingAdmissionPolicy rejects any DELETE of a core.navique.com resource that carries a non-empty core.navique.com/locked annotation.

This requires Kubernetes 1.30+ (ValidatingAdmissionPolicy GA). The policy is installed with the operator (Helm value lockGuard.enabled, default true).

Fields ​

FieldRequiredDescription
targetRef.kindyesKind of the protected resource, e.g. Stack, PostgresCluster, Gateway.
targetRef.nameyesName of the target, in the same namespace as the Lock.
targetRef.apiVersionnoDefaults to core.navique.com/v1alpha1. Only the operator's API group is enforceable.
reasonnoFree-text note, surfaced in status and in the denied-delete message.

Behaviour & limits ​

  • Same namespace, our group only. A Lock can only protect a core.navique.com resource in its own namespace. Other groups (and locking a Lock itself) are rejected with Active: false and an InvalidTarget reason.
  • Multiple locks stack. Several Locks may protect one target; it stays protected until the last one is deleted.
  • Cascade deletes are blocked too. Deleting a Stack whose child is locked will be blocked for that child (garbage-collection deletes also pass through admission), leaving the Stack partially torn down. Remove the child's Lock first, or don't lock individual Stack-owned children if you want the Stack to delete cleanly — lock the Stack instead.
  • It's a guardrail, not a security control. Anyone with RBAC to delete the Lock (or edit the target's annotation) can lift the protection. Use RBAC if you need a hard boundary.
  • Deletes only. A Lock blocks deletion, not updates (Azure's ReadOnly mode is not implemented — it would also block the operator's own reconciles).

Status ​

FieldMeaning
status.activetrue once the target carries the lock annotation (deletion is being rejected).
status.conditions[Ready]True when protecting; False with reason MissingReference (target not found yet) or InvalidTarget.

Open core under AGPL-3.0. Enterprise components are proprietary and license-gated.