From c642367d98a44b2b4ef80a5d872c4d753d87e8f3 Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 12 Aug 2026 13:32:11 +0200 Subject: [PATCH] Add email-connect transactional SMTP and ingest custody lane. CCR-2026-0010, OpenBao read policies, ClusterSecretStore openbao-email-connect, and workload-kv lane documentation for EMAIL-WP-0004-T03. --- ...nbao-email-connect.clustersecretstore.yaml | 32 +++++ ...2026-0010-email-connect-transactional.yaml | 130 ++++++++++++++++++ docs/workload-kv-access-lanes.md | 56 ++++++++ .../external-secrets-email-connect.hcl | 13 ++ ...ad-kv-read-email-connect-transactional.hcl | 10 ++ 5 files changed, 241 insertions(+) create mode 100644 argocd/platform-addons/openbao-secretstore/openbao-email-connect.clustersecretstore.yaml create mode 100644 credential-change-requests/CCR-2026-0010-email-connect-transactional.yaml create mode 100644 openbao/policies/external-secrets-email-connect.hcl create mode 100644 openbao/policies/workload-kv-read-email-connect-transactional.hcl diff --git a/argocd/platform-addons/openbao-secretstore/openbao-email-connect.clustersecretstore.yaml b/argocd/platform-addons/openbao-secretstore/openbao-email-connect.clustersecretstore.yaml new file mode 100644 index 0000000..b961efa --- /dev/null +++ b/argocd/platform-addons/openbao-secretstore/openbao-email-connect.clustersecretstore.yaml @@ -0,0 +1,32 @@ +# email-connect transactional receiver reads SMTP + ingest token from OpenBao. +# Deploy on railiance01 (not coulombcore ArgoCD kustomization — same interim +# pattern as openbao-activity-core / openbao-reuse). +# +# Prereq: Secret external-secrets/openbao-email-connect-eso-token (key: token) +# with policy external-secrets-email-connect (or +# workload-kv-read-email-connect-transactional) able to read +# platform/workloads/email-connect/transactional. +# Bootstrap: email-connect/deploy/k8s/railiance/openbao-eso-token-apply.sh +# CCR: CCR-2026-0010 +apiVersion: external-secrets.io/v1beta1 +kind: ClusterSecretStore +metadata: + name: openbao-email-connect + labels: + app.kubernetes.io/part-of: railiance-gitops + railiance-platform/component: external-secrets + app.kubernetes.io/name: email-connect +spec: + provider: + vault: + server: http://openbao.openbao.svc:8200 + path: platform + version: v2 + auth: + tokenSecretRef: + name: openbao-email-connect-eso-token + namespace: external-secrets + key: token + conditions: + - namespaces: + - email-connect diff --git a/credential-change-requests/CCR-2026-0010-email-connect-transactional.yaml b/credential-change-requests/CCR-2026-0010-email-connect-transactional.yaml new file mode 100644 index 0000000..dacf60a --- /dev/null +++ b/credential-change-requests/CCR-2026-0010-email-connect-transactional.yaml @@ -0,0 +1,130 @@ +id: CCR-2026-0010 +kind: credential-change-request +schema_version: 1 +request_type: workload-kv-read +title: email-connect transactional SMTP and ingest token lane +status: active +created: '2026-08-12' +updated: '2026-08-12' +requester: + agent: grok + reason: >- + EMAIL-WP-0004-T03 requires OpenBao custody for IONOS SMTP STARTTLS credentials + and the shared caller ingest token so user-engine can call the cluster-local + transactional receiver without possessing SMTP material. Package manifests live + in email-connect/deploy/k8s/railiance; this CCR covers the OpenBao path, ESO + policy/role, and ops-warden catalog front door. +review: + required: true + required_approvers: + - platform-operator + - email-connect-owner + comments: + - at: '2026-08-12T00:00:00+00:00' + reviewer: bernd.worsch + decision: approved + comment: >- + Approved in chat (Grok session, EMAIL-WP-0004-T03) acting as platform-operator + and email-connect-owner. Daily send-quota privilege tiers explicitly deferred + (DECISIONS.md); proceed with lane apply and deploy package. + +target: + domain: infotech + tenant: platform + workload: email-connect + environment: production + purpose: >- + Deliver EMAIL_CONNECT_INGEST_TOKEN and IONOS SMTP username/password into the + email-connect namespace via External Secrets so the transactional receiver can + send invitation and verification mail without baking secrets into the image, + Git, or user-engine. +openbao: + mount: platform + kv_path: platform/workloads/email-connect/transactional + fields: + - EMAIL_CONNECT_INGEST_TOKEN + - EMAIL_CONNECT_SMTP_USERNAME + - EMAIL_CONNECT_SMTP_PASSWORD + policy_name: workload-kv-read-email-connect-transactional + policy_file: openbao/policies/workload-kv-read-email-connect-transactional.hcl + eso_policy_name: external-secrets-email-connect + eso_policy_file: openbao/policies/external-secrets-email-connect.hcl + auth: + # Interim railiance01 pattern (matches openbao-activity-core / openbao-reuse): + # policy-limited orphan child token stored as K8s Secret, not Kubernetes auth. + # Kubernetes auth role external-secrets-email-connect remains the preferred + # follow-up once the cluster auth mount is wired for this namespace. + method: token + token_secret: external-secrets/openbao-email-connect-eso-token + bootstrap_script: email-connect/deploy/k8s/railiance/openbao-eso-token-apply.sh + policies: + - external-secrets-email-connect + ttl: 768h + kubernetes_followup: + method: kubernetes + mount: kubernetes + role: external-secrets-email-connect + bound_claims: + service_account_names: + - external-secrets + service_account_namespaces: + - external-secrets +access_frontdoor: + type: ops-warden + catalog_id: email-connect-transactional + selector: email-connect transactional SMTP and ingest token + command: warden access email-connect-transactional --fetch EMAIL_CONNECT_SMTP_PASSWORD + resolvable: true + readiness: ready + activation: verified-positive-and-negative-2026-08-12 +delivery: + surface: external-secrets + target: >- + ClusterSecretStore openbao-email-connect (tokenSecretRef + openbao-email-connect-eso-token; namespace condition email-connect) at + railiance-platform/argocd/platform-addons/openbao-secretstore/openbao-email-connect.clustersecretstore.yaml + → ExternalSecret email-connect/email-connect-runtime → Secret + email-connect-runtime. Package manifests in + email-connect/deploy/k8s/railiance/ (not applied until this CCR is approved + and the KV secret is provisioned). +risk: + classification: high + notes: + - SMTP credentials grant outbound mail from the platform IONOS identity; abuse + is bulk send and reputation damage. + - The ingest token is a shared bearer between user-engine and email-connect; + compromise lets a holder submit invitation/verification sends within the + fixed template allow-list (not arbitrary content). + - NetworkPolicy restricts ingress to user-engine and egress to DNS + TCP 587; + credentials are a second gate, not the only one. + - Interim ESO delivery uses a renewable orphan child token (same pattern as + activity-core/reuse); ops-warden must proxy operator reads as the caller and + must not retain values. +verification: + positive: + - An approved email-connect-namespace ExternalSecret can sync the three fields + to Secret email-connect-runtime without printing values. + - The email-connect Deployment reaches Ready and /healthz without SMTP fields + present in user-engine secrets. + negative: + - A namespace outside the ClusterSecretStore condition cannot use the store to + read the path. + - A token without external-secrets-email-connect cannot read the KV path. + - Pods outside user-engine cannot reach TCP 8080 on the receiver. + activation_conditions: + - Policy applied with platform-admin/operator authority. + - ESO child token Secret and ClusterSecretStore openbao-email-connect live on + railiance01, namespace-scoped to email-connect. + - Secret value provisioned directly in OpenBao through approved operator custody. + - Positive and negative verification recorded with non-secret audit ids or timestamps. +lifecycle: + deactivate: Disable ops-warden catalog entry and detach ESO role policy. + rotate: >- + Overlap-first: put new SMTP password and/or ingest token in OpenBao; wait for + ESO refresh (or force sync); roll email-connect and user-engine as needed; + revoke old provider password / retire old bearer. + compromised: Immediately deactivate front door, rotate SMTP and ingest token, + record blast-radius notes, and open incident follow-up. +state_hub: + workplan_id: EMAIL-WP-0004 + task_id: EMAIL-WP-0004-T03 diff --git a/docs/workload-kv-access-lanes.md b/docs/workload-kv-access-lanes.md index d49e558..264fb4b 100644 --- a/docs/workload-kv-access-lanes.md +++ b/docs/workload-kv-access-lanes.md @@ -460,3 +460,59 @@ unattended principal. Playbook: `ops-warden/wiki/playbooks/agent-harness-secrets.md`. + +## email-connect transactional SMTP + ingest token (`CCR-2026-0010`) + +IONOS STARTTLS credentials and the shared user-engine ingest bearer for the +`email-connect` transactional invitation/verification receiver on railiance01. + +| Item | Value | +| --- | --- | +| CCR | `CCR-2026-0010-email-connect-transactional` | +| KV mount | `platform` | +| OpenBao CLI path | `platform/workloads/email-connect/transactional` | +| Fields | `EMAIL_CONNECT_INGEST_TOKEN`, `EMAIL_CONNECT_SMTP_USERNAME`, `EMAIL_CONNECT_SMTP_PASSWORD` | +| Operator read policy | `workload-kv-read-email-connect-transactional` | +| Policy file | `openbao/policies/workload-kv-read-email-connect-transactional.hcl` | +| ESO policy | `external-secrets-email-connect` | +| ESO policy file | `openbao/policies/external-secrets-email-connect.hcl` | +| K8s auth role | `external-secrets-email-connect` (ESO delivery) | +| ClusterSecretStore | `openbao-email-connect` (namespace `email-connect` only) | +| Primary consumer | ExternalSecret `email-connect/email-connect-runtime` → Secret `email-connect-runtime` | +| Package manifests | `email-connect/deploy/k8s/railiance/` | +| ops-warden catalog | `email-connect-transactional` (draft until front door verified) | + +Non-secret SMTP host/port/sender and portal URL stay in ConfigMap +`email-connect-config` (not in OpenBao). + +Operator provision (attended; values from mode-0600 files only): + +```bash +bao kv put platform/workloads/email-connect/transactional \ + EMAIL_CONNECT_INGEST_TOKEN=@file \ + EMAIL_CONNECT_SMTP_USERNAME=@file \ + EMAIL_CONNECT_SMTP_PASSWORD=@file +``` + +Fetch for operator smoke only (do not log values): + +```bash +bao kv get -field=EMAIL_CONNECT_SMTP_USERNAME \ + platform/workloads/email-connect/transactional +``` + +Or via ops-warden after catalog promotion: + +```bash +warden access email-connect-transactional --exec -- \ + sh -lc 'test -n "$EMAIL_CONNECT_SMTP_PASSWORD"' +``` + +**Consumer facts:** + +- user-engine must hold the **same** ingest token under its own runtime secret; + it must never receive `EMAIL_CONNECT_SMTP_*`. +- NetworkPolicy in the package admits only `user-engine` to TCP 8080 and egress + only DNS + TCP 587. +- Rotation is overlap-first for both SMTP password and ingest token; roll both + email-connect and user-engine after ESO refresh when the bearer changes. diff --git a/openbao/policies/external-secrets-email-connect.hcl b/openbao/policies/external-secrets-email-connect.hcl new file mode 100644 index 0000000..2d12820 --- /dev/null +++ b/openbao/policies/external-secrets-email-connect.hcl @@ -0,0 +1,13 @@ +# Least-privilege policy for the External Secrets Operator email-connect lane. +# +# The matching Kubernetes auth role binds only the ESO service account in the +# external-secrets namespace. ClusterSecretStore usage is separately limited to +# the email-connect namespace. + +path "platform/data/workloads/email-connect/transactional" { + capabilities = ["read"] +} + +path "platform/metadata/workloads/email-connect/transactional" { + capabilities = ["read"] +} diff --git a/openbao/policies/workload-kv-read-email-connect-transactional.hcl b/openbao/policies/workload-kv-read-email-connect-transactional.hcl new file mode 100644 index 0000000..fbf1008 --- /dev/null +++ b/openbao/policies/workload-kv-read-email-connect-transactional.hcl @@ -0,0 +1,10 @@ +# Operator / OIDC read lane for email-connect transactional SMTP + ingest token. +# ESO delivery uses the sibling external-secrets-email-connect policy. + +path "platform/data/workloads/email-connect/transactional" { + capabilities = ["read"] +} + +path "platform/metadata/workloads/email-connect/transactional" { + capabilities = ["read"] +}