Solo queda comprobada cuando algo EXTERNO tiene que estar de acuerdo, y externo significa llegar por una ruta distinta de la que la produjo. Medido en dos frentes: 25 errores en un dia, CERO cazados por relectura. Sesion: superadmin Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .metodo | ||
| doc | ||
| .gitattributes | ||
| CLAUDE.md | ||
| Dockerfile | ||
| entrypoint.sh | ||
| main.cf | ||
| master.cf | ||
| README.md | ||
| smtpd.conf | ||
smtp-relay
Postfix SMTP relay with Cyrus SASL authentication and TLS.
Used by Mailu (personal + Solidaria NGO) on valhalla to route outbound mail through hermes, which has a trusted residential IP accepted by Gmail and Hotmail.
Image: harbor.manabo.org/library/smtp-relay
Deployed on: hermes (clusters/hermes/smtp-relay/ in asgard)
Build
Con mcp-build (2026-07-29). Antes había un scripts/build.sh con Kaniko y un
.gitea/workflows/build.yml: los dos se han borrado. Kaniko y Gitea Actions se están
retirando, y este workflow además llevaba roto desde mayo — era el único del árbol que usaba
actions/checkout@v4, que necesita node, y el runner :host no lo tiene
(Cannot find: node in PATH). Fallaba en 70 s en cada push y nadie se enteraba: la imagen que
sostuvo el correo durante 70 días era del 20 de mayo.
trigger_build(repo_url="https://git.manabo.org/xavor/smtp-relay.git",
ref="main", image_tag="dev", image_name="library/smtp-relay")
⚠️ image_name va sin el host. Con harbor.manabo.org/library/smtp-relay el destino sale
duplicado (harbor.manabo.org/harbor.manabo.org/...) y el push muere con 401.
El despliegue se pinea a mano en asgard/clusters/hermes/smtp-relay/kustomization.yaml.
Configuration
Required Vault secrets (app/smtp-relay/smtp-relay-sasl)
| Key | Description |
|---|---|
relay_user |
SASL username (e.g. relayuser) |
relay_pass |
SASL password (plaintext — stored in Vault) |
relay_domain |
SASL domain (e.g. manabo.org) |
TLS (certs/smtp-relay-tls)
Wildcard cert for relay.manabo.org — pushed to Vault via PushSecret on valhalla.
Env vars (from ExternalSecret)
| Var | Source |
|---|---|
RELAY_AUTH_USER |
relay_user |
RELAY_AUTH_PASS |
relay_pass |
RELAY_AUTH_DOMAIN |
relay_domain |
How it works
- Listens on ports 25 (SMTP, TLS optional) and 587 (submission, TLS required)
- Uses
hostNetwork: true— ports exposed directly on the hermes host IP - Entrypoint creates a
sasldb2user from the env vars on every start - Only clients authenticated via SASL can relay mail
- TLS cert mounted from Vault ExternalSecret
Gotchas
- sasldb2 recreated on every restart: credentials are read from env vars and
saslpasswd2re-creates the sasldb. This is intentional (stateless SASL). - No DKIM: DKIM signing is not implemented in this image. Relay delivers mail as-is; DKIM signatures must be added by the sending MTA (Mailu).
Por qué la config es como es (incidente del 2026-07-29)
El correo saliente de manabo.org estuvo semanas sin salir por cinco fallos encadenados, cada
uno tapando al siguiente. Cuatro de los arreglos vivieron 70 días aplicados a mano sobre el
contenedor vivo, fuera de git. Están en la imagen desde el 2026-07-29. Relato completo en
asgard/clusters/hermes/smtp-relay/README.md. Lo que no se puede tocar sin reabrirlo:
tlsmgrenmaster.cf. Sin ese daemon,smtpdanunciaSTARTTLSen elEHLOy luego no puede hacerlo:454 4.7.0 TLS not available due to local problem. La capacidad anunciada no prueba la capacidad.postfixen el gruposasl(usermod -aG sasl postfixen el Dockerfile)./etc/sasldb2esroot:sasl 0640; sin el grupo,smtpdno puede abrirlo →454 Temporary authentication failure.smtpd_sasl_local_domainsale delentrypoint, no demain.cf. Tiene que ser el mismoRELAY_AUTH_DOMAINcon el quesaslpasswd2crea la entrada del sasldb. Estaba declarado aparte como$myhostname, así que el realm erarelayuser@relay.manabo.orgy la entrada realrelayuser@manabo.org. No lo devuelvas amain.cf: separados, vuelven a poder divergir.chroot = nen todos los servicios demaster.cf. Con chroot, Postfix necesita/var/spool/postfix/etc/resolv.confpoblado en el build; sin él no resuelve MX y el correo se queda en cola conName service error, aunquegetent hostsdentro del contenedor sí resuelva (la herramienta de diagnóstico no ve lo mismo que el consumidor). En un contenedor el chroot no aporta nada: el contenedor es el jail.postlogd+maillog_file. Postfix escribe a syslog y aquí no corre ninguno ⇒kubectl logssale vacío. Sin esto, el incidente se diagnostica a ciegas — y es lo que permite ver si un despliegue futuro sale mal.
Cómo se prueba un cambio de esta imagen antes de desplegarlo (obligatorio: desplegar aquí es
reiniciar el único pod que sostiene el correo, y si falla no hay alerta que lo diga, porque la
alerta es el correo): arranca la imagen candidata aparte con el entrypoint real y las env de
producción, y exige diff vacío de postconf -n y postconf -M contra el contenedor en marcha.
Un diff limpio no basta: ejercita también STARTTLS y AUTH PLAIN contra ella (235 Authentication successful), que es lo que un diff de configuración no caza.