Deploy KeyCape client MFA override for coulomb-social
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Register coulomb-social with mfaRequired: false, roll key-cape image that
honors client policy, and track NK-WP-0025 public registration orchestration.
This commit is contained in:
tegwick 2026-08-09 22:42:51 +02:00
parent 74faee98c4
commit 0e3a24d888
6 changed files with 278 additions and 12 deletions

View file

@ -13,9 +13,10 @@
| workplan | NK-WP-0009 | backlog | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md | | workplan | NK-WP-0009 | backlog | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| workplan | NK-WP-0011 | backlog | — | workplans/NK-WP-0011-enterprise-federation-saml.md | | workplan | NK-WP-0011 | backlog | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| workplan | NK-WP-0021 | finished | — | workplans/NK-WP-0021-activity-core-ops-sso-operators.md | | workplan | NK-WP-0021 | finished | — | workplans/NK-WP-0021-activity-core-ops-sso-operators.md |
| workplan | NK-WP-0022 | active | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md | | workplan | NK-WP-0022 | blocked | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| workplan | NK-WP-0023 | finished | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md | | workplan | NK-WP-0023 | finished | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md |
| workplan | NK-WP-0024 | backlog | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md | | workplan | NK-WP-0024 | active | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md |
| workplan | NK-WP-0025 | active | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| task | ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md | | task | ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md |
| task | ADHOC-2026-07-02-T02 | done | — | workplans/ADHOC-2026-07-02.md | | task | ADHOC-2026-07-02-T02 | done | — | workplans/ADHOC-2026-07-02.md |
| task | NET-WP-0020-T01 | done | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md | | task | NET-WP-0020-T01 | done | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md |
@ -58,9 +59,14 @@
| task | NK-WP-0023-T06 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md | | task | NK-WP-0023-T06 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md |
| task | NK-WP-0023-T07 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md | | task | NK-WP-0023-T07 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md |
| task | NK-WP-0023-T08 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md | | task | NK-WP-0023-T08 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md |
| task | NK-WP-0024-T01 | todo | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md | | task | NK-WP-0024-T01 | progress | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md |
| task | NK-WP-0024-T02 | todo | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md | | task | NK-WP-0024-T02 | progress | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md |
| task | NK-WP-0024-T03 | todo | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md | | task | NK-WP-0024-T03 | progress | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md |
| task | NK-WP-0024-T04 | todo | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md | | task | NK-WP-0024-T04 | progress | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md |
| task | NK-WP-0025-T01 | done | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| task | NK-WP-0025-T02 | progress | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| task | NK-WP-0025-T03 | progress | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| task | NK-WP-0025-T04 | progress | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| task | NK-WP-0025-T05 | todo | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md | | intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |
| intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md | | intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |

View file

@ -0,0 +1,106 @@
# Public registration orchestration contract
Status: implementation contract for NK-WP-0025-T02 and USER-WP-0022-T01/T02.
## Boundary
`user-engine` owns the public registration state machine. It never accepts an
LLDAP password or an LLDAP administrator credential. A verification service
proves mailbox control; the existing identity-provisioner creates the ordinary
LLDAP identity and returns a provider-owned, single-use password-setup URL.
The public browser is not an authenticated user-engine actor. It receives only
opaque registration and verification handles. A verified applicant actor is
constructed from consumed verification evidence and is authorized only for
the registration instance named by that evidence.
## Public flow
1. `POST /api/v1/public/registrations`
accepts `username`, `email`, `display_name`, `client_id`, and optional
`tenant_hint`. Client and tenant values must be registered allow-list
entries; browser-provided return URLs are rejected.
2. The response is always the same `202 Accepted` envelope for syntactically
valid requests, whether the username/email is new, duplicated, throttled,
or already pending. It contains no existence result.
3. user-engine records an expiring registration intent and emits
`registration.verification_requested` through its transactional outbox.
The mail receiver obtains the recipient and an opaque verification handle;
the general event receiver gets only `recipient_present=true`.
4. `POST /api/v1/public/registrations/verify` consumes the opaque handle.
Verification is single-use, expiry checked, purpose bound, and returns
normalized mailbox evidence bound to the registration ID and submitted
username.
5. user-engine attaches the verified email factor, completes registration,
and calls identity-provisioner using the registration ID as the stable
idempotency key. Only the baseline ordinary-user group may be requested.
6. The provisioner creates or resumes exactly one LLDAP identity, links the
provider subject to the user-engine user, and returns its single-use
password-setup URL. Provider collision is a reconciliation result, never a
second privileged identity.
7. After provider password setup, the browser starts a fresh KeyCape
authorization-code/PKCE flow for the allow-listed client. Registration
state is never accepted as an application session.
## Required state
Registration intent persistence must include:
- opaque registration ID and lifecycle status;
- normalized username and email hash, with display values separately
protected from general event/audit output;
- registered client ID and tenant;
- verification handle digest, issued/expiry/consumed timestamps, attempt
counters, and purpose;
- provisioning idempotency key and external provider result;
- allow-listed post-setup authorization target identifier, not a raw URL;
- correlation ID and redacted failure/compensation state.
No password, verification token plaintext, LLDAP admin credential, MFA seed,
or provider bearer token may enter registration state, logs, audit, State Hub,
or the general event lane.
## Validation and abuse controls
- Usernames use the LLDAP-compatible canonical grammar, length limit, and
reserved-name list; case folding happens before uniqueness checks.
- Email is normalized for comparison without provider-specific alias
rewriting. Mailbox verification proves control but does not authorize
linking to an existing user.
- Rate limits apply per source network bucket, normalized email digest, and
username digest. Responses and timing remain non-enumerating.
- CSRF applies to browser form submission; JSON callers additionally require
an allow-listed client registration and idempotency key.
- Verification handles are random, high entropy, stored only as a digest,
purpose-bound, expiring, and atomically consumed.
- Duplicate email/username, expired/replayed verification, and provider
conflict enter explicit reconciliation or terminal states. They never fall
back to auto-linking by email.
## Provider contract
The identity-provisioner request is the existing `ProvisioningRequest` plus a
validated canonical username. The response remains `ProvisioningResult` with
`external_subject`, `status`, and optional `password_setup_url`.
For public registration the adapter must guarantee:
- the same idempotency key returns the same external subject;
- baseline membership only; no tenant-admin or platform groups;
- password setup is provider-owned, single-use, and expiring;
- partial creation is resumable/reconcilable and observable without exposing
credentials;
- the external subject is linked before registration reports completion.
## Activation gates
Public registration stays disabled until all of the following pass together:
1. verification issuer and mail receiver conformance, including replay and
expiry;
2. user-engine persistence migration and anonymous-route abuse controls;
3. flex-auth policy limiting the verified applicant actor to its registration;
4. identity-provisioner canonical-username/idempotency conformance;
5. KeyCape fresh-login return flow and client allow-list checks;
6. disposable-user live tests and cleanup on railiance01.

View file

@ -54,7 +54,7 @@ spec:
# 2026-05-24: direct-imported into railiance01 k3s for the # 2026-05-24: direct-imported into railiance01 k3s for the
# bootstrap-console OIDC/MFA rollout. Use IfNotPresent while the # bootstrap-console OIDC/MFA rollout. Use IfNotPresent while the
# HTTP registry push/pull path is being cleaned up. # HTTP registry push/pull path is being cleaned up.
image: key-cape:90a2078 image: key-cape:mfa-client-20260809
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:

View file

@ -26,6 +26,10 @@ CLIENT = {
"allowedScopes": ["openid", "profile", "email", "groups"], "allowedScopes": ["openid", "profile", "email", "groups"],
"grantTypes": ["authorization_code"], "grantTypes": ["authorization_code"],
"clientType": "public", "clientType": "public",
# Ordinary coulomb.social sessions are AAL1 unless the client explicitly
# requests AAL2 through acr_values. Platform/admin clients keep their
# existing provider-driven mandatory MFA behavior.
"mfaRequired": False,
} }

View file

@ -4,11 +4,11 @@ type: workplan
title: "Cut over NetKingdom identity to railiance01 and retire CoulombCore" title: "Cut over NetKingdom identity to railiance01 and retire CoulombCore"
domain: infotech domain: infotech
repo: net-kingdom repo: net-kingdom
status: active status: blocked
owner: codex owner: codex
topic_slug: netkingdom topic_slug: netkingdom
created: "2026-07-27" created: "2026-07-27"
updated: "2026-07-28" updated: "2026-08-08"
depends_on: depends_on:
- USER-WP-0020 - USER-WP-0020
- NK-WP-0023 - NK-WP-0023
@ -313,9 +313,10 @@ Done when railiance01 is the sole authoritative identity stack, all evidence
is reconciled, and the workplan is marked finished. is reconciled, and the workplan is marked finished.
Retention gate: keep the reversible CoulombCore identity resources through at Retention gate: keep the reversible CoulombCore identity resources through at
least 2026-08-29. T08 then requires a successful railiance01 restore/restart least 2026-08-29. The workplan is blocked until that review date, when T08 can
drill and a new explicit approval for destructive deletion. Until both occur, be checked for readiness to finish. Reaching the date does not authorize
the workplan remains active with only T08 waiting. deletion or completion: T08 still requires a successful railiance01
restore/restart drill and new explicit approval for destructive deletion.
## Safety gates ## Safety gates

View file

