key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md
codex 270a065383 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:13:23 +02:00

332 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: KEY-WP-0004
type: workplan
title: "Binky Hedgehog GmbH as first NetKingdom tenant + qonto runtime workload identity"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-07-23"
updated: "2026-07-29"
state_hub_workstream_id: "817709be-5756-5ef7-9ccd-dcb84b6b9c64"
---
# Binky Hedgehog GmbH as first NetKingdom tenant + qonto runtime workload identity
Binky Hedgehog GmbH ("binky") becomes the **first real tenant** provisioned in
the NetKingdom IAM stack, per the NetKingdom IAM Profile v0.2's tenant claim
model (`net-kingdom/canon/standards/iam-profile_v0.2.md` §"Tenant Claim") —
distinct from `tenant:platform` (control plane) and `tenant:coulomb` (the
ecosystem-development tenant this workstation already operates under).
This closes **Gap #1** flagged in `qonto-assistant`'s
`workplans/QONTO-WP-0003-mcp-surface.md` closure: no OIDC issuer existed
anywhere in the fleet, so `qonto-assistant`'s MCP surface shipped with an
interim shared-secret bearer token instead of real workload identity.
`key-cape` is "Implementation complete (v0.1)" and already implements the
profile's `client_credentials`/workload-identity grant for service accounts
— it's the component that should close this gap, not `user-engine`
(downstream user/profile data layer, not an identity provider) or
`identity-canon` (terminology research only).
**Depends on:** `key-cape` v0.1 (done, `KEY-WP-0001`), NetKingdom IAM Profile
v0.2 tenant + service-account claim rules (already ratified canon — no new
profile work needed, this is provisioning against an existing contract), and
the cross-repo runtime direction now tracked in
`railiance-master/workplans/RMASTER-WP-0019-knative-qonto-runtime-on-reef-railiance.md`.
This workplan no longer targets a permanent direct `qonto-assistant`
deployment shape. Its workload-identity lane is intended for the future
`rapp-qonto` runtime on `rail-knative` hosted by `reef-railiance`, with
current `qonto-assistant` consumption allowed only as a migration bridge until
that package/rail path exists.
## Task: Confirm tenant identifier and provision the tenant plane
```task
id: KEY-WP-0004-T01
status: done
priority: high
state_hub_task_id: "c3bdbdb6-6b3f-51b5-8521-d67f113f1c3d"
```
**Decided 2026-07-23 (Bernd):** the IAM profile's `tenant:customer:<name>` /
`tenant:sandbox:<name>` suggested identifiers name a tenant by **capability
role** (customer, vendor, ...) — but tenants already known to hold multiple
roles at once (see `net-kingdom/docs/princedom-isolation-exploration.md`'s
unratified `PLTF`/`IAM`/`VEN`/`CUS` role model; Binky itself is both `CUS`
and, later, `VEN`). Using a role word as the tenant *grouping* would collide
with that separate role dimension. Decision: classify tenants by an
onboarding-risk / entity-shape **grouping**, orthogonal to capability role:
```text
trial — test/trial/showcase tenants only
friendly — known, easily reached, tolerant of experimentation/instability
single — one-person business entities (freelance consultants)
small — up to 10 employees at time of onboarding (attoo)
medium — up to 100 employees (attoo)
large — up to 1000 employees (attoo)
enterprise — 1001+ employees (attoo)
consumer — private individuals
family — a legal family
community — a non-legal group of people
association — a legal association of people
agentic — financially enabled AI entities
```
`tenant:<grouping>:<name>` replaces `tenant:customer:<name>` /
`tenant:sandbox:<name>` as the identifier shape. **Binky maps to `friendly`**
— known, reachable, tolerant of early instability — giving
**`tenant:friendly:binky`** (slug matches the OpenBao path already in
production, `tenants/binky/qonto-api`).
**Open question, not yet resolved by this decision:** this grouping list
classifies tenants as external-shaped entities: it has no obvious slot for
`tenant:coulomb` itself (the platform's own internal/reference tenant,
ADR-0006) — none of `trial`/`friendly`/`single`/.../`agentic` fit "the
platform building itself." Confirm with Bernd whether `tenant:coulomb` stays
an ungrouped special case (alongside `tenant:platform`) or gets folded into
this scheme somehow, before treating the grouping list as complete.
**Also open:** this is a fleet-wide vocabulary decision — the ratified
`net-kingdom/canon/standards/iam-profile_v0.2.md` "Tenant Claim" section
still documents the old `customer`/`sandbox` suggested identifiers, and
NetKingdom owns that canon per ADR-0011. Recording this grouping list only
in this key-cape workplan lets it drift from canon. Recommend a follow-up:
either a decision record or an ADR in `net-kingdom` adopting this grouping
list as the tenant-identifier standard, superseding the old suggested
identifiers. Not drafted here — flagging so it isn't lost.
Provision whatever tenant/org-unit boundary key-cape's current backend
(LLDAP/Authelia) needs so tokens issued for subjects under this tenant
correctly carry `tenant:friendly:binky` and nothing implies platform-root
authority for it (profile rule: tenant administration for a tenant must
never imply platform-root authority).
Done when: `tenant:coulomb`'s place in the grouping scheme is resolved; the
`net-kingdom` canon-update question above is answered (drafted or explicitly
deferred); a test-subject token carries the `tenant:friendly:binky` claim.
2026-07-27: IAM Profile v0.2 now preserves `tenant:platform` and
`tenant:coulomb` as reserved ungrouped tenants and ratifies the external
grouping vocabulary. The live service-token proof carries
`tenant:friendly:binky` without platform authority.
## Task: Provision bernd.worsch@binky-hedgehog.com as tenant-admin
```task
id: KEY-WP-0004-T02
status: done
priority: high
state_hub_task_id: "202c419e-838f-5c2f-8a52-9b1c36a6d6b9"
```
Create the user in key-cape's current backend, enroll MFA per profile
requirements (`assurance` claim, `net-kingdom/canon/standards/iam-profile_v0.2.md`),
and assign a tenant-admin role/group scoped to `tenant:friendly:binky` only.
2026-07-27 direction update: do not complete this through a one-shot operator
script. `USER-WP-0020` and `NK-WP-0023` now own a reusable self-service and
administration portal plus NetKingdom provisioning adapters. This task is
their first production acceptance case.
The `binky-hedgehog.com` mailbox must exist and be reachable for account
verification, but mailbox availability is now an input to the reusable
registration flow rather than a manual provisioning procedure.
Done when: `bernd.worsch@binky-hedgehog.com` completes OIDC/PKCE + MFA login
and receives a token carrying `tenant:friendly:binky` and a tenant-admin role
claim; the same token does **not** grant access to `tenant:platform` or any
other tenant.
2026-07-28: user-engine's deployed portal API created the tenant-admin
membership and provisioned/linked the LLDAP identity through the reusable
NetKingdom adapter. The identity is intentionally in
`password_setup_required`; LLDAP has no SMTP configuration for its native
reset delivery. First-password handoff, MFA enrollment, and final human-token
claim/denial evidence remain. No operator-set password or raw credential was
used as a shortcut.
2026-07-28 update: the reusable password handoff is now live at the canonical
KeyCape host and is issued/renewed through the user-engine administration UI.
Links are opaque, expire after 15 minutes, are single use, and older links are
revoked on renewal. A disposable-user live test proved password registration,
directory login, replay denial, and cleanup. This task now awaits only the
Binky user's own password choice, MFA enrollment, and final scoped-token
acceptance.
2026-07-29: the first portal attempt correctly denied cross-tenant access
because KeyCape had not projected the established `net-kingdom-admins` group
to the new platform-operator role. Commit `90a2078` adds and tests the explicit
`tenant:platform` / `platform-operator` compatibility mapping and is deployed
Ready. The operator must start a fresh OIDC session so the corrected claims
are minted.
2026-07-29 human acceptance: the Binky administrator completed the reusable
password setup, enrolled a privacyIDEA TOTP factor, and successfully signed in
through KeyCape with password plus OTP. Enrollment revealed that privacyIDEA's
default QR label exposed only the token serial. The live
`coulomb-friendly-token-labels` enrollment policy now emits issuer `Coulomb`
and label `{user}@{realm}` for future tokens; existing authenticator entries
must be renamed locally because wallets do not accept remote label updates.
The live token then carried `tenant:friendly:binky`, roles `user` and
`tenant-admin`, only the Binky users/admins groups, and AAL2 assurance with
`pwd` + `otp` and `mfa: true`. The same session was denied access to both
`tenant:platform` and `tenant:coulomb` administration. Acceptance complete.
## Task: Register a workload-identity OIDC client for the qonto runtime
```task
id: KEY-WP-0004-T03
status: done
priority: high
state_hub_task_id: "0d3ccd35-bad5-5c30-95a3-61fce584425d"
```
Register an OIDC client per the profile's service-account/workload-identity
flow (`client_credentials` or the documented workload-token-exchange
equivalent — §"Service Account Flow"), scoped to `tenant:friendly:binky`,
with the minimum role/scope the qonto runtime surface actually needs. The
intended steady-state consumer is the future `rapp-qonto` runtime on
`rail-knative`; current `qonto-assistant` callers may use the same lane only
as a documented migration bridge until that package split exists. No
interactive login for this client — it's for agent-harness sessions and other
automated callers, not humans.
The resulting `client_secret` must never be pasted into chat, Git, this
workplan, or a State Hub message or progress event — hand-off happens only
through T04's secrets-engine/OpenBao lane.
Done when: a `client_credentials` token exchange against key-cape succeeds
for this client and produces a short-lived service token carrying
`tenant:friendly:binky`; the `client_id`, granted scopes, and intended future
binding to `rapp-qonto` are documented here; no secret material appears
anywhere in this repo's history.
2026-07-26: Added `docs/qonto-runtime-identity-contract.md` with stable
principal/client names, least-privilege claims, non-secret evidence, and the
automation/custody boundary. Actual client issuance remains open.
2026-07-27: `KEY-WP-0006` delivered `rapp-qonto-client`. Live exchange
produced only the documented tenant, role, and `qonto:read` scope; wrong
secret and excessive scope were denied.
## Task: Route client_secret custody through secrets-engine + OpenBao
```task
id: KEY-WP-0004-T04
status: done
priority: high
state_hub_task_id: "9cb09dbf-1fd0-55c5-9c45-b3b7296ecd91"
```
Coordinate with `secrets-engine` to create a scoped OpenBao lane for this
`client_secret`, mirroring the existing `tenants/binky/qonto-api` pattern
already in production use for the Qonto bank credential itself. The steady
target is a dedicated future runtime role for `rapp-qonto`; if a temporary
bridge via the current `qonto-assistant` runtime role is needed before the
package split lands, it must be recorded as temporary rather than treated as
the final contract.
Done when: the intended qonto runtime role can fetch the `client_secret` via
`secrets-engine`/OpenBao; any temporary `qonto-assistant` bridge is explicitly
marked transitional; Bernd never handles the raw value at any point in this
flow.
2026-07-26: `warden plan` classified first-time provisioning as one explicit
founder authority act through `warden desk`. No secret was requested or read.
All repeatable verification and revocation work is designated for sanctioned
agent execution and later functional automation.
2026-07-27: Custody is established at
`platform/workloads/rapp-qonto/keycape-client`, with runtime injection through
`sso/keycape-rapp-qonto-client`. No raw value entered evidence or human
handling.
## Task: Register the credential lane with ops-warden
```task
id: KEY-WP-0004-T05
status: done
priority: high
state_hub_task_id: "39f1f803-732e-5905-97d9-c7493c66c8ee"
```
This is the task that directly minimizes Bernd's ongoing engagement: add an
entry to ops-warden's routing catalog (`registry/routing/catalog.yaml`)
pointing at this new OIDC client / login lane, following the pattern already
documented in `ops-warden/SCOPE.md`'s "Issue vs route" table ("Login / OIDC /
MFA → key-cape / Keycloak → Assist — route; proxy `login` lane when
`exec_capable`"). ops-warden does not mint tokens or take custody here — it
routes/assists, same as every other credential lane it fronts. The lane should
describe the future `rapp-qonto` runtime path on `rail-knative`, not only the
current source repo.
Once the OpenBao lane from T04 exists, run ops-warden's workload security
posture conformance checker
(`ops-warden/scripts/check_secret_posture_conformance.py`) against the qonto
runtime workload that will later become `rapp-qonto`; until the split lands,
`qonto-assistant` may be used as the migration subject if that temporary state
is recorded. Confirm it meets the expected maturity tier (dev/test/prod
posture, M0M3 workload maturity) before this goes live with real
credentials, and record the result (pass or documented exception).
Done when: `warden route show` (or equivalent) resolves this lane correctly;
posture conformance check passes or exceptions are explicitly recorded; from
this point on, discovering and using this credential lane needs `warden
access`, not direct key-cape/OpenBao admin knowledge.
2026-07-26: Routing requirements and required conformance evidence are now
documented. Catalog publication waits for the real client and OpenBao role so
the route does not advertise an unusable lane.
2026-07-27: Published route `rapp-qonto-keycape-client`; route resolution and
the metadata-only M3/prod posture check both pass.
## Task: automation@binky-hedgehog.com service identity (deferred)
```task
id: KEY-WP-0004-T06
status: cancel
priority: low
state_hub_task_id: "fbee11fc-7a60-5dd7-8522-f80ddb4aea55"
```
Forward-looking per Bernd's framing ("I guess it will later be helpful") —
**do not provision until a concrete automation workflow actually needs its
own identity** distinct from the qonto runtime workload client from T03.
Dormant service-account credentials are themselves a posture risk (unused
attack surface, nothing to rotate against), so this stays parked rather than
front-loaded.
When a real consuming workflow exists: same pattern as T02 but scoped to an
automation-appropriate role (not tenant-admin), likely paired with its own
`client_credentials` registration for whichever agent/service authenticates
as it, following T03/T04/T05's pattern rather than inventing a new one.
Done when: explicitly triggered by a named consuming workflow — until then
this task stays `todo` and does not block closing the rest of this workplan.
## Task: Closure review
```task
id: KEY-WP-0004-T07
status: done
priority: low
state_hub_task_id: "77b0556f-62fd-5046-9ba1-718246151a6f"
```
Mark workplan finished when T01T05 are done (T06 may legitimately remain
`todo`/parked by design — see its own task note). Confirm end to end: a
human token (bernd.worsch@binky-hedgehog.com, tenant-admin) and a service
token (future qonto runtime client, with any temporary `qonto-assistant`
bridge explicitly recorded) both resolve to `tenant:friendly:binky` and
nothing else; the credential lane is discoverable via `warden access` without
Bernd touching key-cape/OpenBao admin surfaces directly. The runtime follow-up
then continues in `QONTO-WP-0004` and `RMASTER-WP-0019`. Run
`statehub fix-consistency`.
2026-07-29 closure: T01T05 are complete, T06 is intentionally cancelled,
the human Binky flow passed AAL2 and tenant-isolation acceptance, and the qonto
workload client passed its positive-scope and negative credential/scope checks.