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.
136 lines
5.3 KiB
Markdown
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
|