@ -0,0 +1,149 @@
---
id: NK-WP-0025
type: workplan
title: "Provide NetKingdom self-registration and application first-login provisioning"
domain: infotech
repo: net-kingdom
status: active
owner: codex
topic_slug: netkingdom
created: "2026-08-09"
updated: "2026-08-09"
depends_on:
- NK-WP-0024
- USER-WP-0022
- KEY-WP-0008
- CSOC-WP-0003
state_hub_workstream_id: "1638183b-e2cb-4da2-92b8-d39592d62e86"
---
# NK-WP-0025 - public registration and application JIT provisioning
Provide two safe entry paths for ordinary users of NetKingdom-enabled
applications, beginning with coulomb.social:
1. an existing LLDAP user signs in through KeyCape and receives an
application-local profile on first successful login; and
2. a new person self-registers through a NetKingdom-owned flow, verifies the
address, establishes an LLDAP login, then continues through the same
application first-login path.
The design is additive to coulomb.social's local-account model. NetKingdom
owns authentication and identity lifecycle; coulomb.social/helix-forge owns
its application profile, preferences, content, and application authorization.
An OIDC subject is linked to an application profile, not merged with an
unverified email-only local account.
## T01 - Ratify identity and account-linking semantics
```task
id: NK-WP-0025-T01
status: done
priority: high
state_hub_task_id: "523d5894-6ab6-4097-a28c-0f77880744b1"
```
Record the source-of-truth split, stable key `(issuer, subject)`, optional
verified-email matching rules, explicit-link requirements for pre-existing
local accounts, unlink/recovery behavior, username/display-name handling, and
collision policy for `tegwick` and `bernd.worsch@gmail.com`.
Done when email alone cannot silently take over an existing application
account and the standalone application-account decision remains intact.
Implemented by coulomb.social's unique `(issuer, subject)` Member binding and
idempotent first-login creation. Email and username remain mutable profile
attributes and are not account-link keys.
## T02 - Provide secure LLDAP self-registration orchestration
```task
id: NK-WP-0025-T02
status: progress
priority: high
state_hub_task_id: "bc220e2b-4cd1-4dcf-b107-12ef3595fe14"
```
Expose a NetKingdom registration service behind user-engine. Validate and
normalize username/email, verify mailbox control, create the LLDAP identity
through the existing privileged provisioning adapter, establish password via
a one-time provider-owned handoff, assign only the ordinary baseline group,
and support idempotent resume/expiry/cleanup. Browsers and user-engine must
never receive LLDAP admin credentials.
Done when an unknown user can reach a normal KeyCape login without operator
provisioning and without acquiring tenant-admin or platform authority.
Implementation resumed with
`docs/public-registration-orchestration-contract.md`. The contract fixes the
anonymous/verified actor boundary, non-enumerating start response, durable
verification request, digest-only single-use verification state, baseline
group provisioning, provider-owned password setup, and fresh OIDC handoff.
The user-engine registration domain already requires verified factors and
uses the privileged provisioning adapter; the next implementation slice is
the persisted intent and verification-port API. Source implementation now
includes allow-listed, non-enumerating anonymous start and purpose-bound
verification consumption. Verification attaches an email factor only after
client/tenant/username binding checks and deliberately stops before user or
LLDAP creation. The remaining slice is resumable completion, baseline
provisioning, provider-subject linking, and password-setup handoff.
## T03 - Integrate registration entry points and return flow
```task
id: NK-WP-0025-T03
status: progress
priority: high
state_hub_task_id: "3ec4a9a0-cbce-4c63-8ed8-db36f4cae0fb"
```
Publish a NetKingdom registration URL and allow applications to provide
`return_to`, client, and tenant hints through a signed/allow-listed state
envelope. Add "Create account" beside "Sign in" on the coulomb.social landing
page. After registration, start a fresh OIDC authorization-code/PKCE flow;
never treat the registration browser session as application authentication.
Done when direct NetKingdom registration and coulomb.social-initiated
registration converge on the same verified identity flow without open
redirects or client confusion.
## T04 - Apply optional MFA policy safely
```task
id: NK-WP-0025-T04
status: progress
priority: high
state_hub_task_id: "cf2e4d05-fd07-4bc3-92ff-b2a5ade97846"
```
Replace global mandatory MFA for this consumer with the KeyCape policy from
KEY-WP-0008. Ordinary coulomb.social login is password-only unless the
application profile, requested action, tenant policy, or risk/assurance rule
requires step-up. Platform administration and other existing high-assurance
clients remain MFA protected.
Done when lowering assurance for this application cannot lower assurance for
OpenBao, platform administration, or another client.
Source implementation adds a per-client minimum override and honors explicit
`acr_values=aal2`; only the coulomb-social static registration selects AAL1.
Other clients retain provider-driven mandatory MFA. Live rollout remains.
## T05 - Prove both cases end to end
```task
id: NK-WP-0025-T05
status: todo
priority: high
state_hub_task_id: "5d13f9f6-16b1-44c9-8e1d-4630cfe60a7b"
```
Test an existing LLDAP identity with no coulomb.social profile, a completely
new registrant, repeated callback/JIT, existing-email collision, expired and
replayed verification, duplicate username, provider outage and compensation,
cross-tenant attempts, optional MFA, profile-requested step-up, unlink, and
offboarding. Use `tegwick` only for the attended positive acceptance test;
automated tests use disposable identities.
Done when both user journeys work on railiance01 and all negative cases leave
no orphaned privileged identity or duplicate application profile.