diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index a9c8ef4..054bead 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -44,7 +44,7 @@ | task | NK-WP-0022-T01 | progress | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | | task | NK-WP-0022-T02 | done | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | | task | NK-WP-0022-T03 | done | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | -| task | NK-WP-0022-T04 | wait | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | +| task | NK-WP-0022-T04 | done | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | | task | NK-WP-0022-T05 | wait | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | | task | NK-WP-0022-T06 | progress | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | | task | NK-WP-0022-T07 | wait | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | diff --git a/docs/railiance01-coulombcore-cutover-inventory-2026-07-28.md b/docs/railiance01-coulombcore-cutover-inventory-2026-07-28.md index 49f6ea9..f1a9137 100644 --- a/docs/railiance01-coulombcore-cutover-inventory-2026-07-28.md +++ b/docs/railiance01-coulombcore-cutover-inventory-2026-07-28.md @@ -165,11 +165,30 @@ logs were removed. CoulombCore identity writers remain scaled to zero to prevent divergence while the railiance01 direct-resolution conformance gates run; no PVC, Secret, database, or workload declaration was deleted. +## Runtime dependency reconciliation + +`sso-mfa/k8s/verify-identity-cutover-dependencies.sh` now provides the +repeatable T04 negative search. It decodes Kubernetes Secret values only in +process memory and reports resource/key locators rather than values. + +The live railiance01 run passed all gates: + +- no Secret, ConfigMap, Deployment, or StatefulSet in `sso`, `mfa`, + `user-engine`, or `databases` references CoulombCore, its IP, or the public + LLDAP/privacyIDEA endpoints; +- KeyCape resolves LLDAP, Authelia, and privacyIDEA through cluster-local + service names and retains both user-engine and rapp-qonto client declarations; +- privacyIDEA's migrated LLDAP resolver is cluster-local; +- the KeyCape signing-key fingerprint matches CoulombCore; +- LLDAP, Authelia, KeyCape, privacyIDEA, and user-engine deployments are Ready. + +TLS-preserving direct resolution to `92.205.62.239` returned HTTP 200 with +successful certificate verification for `auth`, `login`, `lldap`, `pink`, +and `pink-account` under `coulomb.social`. + ## Required next evidence -1. Reconcile cluster-local configuration and prove that railiance01 has no - runtime dependency on CoulombCore. -2. Exercise platform-root and Binky login/MFA using TLS-preserving direct +1. Exercise platform-root and Binky login/MFA using TLS-preserving direct resolution to railiance01. -3. Verify KeyCape service-client flows and negative authorization probes. -4. Move LLDAP and privacyIDEA DNS only after those gates pass. +2. Verify KeyCape service-client flows and negative authorization probes. +3. Move LLDAP and privacyIDEA DNS only after those gates pass. diff --git a/sso-mfa/k8s/verify-identity-cutover-dependencies.sh b/sso-mfa/k8s/verify-identity-cutover-dependencies.sh new file mode 100755 index 0000000..e4e6086 --- /dev/null +++ b/sso-mfa/k8s/verify-identity-cutover-dependencies.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# Verify that the active identity stack has no runtime dependency on the +# retired/source host. Secret values are decoded only in process memory and +# never printed; failures report resource and key locators only. +set -euo pipefail + +KUBECTL="${KUBECTL:-kubectl}" +SOURCE_HOST_MARKER="${SOURCE_HOST_MARKER:-coulombcore}" +SOURCE_IP="${SOURCE_IP:-92.205.130.254}" +NAMESPACES="${NAMESPACES:-sso mfa user-engine databases}" + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +for namespace in $NAMESPACES; do + for kind in configmap secret deployment statefulset; do + "$KUBECTL" -n "$namespace" get "$kind" -o json \ + >"$tmp/${namespace}-${kind}.json" + done +done + +python3 - "$tmp" "$SOURCE_HOST_MARKER" "$SOURCE_IP" <<'PY' +import base64 +import json +import pathlib +import sys + +root = pathlib.Path(sys.argv[1]) +patterns = { + "source-host": sys.argv[2].lower(), + "source-ip": sys.argv[3].lower(), + "public-lldap": "lldap.coulomb.social", + "public-privacyidea": "pink.coulomb.social", + "public-privacyidea-account": "pink-account.coulomb.social", +} +failures = [] + +for path in sorted(root.glob("*.json")): + namespace, kind = path.stem.rsplit("-", 1) + for item in json.loads(path.read_text()).get("items", []): + name = item["metadata"]["name"] + if kind in {"configmap", "secret"}: + for key, raw in (item.get("data") or {}).items(): + try: + value = ( + base64.b64decode(raw).decode("utf-8", "replace") + if kind == "secret" + else raw + ) + except Exception: + continue + lowered = value.lower() + hits = [label for label, needle in patterns.items() if needle in lowered] + if hits: + failures.append( + f"{kind}/{namespace}/{name}:{key} matches={','.join(hits)}" + ) + else: + lowered = json.dumps(item.get("spec", {}), sort_keys=True).lower() + hits = [label for label, needle in patterns.items() if needle in lowered] + if hits: + failures.append( + f"{kind}/{namespace}/{name} matches={','.join(hits)}" + ) + +if failures: + print("stale_dependencies=found") + for failure in failures: + print(failure) + raise SystemExit(1) +print("stale_dependencies=absent") +PY + +keycape_config="$( + "$KUBECTL" -n sso get secret keycape-config \ + -o jsonpath='{.data.config\.yaml}' | base64 -d +)" + +for expected in \ + lldap.sso.svc.cluster.local \ + authelia.sso.svc.cluster.local \ + privacyidea.mfa.svc.cluster.local \ + user-engine \ + rapp-qonto +do + if [[ "$keycape_config" != *"$expected"* ]]; then + echo "keycape_expected_dependency_missing=$expected" >&2 + exit 1 + fi +done +unset keycape_config +echo "keycape_dependencies=cluster-local-and-complete" + +pg_pod="$( + "$KUBECTL" -n databases get pod \ + -l 'cnpg.io/cluster=net-kingdom-pg,role=primary' \ + -o jsonpath='{.items[0].metadata.name}' +)" +resolver_class="$( + "$KUBECTL" -n databases exec "$pg_pod" -- \ + psql -X -A -t -U postgres -d privacyidea_db -c \ + "select case + when lower(\"Value\") like '%lldap.sso.svc.cluster.local%' + then 'cluster-local' + else 'other' + end + from resolverconfig + where \"Key\"='LDAPURI';" 2>/dev/null +)" +if [[ "$resolver_class" != "cluster-local" ]]; then + echo "privacyidea_lldap_resolver=$resolver_class" >&2 + exit 1 +fi +echo "privacyidea_lldap_resolver=cluster-local" + +for target in \ + sso/lldap \ + sso/authelia \ + sso/keycape \ + mfa/privacyidea \ + user-engine/user-engine +do + namespace="${target%/*}" + name="${target#*/}" + ready="$( + "$KUBECTL" -n "$namespace" get deployment "$name" \ + -o jsonpath='{.status.readyReplicas}' + )" + if [[ "${ready:-0}" -lt 1 ]]; then + echo "deployment_not_ready=$target" >&2 + exit 1 + fi +done +echo "identity_deployments=ready" +echo "identity_cutover_dependencies=PASS" diff --git a/workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md b/workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md index 1ae61c2..2cf9491 100644 --- a/workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md +++ b/workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md @@ -141,7 +141,7 @@ CoulombCore writers remain frozen against divergence. ```task id: NK-WP-0022-T04 -status: wait +status: done priority: high state_hub_task_id: "33591ee3-586e-4ac6-82e6-0ab4642cea5e" ``` @@ -155,6 +155,15 @@ points back to CoulombCore. Done when an automated dependency graph and negative search prove there are no hidden CoulombCore runtime dependencies. +2026-07-28: added +`sso-mfa/k8s/verify-identity-cutover-dependencies.sh`. Its live railiance01 +run found no CoulombCore IP/host or public cross-cluster endpoint in decoded +identity Secrets, ConfigMaps, Deployments, or StatefulSets. It verified +cluster-local KeyCape and privacyIDEA dependencies, retained user-engine and +rapp-qonto clients, and Ready deployments. KeyCape signing fingerprints +match. TLS-preserving direct resolution returned HTTP 200 with valid +certificates for `auth`, `login`, `lldap`, `pink`, and `pink-account`. + ## T05 - Run full pre-cutover identity conformance ```task