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
|
||||
30
deploy/k8s/railiance/clustersecretstore.yaml.template
Normal file
30
deploy/k8s/railiance/clustersecretstore.yaml.template
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Template copy of the railiance-platform add-on store. Prefer applying from:
|
||||
# ~/railiance-platform/argocd/platform-addons/openbao-secretstore/openbao-email-connect.clustersecretstore.yaml
|
||||
#
|
||||
# Do not apply until CCR-2026-0010 is approved and Secret
|
||||
# external-secrets/openbao-email-connect-eso-token exists
|
||||
# (./openbao-eso-token-apply.sh).
|
||||
---
|
||||
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:
|
||||
# In-cluster OpenBao on railiance01 (not the public bao.coulomb.social UI).
|
||||
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
|
||||
23
deploy/k8s/railiance/configmap.yaml
Normal file
23
deploy/k8s/railiance/configmap.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: email-connect-config
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
data:
|
||||
# Non-secret runtime shape only. SMTP auth and the caller ingest token stay
|
||||
# in OpenBao → ExternalSecret (see externalsecret.yaml).
|
||||
EMAIL_CONNECT_HTTP_PORT: "8080"
|
||||
EMAIL_CONNECT_DATABASE_PATH: "/data/email-connect.db"
|
||||
EMAIL_CONNECT_SMTP_HOST: "smtp.ionos.de"
|
||||
EMAIL_CONNECT_SMTP_PORT: "587"
|
||||
# Public portal base used in invitation and verification links.
|
||||
# Matches the live user-engine Ingress host on railiance01 (nip.io until a
|
||||
# coulomb.social alias is cut over). Update when the public hostname changes.
|
||||
EMAIL_CONNECT_PORTAL_URL: "https://users.92-205-62-239.nip.io"
|
||||
# Envelope From — interim identity matches the live forgejo IONOS SMTP lane
|
||||
# (forgejo@coulomb.social) used to seed CCR-2026-0010. Switch to a dedicated
|
||||
# noreply@ mailbox when that credential is minted separately.
|
||||
EMAIL_CONNECT_SENDER: "forgejo@coulomb.social"
|
||||
97
deploy/k8s/railiance/deployment.yaml
Normal file
97
deploy/k8s/railiance/deployment.yaml
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# email-connect transactional receiver — railiance01 (EMAIL-WP-0004-T03).
|
||||
#
|
||||
# Conventions match audit-core / user-engine: digest-pinned image from
|
||||
# forgejo.coulomb.social, non-root, read-only root, probes on named http port.
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: email-connect
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
annotations:
|
||||
email-connect.railiance.io/rollback-note: >-
|
||||
SQLite schema is additive (CREATE TABLE IF NOT EXISTS + optional
|
||||
canceled_at column). Rolling back to the previous digest keeps the
|
||||
same store path; duplicate event_id rows remain authoritative.
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 5
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
spec:
|
||||
serviceAccountName: email-connect
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
fsGroup: 10001
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
terminationGracePeriodSeconds: 15
|
||||
containers:
|
||||
- name: email-connect
|
||||
# Pin at release. T04 failure-matrix image (provider classification +
|
||||
# suppression + redacted diagnostics), 2026-08-12.
|
||||
image: forgejo.coulomb.social/coulomb/email-connect@sha256:a9de1994eea88502ec30ba7c2fc029ec901d873140d51e11d2c50e363a0578b1
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: email-connect-config
|
||||
- secretRef:
|
||||
name: email-connect-runtime
|
||||
optional: false
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10001
|
||||
runAsGroup: 10001
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
startupProbe:
|
||||
httpGet: {path: /healthz, port: http}
|
||||
periodSeconds: 3
|
||||
failureThreshold: 20
|
||||
readinessProbe:
|
||||
httpGet: {path: /readyz, port: http}
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet: {path: /healthz, port: http}
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: email-connect-data
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
44
deploy/k8s/railiance/externalsecret.yaml
Normal file
44
deploy/k8s/railiance/externalsecret.yaml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Credential delivery for the transactional receiver (EMAIL-WP-0004-T03).
|
||||
#
|
||||
# PREREQUISITES (railiance-platform, not this repo):
|
||||
# - ClusterSecretStore openbao-email-connect limited to this namespace
|
||||
# - OpenBao policy external-secrets-email-connect + role external-secrets-email-connect
|
||||
# - Operator-provisioned KV at platform/workloads/email-connect/transactional
|
||||
# (fields listed below; no secret values in Git)
|
||||
# Apply order: ClusterSecretStore → this ExternalSecret → Deployment.
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: email-connect-runtime
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: openbao-email-connect
|
||||
target:
|
||||
name: email-connect-runtime
|
||||
creationPolicy: Owner
|
||||
deletionPolicy: Retain
|
||||
data:
|
||||
# Shared bearer for user-engine → email-connect. user-engine holds the same
|
||||
# value as EMAIL_CONNECT_INGEST_TOKEN under its own custody path; this
|
||||
# service never receives SMTP material from the caller.
|
||||
- secretKey: EMAIL_CONNECT_INGEST_TOKEN
|
||||
remoteRef:
|
||||
key: platform/workloads/email-connect/transactional
|
||||
property: EMAIL_CONNECT_INGEST_TOKEN
|
||||
- secretKey: EMAIL_CONNECT_SMTP_USERNAME
|
||||
remoteRef:
|
||||
key: platform/workloads/email-connect/transactional
|
||||
property: EMAIL_CONNECT_SMTP_USERNAME
|
||||
- secretKey: EMAIL_CONNECT_SMTP_PASSWORD
|
||||
remoteRef:
|
||||
key: platform/workloads/email-connect/transactional
|
||||
property: EMAIL_CONNECT_SMTP_PASSWORD
|
||||
12
deploy/k8s/railiance/kustomization.yaml
Normal file
12
deploy/k8s/railiance/kustomization.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: email-connect
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- configmap.yaml
|
||||
- pvc.yaml
|
||||
- service.yaml
|
||||
- networkpolicies.yaml
|
||||
- externalsecret.yaml
|
||||
- deployment.yaml
|
||||
9
deploy/k8s/railiance/namespace.yaml
Normal file
9
deploy/k8s/railiance/namespace.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: email-connect
|
||||
labels:
|
||||
kubernetes.io/metadata.name: email-connect
|
||||
railiance.io/workload-class: platform
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
78
deploy/k8s/railiance/networkpolicies.yaml
Normal file
78
deploy/k8s/railiance/networkpolicies.yaml
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Default-deny plus the narrowest exceptions (EMAIL-WP-0004-T03).
|
||||
#
|
||||
# Reachability is part of the threat model: only user-engine may call the
|
||||
# receiver, and egress is limited to DNS + the IONOS SMTP STARTTLS port.
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: email-connect-default-deny
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes: [Ingress, Egress]
|
||||
# No rules: everything not permitted below is denied.
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: email-connect-user-engine-ingress
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
# user-engine is the only approved sender. Adding a second caller is a
|
||||
# deliberate change here and a matching credential binding.
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: user-engine
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: user-engine
|
||||
ports:
|
||||
- {protocol: TCP, port: 8080}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: email-connect-egress
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
policyTypes: [Egress]
|
||||
egress:
|
||||
# Cluster DNS
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- {protocol: UDP, port: 53}
|
||||
- {protocol: TCP, port: 53}
|
||||
# IONOS SMTP STARTTLS only. No general internet egress.
|
||||
# smtp.ionos.de resolves outside the cluster; CIDR is intentionally broad
|
||||
# but port-locked to 587. Tighten to provider CIDRs if inventory is fixed.
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except:
|
||||
- 10.0.0.0/8
|
||||
- 172.16.0.0/12
|
||||
- 192.168.0.0/16
|
||||
ports:
|
||||
- {protocol: TCP, port: 587}
|
||||
83
deploy/k8s/railiance/openbao-eso-token-apply.sh
Executable file
83
deploy/k8s/railiance/openbao-eso-token-apply.sh
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env bash
|
||||
# Mint a read-limited OpenBao token and store it on railiance01 for
|
||||
# ClusterSecretStore openbao-email-connect / ExternalSecret email-connect-runtime.
|
||||
#
|
||||
# Policies (files in railiance-platform/openbao/policies/):
|
||||
# external-secrets-email-connect
|
||||
# workload-kv-read-email-connect-transactional (optional operator OIDC sibling)
|
||||
#
|
||||
# Does not print secret values. Requires an attended operator OpenBao token.
|
||||
# CCR-2026-0010 must be approved before production use.
|
||||
set -euo pipefail
|
||||
|
||||
DEFAULT_POLICIES="external-secrets-email-connect"
|
||||
POLICIES="${OPENBAO_EMAIL_CONNECT_POLICIES:-$DEFAULT_POLICIES}"
|
||||
POLICY_DIR="${OPENBAO_POLICY_DIR:-$HOME/railiance-platform/openbao/policies}"
|
||||
BAO_ADDR="${BAO_ADDR:-https://bao.coulomb.social}"
|
||||
RAILIANCE01_KUBECONFIG="${RAILIANCE01_KUBECONFIG:-$HOME/.kube/config-hosteurope}"
|
||||
SECRET_NAME="${OPENBAO_EMAIL_CONNECT_ESO_SECRET:-openbao-email-connect-eso-token}"
|
||||
SECRET_NS="${OPENBAO_EMAIL_CONNECT_ESO_NAMESPACE:-external-secrets}"
|
||||
TTL="${OPENBAO_EMAIL_CONNECT_ESO_TTL:-768h}"
|
||||
|
||||
if ! command -v bao >/dev/null 2>&1; then
|
||||
echo "ERROR: bao CLI not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v kubectl >/dev/null 2>&1; then
|
||||
echo "ERROR: kubectl not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OpenBao addr: $BAO_ADDR"
|
||||
echo "Policies: $POLICIES"
|
||||
echo "K8s secret: $SECRET_NS/$SECRET_NAME (railiance01)"
|
||||
|
||||
if [[ -n "${BAO_TOKEN:-}" ]]; then
|
||||
:
|
||||
elif [[ -n "${OPENBAO_TOKEN_FILE:-}" && -f "${OPENBAO_TOKEN_FILE}" ]]; then
|
||||
BAO_TOKEN="$(head -n 1 "${OPENBAO_TOKEN_FILE}")"
|
||||
else
|
||||
read -r -s -p "OpenBao operator token: " BAO_TOKEN
|
||||
echo >&2
|
||||
fi
|
||||
if [[ -z "${BAO_TOKEN:-}" ]]; then
|
||||
echo "ERROR: empty OpenBao token" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export BAO_ADDR BAO_TOKEN
|
||||
|
||||
health="$(curl -fsS "$BAO_ADDR/v1/sys/health")"
|
||||
if echo "$health" | grep -q '"sealed":true'; then
|
||||
echo "ERROR: OpenBao at $BAO_ADDR reports sealed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for policy in $POLICIES; do
|
||||
policy_file="$POLICY_DIR/${policy}.hcl"
|
||||
if [[ -f "$policy_file" ]]; then
|
||||
bao policy write "$policy" "$policy_file"
|
||||
echo "policy written: $policy"
|
||||
else
|
||||
echo "WARN: policy file missing ($policy_file); using existing OpenBao policy '$policy'" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
# Child token: renewable, orphan so operator logout does not revoke delivery.
|
||||
# shellcheck disable=SC2086
|
||||
token_json="$(bao token create -policy="$(echo $POLICIES | tr ' ' ',')" -ttl="$TTL" -renewable=true -orphan -format=json)"
|
||||
child_token="$(printf '%s' "$token_json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["auth"]["client_token"])')"
|
||||
if [[ -z "$child_token" || ${#child_token} -lt 8 ]]; then
|
||||
echo "ERROR: failed to mint child token" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "minted child token length=${#child_token} (value not printed)"
|
||||
|
||||
export KUBECONFIG="$RAILIANCE01_KUBECONFIG"
|
||||
kubectl -n "$SECRET_NS" create secret generic "$SECRET_NAME" \
|
||||
--from-literal=token="$child_token" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
unset child_token BAO_TOKEN
|
||||
echo "Secret $SECRET_NS/$SECRET_NAME applied on railiance01."
|
||||
echo "Next: apply ClusterSecretStore openbao-email-connect, then email-connect package."
|
||||
14
deploy/k8s/railiance/pvc.yaml
Normal file
14
deploy/k8s/railiance/pvc.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: email-connect-data
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
# SQLite idempotency + verification digest store only.
|
||||
storage: 1Gi
|
||||
17
deploy/k8s/railiance/service.yaml
Normal file
17
deploy/k8s/railiance/service.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: email-connect
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: email-connect
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
9
deploy/k8s/railiance/serviceaccount.yaml
Normal file
9
deploy/k8s/railiance/serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: email-connect
|
||||
namespace: email-connect
|
||||
labels:
|
||||
app.kubernetes.io/name: email-connect
|
||||
app.kubernetes.io/part-of: email-connect
|
||||
automountServiceAccountToken: false
|
||||
Loading…
Add table
Add a link
Reference in a new issue