Configurar correo electrónico entrante de entrega directa para sitios autoalojados con Mail-Receiver

¿Cómo se deshabilita exactamente el soporte de DMARC?

Es decir, añadir INCLUDE_DMARC: false a la sección env de mail-receiver.yml no parece hacerlo. Esto sí parece hacer que los demonios opendkim y opendmarc no se ejecuten (lo que provoca una advertencia en los registros), pero la verificación de SPF todavía se está realizando.

Editado para añadir:
Creo que logré deshabilitar las comprobaciones de SPF añadiendo también la siguiente línea POSTCONF_ a la sección env:

env:
  ...
  INCLUDE_DMARC: false
  POSTCONF_smtpd_recipient_restrictions: check_policy_service unix:private/policy
  ...

Obtuve esto al mirar el commit que introdujo las comprobaciones de DMARC, y ver lo que debería suceder cuando INCLUDE_DMARC es falso.

No sé casi nada sobre cómo se construyen las imágenes de docker, pero tengo la impresión de que la bandera INCLUDE_DMARC es algo que debe ser configurado por alguien más, en algún otro lugar, en algún otro momento — no algo que se pueda hacer en mail-receiver.yml.

2 Me gusta

He encontrado la necesidad de abrir el puerto 443 en ufw; de lo contrario, obtengo API Request Preparation Failed en los logs. Creo que esto es mejor mencionarlo porque las instrucciones de instalación estándar mencionan la habilitación de ufw.

El puerto 25 se menciona en mail-receiver.yml y parece eludir ufw.

2 Me gusta

¿Debería el repositorio de GitHub estar en el OP?

3 Me gusta

Usuarios del receptor de correo, por favor, vean Remove smtp_should_reject & discourse-smtp-fast-rejection

Vamos a eliminar completamente el rechazo rápido ya que la característica original estaba rota y causaba problemas a los usuarios, específicamente este tipo de cosas:

y también afecta al correo reenviado ya que la prueba previa a la entrega comprobaba el envelope-from y el envelope-to, mientras que Discourse solo utiliza los valores en las cabeceras.

1 me gusta

Acabo de enviar este PR para eliminar las comillas innecesarias alrededor del valor de DISCOURSE_BASE_URL en el archivo de muestra mail-receiver.yml. Las comillas estaban rompiendo mi configuración. Eliminar las comillas permite la finalización exitosa de este documento.

¿Puedes explicar cómo? La presencia/ausencia de comillas alrededor de este valor no produce ninguna diferencia:

[2] pry(main)=> YAML::load("env:\n  DISCOURSE_BASE_URL: 'https://discourse.example.com'")
=> {"env"=>{"DISCOURSE_BASE_URL"=>"https://discourse.example.com"}}

[3] pry(main)=> YAML::load("env:\n  DISCOURSE_BASE_URL: https://discourse.example.com")
=> {"env"=>{"DISCOURSE_BASE_URL"=>"https://discourse.example.com"}}

Cuando seguía los registros de ese contenedor y le enviaba mensajes, veía muchos errores que mencionaban algo como discourse.example.com no forma parte de los registros MX o algo parecido. Quité las comillas, reconstruí el contenedor y empezó a funcionar :person_shrugging:

La secuencia de eventos también puede ser importante:

  1. Configuré y lancé el contenedor receptor de correo (mail-receiver)
  2. Días después configuré los registros DNS MX
  3. Validé que los registros MX estaban configurados correctamente y luego comencé a probar. No funcionaba
  4. Quité las comillas, reconstruí el contenedor, empezó a funcionar

Así que no estoy seguro de si la solución estuvo relacionada con la eliminación de las comillas, o con la reconstrucción del contenedor después de que se crearan los registros MX.

En el peor de los casos, el PR hace que el yml se vea consistente :slight_smile:

1 me gusta

Parece que existe la suposición de que el receptor de correo siempre será el mismo dominio que el foro base. Cuando ese no es el caso, ¿cómo configuramos TLS?

Por ejemplo:
foro => forum.domain.tld
receptor-correo => mail.domain.tld

En mail-receiver.yml, el TLS apunta a los certificados del foro base. ¿Hay alguna manera de que el receptor de correo obtenga sus propios certificados?

No conozco la respuesta directa, aunque sospecho que requeriría opciones adicionales en el yml para realizar modificaciones en el contenedor durante la compilación.

Más sobre eso a continuación, pero me pregunto cuál es tu razón para querer ejecutarlo en un dominio diferente. El receptor de correo está fuertemente adaptado y, sin modificaciones, funciona exclusivamente para recibir correos electrónicos para una instancia de Discourse emparejada, por lo que normalmente es razonable que opere en el mismo dominio que esa instancia.


Si miras algunas de las plantillas para incluir en tu Discourse yml, algunas de las cuales ya se estarán utilizando, deberías poder obtener algunas pistas sobre cómo ejecutar comandos y modificar archivos a través del yml (durante la compilación del contenedor).

web.onion.template.yml tiene algunos ejemplos de cómo reemplazar cadenas dentro de archivos y web.letsencrypt.ssl.template.yml es el que agrega Let’s Encrypt al contenedor principal de Discourse.

No sé cuánto de eso depende de las cosas en la imagen base, por lo que potencialmente podría ser más simple hacer que el contenedor principal de Discourse obtenga un segundo certificado, y luego simplemente cambiar las rutas del certificado/clave en mail-receiver.yml para que coincidan.

Ten cuidado con este tipo de cambios si tomas este enfoque, asegurándote de saber exactamente qué efecto tendrá el cambio. Un cambio erróneo en las cosas de Let’s Encrypt podría llevar a que la renovación de certificados falle silenciosamente, por ejemplo, lo que podrías no notar hasta $\sim$3 meses después, cuando los visitantes comiencen a recibir errores de certificado caducado.

Caso de Uso de CloudFlare

Estas instrucciones son para foros de Discourse autohospedados que utilizan el Proxy de Cloudflare.

Cuando utiliza el Proxy de Cloudflare, esto impide que todo el tráfico SMTP (Puerto 25) llegue a su servidor. Esto requiere que configure un subdominio diferente para que el receptor de correo funcione.

Por ejemplo, si su dominio es forums.domain.tld, deberá crear un nuevo subdominio, como mail.domain.tld.

Con Cloudflare, tiene los pasos adicionales a continuación.

  1. Cree el registro A para el nuevo subdominio. Utilizará la misma dirección IP que su forums.domain.tld.
  2. Cree el registro MX para el nuevo subdominio, tal como se indica en las instrucciones principales.

Siga los conjuntos de instrucciones principales con este cambio menor. Funcionará perfectamente con la seguridad TLS desactivada.

Si desea ejecutar la seguridad TLS, esto requerirá trabajo adicional.

Visión General de la Configuración TLS

Estas instrucciones instalarán Certbot y un complemento de Certbot para CloudFlare. Los comandos obtendrán certificados LetsEncrypt en modo independiente a través del proceso de certificación DNS. Una vez que los certificados estén disponibles, se copian al área compartida del receptor de correo para que el contenedor los utilice. Tenemos que usar el modelo DNS, ya que Discourse ya tiene el puerto 80.

Desafío DNS

En lugar de probar la propiedad del dominio a través de HTTP, certbot lo demuestra creando un registro TXT en su DNS. Dado que su DNS es Cloudflare, esto se puede automatizar completamente con un token de API de Cloudflare: no se necesita el puerto 80 ni el apagado del servidor web.

Cómo funciona

Certbot → Crea el registro TXT _acme-challenge.mail.lotuselan.net en Cloudflare
Let's Encrypt → Busca ese registro TXT → Valida → Emite el certificado
Certbot → Elimina el registro TXT

Todo esto se realiza en su servidor base, no dentro del contenedor de Discourse.

Configuración

1 — Instalar cerbot y el complemento de Certbot para Cloudflare:

bash

apt install certbot python3-certbot-dns-cloudflare -y

2 — Crear un token de API de Cloudflare:

  1. Vaya a Cloudflare → Mi Perfil → Tokens de API → Crear Token
  2. Use la plantilla “Editar DNS de zona”
  3. Permisos: Zona → DNS → Editar
  4. Recursos de Zona: Incluir → Zona específica → lotuselan.net
  5. Restricciones de IP: configure solo para permitir desde la dirección IP de su servidor
  6. Copie el token

3 — Guardar el token en un archivo de credenciales:

bash

mkdir -p /etc/letsencrypt/cloudflare
nano /etc/letsencrypt/cloudflare/credentials.ini

Pegue:

dns_cloudflare_api_token = SU_TOKEN_DE_API_DE_CLOUDFLARE

Bloquee el archivo:

bash

chmod 600 /etc/letsencrypt/cloudflare/credentials.ini

4 — Solicitar el certificado:

Actualice el siguiente comando con su correo electrónico de administrador y el nombre de dominio.

bash

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  --non-interactive \
  --agree-tos \
  --email su-email@dominio.tld \
  -d mail.domain.tld

En sus resultados, debería haber una declaración que diga:

Certbot ha configurado una tarea programada para renovar automáticamente este certificado en segundo plano.

Certbot configurará un cron para verificar la caducidad del certificado dos veces al día. Renovará los certificados cuando estén a menos de 30 días de caducar. Puede validarlo mediante:

# Comprobar si el temporizador de systemd está activo (la mayoría de los sistemas Ubuntu modernos)
systemctl status certbot.timer

# O comprobar si se ha añadido un trabajo cron
cat /etc/cron.d/certbot

Ahora tiene los certificados TLS en su servidor para el nuevo nombre de dominio del receptor de correo. No están en un lugar donde puedan ser utilizados.

5 — Configurar un script de implementación para mover archivos
Dado que certbot se renueva automáticamente, solo necesita que su script maneje las partes específicas de Discourse: copiar los certificados renovados y reconstruir el receptor de correo. Puede simplificar significativamente el script utilizando el hook de implementación incorporado de certbot, que se ejecuta automáticamente después de una renovación exitosa.

Cree un archivo de hook de implementación:

bash

nano /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh
chmod +x /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh

Pegue esto:

bash

#!/bin/bash
DOMAIN="mail.domain.tld"
DISCOURSE_DIR="/var/discourse"
CERT_SRC="/etc/letsencrypt/live/${DOMAIN}"
CERT_DEST_1="${DISCOURSE_DIR}/shared/mail-receiver/letsencrypt/${DOMAIN}"
CERT_DEST_2="${DISCOURSE_DIR}/shared/mail-receiver/letsencrypt/${DOMAIN}_ecc"
ADMIN_EMAIL="dirección de correo electrónico de administrador"
LOG_FILE="/var/log/mail-cert-renewal.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "=== Activado el hook de implementación de Certbot para ${DOMAIN} ==="

# Copiar certificados (usar -L para resolver enlaces simbólicos)
for DEST in "$CERT_DEST_1" "$CERT_DEST_2"; do
    mkdir -p "$DEST"
    cp -L "${CERT_SRC}/fullchain.pem" "${DEST}/fullchain.pem"
    cp -L "${CERT_SRC}/privkey.pem"   "${DEST}/privkey.pem"
    cp -L "${CERT_SRC}/cert.pem"      "${DEST}/cert.pem"
    cp -L "${CERT_SRC}/chain.pem"     "${DEST}/chain.pem"
    chmod 644 "${DEST}/fullchain.pem" "${DEST}/cert.pem" "${DEST}/chain.pem"
    chmod 600 "${DEST}/privkey.pem"
    log "Certificados copiados a ${DEST}"
done

# Reconstruir receptor de correo
cd "$DISCOURSE_DIR" || { echo "No se puede cd a ${DISCOURSE_DIR}" | mail -s "[FALLO] El hook de implementación de certificado de correo falló" "$ADMIN_EMAIL"; exit 1; }
log "Reconstruyendo receptor de correo..."
if ./launcher rebuild mail-receiver >> "$LOG_FILE" 2>&1; then
    log "receptor de correo reconstruido con éxito"
else
    log "ERROR: la reconstrucción falló"
    echo "La reconstrucción del receptor de correo falló después de la renovación del certificado. Consulte ${LOG_FILE}" | \
        mail -s "[FALLO] El hook de implementación de certificado de correo falló" "$ADMIN_EMAIL"
    exit 1
fi

log "=== El hook de implementación se completó con éxito ==="

No se necesita ningún trabajo cron manual; certbot orquesta todo el proceso. El hook de implementación solo se activa cuando realmente se realiza una renovación, por lo que su receptor de correo no tendrá reconstrucciones innecesarias en los días en que certbot comprueba pero no renueva.

Para probar el hook de renovación, ejecute lo siguiente:

bash

bash /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh

Si todo se configuró correctamente, esto hará:
→ copia los certificados a los directorios de Discourse
→ reconstruye el receptor de correo
→ registra todo

6 — Configurar TLS en mail-receiver.yml

El principal problema aquí parece ser que el registro A para forum.domain.tld está enmascarado por el proxy, en lugar de desear explícitamente el servidor de correo en un dominio separado.

Al negociar TLS, el nombre común del certificado se compara con el nombre de host del registro MX, es decir, el nombre de host al que el cliente (que podría ser otro servidor de correo) está intentando conectarse, en lugar del registro A al que hace referencia. Esto significa que puedes crear tu registro A mail.domain.tld configurado en Solo modo DNS, luego crear un registro MX para forum.domain.tld que haga referencia a mail.domain.tld y no se necesitan más pasos especiales en esa disposición.

Sí, puedes usar solo el modo DNS para el registro A de tu foro principal. Usar este enfoque significa que pierdes las capacidades de proxy global inverso de CloudFlare. (Esta no fue una opción para mi instalación de Discourse).

Por eso la primera línea define que esta solución es para sitios que usan el Proxy de CloudFlare.

Me refería al registro A de mail.domain.tld configurado en Solo modo DNS en lugar del registro A de forum.domain.tld, sin embargo, me di cuenta de que había estado malinterpretando cómo los clientes SMTP autentican los certificados TLS.

El comportamiento que estaba viendo era un artefacto del método oportunista predeterminado que no valida el nombre de host, por lo que mi afirmación de que valida el nombre de host del registro MX en lugar del destino del registro MX era incorrecta. Funcionaría en la mayoría de los casos, pero no si se utilizan DANE o MTA-STS para imponer la autenticación de identidad TLS.

Tener el registro A proxyficado y el registro MX Solo DNS no funciona. La documentación de CloudFlare establece que cualquier dominio que tenga el registro A proxyficado bloquea todo el tráfico SMTP.

Lo valido con varias rondas de pruebas. En el momento en que dejas de proxificar el registro A, los datos SMTP fluyen. Activas el Proxy, los datos SMTP nunca fluyen. (Las pruebas se realizaron usando TELNET al puerto 25).

Así que si quieres:

  • Que tu foro Discourse use los servicios de Proxy de CloudFlare
  • Que tu Receptor de Correo SMTP acepte correo

Tienes que tener dominios diferentes para tu correo entrante.

Si quieres TLS para tu Receptor de Correo SMTP:

  • Tienes que configurar un LetsEncrypt mediante Verificación DNS

Las instrucciones parecen intimidantes, pero llevar escribir las instrucciones llevó más tiempo que implementar la solución en sí.

No es lo que quise decir, específicamente lo que estaba sugiriendo eran tres registros DNS:
A: forum.domain.tld → dirección IP del host (proxy habilitado)
A: mail.domain.tld → dirección IP del host (Solo modo DNS)
MX: forum.domain.tldmail.domain.tld

Sin embargo, como mencioné, más tarde me di cuenta de que eso solo funcionaría en el modo TLS oportunista predeterminado, no funcionará si usted (alguien) también quiere habilitar DANE o MTA-STS para forzar la autenticación de identidad (asegurarse de que se está conectando al servidor correcto en lugar de solo cifrar el tráfico).

Se ven muy bien, son fáciles de seguir y hacen todo fuera del contenedor, por lo que no hay riesgo de que se rompa potencialmente con las actualizaciones de Discourse. Me gusta particularmente el uso de un hook de renovación de certbot con el que no estaba familiarizado antes.

1 me gusta

Ten en cuenta que esto expone la dirección IP de tu foro y hace posible que las personas eviten los mecanismos de protección de Cloudflare como la protección DDoS y WAF. Es mejor ejecutar el receptor de correo en un servidor separado.

Mi primera intención fue ejecutar el receptor de correo en un servidor diferente por esta razón. Cada vez que intentaba ejecutar la aplicación lanzadora para iniciar el receptor de correo, quería instalar el sistema completo de Discourse. ¿Hay una forma sencilla de solo iniciar y ejecutar el receptor de correo en un servidor Docker independiente?

No estoy muy familiarizado con la aplicación de lanzamiento porque desarrollamos nuestra propia infraestructura, pero mi idea sería no usar la aplicación de lanzamiento y luego iniciarla como cualquier otro contenedor. Las variables de entorno que necesitas están en el readme.

@Simon_Manning y @RGJ: El artículo de Cloudflare se ha actualizado para incluir las 3 opciones principales y sus compensaciones. Esperemos que esto aborde los diversos problemas que planteaste con las primeras opciones presentadas.

@kelv Podrías considerar agregar una nota al pie en la descripción principal del artículo de Cloudflare. Esto ahorrará tiempo a las personas a las que aplica. Configure direct-delivery incoming email for self-hosted sites with Mail-Receiver - #541 by LotusJeff

2 Me gusta