net-kingdom/workplans/NK-WP-0023-user-engine-portal-platform-integration.md
codex 82452d655f
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
fix(workplans): adopt ADR-007 derived identifiers for unregistered records
These workplans exist only in the retired local hub. Their random pre-ADR-007
identifiers are refused by C-06 as stale references, so they cannot be
registered. Deriving from the canonical record id takes no identity from
anything: central does not hold them and the old ids die with the cache.

Records central already holds were deliberately left untouched.

Refs CUST-WP-0068-T06

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-25 20:14:28 +02:00

313 lines
15 KiB
Markdown

---
id: NK-WP-0023
type: workplan
title: "Integrate and deploy the user-engine onboarding portal"
domain: infotech
repo: net-kingdom
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-07-27"
updated: "2026-07-30"
depends_on:
- USER-WP-0020
- KEY-WP-0004
state_hub_workstream_id: "fdb3deee-06c3-5d12-91ce-9c50b2ac0618"
---
# NK-WP-0023 - user-engine portal platform integration
Provide the NetKingdom-owned adapters and production integration required by
`USER-WP-0020`, using the Binky tenant-admin onboarding from `KEY-WP-0004-T02`
as the first acceptance case.
## T01 - Define source-of-truth and provisioning contracts
```task
id: NK-WP-0023-T01
status: done
priority: high
state_hub_task_id: "05d69347-ec34-59b7-a107-80db015ef0e6"
```
Ratify lifecycle sequencing and compensation across user-engine, LLDAP,
KeyCape, privacyIDEA, flex-auth, email verification, audit, and outbox
delivery. user-engine owns user-domain and membership intent; NetKingdom IAM
owns credentials, authentication factors, coarse authentication claims, and
provider subjects. Define externally-provisioned/federated ownership metadata
now so later enterprise directories do not require a domain rewrite.
## T02 - Implement the NetKingdom identity provisioning adapter
```task
id: NK-WP-0023-T02
status: done
priority: high
state_hub_task_id: "22113719-905b-54df-9ecb-0a0c456e0d35"
```
Implement idempotent create/link/suspend/reactivate/deprovision operations for
the lightweight LLDAP + privacyIDEA stack behind user-engine's
`IdentityProvisioningPort`. Use scoped service identity and approved secret
transport. Never expose directory admin credentials to the browser or
user-engine domain. Add reconciliation, retry, compensation, and drift
reporting rather than assuming a distributed transaction.
2026-07-28 implementation evidence: `identity-provisioner` now implements the
provider-neutral lifecycle API over LLDAP and runs as a non-root, internal-only
service in `sso`. Its directory credential remains confined to that pod;
user-engine receives only a namespace-local bearer token. NetworkPolicy allows
only user-engine to call it and only LLDAP/DNS egress. A live create/replay/delete
conformance run returned `resumed: false`, then `resumed: true`, and removed the
disposable subject. Reconciliation, compensation, and drift reporting remain.
2026-07-29 completion: image `identity-provisioner:12cdef5` adds read-only
drift inspection, active convergence of adapter-owned tenant groups and
suspension state, replay-safe deprovisioning, and best-effort compensation
when initial group projection fails. User-engine exposes these operations
through its provider-neutral port and retries one transient failure only under
the mandatory idempotency key. A disposable live probe proved clean initial
state, detected induced suspension drift, reported both the unexpected group
and status mismatch, removed the drift, returned to `in_sync`, deprovisioned,
and accepted the repeated deletion. The probe identity was removed.
## T03 - Integrate KeyCape login, claims, and MFA handoffs
```task
id: NK-WP-0023-T03
status: done
priority: high
state_hub_task_id: "5721c5f9-6161-522c-a439-95f2613b5460"
```
Register the portal OIDC client with authorization code + PKCE, configure
callback/logout routes, verify issuer/audience/tenant/assurance claims, and
provide safe password and MFA enrollment/recovery handoffs. Preserve
platform-root separation and ensure tenant administration never implies
platform authority.
2026-07-27 implementation evidence: `user-engine-portal` is registered as a
public static KeyCape client with an exact callback and S256 PKCE. The portal
is live at `https://users.92-205-62-239.nip.io`, has a trusted ACME
certificate, begins the KeyCape/Authelia flow, and rejects an unregistered
callback. KeyCape image `key-cape:e8b4ede` maps an unambiguous
`tenant:<kind>:<slug>:users|admins` directory group envelope into the tenant
and coarse tenant-admin claims while refusing ambiguous multi-tenant mapping.
2026-07-28 update: KeyCape image `key-cape:909bb32` fails closed for directory
subjects in `netkingdom-suspended`: authorization-code exchange consumes the
code and returns a safety rejection, while `/userinfo` rejects previously
issued tokens. Unit and profile suites pass. Live Binky MFA acceptance remains.
2026-07-28 password-handoff evidence: NetKingdom now serves a short-lived,
single-use password setup form at the canonical KeyCape host. The portal
receives only an opaque HTTPS handoff URL and offers both initial creation and
renewal for linked identities; it never handles or stores the password. A live
disposable-user run proved prior-link revocation (400), current-link rendering
(200), non-consuming validation failure (400 then 200), successful OPAQUE
password registration and LLDAP login (200), replay denial (400), and cleanup.
The helper image is digest-pinned and the service is non-root, read-only, and
NetworkPolicy restricted. Binky's user-chosen password and MFA acceptance
remain.
2026-07-29 correction: live platform-root access exposed that the established
`net-kingdom-admins` bootstrap group was not mapped into the newer IAM
envelope. KeyCape `90a2078` now maps that group explicitly to
`tenant:platform` plus `platform-operator`, with regression coverage, while
tenant-admin mappings remain tenant scoped. The fixed image is Ready on
railiance01.
The subsequent live MFA callback exposed a missing destination-side
NetworkPolicy: user-engine allowed egress to KeyCape, but the SSO default deny
had no matching ingress. Commit `8e7229a` adds the namespace-and-pod-scoped
`:8080` rule plus verifier coverage. After applying it, an in-pod discovery
request from user-engine to KeyCape returns HTTP 200.
2026-07-29 complete: the live Binky browser flow verified the canonical
issuer/audience, Binky-only tenant/groups, tenant-admin role, AAL2
password-plus-OTP assurance, and denial of platform/Coulomb administration.
Password and MFA handoffs remain on NetKingdom-owned surfaces.
The live journey also exposed uid-only first-factor input. Authelia now uses
its documented LLDAP filter accepting either `uid` or `mail` while continuing
to resolve the stable uid identity. The updated ConfigMap passed server-side
validation and the restarted deployment is Ready without configuration
errors, so future users may enter either `bernd.worsch` or the full email.
## T04 - Integrate authorization, email, audit, and events
```task
id: NK-WP-0023-T04
status: done
priority: high
state_hub_task_id: "efad8829-5d75-5ea1-9564-1618b1846525"
```
Define and implement flex-auth resources/actions for self, tenant-admin, and
platform-admin operations; route verification/invitation email without making
mailbox ownership an authorization fact; correlate user-engine, IAM,
authorization, and platform audit records; and connect durable outbox
delivery with replay and dead-letter evidence.
## T05 - Deploy on reef-railiance
```task
id: NK-WP-0023-T05
status: done
priority: high
state_hub_task_id: "0c454447-c14f-586b-835b-4f00d43943d0"
```
Package the portal as a managed platform workload on the default
`rail-kubernetes` path on `reef-railiance`, with Postgres, OpenBao-backed
runtime references, NetworkPolicies, TLS ingress, backups, observability,
resource limits, rollout/rollback, and availability evidence. Do not place
this stateful platform control surface on scale-to-zero Knative.
Implementation evidence (2026-07-27): the internal-only portal foundation is
running on `reef-railiance` as `Deployment/user-engine` with dedicated healthy
`Cluster/user-engine-pg`, default-deny NetworkPolicies, immutable directly
imported image `user-engine:portal-c27012a`, and successful `/readyz` probes.
Public ingress remains intentionally absent until T03 supplies the verified
KeyCape OIDC edge. Forgejo OCI publication is pending restoration of the
OpenBao package-publisher capability; no credential was bypassed or exposed.
2026-07-28 update: public TLS ingress and KeyCape OIDC are active. The current
`user-engine:portal-e23674d` deployment is healthy with the dedicated CNPG
cluster, and the companion `identity-provisioner:dbf7cfd` deployment is
healthy behind namespace-scoped credentials and default-deny policy. Registry
publication, backup/restore evidence, metrics, and automated rollback remain.
2026-07-29 operability increment: deployed protected Prometheus metrics in
`user-engine:portal-2bcda7f`; an unmarked request is denied with 403 while the
trusted workload marker receives readiness plus 18 aggregate record series
without identity values. Added a daily atomic `pg_dump` schedule using the
existing CNPG application Secret, a restricted backup PVC, checksum/catalog
validation, and seven-day retention. The isolated restore drill verified the
latest archive in an ephemeral PostgreSQL instance with four public tables and
one migration row. `verify-operability.sh --exercise-rollback` rolled back to
`portal-746bf21`, verified readiness, and restored the declared image.
Machine-readable evidence is in
`docs/evidence/user-engine-operability-2026-07-29.json`.
The remaining production gaps are registry publication and an independently
custodied/off-node backup copy; the current PVC shares reef-railiance's single
failure domain, so T05 remains in progress.
2026-07-30 follow-up: both former gaps are implemented. The activity-core
ExternalSecret boundary published the portal and provisioner images to
`forgejo.coulomb.social` without exposing the package credential. Anonymous
digest inspection passed, and both live Deployments now pull immutable public
digests. The prior verified portal image is also published by digest;
rollback/roll-forward passed without node-local image reliance.
The established Option A activity-core lane produced and uploaded
`user-engine-pg-user_engine-20260729T220653Z.dump.age` to independent
Nextcloud custody. The plaintext dump was removed immediately and only
non-secret sizes and the artifact name were recorded. The local isolated
restore drill already proves the same logical dump/restore path.
2026-07-30 completion: the approved ops-mason construction plan established
the exact OpenBao KV path `platform/workloads/user-engine/runtime`, an
exact-path read policy, and separate bounded AppRoles for the `user-engine`
and `sso` External Secrets consumers. Namespace-restricted stores and three
ExternalSecrets preserve the existing application Secret interfaces.
A coordinated rotation rehearsal changed both runtime values at the
authoritative path, waited for all three derived Secrets to converge, and
returned both Deployments to Ready. Post-rotation checks proved matching
provisioner tokens, exact-path `read`, sibling-path `deny`, and a complete
disposable provision/drift/reconcile/deprovision lifecycle. No credential
value entered recorded output. All T05 acceptance criteria are complete.
## T06 - Prove role-scoped administration and failure safety
```task
id: NK-WP-0023-T06
status: done
priority: high
state_hub_task_id: "f5f0d5d0-09fa-59de-b83f-7a6d4ff736a8"
```
Run end-to-end conformance for registration, login, MFA, tenant creation,
first-admin bootstrap, invitations, suspension/reactivation, cross-tenant
denial, platform-admin-only actions, provider outages, replay/idempotency,
backup restore, and reconciliation after partial failure.
2026-07-28 evidence: deployed-path health, service authentication, directory
creation, replay-safe linking, tenant user/admin group creation, and cleanup
passed. The test also detected and corrected newline handling for mounted
Kubernetes secrets before any identity was created. The broader negative,
outage, suspension, restore, and browser/MFA matrix remains.
2026-07-29 increment: provider-path conformance now also covers
machine-readable drift detection, automated repair, compensation semantics,
transient retry, and replay-safe deletion. The broader browser, authorization,
provider-outage, and restore matrix remains open.
2026-07-29 operability evidence adds an isolated database restore and a
reversible deployment rollback/roll-forward cycle. Backup and metrics access
are secret-free in recorded evidence. Independent disaster-recovery custody
and the remaining browser/provider-outage matrix keep T06 open.
## T07 - Complete KEY-WP-0004 through the reusable portal
```task
id: NK-WP-0023-T07
status: done
priority: high
state_hub_task_id: "04664946-c38d-50e8-bc3f-5533801494d8"
```
Use the production portal to onboard
`bernd.worsch@binky-hedgehog.com` into `tenant:friendly:binky`, complete
OIDC/PKCE + MFA, and verify the Binky-only tenant-admin token and lifecycle
controls. Publish only non-secret evidence to `KEY-WP-0004-T02/T07`, then
finish that workplan.
2026-07-28: the deployed versioned portal API created the Binky tenant-admin
user and membership and provisioned/linked its LLDAP identity. The provider
correctly reports `password_setup_required`. During this flow a newline in the
mounted trusted-proxy Secret proved HTTP-incompatible; user-engine `0ef2ae5`
normalizes runtime transport whitespace, has 105 passing tests, and is live.
LLDAP SMTP is not configured, so first-password handoff and MFA/claim
acceptance remain rather than falling back to an operator-set password.
2026-07-29 complete: the reusable portal issued the password handoff, the
Binky user selected the password and enrolled TOTP, and a fresh login produced
Binky-only groups, roles `user` + `tenant-admin`, and AAL2 `pwd` + `otp`.
Administration of both `tenant:platform` and `tenant:coulomb` was denied.
## T08 - Document enterprise integration extension points
```task
id: NK-WP-0023-T08
status: done
priority: medium
state_hub_task_id: "83a00e26-aab9-51f1-afb4-cb5d354ab436"
```
Document later adapters for customer IdPs/directories, SAML/OIDC federation,
SCIM, JIT provisioning, directory group mapping, customer-owned offboarding,
and conflict/freshness rules. Keep `NK-WP-0011` demand-triggered; this task
defines compatibility seams, not enterprise implementation.
2026-07-29: added
`docs/user-engine-enterprise-identity-extension-points.md`, covering OIDC/SAML
federation, SCIM, JIT, group mapping, provenance, source ownership,
conflict/freshness rules, offboarding, failure behavior, and demand triggers
without starting the demand-gated enterprise implementation.
## Milestone closure (2026-07-30)
The live Binky MVP consumes verified KeyCape claims, enforces tenant/platform
role separation through user-engine's authorization port, correlates durable
audit/outbox records, and keeps password and MFA interactions on
NetKingdom-owned surfaces. Role-scoped administration, platform and unrelated
tenant denial, AAL2 login, drift/reconciliation, replay-safe deletion, restore,
rollback, secret rotation, and dependency failure/recovery are proven.
Replacing the pre-production authorization adapter with a deployed flex-auth
policy package, invitation mail, operated outbox replay/dead-letter delivery,
and the broader integration failure matrix transfer explicitly to
`NK-WP-0024`; they are not claimed as part of this completed milestone.