net-kingdom/sso-mfa/k8s/lldap/GROUP-MAINTENANCE.md
tegwick ab25dde80d
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
security: repair LDAP group callers and fail incomplete T07 acceptance
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-14 01:17:35 +02:00

6.3 KiB

Owner-invoked LLDAP group maintenance

Source repair: NK-WP-0049-T02, 2026-10-14. The shell entry points now delegate to lldap_admin.py, which reuses the existing identity-provisioner directory operations and service-group validation. This is the bootstrap/maintenance path, not a new account lifecycle service, credential broker or authority boundary.

Scope and custody

An authorized IAM custodian supplies an explicit private temporary custody root outside Git. The root and lldap/ directory must be owned by the caller and mode 0700; lldap/secrets.env must be an owned, single-link, non-symlink regular file with mode 0600. The existing custody reader parses LLDAP_LDAP_USER_PASS literally. Do not add shell quotes, export, or executable commands. Other generated fields may remain in that file; this tool does not rewrite it.

Credential ownership/routing and an exact live change approval remain required. The adapter does not fetch Kubernetes Secrets, use an ambient admin password, create accounts or credentials, invoke curl/kubectl, or install a permanent key. A preview digest is an observation binding, not an approval, caller identity or runtime isolation proof. Use the owner's protected execution context; do not hand the directory admin credential to an unisolated agent.

The default origin is https://lldap.coulomb.social. An explicit literal 127.0.0.1 or [::1] origin with a port is accepted for an owner-approved forward or isolated fixture. localhost, arbitrary hosts, HTTP on the public host, userinfo, paths, queries and fragments are refused. TLS uses system trust; redirects and ambient proxies are disabled. Credentials stay in Python memory and the HTTP body/header, never in child-process arguments. Provider errors are contained; output has fixed failure phases, not raw responses.

Bootstrap: only three empty groups

./bootstrap-users.sh --root /absolute/private/custody

The default is a nonmutating preview. It names missing net-kingdom-users, net-kingdom-admins and activity-core-operators groups, with a snapshot_sha256. After reviewing that exact operation under the existing live approval, apply using the preview digest:

./bootstrap-users.sh --root /absolute/private/custody \
  --apply --expected-digest REVIEWED_PREVIEW_SHA256

Existing groups, IDs, users and memberships must survive unchanged. This command only creates missing groups without members. It cannot grant platform-admin membership, reset a password or reconstruct missing users after data loss.

App-scoped membership

Ordinary account onboarding stays in User Engine / identity-provisioner. To preview an exact service-group change for an existing directory identity:

./manage-group-members.sh add --user agent-codex --group activity-core-operators \
  --root /absolute/private/custody
./manage-group-members.sh remove --user agent-codex --group activity-core-operators \
  --root /absolute/private/custody
./manage-group-members.sh list --group activity-core-operators \
  --root /absolute/private/custody

Add/remove require --apply --expected-digest REVIEWED_PREVIEW_SHA256 to mutate. List is read-only and emits only member identifiers. Group and user must already exist; this path never creates either. Tenant, platform (net-kingdom-), suspension (netkingdom-) and LLDAP administrative groups are refused by the existing service-group validator before login. Use their respective owners for those operations; the old unrestricted membership helper is not a fallback.

Preservation, interruption and recovery

The digest binds origin, operation, target and the observed directory users, groups, IDs and memberships. Apply re-reads before its first write and refuses stale state. Final readback must equal precisely the intended change, including unchanged unrelated memberships. An already-satisfied change is a verified no-op.

LLDAP does not provide a cross-query compare-and-swap transaction here. The owner must hold an exclusive maintenance window across preview/apply; two reads are not a lock. A failure during multiple group creations can leave partial state. There is no blind rollback or retry and no group deletion. On failure retain the non-secret receipt, inspect current state and reconcile under the owner before a new reviewed preview. Do not infer failure means nothing changed.

T07 is a structural diagnostic

../verify-t07.sh --root /absolute/private/custody

This checks actual directory structure and reports missing baseline groups, user count and break-glass account/group presence without declaring them usable. It does not read KeyCape's configuration Secret, execute inside a pod, infer OIDC claims or treat a reachable portal as user acceptance. Even complete structural observations return 2, with native_acceptance: unverified. Invalid input, authentication/transport failure or malformed responses return 1. Both mean the old T07 completion gate is unsatisfied. Do not use this diagnostic alone to close user lifecycle, MFA, session revocation, client access or recovery tasks.

Use current owner-specific issuer/consumer checks and actual authorized human lifecycle/login/recovery receipts for those properties. The legacy lifecycle retirement notice and verification audit describe these limits. openbao-client-config.py remains the specific OpenBao client configuration checker; a configuration check is not actual client login.

Verification and live limits

Regression tests cover private input, refusals, previews, state drift, preservation and malformed/failed readbacks. tests/lldap_groups_native_fixture.py exercises the actual adapter against an immutable existing local LLDAP image, with synthetic credentials, loopback-only publication and disposable storage. It removes its own fixture container and private files. The pinned native test image and results are in Custodian's lldap-group-maintenance.json receipt.

Those receipts do not prove the currently deployed directory version, current credential validity, attended authority, independent recovery or live enforcement of earlier identity-provisioner source fixes. NK-WP-0049-T03 retains native owner acceptance, and T02 retains incident-cutover/full caller-inventory work.