NK-WP-0054-T05: post-split restore drill passed as admin; SI-X1 retired
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

All five NK-WP-0053 stages passed on generation 20261031T034845Z-a13dc92e2c61,
the first capture after the 2026-10-30 bind split, operated by admin. Uncounted
attempts are kept with their causes. The drill document gains the OpenBao
platform-admin login that stage 1 silently requires. The LLDAP declaration moves
ld-admin and the svc-* binds to active and admin to human-only; SI-X1 is closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
This commit is contained in:
tegwick 2026-10-31 08:31:17 +01:00
parent 87e57aa710
commit 436c5fe585
16 changed files with 412 additions and 45 deletions

View file

@ -10,43 +10,37 @@ complete: false
identities:
- name: admin
principal_type: HUMAN
conventions: [bind, service-admin]
purpose: "The human platform administrator; with the same password also the directory super-admin and the bind of four services"
purpose: "The human platform administrator; directory rights through lldap_admin on its own password and OTP; no service binds as admin (bind split 2026-10-30)"
grants:
- {mode: ADMINISTER, scope: directory, activation: STANDING, environments: [PRODUCTION]}
- {mode: USE, scope: directory, activation: STANDING, environments: [PRODUCTION], read_only: true, note: "service binds"}
- {mode: ADMINISTER, scope: directory, activation: STANDING, environments: [PRODUCTION], note: "lldap_admin membership"}
interactive_login: true
second_factor: required
custody: [delivered-secret, personal-safe]
custody: [personal-safe]
consumers:
- {name: founder, kind: person}
- {name: authelia, kind: service}
- {name: keycape, kind: service}
- {name: privacyidea, kind: service}
- {name: identity-provisioner, kind: service}
owner: "NetKingdom directory and User Engine owners"
rotation:
path: "LLDAP_LDAP_USER_PASS in lldap-secrets (Credential Management section 6)"
coupling: [authelia, keycape, privacyidea-resolver, identity-provisioner, lldap-web-ui]
path: "Human password change; no service Secret holds it"
coupling: []
status: active
recovery: {needed: true, works_with_sso_down: false, in_crisis_list: true}
- name: ld-admin
principal_type: HUMAN
conventions: [service-admin]
purpose: "LLDAP only: the directory's configured super-admin, a sealed fallback; introduced by configuration after a rehearsal"
purpose: "LLDAP only: the directory's configured super-admin (LLDAP_LDAP_USER_DN), a sealed fallback; introduced 2026-10-30"
grants:
- {mode: ADMINISTER, scope: lldap, activation: BREAK_GLASS, environments: [PRODUCTION]}
interactive_login: true
second_factor: unknown
custody: [unknown]
custody: [delivered-secret, offline-key]
consumers: []
owner: "NetKingdom directory and User Engine owners"
rotation: {path: "To be decided in NK-WP-0054-T06", coupling: []}
status: planned
rotation: {path: "LLDAP_LDAP_USER_PASS in lldap-secrets and the LLDAP custody file (apply-service-secrets.py named rotation)", coupling: [lldap]}
status: active
- name: svc-authelia
principal_type: SERVICE
conventions: [bind]
purpose: "Target: Authelia's read-only directory lookup"
purpose: "Authelia's read-only directory lookup"
grants: [{mode: USE, scope: directory, activation: STANDING, environments: [PRODUCTION], read_only: true}]
interactive_login: false
second_factor: not_applicable
@ -54,11 +48,11 @@ identities:
consumers: [{name: authelia, kind: service}]
owner: "NetKingdom directory and User Engine owners"
rotation: {path: "Secret rotation with Authelia only", coupling: [authelia]}
status: planned
status: active
- name: svc-keycape
principal_type: SERVICE
conventions: [bind]
purpose: "Target: KeyCape's read-only directory lookup"
purpose: "KeyCape's read-only directory lookup"
grants: [{mode: USE, scope: directory, activation: STANDING, environments: [PRODUCTION], read_only: true}]
interactive_login: false
second_factor: not_applicable
@ -66,11 +60,11 @@ identities:
consumers: [{name: keycape, kind: service}]
owner: "NetKingdom directory and User Engine owners"
rotation: {path: "Secret rotation with KeyCape only", coupling: [keycape]}
status: planned
status: active
- name: svc-privacyidea
principal_type: SERVICE
conventions: [bind]
purpose: "Target: the privacyIDEA resolver's read-only directory lookup"
purpose: "The privacyIDEA resolver's read-only directory lookup"
grants: [{mode: USE, scope: directory, activation: STANDING, environments: [PRODUCTION], read_only: true}]
interactive_login: false
second_factor: not_applicable
@ -78,10 +72,10 @@ identities:
consumers: [{name: privacyidea, kind: service}]
owner: "NetKingdom directory and User Engine owners"
rotation: {path: "Resolver bind password, stored encrypted in the privacyIDEA database", coupling: [privacyidea]}
status: planned
status: active
- name: svc-provisioner
principal_type: SERVICE
purpose: "Target: identity-provisioner; member of lldap_admin, the narrowest LLDAP group that can create users (rehearsal 2026-10-28)"
purpose: "identity-provisioner; member of lldap_admin, the narrowest LLDAP group that can create users (rehearsal 2026-10-28)"
grants: [{mode: ADMINISTER, scope: directory, activation: STANDING, environments: [PRODUCTION], note: "lldap_admin; exception SI-X6"}]
interactive_login: false
second_factor: not_applicable
@ -89,16 +83,8 @@ identities:
consumers: [{name: identity-provisioner, kind: service}]
owner: "NetKingdom directory and User Engine owners"
rotation: {path: "Secret rotation with the provisioner only", coupling: [identity-provisioner]}
status: planned
status: active
exceptions:
- id: SI-X1
rules: [SI-1, SI-2, SI-3, SI-4, SI-9, NAMING]
identity: admin
reason: "One account is the human, the super-admin and the shared bind of four services, and its password is in a person's safe and in a Kubernetes Secret"
owner: "Founder"
expires: 2027-01-31
compensating_control: "Web UI restricted by IP allowlist; OTP on the human login"
elimination_path: "NK-WP-0054-T05: svc-* binds, a provisioner account, ld-admin as the configured super-admin, admin human-only"
- id: SI-X6
rules: [SI-3]
identity: svc-provisioner

