Skip to content

Add cross-field CRD validation for LldapUser and LldapMembership#33

Merged
lukidoescode merged 1 commit into
mainfrom
lwagner/4-cross-field-crd-validation
May 21, 2026
Merged

Add cross-field CRD validation for LldapUser and LldapMembership#33
lukidoescode merged 1 commit into
mainfrom
lwagner/4-cross-field-crd-validation

Conversation

@lukidoescode

Copy link
Copy Markdown
Owner

Description

Adds the cross-field invariants the structural OpenAPI schema cannot express. The LldapUser admission-time CEL rule that requires spec.passwordSecretRef whenever spec.passwordPolicy is Manage or InitialOnly is preserved, with a refined error message that names the policies requiring the secret. For LldapMembership, a stateless matcher (validate_membership_references in lldap-operator-reconciler) checks that a membership's referenced user/group exist among caller-supplied candidates within the same namespace and same lldap-operator.lukidoescode.com/lldap-instance label value. The validator has no kube client and no I/O — the future membership reconciler (#14) will list candidates and feed them in.

Negative and positive CEL evaluation tests now run against the actual rule expressions via cel-interpreter, replacing the previous rule-text-presence-only coverage. The CRD reference docs gain a ## Validation Rules section on the LldapUser and LldapMembership pages describing both admission-time and reconcile-time checks.

Decisions Made

  • CEL (Option B), not a validating admission webhook. The deployment target is up-to-date Kubernetes (1.25+); CEL avoids the operational cost of webhook TLS while giving immediate, in-API-server feedback to kubectl apply.
  • Stateless validator placed in lldap-operator-reconciler, not lldap-operator-crds. Although the matcher is pure, its concern is reconciliation semantics; the CRD crate stays a pure schema crate.
  • Validator depends on its own borrowed candidate types (MembershipReference, UserCandidate, GroupCandidate), not on ObjectMeta or the CRD wrappers. This keeps lldap-operator-reconciler free of k8s-openapi and lldap-operator-crds as production deps; the adapter (ObjectMeta + spec → candidate) lives at the boundary in whatever code wires the reconciler.
  • UserNotFound / GroupNotFound are hard errors at the validator level. The future membership reconciler (Membership Reconciler #14) translates them into a Pending condition + requeue at the call site; the validator itself does not bake in retry semantics. Noted on Membership Reconciler #14.
  • No MissingNamespace / MissingInstanceLabel variants on the error enum. Those are caller pre-conditions: a MembershipReference cannot be constructed without all four borrowed strings.

CRD / API Compatibility

  • LldapUser CRD: the x-kubernetes-validations message for the password-secret rule changes wording. The rule expression and rejection semantics are unchanged; only the human-readable error text differs. No stored-version migration; existing valid resources remain valid.
  • No RBAC, webhook, status-field, or finalizer changes.
  • New public API in lldap-operator-reconciler: MembershipReference, UserCandidate, GroupCandidate, MembershipValidationError, validate_membership_references. The crate was previously empty, so this is additive only.

Risks and Mitigations

  • CEL rule drift between Rust source and chart YAML. The crd_yaml_snapshot test compares generated CRD YAML against the committed chart files and fails CI on divergence; cargo run -p lldap-operator-crds --bin crdgen regenerates them.
  • The reconcile-time membership check is not yet wired. The LldapMembership documentation states the constraint and Membership Reconciler #14 carries the wiring task with a comment pointing implementers at validate_membership_references and the UserNotFoundPending/requeue translation.
  • CEL rule semantic regressions. Mitigated by cel-interpreter-based unit tests that evaluate each rule against constructed positive and negative self payloads, so any future edit that breaks the intended semantics fails tests rather than silently shipping.

Rollback Strategy

Revert this commit. The CEL message change is cosmetic at the API-server layer — older chart versions ship the previous message text and continue to enforce identical semantics. The new lldap-operator-reconciler symbols have no in-tree callers yet, so rolling them back removes nothing operationally.

Verification

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo clippy --workspace --all-targets --tests -- -D warnings — clean
  • cargo test --workspace — 103 tests pass (67 in lldap-operator-crds including 13 new CEL eval tests; 8 in lldap-operator-reconciler covering every MembershipValidationError variant and the IntoIterator signature)
  • cargo changeset verify --base main — all changed crates have changeset coverage
  • grep 'passwordSecretRef is required' charts/lldap-operator/crds/lldap-user.yaml confirms the refined message in the generated CRD YAML

Refines the LldapUser CEL message to name the policies that require a
passwordSecretRef ('Manage' or 'InitialOnly'), and adds a stateless
validate_membership_references in lldap-operator-reconciler for matching
an LldapMembership's user/group against caller-supplied candidates.

Closes #4
@lukidoescode lukidoescode merged commit 3ce727e into main May 21, 2026
9 checks passed
@lukidoescode lukidoescode deleted the lwagner/4-cross-field-crd-validation branch May 21, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cross-Field CRD Validation

1 participant