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
238 lines
12 KiB
Markdown
238 lines
12 KiB
Markdown
---
|
|
id: inbound-library-imap-approle
|
|
demand_source: inbound-library/workplans/ILIB-WP-0003-T01
|
|
consumer_repo: inbound-library
|
|
credential_type: openbao-approle-kv
|
|
status: built
|
|
approved_by: "Bernd Worsch"
|
|
approved_at: "2026-10-05"
|
|
approved_spec_sha256: "c62765c8a4505821e77b58c5e0daf0511dec39fb569a05e190ff51f5cdc3e2ba"
|
|
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.
|
|
|
|
**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.
|