View file

@ -266,15 +266,15 @@ class CheckedInDeclarations(unittest.TestCase):
agent = [i for r in reports for i in r["identities"] if i["identity"] == "coding-agent"][0]
self.assertEqual((agent["result"], agent["rules"]), ("non-conformant", ["SI-14"]))
def test_the_shared_admin_credential_is_excepted_today_and_fails_after_expiry(self):
def admin(as_of):
reports, _ = V.run(self.paths(), as_of)
return [i for r in reports for i in r["identities"] if r["service"] == "lldap" and i["identity"] == "admin"][0]
today = admin(NOW)
self.assertEqual(today["result"], "excepted")
self.assertEqual(set(today["excepted"]), {"SI-1", "SI-2", "SI-3", "SI-4", "SI-9", "NAMING"})
after = admin(dt.date(2027, 2, 1))
self.assertEqual(after["result"], "non-conformant")
def test_the_bind_split_retired_the_shared_admin_exception(self):
# SI-X1 closed 2026-10-31 (NK-WP-0054-T05): admin is human-only, each service binds as its own account.
reports, _ = V.run(self.paths(), NOW)
lldap = [r for r in reports if r["service"] == "lldap"][0]
results = {i["identity"]: i["result"] for i in lldap["identities"]}
self.assertEqual(results["admin"], "conformant")
self.assertNotIn("planned", results.values())
doc = V.load_declaration(DECLARATIONS / "lldap.yaml")
self.assertNotIn("SI-X1", [e["id"] for e in doc["exceptions"]])
def test_no_declaration_claims_owner_confirmation_yet(self):
for path in self.paths():