diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 5ec7346..8ced6ee 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -13,9 +13,10 @@ | 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-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-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-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 | @@ -58,9 +59,14 @@ | 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-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-T02 | todo | — | 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-T04 | 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 | progress | — | 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 | 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-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md | diff --git a/docs/public-registration-orchestration-contract.md b/docs/public-registration-orchestration-contract.md new file mode 100644 index 0000000..2975d43 --- /dev/null +++ b/docs/public-registration-orchestration-contract.md @@ -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. + diff --git a/sso-mfa/k8s/keycape/deployment.yaml b/sso-mfa/k8s/keycape/deployment.yaml index a94c2ba..f4cbba8 100644 --- a/sso-mfa/k8s/keycape/deployment.yaml +++ b/sso-mfa/k8s/keycape/deployment.yaml @@ -54,7 +54,7 @@ spec: # 2026-05-24: direct-imported into railiance01 k3s for the # bootstrap-console OIDC/MFA rollout. Use IfNotPresent while the # HTTP registry push/pull path is being cleaned up. - image: key-cape:90a2078 + image: key-cape:mfa-client-20260809 imagePullPolicy: IfNotPresent ports: diff --git a/sso-mfa/k8s/keycape/register-coulomb-social.py b/sso-mfa/k8s/keycape/register-coulomb-social.py index 044816f..b940bd6 100755 --- a/sso-mfa/k8s/keycape/register-coulomb-social.py +++ b/sso-mfa/k8s/keycape/register-coulomb-social.py @@ -26,6 +26,10 @@ CLIENT = { "allowedScopes": ["openid", "profile", "email", "groups"], "grantTypes": ["authorization_code"], "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, } diff --git a/workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md b/workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md index eaec60c..e6d301f 100644 --- a/workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md +++ b/workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md @@ -4,11 +4,11 @@ type: workplan title: "Cut over NetKingdom identity to railiance01 and retire CoulombCore" domain: infotech repo: net-kingdom -status: active +status: blocked owner: codex topic_slug: netkingdom created: "2026-07-27" -updated: "2026-07-28" +updated: "2026-08-08" depends_on: - USER-WP-0020 - 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. Retention gate: keep the reversible CoulombCore identity resources through at -least 2026-08-29. T08 then requires a successful railiance01 restore/restart -drill and a new explicit approval for destructive deletion. Until both occur, -the workplan remains active with only T08 waiting. +least 2026-08-29. The workplan is blocked until that review date, when T08 can +be checked for readiness to finish. Reaching the date does not authorize +deletion or completion: T08 still requires a successful railiance01 +restore/restart drill and new explicit approval for destructive deletion. ## Safety gates diff --git a/workplans/NK-WP-0025-public-self-registration-and-application-jit.md b/workplans/NK-WP-0025-public-self-registration-and-application-jit.md new file mode 100644 index 0000000..58244ec --- /dev/null +++ b/workplans/NK-WP-0025-public-self-registration-and-application-jit.md @@ -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.