Phase-2 follow-up to #883: replace mothership Stalwart relay (mail.openova.io:587) with a Sovereign-local Stalwart so Console PIN/magic-link mail originates from `noreply@<sovereignFQDN>` with per-Sovereign SPF/DKIM/DMARC posture, eliminating the mothership SMTP SPOF for Sovereign Console login. What ships: 1. NEW blueprint platform/stalwart-sovereign/ (otech-level — distinct from per-tenant bp-stalwart-tenant). Single Stalwart instance per Sovereign cluster, scoped to Sovereign Console system mail. NO Keycloak OIDC, NO webmail UI — Sovereign Console is the only consumer. Auto-provisioned admin + submission Secrets via the lookup-or-generate pattern (#898/#830/#887). Post-install Job: - registers the noreply submission principal in Stalwart - allows send-as for noreply@<sovereignFQDN> - reads DKIM public key, patches dns-records ConfigMap - materialises catalyst-system/sovereign-smtp-credentials with Sovereign-local infrastructure addresses + credentials, carrying BOTH key shapes (smtp-user/smtp-pass + legacy user/password) so the consumer chart works either way. 2. NEW bootstrap-kit slot 95 (clusters/_template/bootstrap-kit/ 95-bp-stalwart-sovereign.yaml). dependsOn: bp-cert-manager, bp-catalyst-platform. Sequenced after bp-catalyst-platform (slot 13) so the chart's post-install Job lands its mirror Secret in an already-existing catalyst-system namespace. 3. bp-catalyst-platform 1.4.19 → 1.4.20: SOURCE-wins precedence extended to (a) non-secret fields smtp-host/smtp-port/smtp-from so Sovereign-local infra addresses (`mail.<sovereignFQDN>`) take over from mothership defaults (`mail.openova.io`) on the next reconcile after slot 95 lands, and (b) canonical key shape `smtp-user`/`smtp-pass` in addition to legacy `user`/`password` source key shape. 4. expected-bootstrap-deps.yaml: declare slot 95 graph edge. 5. catalyst-api handler/sovereign_smtp_seed.go: documentation-only update to note this Phase-1 step is now a graceful fallback — the Phase-2 chart's post-install Job overwrites the mirror Secret on first reconcile so the cutover from mothership relay to Sovereign-local relay is automatic, no operator action. Verification: - `helm template smoke ./platform/stalwart-sovereign/chart` clean (smoke-render-safe; per-template gates skip when sovereignFQDN unset). - `helm template smoke -f operator-values.yaml` emits StatefulSet, LoadBalancer Service, ClusterIP HTTP Service, DKIM-signing config, dns-records ConfigMap, Setup Job + RBAC. - `chart/tests/sovereign-render.sh` 3 cases all PASS. - `helm template smoke ./products/catalyst/chart` (1.4.20) clean. - `helm lint` both charts: clean (only icon-recommended INFO). - `bash scripts/check-bootstrap-deps.sh` PASSED — bootstrap-kit dependency graph audit, 0 drift, 0 cycles. - `go test -run TestSeedSovereignSMTP` — Phase-1 seed tests pass. - `go test -run TestBootstrapKit_TemplateClusterParses` — slot 95 YAML parses cleanly. Out of scope (sub-PR follow-up under #924): - DKIM keypair generation in catalyst-api orchestrator + DNS records (MX/A/SPF/DMARC/DKIM-pubkey) registration via PDM dynadot adapter at omani.works. - Hetzner PTR (rDNS) auto-registration via the Hetzner cloud API. - Cert-manager Certificate adding mail.<sovereignFQDN> SAN to the Sovereign wildcard cert (chart relies on the existing wildcard cert from bp-catalyst-platform 1.4.0+'s per-zone Certificate template — when that wildcard chain covers the Sovereign FQDN, `mail.<sovereignFQDN>` is already covered). Acceptance (lands when sub-PR follow-up ships): - Sovereign Console PIN delivery uses noreply@<sov-fqdn>. - External mail server (e.g. Gmail) accepts mail with valid SPF + DKIM. - Mothership SMTP no longer SPOF for Sovereign Console login. Co-authored-by: hatiyildiz <hatiyildiz@openova.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
158 lines
5.6 KiB
YAML
158 lines
5.6 KiB
YAML
apiVersion: catalyst.openova.io/v1alpha1
|
|
kind: Blueprint
|
|
metadata:
|
|
name: bp-stalwart-sovereign
|
|
labels:
|
|
catalyst.openova.io/category: communication
|
|
catalyst.openova.io/section: pts-4-5-communication
|
|
spec:
|
|
# 0.1.0 (#924) — initial release. Phase-2 follow-up to the Phase-1
|
|
# SMTP seed seam (#883): the Sovereign Console's PIN/magic-link mail
|
|
# is delivered by a Sovereign-LOCAL Stalwart instance instead of being
|
|
# relayed through the mothership at mail.openova.io:587. Sender flips
|
|
# from `noreply@openova.io` to `noreply@<sovereignFQDN>` and SPF/DKIM/
|
|
# DMARC posture is per-Sovereign, eliminating the mothership-as-SPOF.
|
|
#
|
|
# Distinct from `bp-stalwart-tenant`:
|
|
# - bp-stalwart-tenant — per-SME (per-vcluster) instance, OIDC-authn
|
|
# against the per-tenant Keycloak realm, customer mailboxes.
|
|
# - bp-stalwart-sovereign (THIS) — single instance per Sovereign,
|
|
# scoped to Sovereign Console mail (PIN delivery, ops alerts, the
|
|
# `noreply@<sovereignFQDN>` system mailbox). Authn is the local
|
|
# admin principal materialised by the chart's auto-provisioned
|
|
# `sovereign-smtp-credentials` Secret (read by bp-catalyst-platform's
|
|
# `catalyst-openova-kc-credentials` chart-rendered Secret via
|
|
# Helm `lookup`). NO Keycloak OIDC — Console is the only client.
|
|
#
|
|
# Per #817 Chart.yaml version MUST equal blueprint.yaml spec.version.
|
|
version: 0.1.0
|
|
card:
|
|
title: Stalwart (Sovereign Console mail)
|
|
summary: |
|
|
Sovereign-local Stalwart for Sovereign Console PIN/magic-link
|
|
mail delivery from `noreply@<sovereignFQDN>`. Phase-2 sovereignty
|
|
replacement for the mothership Stalwart relay (Phase-1 #883).
|
|
|
|
Single instance per Sovereign cluster. Materialises the
|
|
`catalyst-system/sovereign-smtp-credentials` Secret read by
|
|
bp-catalyst-platform 1.4.17+'s
|
|
`catalyst-openova-kc-credentials` chart-rendered Secret. DKIM
|
|
keypair generated at first install, public component published
|
|
via the `stalwart-sovereign-dns-records-required` ConfigMap for
|
|
the orchestrator to register at the Sovereign's parent zone via
|
|
PDM (sub-PR follow-up — chart ships first per #924 scope split).
|
|
|
|
External mail surface: `mail.<sovereignFQDN>` LoadBalancer
|
|
(SMTP 25, submission 587, submissions 465, IMAP 143/993). NO
|
|
webmail UI in the Sovereign-level instance — Sovereign Console
|
|
is the only consumer.
|
|
icon: stalwart.svg
|
|
category: communication
|
|
tags: [mail, smtp, sovereign, console, pin, dkim, spf, dmarc]
|
|
documentation: https://stalw.art/
|
|
license: AGPL-3.0
|
|
visibility: listed
|
|
owner:
|
|
team: platform
|
|
contact: catalyst@openova.io
|
|
configSchema:
|
|
type: object
|
|
required: []
|
|
properties:
|
|
sovereignFQDN:
|
|
type: string
|
|
description: |
|
|
Sovereign FQDN (e.g. `omantel.omani.works`). Drives the
|
|
SMTP sender domain (`noreply@<sovereignFQDN>`), the public
|
|
MX hostname (`mail.<sovereignFQDN>`), and the DKIM signing
|
|
domain. Resolved from `global.sovereignFQDN` at render time
|
|
per the bootstrap-kit overlay convention.
|
|
persistence:
|
|
type: object
|
|
properties:
|
|
spool:
|
|
type: object
|
|
properties:
|
|
size:
|
|
type: string
|
|
default: 10Gi
|
|
storageClassName:
|
|
type: string
|
|
description: Empty = cluster default; per-Sovereign overlay supplies.
|
|
admin:
|
|
type: object
|
|
properties:
|
|
secretName:
|
|
type: string
|
|
default: stalwart-sovereign-admin
|
|
username:
|
|
type: string
|
|
default: postmaster
|
|
smtp:
|
|
type: object
|
|
description: |
|
|
Submission credential the chart materialises into Stalwart's
|
|
runtime AND mirrors into `catalyst-system/sovereign-smtp-credentials`
|
|
for bp-catalyst-platform's `catalyst-openova-kc-credentials`
|
|
chart to read via Helm `lookup`. The chart auto-generates
|
|
a random 32-char password on first install; subsequent
|
|
reconciles read the existing bytes via `lookup`.
|
|
properties:
|
|
submissionUser:
|
|
type: string
|
|
default: noreply
|
|
submissionSecretName:
|
|
type: string
|
|
default: stalwart-sovereign-submission
|
|
dns:
|
|
type: object
|
|
properties:
|
|
dkim:
|
|
type: object
|
|
properties:
|
|
selector:
|
|
type: string
|
|
default: dkim
|
|
algorithm:
|
|
type: string
|
|
default: ed25519-sha256
|
|
spf:
|
|
type: object
|
|
properties:
|
|
policy:
|
|
type: string
|
|
enum: ["-all", "~all"]
|
|
default: "~all"
|
|
dmarc:
|
|
type: object
|
|
properties:
|
|
policy:
|
|
type: string
|
|
enum: [reject, quarantine, none]
|
|
default: quarantine
|
|
rua:
|
|
type: string
|
|
description: Empty = composed as `postmaster@<sovereignFQDN>`.
|
|
placementSchema:
|
|
modes: [single-region]
|
|
default: single-region
|
|
manifests:
|
|
chart: ./chart
|
|
depends:
|
|
- blueprint: bp-cert-manager
|
|
version: ^1.0
|
|
alias: certs
|
|
- blueprint: bp-cert-manager-powerdns-webhook
|
|
version: ^1.0
|
|
alias: certs-dns01
|
|
optional: true
|
|
- blueprint: bp-powerdns
|
|
version: ^1.0
|
|
alias: dns
|
|
optional: true
|
|
upgrades:
|
|
from: []
|
|
observability:
|
|
metrics: prometheus
|
|
logs: stdout
|