Close the inbound-library IMAP AppRole lane: built, verified, routed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 1170398@bnt-lap001 Assistant-Session: 622cd705-cb31-4322-ba08-dd42e44fa583
This commit is contained in:
parent
e4c68e2d0b
commit
4e3fc02f86
2 changed files with 213 additions and 1 deletions
183
plans/fluid-telegram-operator-credential-lane.md
Normal file
183
plans/fluid-telegram-operator-credential-lane.md
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
---
|
||||
id: fluid-telegram-operator-credential-lane
|
||||
demand_source: fluid-telegram/workplans/FT-WP-0002 and State Hub message 287cf897-7f47-4bcc-87d6-085f43d5f03e
|
||||
consumer_repo: fluid-telegram
|
||||
credential_type: openbao-oidc-kv-read-write
|
||||
status: draft
|
||||
approved_by: null
|
||||
approved_at: null
|
||||
created: "2026-09-04"
|
||||
updated: "2026-09-04"
|
||||
---
|
||||
|
||||
# Construction plan: fluid-telegram operator credential lane
|
||||
|
||||
## 1. Demand
|
||||
|
||||
`fluid-telegram` has an attended provisioner that creates a Telegram bot and
|
||||
channels from a declared campaign specification. The provisioner needs an
|
||||
operator OIDC token that reads four campaign-specific KV entries, writes the
|
||||
MTProto session and bot token, and creates the redaction salt once. The
|
||||
MTProto session is a full-account credential, so this is a high-risk operator
|
||||
lane, never an autonomous agent or adapter lane. ops-mason constructs only the
|
||||
OpenBao structure and never handles Telegram values.
|
||||
|
||||
The first consumer is campaign `hall-of-helix`. A future adapter has a
|
||||
different, narrower need and is intentionally excluded from this plan.
|
||||
|
||||
## 2. Existing-structure survey
|
||||
|
||||
### Disk survey completed 2026-09-04
|
||||
|
||||
- `railiance-platform/docs/workload-kv-access-lanes.md` documents attended
|
||||
OIDC through `auth/netkingdom`, exact group-bound roles, three callback URIs,
|
||||
and 15-minute tokens.
|
||||
- The nearest concrete tenant-shaped path is
|
||||
`platform/workloads/coulomb/whynot-design/npm-publish`. Its CCR history says
|
||||
the owner corrected the first segment to the organisation/tenant, not the
|
||||
State Hub domain.
|
||||
- Existing policy files contain no `fluid-telegram` lane.
|
||||
- `ops-warden/registry/routing/catalog.yaml` contains only the generic
|
||||
`openbao-api-key` template for this need, and its login string omits
|
||||
`-path=netkingdom`. No concrete Telegram lane exists.
|
||||
- `ops-mason/src/ops_mason/executor.py` supports AppRole and Kubernetes KV
|
||||
lanes. It has no OIDC role writer and its generic policy helper emits the
|
||||
same capabilities for one data and metadata path, which cannot express this
|
||||
four-path matrix.
|
||||
- `railiance-platform/schemas/credential-change-request.schema.yaml` permits
|
||||
only `request_type: workload-kv-read`; its validator also requires the
|
||||
`workload-kv-read-` policy prefix and `-workload-kv-read` OIDC role suffix.
|
||||
|
||||
### Live survey pending
|
||||
|
||||
No valid scoped OpenBao session was available during intake, so the required
|
||||
live outputs from `bao secrets list`, `bao auth list`, `bao policy list`, and
|
||||
the relevant `auth/netkingdom/role/*` reads are not yet attached. Phase 2 must
|
||||
repeat the survey with `./scripts/bao-session.sh grant MASON-WP-0005-T01`.
|
||||
|
||||
**Existing-lane answer:** none is known from source state. This remains a
|
||||
draft finding until the live survey confirms it.
|
||||
|
||||
## 3. Proposed changes
|
||||
|
||||
The recommended CLI prefix is
|
||||
`platform/workloads/coulomb/fluid-telegram/hall-of-helix`. `coulomb` is a
|
||||
recommendation from the existing owner precedent, not an authorization; T01
|
||||
must replace this paragraph with railiance-platform's explicit decision before
|
||||
review.
|
||||
|
||||
The recommended objects are:
|
||||
|
||||
| # | Action | Object | Rationale |
|
||||
|---|---|---|---|
|
||||
| 1 | create | Policy `workload-kv-fluid-telegram-hall-of-helix-operator` | A new exact-path matrix is required; no read-only lane can satisfy the writes. |
|
||||
| 2 | create | OIDC role `fluid-telegram-hall-of-helix-workload-kv` at `auth/netkingdom` | The caller is an attended operator; AppRole and Kubernetes auth are the wrong identity shapes. |
|
||||
| 3 | modify | ops-mason executor with an approved OIDC KV matrix spec | The current executor cannot safely express OIDC or per-path capabilities. |
|
||||
| 4 | modify | railiance-platform CCR schema/validator with an approved read/write request type and naming rules | The platform owner explicitly rejects this shape today; bypassing that gate is forbidden. |
|
||||
| 5 | modify | railiance-platform agent high-risk boundary with exact data and metadata denies for all four paths | Agent-bound identities must not inherit this lane through another policy. |
|
||||
| 6 | propose | ops-warden entry `fluid-telegram-operator`, pointer-only, `status: draft`, `resolvable: false` | A concrete selector is useful before activation; no placeholder prefix becomes routable. |
|
||||
|
||||
### Exact policy matrix
|
||||
|
||||
All entries are individual KV-v2 API paths. There is no `*` grant, parent
|
||||
`list`, metadata mutation, delete, destroy, undelete, or sudo capability.
|
||||
|
||||
| CLI entry | KV-v2 data-path capabilities | Risk and reason |
|
||||
|---|---|---|
|
||||
| `.../operator-app` | `read` | High confidentiality: Telegram application id/hash; entered only through sanctioned attended value entry. |
|
||||
| `.../operator-session` | `create,read,update` | High: full-account MTProto credential. Provisioning only; never adapter-visible. |
|
||||
| `.../bot-token` | `create,read,update` | High: controls Bot API publication. Provisioner writes immediately after BotFather issues it. |
|
||||
| `.../redaction-salt` | `create,read` | High integrity and sensitive pseudonymisation material. Overwrite, patch, and deletion are denied. |
|
||||
|
||||
OpenBao KV v2 documents that it honors the ACL distinction between `create`
|
||||
and `update`; it also documents CAS `0` as create-if-absent. The fluid-telegram
|
||||
client currently performs a read followed by a normal POST. The ACL therefore
|
||||
supplies the decisive server-side write-once boundary: the first POST is a
|
||||
create and a later POST is an update, which this policy denies. The client
|
||||
should still add CAS `0` to make its intent race-safe and explicit, but this
|
||||
plan does not depend on client-only behavior.
|
||||
|
||||
This is operational write-once for the provisioner, not immutable storage.
|
||||
A platform administrator can recover from a corrupt first write through a
|
||||
separately approved attended action. That action must preserve the old version
|
||||
until recovery is verified and must never be exposed through this OIDC role.
|
||||
|
||||
### OIDC role shape
|
||||
|
||||
- auth mount: `netkingdom`
|
||||
- role: `fluid-telegram-hall-of-helix-workload-kv`
|
||||
- token policies: exactly
|
||||
`workload-kv-fluid-telegram-hall-of-helix-operator`; set
|
||||
`token_no_default_policy=true`
|
||||
- role type: `oidc`
|
||||
- token TTL: `15m`, maximum TTL `30m`
|
||||
- user claim: `sub`; groups claim: `groups`
|
||||
- scopes: `openid,profile,email,groups`
|
||||
- proposed bound claim: `groups=fluid-telegram-operators` — **unconfirmed**
|
||||
- callbacks: Bao UI plus localhost and loopback CLI callbacks already accepted
|
||||
by the platform contract
|
||||
|
||||
The role is campaign-specific because its policy is campaign-specific. A
|
||||
second campaign receives a second policy and role after its own review; it is
|
||||
not added below this prefix.
|
||||
|
||||
### Value flow
|
||||
|
||||
- An attended operator enters `operator-app` with the existing sanctioned
|
||||
paste-once/value-entry mechanism.
|
||||
- The provisioner creates or updates `operator-session` and `bot-token`.
|
||||
- The provisioner creates `redaction-salt` once.
|
||||
- ops-mason records paths, policy/auth metadata, capabilities, versions,
|
||||
timestamps, and audit references only. It never requests or reads a value.
|
||||
|
||||
## 4. Review notes (phase 2)
|
||||
|
||||
This section is incomplete while T01 and T02 are open; the plan must remain
|
||||
`draft`.
|
||||
|
||||
- **Reuse:** reuse the existing `netkingdom` auth mount, callback set, group
|
||||
claim pattern, 15-minute TTL, and KV-v2 mount. Create only the policy and
|
||||
role that do not exist.
|
||||
- **Least privilege:** exact entry paths are safer than one campaign-prefix
|
||||
wildcard and are necessary for the salt's create-only rule. The role has no
|
||||
access to KV metadata because the provisioner uses data GETs to detect
|
||||
absence; metadata evidence comes from the separately scoped builder/operator
|
||||
verification path.
|
||||
- **Identity binding:** an unconfirmed group is a hard blocker, not a warning.
|
||||
The role must not be readable by every OIDC user.
|
||||
- **Write-once semantics:** KV-v2 create/update distinction is documented, but
|
||||
activation also requires an owner-run non-secret probe proving the installed
|
||||
engine behaves as expected. The resulting evidence contains no probe value.
|
||||
- **Agent boundary:** explicit denies must cover all four data paths and their
|
||||
metadata paths. The operator-session path is the decisive negative check.
|
||||
- **Adapter separation:** `MASON-IN-0003` tracks a later runtime lane that
|
||||
reads only `bot-token` and `redaction-salt`; no role or policy in this plan is
|
||||
reused by the adapter.
|
||||
- **Reversal:** disable/delete the OIDC role first, then retire the policy. KV
|
||||
entries remain in operator custody for an explicit retention/recovery
|
||||
decision; teardown does not silently delete values.
|
||||
- **Implementation gates:** both ops-mason and railiance-platform must extend
|
||||
their current read-lane tooling before construction. Direct `bao policy
|
||||
write`/`bao write auth/netkingdom/role/...` commands outside those approved
|
||||
surfaces are not an alternative.
|
||||
|
||||
### Verification required before routing activation
|
||||
|
||||
1. Positive capabilities on each exact path match the matrix above.
|
||||
2. Parent, sibling workload, and sibling campaign paths report `deny`.
|
||||
3. A non-member OIDC identity cannot obtain the role's policy.
|
||||
4. Agent-bound identities report `deny` on all four data and metadata paths.
|
||||
5. An owner-run disposable KV-v2 probe proves create succeeds and a second
|
||||
write with the same policy is denied; only timestamps, versions, path, and
|
||||
pass/fail are returned.
|
||||
6. The value-entry/provisioner flow confirms all required entries exist using
|
||||
metadata/version evidence only.
|
||||
7. ops-warden keeps `fluid-telegram-operator` non-resolvable until all checks
|
||||
pass and the exact built pointers are returned.
|
||||
|
||||
## 5. Executive summary (phase 3)
|
||||
|
||||
Not yet renderable. The owner-approved tenant segment, OIDC bound claim, live
|
||||
structure survey, CCR/executor extension, and installed-engine create/update
|
||||
probe are open inputs. When those close, this section must ask for one explicit
|
||||
approval covering the exact objects and capability matrix above.
|
||||
|
|
@ -3,7 +3,7 @@ id: inbound-library-imap-approle
|
|||
demand_source: inbound-library/workplans/ILIB-WP-0003-T01
|
||||
consumer_repo: inbound-library
|
||||
credential_type: openbao-approle-kv
|
||||
status: approved
|
||||
status: built
|
||||
approved_by: "Bernd Worsch"
|
||||
approved_at: "2026-10-05"
|
||||
approved_spec_sha256: "c62765c8a4505821e77b58c5e0daf0511dec39fb569a05e190ff51f5cdc3e2ba"
|
||||
|
|
@ -207,3 +207,32 @@ binding.
|
|||
**Decision:** approve / reject / send back to phase 1 with what to change.
|
||||
|
||||
**Decision 2026-10-05: approved** by Bernd Worsch in a Claude Code session ("I approve"), for the exact build_spec above (digest `c62765c8…e2ba`).
|
||||
|
||||
## 6. Build result (phase 4)
|
||||
|
||||
Built 2026-10-05 with `build_approle_kv_lane` under the grant
|
||||
`token-claude-ILIB-WP-0003-T03` (ops-mason-build). The builder reloaded the plan
|
||||
and matched approval and digest `c62765c8…e2ba` before any OpenBao command. It
|
||||
added a metadata-only record to `audit/build-log.jsonl`.
|
||||
|
||||
- **created** policy `workload-kv-read-inbound-library-imap`: `read` on
|
||||
`platform/data/workloads/inbound-library/imap` and
|
||||
`platform/metadata/workloads/inbound-library/imap`, and nothing else.
|
||||
Confirmed with `bao policy read`.
|
||||
- **created** AppRole `inbound-library`: `token_policies=[workload-kv-read-inbound-library-imap]`,
|
||||
`token_ttl=900`, `token_max_ttl=1800`, `token_num_uses=4`, `secret_id_ttl=0`,
|
||||
`bind_secret_id=true`. Confirmed with `bao read`.
|
||||
- **delivered** `role_id` and `secret_id` to `/home/worsch/.local/inbound-library/approle/`
|
||||
(directory 0700, files 0600). The values were not printed or read by the agent.
|
||||
|
||||
Capabilities-safe verification:
|
||||
- **Negative:** a `default`-only token (2 minutes, 2 uses, then revoked) gets
|
||||
`deny` on the data path.
|
||||
- **Positive:** proven by the consumer, not by ops-mason, which reads no values.
|
||||
The first systemd run of `inbound-library.service` (2026-10-05 16:09 CEST) did
|
||||
the AppRole login, read the entry, revoked its token, and scanned
|
||||
`library@coulomb.social` read-only. It retained 2 messages. No secret appeared
|
||||
in the journal.
|
||||
|
||||
The ops-warden catalog entry is proposed under inbound-library ILIB-WP-0003-T05.
|
||||
No paste-once step remains: the value (version 2) was supplied by the founder before the build.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue