net-kingdom/sso-mfa/k8s/lldap/GROUP-MAINTENANCE.md
tegwick 9442858535
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Complete NK-WP-0051 with attended member and refusal evidence
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 19:23:19 +02:00

136 lines
6.8 KiB
Markdown

# 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
```bash
./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:
```bash
./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:
```bash
./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.
## Sign-in diagnosis: one uid's groups
To see which directory groups a login will carry (NK-WP-0051,
[`docs/sign-in-diagnosis.md`](../../../docs/sign-in-diagnosis.md)):
```bash
python3 lldap_admin.py groups --user tegwick \
--root /absolute/private/custody
```
It is read-only and has no `--apply`. It prints `uid` and `groups` (sorted
display names, never DNs, mail or credentials). An unknown uid is refused, not
reported as an empty list. Matching the names to an OpenBao role stays a
metadata read in ops-mason.
## 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
```bash
../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](../../../docs/legacy-lldap-lifecycle-retirement.md)
and [verification audit](../../../docs/verification-audit-2026-09-05.md) 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.