diff --git a/plans/inbound-library-imap-approle.md b/plans/inbound-library-imap-approle.md new file mode 100644 index 0000000..c6ee956 --- /dev/null +++ b/plans/inbound-library-imap-approle.md @@ -0,0 +1,207 @@ +--- +id: inbound-library-imap-approle +demand_source: inbound-library/workplans/ILIB-WP-0003-T01 +consumer_repo: inbound-library +credential_type: openbao-approle-kv +status: reviewed +approved_by: null +approved_at: null +approved_spec_sha256: null +created: "2026-10-05" +updated: "2026-10-05" +build_spec: + engine: openbao-approle-kv + policy_name: workload-kv-read-inbound-library-imap + kv_path: platform/workloads/inbound-library/imap + approle_name: inbound-library + token_num_uses: 4 + kv_capabilities: + - read + token_ttl: 15m + token_max_ttl: 30m + secret_id_ttl: '0' + delivery_dir: /home/worsch/.local/inbound-library/approle + bao_bin: bao + audit_log_path: /home/worsch/ops-mason/audit/build-log.jsonl + reuse_policy: false + reuse_policy_sha256: null +--- + +# Construction plan: inbound-library IMAP AppRole + +## 1. Demand + +`inbound-library` collects material delivered by email for the rest of the +ecosystem. Its first use is the daily and weekly Grok and ChatGPT intelligence +briefs for intelligence-radar. A systemd user timer on the workstation reads the +dedicated mailbox `library@coulomb.social` (IONOS, `imap.ionos.de:993`) +read-only every 15 minutes. It must authenticate without an operator, and the +founder ruled out caching the password in a file. On every run it should log in +to OpenBao, read the IMAP entry once, and revoke its token. The consumer code is +already implemented and tested against a fake OpenBao +(`inbound_library/vault.py`, ILIB-WP-0003-T04). + +The KV entry already exists. `railiance-platform/scripts/inbound_library_imap_entry.py` +created it password-free, and the founder added `IMAP_PASSWORD` as version 2 +(`railiance-platform/docs/inbound-library-imap-custody.md`). This plan only +creates the read path to it. + +## 2. Existing-structure survey + +Generated by `scripts/plan-survey.py` at 2026-10-05T12:31:21Z against `BAO_ADDR=http://127.0.0.1:18200`, token display_name `token-claude-MASON-WP-0008-T05` (policies: default, ops-mason-build). Metadata only: no `kv get` and no `*/data/*` read was issued; policy bodies were read with `bao policy read` for name-matched candidates only. + +Demand: KV path `platform/workloads/inbound-library/imap`, proposed policy `workload-kv-read-inbound-library-imap`, AppRole `inbound-library`, Kubernetes role `-`, consumer `inbound-library`. Match terms: `inbound-library`, `imap`. + +### `bao secrets list` (10 mounts, 1 relevant) + +```text +platform/ kv version=2 +``` + +### `bao auth list` (5 auth mounts, 2 relevant) + +```text +approle/ approle +kubernetes/ kubernetes +``` + +### `bao policy list` (76 policies, 1 relevant) + +```text +workload-kv-read-binky-company-email-imap matches: imap +``` + +### `bao list auth/approle/role` (approle) (13 roles, 0 relevant) + +```text +(no relevant entries) +``` + +### `bao list auth/kubernetes/role` (kubernetes) (25 roles, 0 relevant) + +```text +(no relevant entries) +``` + +### `bao policy read` of name-matched candidates (target `platform/data/workloads/inbound-library/imap`) + +```text +workload-kv-read-binky-company-email-imap: no stanza covers platform/data/workloads/inbound-library/imap +``` + +### `bao kv metadata get` / `bao kv list` (metadata only) + +```text +platform/workloads/inbound-library/imap: exists, current_version=2, created=2026-10-05, updated=2026-10-05 +platform/workloads/inbound-library/: imap +``` + +### ops-warden routing catalog `/home/worsch/ops-warden/registry/routing/catalog.yaml` (40 entries, 2 relevant) + +```text +binky-company-email-imap owner=railiance-platform status=active matches: imap + path_template: tenants/binky/company-email/imap + auth_method: caller's own OpenBao token (OIDC netkingdom role binky-company-email-imap-workload-kv-read) +agent-harness-binky-mail-approle owner=railiance-platform status=active matches: imap + path_template: tenants/binky/company-email/imap + auth_method: AppRole login role=agent-harness-binky-mail (role_id+secret_id on worker host via EXECUTOR_APPROLE_DIR) +``` + +### Does an existing lane already satisfy this demand? + +**Draft answer: partial.** Review before committing; this is generated, not a decision. + +- KV path `platform/workloads/inbound-library/imap` already exists (v2) +- policy grants were checked only for name-matched candidates; a policy with an unrelated name that covers `platform/data/workloads/inbound-library/imap` is not detected + +**Reviewed answer: no existing lane satisfies this demand.** The survey's +"partial" refers only to the KV entry, which already exists and is not touched +by this plan. The two IMAP lanes it found (`binky-company-email-imap`, +`agent-harness-binky-mail-approle`) serve a different mailbox, on mount +`tenants/`, for a different tenant. Binding inbound-library to their policy +would grant it the Binky company mailbox, and adding this path to their policy +would grant the Binky consumers the library mailbox. Both would widen scope. No +policy name in the 76 listed names covers the path, and no AppRole or Kubernetes +role is named for the consumer. The survey only inspects policies whose names +match, so a policy with an unrelated name covering +`platform/data/workloads/inbound-library/imap` would be missed. Any such grant +would be a pre-existing over-grant, and this plan does not depend on it. + +Live precedent settings, read with the same grant (`bao read +auth/approle/role/`): `agent-harness-binky-mail` and `rein-openweights` +both have `token_ttl=900`, `token_max_ttl=1800`, `token_num_uses=8`, +`secret_id_ttl=0`, `secret_id_num_uses=0` and `bind_secret_id=true`, with no CIDR +binding. + +## 3. Proposed changes + +- **create** policy `workload-kv-read-inbound-library-imap`: `read` on + `platform/data/workloads/inbound-library/imap` and + `platform/metadata/workloads/inbound-library/imap`, nothing else. It is new + because, per section 2, no existing policy is scoped to this entry, and reusing + a Binky policy would widen scope in both directions. +- **create** AppRole `inbound-library` with `token_policies = + [workload-kv-read-inbound-library-imap]`, `token_ttl=15m`, `token_max_ttl=30m`, + `token_num_uses=4`, `secret_id_ttl=0`. It is new because no role exists for the + consumer. Its shape follows the two workstation precedents. +- **create** the delivery files `/home/worsch/.local/inbound-library/approle/role_id` + and `secret_id` (private, exclusive), at the location `config/library.json` + `credential.approle_dir` already points to. +- **unchanged**: the KV entry itself, every other policy, role and mount, and the + ops-warden catalog. A pointer-only entry is proposed after the build, in + ILIB-WP-0003-T05. + +## 4. Review notes (phase 2) + +- **Naming:** `workload-kv-read--` matches the + `workload-kv-read-binky-company-email-imap` and + `workload-kv-read-rein-openweights-openrouter` convention. The AppRole is named + after the consumer, as `rein-openweights` is. +- **TTL:** 15m/30m matches both precedents. One run takes seconds. +- **`token_num_uses=4` instead of the precedents' 8:** the consumer uses exactly + two operations per run, the KV read and `auth/token/revoke-self`. 4 allows one + retry and is tighter than the precedent. Below 2 would break revoke-self. +- **`secret_id_ttl=0`** (no expiry) matches the current build-phase precedent, + and the builder requires it as an explicit, reviewable choice. Alternative: a + bounded secret-id TTL with a rotation job. That is not proposed now because the + consumer has no rotation path yet. It is recorded as the residual for a + fleet-wide rotation decision, not one made for this lane alone. +- **CIDR binding:** not used. The precedents do not use it, and the workstation + reaches OpenBao through the `openbao-ui-railiance01` bridge forward + (`127.0.0.1:18200`), so the source address OpenBao sees is the tunnel endpoint. + A binding there would add little. +- **Redundancy and compaction:** nothing to compact. The survey found no near + duplicate on `platform/`. +- **Value handling:** ops-mason never reads the IMAP value. Proof follows the + `binky-company-email-imap` CCR pattern (capabilities-safe): the lane token is + checked for `read` on the data path, and a default-policy token for `deny`, + using `sys/capabilities`. The consumer's first live run proves the value works + (ILIB-WP-0003-T06). +- **Spec digest:** `build_spec_digest(build_spec)` = + `c62765c8a4505821e77b58c5e0daf0511dec39fb569a05e190ff51f5cdc3e2ba`. This is a + candidate only. Record it as `approved_spec_sha256` only on approval. + +## 5. Executive summary (phase 3) + +1. **One-line ask.** Let the inbound-library timer on the workstation read the + password for the `library@coulomb.social` mailbox from OpenBao by itself, so no + copy of it is kept on disk. +2. **Who/what gets access.** One new machine identity, the AppRole + `inbound-library`, whose login files live only on the workstation. Nothing + else gains anything. +3. **To what.** Read-only access to exactly one entry: the library mailbox + credential. No other entry, mailbox or tenant. +4. **For how long.** Each login gives a token valid for 15 minutes (30 at most), + usable for 4 operations, and the library revokes it after each run. The login + files do not expire. That is the same build-phase choice as the existing + workstation lanes, and their rotation is a separate, fleet-wide decision. +5. **Blast radius if the credential leaks.** Whoever holds the two login files + can read the library mailbox password, and with it read, and through IMAP + change or delete, mail in `library@coulomb.social`. That mailbox only + receives automation briefs and similar inbound material. It is not a personal + or company mailbox, and it gives no access to other OpenBao entries. +6. **Cost to reverse.** Fully isolated. Deleting one role and one policy, and + removing the two login files, touches no other consumer. The mailbox password + can be rotated independently at IONOS. + +**Decision:** approve / reject / send back to phase 1 with what to change.