key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md

249 lines
11 KiB
Markdown
Raw Normal View History

---
id: KEY-WP-0004
type: workplan
title: "Binky Hedgehog GmbH as first NetKingdom tenant + qonto-assistant workload identity"
domain: infotech
repo: key-cape
status: ready
owner: codex
topic_slug: netkingdom
created: "2026-07-23"
updated: "2026-07-23"
state_hub_workstream_id: "ad6fa711-ba5b-4e7d-86e3-641f9acaf853"
---
# Binky Hedgehog GmbH as first NetKingdom tenant + qonto-assistant 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).
**Downstream, not in this workplan:** once T03/T04 land, `qonto-assistant`
needs its own follow-up workplan to swap `mcp_auth.BearerTokenAuthMiddleware`
for `FastMCP`'s built-in `TokenVerifier` + `AuthSettings(issuer_url=<key-cape>)`
against the client registered here. Tracked as a pointer only — not created
by this workplan, since it's a different repo's scope and code change.
## Task: Confirm tenant identifier and provision the tenant plane
```task
id: KEY-WP-0004-T01
status: todo
priority: high
state_hub_task_id: "ec65d1be-11cb-421f-84dd-f401206526fe"
```
**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.
## Task: Provision bernd.worsch@binky-hedgehog.com as tenant-admin
```task
id: KEY-WP-0004-T02
status: todo
priority: high
state_hub_task_id: "b5cb4497-095c-4dd7-af31-831deddd422e"
```
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.
**Prerequisite outside this repo's control:** the `binky-hedgehog.com` mailbox
for `bernd.worsch@` must exist and be reachable for account verification and
MFA enrollment. Flag to Bernd before starting — this task cannot complete
without it.
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.
## Task: Register a workload-identity OIDC client for qonto-assistant callers
```task
id: KEY-WP-0004-T03
status: todo
priority: high
state_hub_task_id: "fbe9d2e0-7532-4403-8e12-947aa8fd0da7"
```
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 `qonto-assistant`'s MCP and REST surfaces
actually need. 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` and granted scopes are documented
here; no secret material appears anywhere in this repo's history.
## Task: Route client_secret custody through secrets-engine + OpenBao
```task
id: KEY-WP-0004-T04
status: todo
priority: high
state_hub_task_id: "80901eb3-a959-49ab-bcde-01ddaafa8e59"
```
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 — so
`qonto-assistant`'s runtime fetches this the same way it already fetches the
Qonto API key, via its own dedicated runtime role
(`qonto-assistant-runtime`, per `qonto-assistant/specs/ArchitectureBlueprint.md`
§4.7), not a shared or ad hoc role.
Done when: `qonto-assistant`'s runtime role can fetch the `client_secret` via
`secrets-engine`/OpenBao; Bernd never handles the raw value at any point in
this flow.
## Task: Register the credential lane with ops-warden
```task
id: KEY-WP-0004-T05
status: todo
priority: high
state_hub_task_id: "a91326a6-f498-4569-a56b-95a49fe5794d"
```
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.
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 new
`qonto-assistant` workload to 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.
## Task: automation@binky-hedgehog.com service identity (deferred)
```task
id: KEY-WP-0004-T06
status: todo
priority: low
state_hub_task_id: "d3460058-6e4c-426b-b575-1bbad192e40c"
```
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-assistant` 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: todo
priority: low
state_hub_task_id: "9511a7ef-0a04-45db-8a7b-598ab77ce737"
```
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 (qonto-assistant workload client) 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. Note the
`qonto-assistant`-side follow-up workplan (swap bearer token for
`TokenVerifier`) as the next step, out of this workplan's scope. Run
`statehub fix-consistency`.