key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md
tegwick b9a7d1b008 Draft KEY-WP-0004: Binky Hedgehog GmbH as first tenant + qonto-assistant workload identity
Provisions the first real NetKingdom tenant (tenant:customer:binky, matching
the slug already live in production OpenBao paths) with a human tenant-admin
user and a workload-identity OIDC client for qonto-assistant's MCP callers,
routed through secrets-engine for secret custody and ops-warden for
discoverable, minimal-touch credential access. Closes Gap #1 flagged in
qonto-assistant's QONTO-WP-0003 closure (no OIDC issuer existed in the fleet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 14:34:40 +02:00

206 lines
8.8 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-assistant workload identity"
domain: infotech
repo: key-cape
status: ready
owner: codex
topic_slug: netkingdom
created: "2026-07-23"
updated: "2026-07-23"
---
# 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
```
The IAM profile's tenant identifiers (`tenant:platform`, `tenant:coulomb`,
`tenant:sandbox:<name>`, `tenant:customer:<name>`) are documented as
*suggested*, not exhaustive — but `tenant:customer:<name>` is the closest fit
for the first real operating/customer-plane tenant, distinct from the
`tenant:coulomb` ecosystem-development plane.
**Recommended default, to confirm before implementing:** `tenant:customer:binky`
— matching the slug already live in production OpenBao paths
(`tenants/binky/qonto-api`, used by `qonto-assistant` today) rather than
`tenant:customer:binky-hedgehog`, to avoid a slug mismatch between the new
IAM tenant claim and the existing secret-custody path convention. Flag to
Bernd for explicit confirmation before provisioning — tenant identifiers are
expensive to rename once tokens and downstream config reference them.
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:customer: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 identifier confirmed and recorded (decision record or
canon note); a test-subject token carries the correct `tenant` claim.
## Task: Provision bernd.worsch@binky-hedgehog.com as tenant-admin
```task
id: KEY-WP-0004-T02
status: todo
priority: high
```
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:customer: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:customer: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
```
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:customer: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:customer: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
```
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
```
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
```
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
```
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:customer: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`.