Plan the inbound-library IMAP AppRole lane (reviewed, awaiting approval).
Demand: inbound-library ILIB-WP-0003-T01. First live plan-survey run (MASON-WP-0008-T05) included verbatim in section 2. 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
b9c9f082cf
commit
f7d5624f56
1 changed files with 207 additions and 0 deletions
207
plans/inbound-library-imap-approle.md
Normal file
207
plans/inbound-library-imap-approle.md
Normal file
|
|
@ -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/<name>`): `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-<consumer>-<purpose>` 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue