# ============================================================================= # L2 BEHAVIORAL — identity config as code (R-IDP-03, R-IDP-04, R-IDP-06) [RUNNABLE] # Spec: docs/15 identity-and-access; ADR-0014. Impl: platform/keycloak/keycloak-realm-import.yaml. # # GIVEN the `homelab` realm (groups platform-admins/media-users/viewers, OIDC clients, brute-force, # and the conditional MFA browser flow) is declared as a KeycloakRealmImport in git, # WHEN the Keycloak Operator reconciles it # -> THEN the import reaches Done=True — realm/groups/clients/roles + the MFA flow are applied # PURELY from git (R-IDP-03 config-as-code; R-IDP-04 MFA flow bound; R-IDP-06 groups/roles for # least-privilege). No console clicking. # # Goes GREEN once Keycloak is up + the realm client-secret hand-off (keycloak-realm-clients) is filled # so the placeholder resolves and the import completes. chainsaw v0.2.12: script + block contains check. # # NOT PROVEN HERE — READ THIS BEFORE TRUSTING A GREEN RUN (added 2026-09-06). # Done=True means the import JOB EXITED 0. It does NOT mean the placeholders resolved: when the # operator cannot resolve one it stores the LITERAL `(env:...)` text as the client secret and the # import still reports success, then rests at Done=True / Started=False and never re-runs. THIS # TEST PASSED THROUGHOUT the 2026-09-06 outage in which all 12 prod clients held the literal # placeholder and every OIDC login on the cluster failed. Done=True is a necessary, badly # insufficient condition. # The sufficient check is tests/l2-behavioral/keycloak-client-secrets-match-git, which compares # each client's STORED secret against the SOPS-declared one; the static half is # tests/l0-static/keycloak-client-secret-reconciler. Do not "simplify" by folding those into this # test: the whole lesson is that the import's own success signal is not evidence. # ============================================================================= apiVersion: chainsaw.kyverno.io/v1alpha1 kind: Test metadata: name: keycloak-config-as-code labels: conformance.home.k3s/R-IDP-03: "true" conformance.home.k3s/R-IDP-04: "true" conformance.home.k3s/R-IDP-06: "true" annotations: conformance.home.k3s/requirements: "R-IDP-03, R-IDP-04, R-IDP-06" conformance.home.k3s/layer: "L2" conformance.home.k3s/safety-critical: "false" conformance.home.k3s/executable: "true" conformance.home.k3s/intent: >- The homelab realm (groups, clients, MFA flow) reconciles to Done purely from the git-managed KeycloakRealmImport — no manual console configuration. spec: timeouts: exec: 5m steps: - name: realm-import-reconciles-from-git try: - script: content: | set -u # Poll the KeycloakRealmImport until the operator reports Done=True. for i in $(seq 1 30); do st="$(kubectl -n keycloak get keycloakrealmimport homelab \ -o jsonpath='{.status.conditions[?(@.type=="Done")].status}' 2>/dev/null || true)" echo "poll $i: Done=$st" if [ "$st" = "True" ]; then echo "REALM_IMPORT_DONE_OK" break fi sleep 10 done check: (contains($stdout, 'REALM_IMPORT_DONE_OK')): true - name: privileged-group-and-mfa-flow-present try: - script: content: | set -u # The realm import declares the browser-mfa flow + platform-admins group. Confirm the # realm CR / import spec carries them (declared-in-git evidence; the live realm state is # exercised by the login flows). flow="$(kubectl -n keycloak get keycloakrealmimport homelab \ -o jsonpath='{.spec.realm.browserFlow}' 2>/dev/null || true)" grp="$(kubectl -n keycloak get keycloakrealmimport homelab \ -o jsonpath='{.spec.realm.groups[*].name}' 2>/dev/null || true)" echo "browserFlow=$flow groups=$grp" echo "$flow" | grep -q 'browser-mfa' || { echo "FAIL: MFA browser flow not bound"; exit 1; } echo "$grp" | grep -q 'platform-admins' || { echo "FAIL: platform-admins group missing"; exit 1; } echo "MFA_AND_GROUPS_OK" check: (contains($stdout, 'MFA_AND_GROUPS_OK')): true