NK-WP-0054-T05: post-split restore drill passed as admin; SI-X1 retired
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:
parent
87e57aa710
commit
436c5fe585
16 changed files with 412 additions and 45 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue