Files
clusterlogs/content/memorias/2026-08-24-mem-20260824-254312-configurar-correo-saliente-para-recuperacion-de-contrasena-en-zammad-y-grafana.md
T
fernandoandClaude Sonnet 5 622578b454
CI / validate-and-build (push) Successful in 7s
CI / runme (push) Failing after 5s
fix(content): escapar {{...}} literal en dos memorias (rompía zola check)
Los comandos de ejemplo "podman inspect ... --format '{{range
.Config.Env}}{{println .}}{{end}}'" son sintaxis de Go templates, no de
Tera, pero Zola igual intenta interpretarlos como expresiones Tera al
renderizar la página — zola check fallaba con "Found `}}` but expected
identifier". Encontrado por la propia CI al validar --require-zola tras
la conversión a instancia (antes nunca se había corrido zola check
sobre este contenido en CI con la ruta real). Envueltos en {% raw %}.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 01:20:53 -06:00

71 lines
7.5 KiB
Markdown

+++
title = "Configurar correo saliente para recuperación de contraseña en Zammad y Grafana"
description = "Zammad tenía la credencial SMTP de salida desincronizada del buzón real (los resets de contraseña fallaban silenciosamente); Grafana no tenía SMTP configurado en absoluto. Se creó una cuenta dedicada de solo-salida y se configuró en ambos servicios, sin tocar el buzón soporte@ que Zammad usa para recibir tickets."
date = 2026-08-24T20:49:04Z
authors = ["gen"]
slug = "mem-20260824-254312-configurar-correo-saliente-para-recuperacion-de-contrasena-en-zammad-y-grafana"
template = "memory.html"
[taxonomies]
categories = ["memorias"]
tags = ["correo", "docker-mailserver", "grafana", "smtp", "zammad"]
systems = ["grafana", "mail-server", "mcps-hpc-01", "mcps-vpn-server", "zammad"]
[extra]
id = "MEM-20260824-254312"
kind = "memory"
author_id = "gen"
author_fingerprint = "SHA256:JhN1Tt4jYqWoJIznDJ6QkMAiOR6HFHr3xLz9aBJqmPg"
risk = "medium"
ticket = ""
review_required = true
review_policy = "all-active"
reviewer_snapshot = true
reviewers = ["gen"]
started_at = ""
finished_at = ""
duration_minutes = 0
agent = "claude-code"
+++
## Contexto
El usuario pidió configurar correo saliente en mail.mcps-epcm.org (10.8.0.1, mcps-vpn-server) para que soporte.mcps-epcm.org (Zammad) y grafana.mcps-epcm.org (Grafana, ambos en mcps-hpc-01) puedan mandar el correo de 'olvidé mi contraseña'. Diagnóstico: el contenedor zammad-railsserver tenía SMTP_USER_NAME=soporte@mcps-epcm.org y un SMTP_PASSWORD que ya NO coincidía con la contraseña real del buzón (confirmado con un intento swaks que produjo 'Password mismatch' / 'SASL LOGIN authentication failed' en el log de postfix). Ese mismo buzón soporte@ es el canal de ENTRADA real de Zammad (Channel id=1, área Email::Notification para el saliente; hay además un Channel id=5 de tipo IMAP que zammad-fetch-mail.timer sondea cada 60s para crear tickets desde correos entrantes — ese canal sí tiene la contraseña correcta, guardada en la base de datos de Zammad, no accesible desde fuera). Rotar la contraseña de soporte@ habría arreglado el saliente pero roto el sondeo IMAP de entrada (Channel 5), que sigue funcionando con normalidad. Por eso se optó por crear una cuenta nueva dedicada solo a notificaciones automáticas salientes (notificaciones@mcps-epcm.org), dejando soporte@ intacto. Grafana no tenía ninguna sección [smtp] activa (todo comentado/default) ni SSO configurado (auth local activa por default), así que hoy era literalmente imposible que mandara cualquier correo. Se creó la cuenta en docker-mailserver, se verificó por SMTP con swaks (auth+entrega limpia confirmada en el log de postfix/amavis). Se actualizó el Channel id=1 de Zammad (área Email::Notification) por rails runner dentro del contenedor zammad-railsserver, apuntando host/user/password/domain a la cuenta nueva, dejando notification_sender ('Soporte MCPS <soporte@mcps-epcm.org>') sin cambios para que el remitente visible siga siendo reconocible aunque la autenticación SMTP real sea con notificaciones@. Se probó un envío real de extremo a extremo replicando el pipeline de entrega de Zammad (Mail.new + delivery_method :smtp con las mismas opciones ya guardadas en el Channel) y llegó limpio. Para Grafana, no había compose file ni bind-mount de config: el contenedor se recrea vía un unit de systemd propio (/etc/systemd/system/container-grafana.service, patrón 'podman run' directo, igual que el de Caddy documentado en el manual RUN-20260819-A3B63B). Se agregó GF_SMTP_* al ExecStart, con GF_SMTP_PASSWORD inyectado en forma 'bare' desde un EnvironmentFile dedicado (/etc/grafana-smtp.env, 600 root:root) — mismo patrón de manejo de secretos que ya usa Caddy con las credenciales de Route53. Se respaldó la unit original antes de editar (container-grafana.service.bak-2026-08-24). Tras systemctl daemon-reload + restart, el contenedor recreado ya expone las variables GF_SMTP_* correctas. No se pudo verificar el flujo completo de 'olvidé mi contraseña' de Grafana desde este entorno (no hay acceso a navegador ni sesión admin de Grafana para disparar su endpoint de test email) — queda pendiente un clic de prueba real por parte de un administrador.
## Cambios realizados
- Creada la cuenta notificaciones@mcps-epcm.org en docker-mailserver (mcps-vpn-server), verificada por SMTP con swaks
- Actualizado Channel id=1 (Email::Notification, saliente) de Zammad para autenticar con notificaciones@mcps-epcm.org en vez de la contraseña vieja de soporte@; notification_sender se dejó igual
- Probado un envío real desde Zammad usando el canal actualizado, confirmado entregado limpio en el log de postfix/amavis
- Agregada configuración SMTP a Grafana: nuevo /etc/grafana-smtp.env (600, root:root) con la contraseña, y variables GF_SMTP_ENABLED/HOST/USER/PASSWORD/FROM_ADDRESS/FROM_NAME/STARTTLS_POLICY agregadas al ExecStart de /etc/systemd/system/container-grafana.service (unit original respaldada como .bak-2026-08-24)
- systemctl daemon-reload + restart de container-grafana.service; confirmado que el contenedor recreado expone las variables GF_SMTP_* nuevas
## Comandos relevantes (secretos redactados)
{% raw %}
```console
podman exec mailserver setup email list
podman exec mailserver setup email add notificaciones@mcps-epcm.org <password redactado>
swaks --to prueba@mcps-epcm.org --from notificaciones@mcps-epcm.org --server mail.mcps-epcm.org:587 --auth LOGIN --auth-user notificaciones@mcps-epcm.org --auth-password <redactado> --tls --hide-all
podman exec zammad-railsserver bash -lc "rails r 'Channel.where(area: %q(Email::Notification))...'"
podman exec zammad-railsserver bash -lc "rails r 'c = Channel.find(1); ... c.options[:outbound][:options][:password] = <redactado>; c.save!'"
podman exec zammad-railsserver bash -lc "rails r 'Mail.new(...).delivery_method(:smtp, ...).deliver'" # prueba end-to-end
systemctl cat container-grafana.service
cp /etc/systemd/system/container-grafana.service /etc/systemd/system/container-grafana.service.bak-2026-08-24
tee /etc/grafana-smtp.env # GF_SMTP_PASSWORD=<redactado>, chmod 600
systemctl daemon-reload && systemctl restart container-grafana.service
podman inspect grafana --format '{{range .Config.Env}}{{println .}}{{end}}'
```
{% endraw %}
## Validación
- swaks contra la cuenta notificaciones@ terminó con exit 0 y el log de postfix mostró sasl_username=notificaciones@mcps-epcm.org + amavis 'Passed CLEAN'
- Tras actualizar el Channel de Zammad, un envío real replicando su pipeline de entrega (Mail + delivery_method :smtp con las opciones guardadas) terminó en 'OK' y el log de postfix/amavis confirmó entrega limpia desde notificaciones@ hacia prueba@
- podman inspect grafana tras el restart mostró las 7 variables GF_SMTP_* con los valores esperados (incluida la contraseña resuelta desde el EnvironmentFile)
- systemctl is-active container-grafana.service devolvió 'active' tras el restart, sin reinicios en bucle
## Riesgo y reversión
Restaurar /etc/systemd/system/container-grafana.service.bak-2026-08-24 sobre container-grafana.service, borrar /etc/grafana-smtp.env, systemctl daemon-reload && systemctl restart container-grafana.service para volver Grafana a su estado sin SMTP. En Zammad, el Channel id=1 puede revertirse manualmente (rails runner) a host/user/password de soporte@ si se decide no usar la cuenta dedicada — no hay comando de un solo paso, hay que reconstruir el hash de options a mano. La cuenta notificaciones@mcps-epcm.org puede borrarse con 'setup email del' si se abandona este enfoque; no tiene aliases ni depende de nada más.