email-connect/deploy/k8s/railiance/README.md
tegwick 04897a149f
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Complete EMAIL-WP-0004 transactional invitation mail delivery.
Ship the railiance01 deploy package (OpenBao/ESO custody, NetworkPolicy,
probes), provider failure classification and suppression, T04 unit and live
proof, and non-secret NK-WP-0024 hand-back evidence. Workplan finished.
2026-08-12 13:32:11 +02:00

136 lines
5.3 KiB
Markdown

# email-connect transactional receiver — railiance01
Deploy the narrow invitation/verification HTTP receiver so **user-engine** can
send mail without holding SMTP credentials, and so unrelated workloads cannot
reach the send API.
**Target cluster:** railiance01 only (`KUBECONFIG=~/.kube/config-hosteurope`).
Stable in-cluster URL after apply:
```text
http://email-connect.email-connect.svc.cluster.local:8080
```
## Ownership split
| Concern | Owner | Where |
| --- | --- | --- |
| Package, manifests, probes, NetworkPolicy, rollback | `email-connect` | this directory |
| OpenBao path, ESO role/policy, ClusterSecretStore | `railiance-platform` | CCR-2026-0010, `openbao/policies/` |
| Credential routing catalog | `ops-warden` | `email-connect-transactional` |
Never paste SMTP passwords, ingest tokens, or OpenBao tokens into Git, State
Hub, workplans, logs, or chat. Route first:
```bash
warden route find "email-connect SMTP transactional" --json
warden route show email-connect-transactional --json
```
## Runtime secrets (OpenBao → ESO)
| Item | Value |
| --- | --- |
| KV path | `platform/workloads/email-connect/transactional` |
| Fields | `EMAIL_CONNECT_INGEST_TOKEN`, `EMAIL_CONNECT_SMTP_USERNAME`, `EMAIL_CONNECT_SMTP_PASSWORD` |
| Read / ESO policy | `external-secrets-email-connect` (+ `workload-kv-read-email-connect-transactional` for operator OIDC) |
| ClusterSecretStore | `openbao-email-connect` (namespace-limited; token auth like activity-core/reuse) |
| ESO token Secret | `external-secrets/openbao-email-connect-eso-token` (via `openbao-eso-token-apply.sh`) |
| ExternalSecret | `email-connect/email-connect-runtime` → Secret `email-connect-runtime` |
| ops-warden catalog | `email-connect-transactional` (draft until provisioned) |
Non-secret config (host, port, sender, portal URL) lives in ConfigMap
`email-connect-config`.
### Operator provision (attended, after CCR approval)
```bash
# Values from mode-0600 files only; shred after.
bao kv put platform/workloads/email-connect/transactional \
EMAIL_CONNECT_INGEST_TOKEN=@/path/to/ingest.token \
EMAIL_CONNECT_SMTP_USERNAME=@/path/to/smtp.user \
EMAIL_CONNECT_SMTP_PASSWORD=@/path/to/smtp.pass
```
user-engine must receive the **same** ingest token under its own runtime secret
path so it can call `Authorization: Bearer …` without ever seeing SMTP fields.
## Network posture
- **Default deny** ingress and egress in `email-connect`.
- **Ingress:** only pods labeled `app.kubernetes.io/name=user-engine` in
namespace `user-engine`, TCP 8080.
- **Egress:** kube-system DNS + TCP 587 to the public internet (IONOS
STARTTLS). No cluster-lateral egress and no general HTTPS.
Bearer auth on `/v1/*` is the second gate; NetworkPolicy is the outer gate.
## Apply
Prerequisites:
1. CCR-2026-0010 approved.
2. KV secret provisioned at `platform/workloads/email-connect/transactional`
(fields non-empty; do not log values).
3. ESO delivery token + ClusterSecretStore on railiance01 (token pattern matches
`openbao-activity-core` / `openbao-reuse`, not Kubernetes auth yet).
4. Image available on the node/registry at the digest pinned in `deployment.yaml`.
```bash
export KUBECONFIG=~/.kube/config-hosteurope
# 1) Policy + limited ESO child token → external-secrets Secret
./deploy/k8s/railiance/openbao-eso-token-apply.sh
# 2) Namespace-scoped ClusterSecretStore (platform add-on copy)
kubectl apply -f ~/railiance-platform/argocd/platform-addons/openbao-secretstore/openbao-email-connect.clustersecretstore.yaml
# 3) Optional: load a freshly built image when the registry is not yet updated
# docker build -f Containerfile -t forgejo.coulomb.social/coulomb/email-connect:local .
# docker save … | ssh railiance01 sudo k3s ctr -n k8s.io images import -
# 4) Package
kubectl apply -k deploy/k8s/railiance
kubectl -n email-connect rollout status deployment/email-connect
kubectl -n email-connect get pods,svc,networkpolicy,externalsecret,secret
```
Expect ExternalSecret `SecretSynced` and a Ready pod. If the Secret is missing,
do not create a bootstrap Secret with values from chat — fix OpenBao/ESO.
## Smoke (no SMTP proof yet)
From a **user-engine** pod (or a temporary pod in that namespace with the same
labels — NetworkPolicy will otherwise drop the packet):
```bash
# Health only (no auth)
kubectl -n user-engine exec deploy/user-engine -- \
wget -qO- http://email-connect.email-connect.svc.cluster.local:8080/healthz
# Auth check: wrong token must be 401; correct token is exercised by T04.
```
Negative checks:
- A pod in another namespace cannot reach port 8080 (connection timeout/reset).
- user-engine runtime must not contain `EMAIL_CONNECT_SMTP_*` keys.
## Rollback
```bash
kubectl -n email-connect rollout undo deployment/email-connect
kubectl -n email-connect rollout status deployment/email-connect
```
`revisionHistoryLimit: 5` keeps prior digests. SQLite on the PVC is additive;
rolling back does not clear delivery/idempotency rows (by design).
## Done criteria (EMAIL-WP-0004-T03)
- [ ] user-engine calls the cluster-local receiver without possessing SMTP credentials
- [ ] Unrelated workloads cannot open TCP 8080 on the receiver
- [ ] SMTP password and ingest token exist only in OpenBao + the scoped K8s Secret
- [ ] Probes green; NetworkPolicy default-deny + SMTP-only egress applied
- [ ] Documented rollback path works against the previous revision