ops-mason/plans/mas-tem-relay-smtp.md
tegwick 2f7237fe01 MASON-WP-0014: record CCR-2026-0051
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 278674@bnt-lap001
Assistant-Session: 64827407-e196-4ce3-b70c-76e7b537493f
2026-10-18 12:26:14 +02:00

255 lines
14 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: mas-tem-relay-smtp
demand_source: mail-address-semantics/workplans/MAS-WP-0003-T03
consumer_repo: mail-address-semantics
credential_type: openbao-kv-founder-provision
status: reviewed
approved_by: null
approved_at: null
created: "2026-10-18"
updated: "2026-10-18"
---
# Construction plan: MAS live-test TEM SMTP relay credential
## 1. Demand
`MAS-WP-0003-T03` runs a temporary live-test MX (`mas/mas-mx`) on
railiance01. It sends service replies through Scaleway Transactional Email
at `[smtp.tem.scaleway.com]:587`, because outbound port 25 is blocked on the
node. The MX needs one SMTP credential: `username` (the Scaleway project ID,
which is not secret) and `password` (a Scaleway API secret key). The founder
types the value into the OpenBao UI. ops-mason builds an empty home and a
read path into namespace `mas`, delivered as Secret `mas-relay`, which the MX
mounts optionally at `/etc/mas-relay`. The lane is temporary: it is removed
with the `mas` namespace when MAS-WP-0003 closes.
Requested by hub message `14bdd217` (2026-10-18). The request suggested
`kv/mas/tem-relay`; it is placed at `platform/suppliers/scaleway/mas-tem-relay`
instead (supplier convention). It also asked for a ClusterSecretStore `openbao-mas` and an ESO token.
This plan keeps the shape but adapts it to the current conventions (§3).
## 2. Existing-structure survey
Generated by `scripts/plan-survey.py` at 2026-10-18T10:13:55Z against `BAO_ADDR=http://127.0.0.1:18200`, token display_name `token-claude-MASON-WP-0014-T02` (policies: default, ops-mason-survey). 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/suppliers/scaleway/mas-tem-relay`, proposed policy `workload-kv-read-mas-tem-relay`, AppRole `-`, Kubernetes role `external-secrets-mas-tem-relay`, consumer `mail-address-semantics`. Match terms: `mail-address-semantics`, `external-secrets-mas-tem-relay`, `tem`, `smtp`, `mas`, `suppliers`, `scaleway`, `mas-tem-relay`.
### `bao secrets list` (10 mounts, 1 relevant)
```text
platform/ kv version=2
```
### `bao auth list` (6 auth mounts, 2 relevant)
```text
approle/ approle
kubernetes/ kubernetes
```
### `bao policy list` (94 policies, 5 relevant)
```text
authelia-smtp-eso matches: smtp
operator-kv-scaleway-bootstrap matches: scaleway
ops-mason-construct matches: mas
ops-mason-survey matches: mas
telemetry-smtp-eso matches: smtp
```
### `bao list auth/approle/role` (approle) (14 roles, 0 relevant)
```text
(no relevant entries)
```
### `bao list auth/kubernetes/role` (kubernetes) (35 roles, 2 relevant)
```text
authelia-smtp-eso matches: smtp
token_policies=['authelia-smtp-eso']
token_ttl=900
token_max_ttl=900
token_num_uses=0
bound_service_account_names=['authelia-smtp-eso']
bound_service_account_namespaces=['sso']
audience=openbao
telemetry-smtp-eso matches: smtp
token_policies=['telemetry-smtp-eso']
token_ttl=900
token_max_ttl=900
token_num_uses=0
bound_service_account_names=['telemetry-smtp-eso']
bound_service_account_namespaces=['telemetry']
audience=openbao
```
### `bao policy read` of name-matched candidates (target `platform/data/suppliers/scaleway/mas-tem-relay`)
```text
authelia-smtp-eso: no stanza covers platform/data/suppliers/scaleway/mas-tem-relay
operator-kv-scaleway-bootstrap: no stanza covers platform/data/suppliers/scaleway/mas-tem-relay
ops-mason-construct: path "platform/data/suppliers/*" capabilities=['create']
ops-mason-construct: path "platform/metadata/suppliers/*" capabilities=['create', 'read', 'update', 'delete', 'list']
ops-mason-construct: path "platform/metadata/*" capabilities=['read', 'list']
ops-mason-construct: path "platform/data/*" capabilities=['deny']
ops-mason-survey: not permitted by ops-mason-survey
telemetry-smtp-eso: no stanza covers platform/data/suppliers/scaleway/mas-tem-relay
```
### `bao kv metadata get` / `bao kv list` (metadata only)
```text
platform/suppliers/scaleway/mas-tem-relay: exists, current_version=2, created=2026-10-18, updated=2026-10-18
owner: railiance-platform
platform/suppliers/scaleway/: backup, bootstrap, mas-tem-relay
```
### ops-warden routing catalog `/home/worsch/ops-warden/registry/routing/catalog.yaml` (48 entries, 10 relevant)
```text
state-hub-forge-derivation-read owner=ops-mason status=active matches: mas
path_template: platform/workloads/state-hub/forge-derivation (field FORGE_READ_TOKEN)
auth_method: Kubernetes auth (role state-hub-forge-derivation, ServiceAccount state-hub/state-hub) from the in-cluster state-hub workload
object-storage-sts owner=net-kingdom status=draft matches: tem
rein-openweights-openrouter-approle owner=ops-mason status=active matches: mas
path_template: reins/rein-openweights/openrouter
auth_method: AppRole login role=rein-openweights (role_id+secret_id via REIN_OPENWEIGHTS_APPROLE_DIR)
inbound-library-imap-approle owner=ops-mason status=active matches: mas
path_template: platform/workloads/inbound-library/imap
auth_method: AppRole login role=inbound-library (role_id+secret_id in ~/.local/inbound-library/approle, config/library.json credential.approle_dir)
audit-core-senders owner=ops-mason status=active matches: mas
path_template: platform/workloads/audit-core/senders
policy_ref: external-secrets-audit-core
auth_method: OpenBao KV v2 custody; audit-core reads via ESO (policy external-secrets-audit-core, token auth). Operators and agents never read the registry; a sender gets its own exact-path projection.
email-connect-transactional owner=railiance-platform status=active matches: smtp
path_template: platform/workloads/email-connect/transactional
policy_ref: flex-auth check secret.read:email-connect
auth_method: caller's own OpenBao token (operator OIDC, or ESO child token openbao-email-connect-eso-token)
scaleway-bootstrap owner=railiance-platform status=draft matches: mas, scaleway
path_template: platform/workloads/railiance/scaleway/bootstrap
auth_method: caller's own OpenBao token (founder / operator workstation)
openbao-structure-survey owner=ops-mason status=active matches: mas
auth_method: founder-minted scoped grant: ops-mason scripts/bao-session.sh grant <task> (policy ops-mason-survey, 45m, display_name claude-<task>)
platform-supplier-credentials owner=railiance-platform status=active matches: suppliers
path_template: platform/suppliers/<supplier>/<account>
auth_method: railiance-platform delegated credential change (CCR); founder-attended provision from a mode-0600 file
fluid-telegram-operator owner=ops-mason status=draft matches: mas
path_template: platform/workloads/coulomb/fluid-telegram/hall-of-helix/{operator-app,operator-session,bot-token,redaction-salt}
auth_method: bao login -method=oidc -path=netkingdom role=fluid-telegram-hall-of-helix-workload-kv (members of fluid-telegram-operators, aal2, 15m token)
```
### Does an existing lane already satisfy this demand?
**Decision (review 2026-10-18): no.** Edited from the generated "partial".
- The KV path exists because ops-mason created the placeholder (v1,
10:03Z), and the founder wrote the TEM value as v2 (10:11Z). That is the
home this plan builds, not an existing lane.
- No policy or role reads `platform/data/suppliers/scaleway/mas-tem-relay`.
The only name-matched policy that touches the path is `ops-mason-construct`,
which has create-only access and cannot read.
- The two SMTP ESO lanes, `authelia-smtp-eso` and `telemetry-smtp-eso`, both
read `platform/data/workloads/railiance-telemetry/smtp`, which is the IONOS
mailbox `platform@coulomb.social` (CCR-2026-0035). That is a different
supplier and belongs to production senders, so it is not reused.
- Repo sources (checked earlier the same day): no catalog entry or CCR for
`mas` or TEM. `openbao-email-connect` is a separate production sender.
- Not detected by design: a policy with an unrelated name covering the path.
## 3. Proposed changes
SEC-DSM §37:
- **Q1** Can the operation use identity directly? No. TEM SMTP
authenticates only with username/password.
- **Q2** Can the target accept federated identity? No. TEM SMTP has no
federation.
- **Q3** Can the target issue an ephemeral credential? Not viably. Scaleway
API keys can carry an expiry, but adding an issuer for a temporary test
lane would be issuance for show (`DSM-TRANS-003`). Instead the founder sets
the key's own Scaleway expiry to the end of MAS-WP-0003.
- **Q4** Can an existing static credential be rotated automatically? No
automated rotation for a lane this short-lived.
- **Q5** Must a persistent static credential exist? Yes, for the life of
MAS-WP-0003 only. This is §5 level "vaulted static secret".
**Credential facts:**
| Field | Value |
|---|---|
| Owner | founder (Scaleway IAM) |
| Consumer | `mas/mas-mx` |
| Authority | Scaleway IAM API key on a dedicated application with only the Transactional Email permission set, on the TEM project |
| Delivery | ESO → Secret `mas/mas-relay` |
| Copies | OpenBao KV, plus one Kubernetes Secret (a delivery copy, not the authority) |
| Rotation | Replace the KV version; ESO refreshes |
| Revocation | Delete the API key in Scaleway, then tear down (rows 6–7 below) |
| Detection | TEM sending logs and the OpenBao audit log for reads of the path |
| What would eliminate it | Removing the MX, or an MTA that relays through `email-connect`'s API instead of SMTP |
| # | Action | Object | Reuse-vs-new rationale |
|---|---|---|---|
| 1 | create | KV v2 `platform/suppliers/scaleway/mas-tem-relay`, fields `username`, `password` (placeholder `xxx`) | No existing home (§2). Supplier account path `platform/suppliers/<supplier>/<account>` (founder decision 2026-10-08, ops-warden `platform-supplier-credentials`); `credential-change.py` accepts it |
| 2 | create | policy `workload-kv-read-mas-tem-relay`: `read` on `platform/data/suppliers/scaleway/mas-tem-relay`, plus token lookup-self and revoke-self (CCR-2026-0051 policy file, used verbatim) | Single-path ESO reader; no metadata read needed |
| 3 | create | Kubernetes auth role `external-secrets-mas-tem-relay`: SA `mas-tem-relay-eso`, ns `mas`, audience `openbao`, ttl/max 15m, `token_no_default_policy` | Current pattern (CCR-2026-0050), with no static ESO token to deliver or expire |
| 4 | create (consumer repo) | ServiceAccount `mas-tem-relay-eso`, **namespaced** SecretStore `openbao-mas-tem-relay`, ExternalSecret `mas-relay` (keys `username`, `password`), all in ns `mas` | Namespaced instead of a ClusterSecretStore, so the store is deleted with the namespace |
| 5 | reuse (railiance-platform) | **CCR-2026-0051** (`railiance-platform@4398f89`), with policy file `openbao/policies/workload-kv-read-mas-tem-relay.hcl` | Platform's record of the lane. ops-mason applies rows 2–3 under `ops-mason-construct` after approval and records evidence against the CCR |
| 6 | tear-down (at MAS-WP-0003 close) | auth role, policy, KV path (`metadata delete`) | Cluster-scoped OpenBao objects do not go away with the namespace |
| 7 | tear-down (founder) | Scaleway API key | Revokes the authority itself |
No catalog entry: the lane is temporary and has a single consumer.
## 4. Review notes
- **Naming:** `mas-tem-relay` is used for the supplier account segment,
policy, role, SA and store, so one grep finds every object to retire. Secret `mas-relay` keeps
the name the consumer asked for.
- **TTL/scoping:** 15m role TTL matches CCR-2026-0050. The policy covers one
data path and no metadata. Binding is to exact SA and namespace claims.
- **Redundancy:** does not reuse the `email-connect` sender key (§2).
- **Deviation from the request:** it asked for a ClusterSecretStore
`openbao-mas` and an ESO token. Both are replaced, by Kubernetes auth
(DSM-K8S-001, projected identity before a broker token) and by a
namespaced store (easier to retire). The consumer applies rows 4 itself.
- **Secret values:** ops-mason neither reads nor writes them. The policy
grants `read` only to the ESO role.
- **Naming vs existing SMTP lanes:** `authelia-smtp-eso` and
`telemetry-smtp-eso` use one name for role, policy and SA. This lane keeps
the `external-secrets-*` / `workload-kv-read-*` shape, which is what
`ops-mason-construct` may build (MASON-IN-0005).
- **Build path:** after approval, ops-mason applies rows 2–3 itself under
`bao-session.sh build`. Row 1 is done: the placeholder came first and the
founder wrote the value on 2026-10-18.
- **CCR:** CCR-2026-0051 (approvers: founder, railiance-platform-owner,
mail-address-semantics-owner). The post-build check compares the written
policy with its policy file.
- **Open:** founder approval; `bound_claims_confirmed` after the SA exists.
## 5. Executive summary
**One-line ask:** let the temporary MAS test mail server read its TEM SMTP
login from OpenBao, and nothing else, until MAS-WP-0003 ends.
**Who gets access:** the External Secrets reader in namespace `mas`, and
only when it signs in as ServiceAccount `mas-tem-relay-eso`. No person, no
agent, no other namespace.
**To what:** read on `platform/suppliers/scaleway/mas-tem-relay`
(`username`, `password`), delivered as Secret `mas/mas-relay` to the `mas-mx`
container at `/etc/mas-relay`.
**For how long:** 15-minute OpenBao sessions, renewed by ESO, until teardown
at MAS-WP-0003 close. The Scaleway key's own expiry is the hard stop.
**Blast radius if the credential leaks:** someone can send mail as your TEM
project, which risks reputation, quota and cost, until you revoke the key in
the Scaleway console. If the key is TEM-only it gives no other Scaleway
access.
**Cost to reverse:** low. Delete the role, the policy and the KV path, then
revoke the key. Nothing else depends on it.
**Decision:** pending.