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.
This commit is contained in:
parent
5d1c5e1c5c
commit
04897a149f
18 changed files with 1269 additions and 117 deletions
136
deploy/k8s/railiance/README.md
Normal file
136
deploy/k8s/railiance/README.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue