Instala Postfix desde tu distribución y acepta los valores por defecto, y tienes un servidor de correo que envía tu correo en claro a cualquier servidor que no ofrezca TLS, y que acepta correo de cualquiera que diga ser cualquiera. El main.cf que trae Debian pone los dos niveles de TLS en may. Nada comprueba SPF. Nada comprueba DKIM. Un dominio de remitente que no existe se acepta.
Eso no es un fallo. Es lo que los estándares pidieron hace veinte años, y es como sigue funcionando hoy una buena parte de los servidores de correo de internet.
Este artículo construye el otro tipo. Toma las reglas de la casa de el artículo que abre esta serie y mete cada una de ellas en un Postfix que funciona, con la respuesta que produce cada una cuando rechaza algo. El argumento a favor de las reglas vive en aquel artículo y no lo voy a repetir. Este es la construcción.
| Regla | Postfix de Debian tal cual viene | Esta construcción | Qué consigue una brecha |
|---|---|---|---|
| Sin texto plano a la entrada | TLS ofrecido, nunca exigido | encrypt en cada listener | 530 5.7.0 Must issue a STARTTLS command first |
| Sin texto plano a la salida | TLS si se ofrece, en claro si no | encrypt, DANE o MTA-STS donde esté publicado | aplazado, dsn=4.7.4 o dsn=4.7.5 |
| IPv6 en todo | inet_protocols = all | lo mismo, más AAAA en cada MX | cada prueba de abajo corrió en ambas familias |
| Retransmitir solo con login | retransmite para mynetworks | el puerto 25 nunca retransmite, 587 y 465 exigen login | 554 5.7.1 … Relay access denied |
| Un login envía como sí mismo | no se comprueba | sobre y From: atados ambos al login | 553 5.7.1 … not owned by user |
| El dominio del remitente debe existir | no se comprueba | reject_unknown_sender_domain, código 550 | 550 5.1.8 … Domain not found |
| Fallo de SPF, fallo de DKIM | no se comprueba | Rspamd, con rechazos forzados | 554 5.7.1 SPF hard fail / DKIM signature does not verify |
Cada respuesta de este artículo se capturó de un servidor en marcha, sobre IPv4 e IPv6. Varios ajustes resultaron no hacer nada hasta que algo más estaba también puesto. Encontré cada uno de ellos viéndolo no hacer nada. Quedan señalados abajo, porque son los que te van a morder.
El laboratorio
| Pieza | Versión | Función |
|---|---|---|
| Postfix | 3.10.13, Debian trixie | el MX y el servidor de envío de alpha.test |
| Dovecot | 2.4.1 | contraseñas para los logins, todos los protocolos de correo apagados |
| Rspamd, Redis | 3.12.1, 8.0.2 | SPF, DKIM, DMARC, firma DKIM |
| postfix-tlspol | 1.14.4 | política de salida desde TLSA y MTA-STS |
| BIND, unbound | 9.20.30, 1.26.1 | una zona test. privada y firmada, y un resolver que la valida |
| Dominio | Para qué sirve |
|---|---|
alpha.test | el servidor que construye este artículo |
bravo.test | Stalwart, que tiene su propio artículo |
plain.test | el mal vecino: zona sin firmar, sin STARTTLS |
sender.test | SPF -all, DKIM, DMARC p=reject: para falsificar, manipular y reenviar |
Cada listener se liga a ambas familias, y cada MX tiene un registro A y uno AAAA. IPv6 no es un extra aquí. Un servidor de correo sin él es un defecto, y probar que este lo tiene es sencillo: corre cada comprobación dos veces.
Etapa por etapa
Una sesión SMTP es una secuencia fija, y cada regla de esta construcción tiene su sitio natural en ella. El orden importa por una razón: rechaza tan pronto como la evidencia lo permita. Un dominio de remitente que no existe se conoce en MAIL FROM, así que no hay razón para aceptar el cuerpo antes de rechazarlo. Un cuerpo manipulado solo se conoce después de DATA, así que ahí es donde se rechaza.
El resto del artículo recorre esas etapas en orden: TLS a la entrada, TLS a la salida, logins y retransmisión, direcciones de remitente, y luego lo que Rspamd hace con el cuerpo.
Sin texto plano a la entrada
Una línea:
smtpd_tls_security_level = encrypt
Un cliente que intenta saltarse STARTTLS no obtiene más que el rechazo:
<- 220 mail.alpha.test ESMTP Postfix
-> MAIL FROM:<carol@sender.test>
<** 530 5.7.0 Must issue a STARTTLS command first
Ahora lee lo que el propio manual de Postfix dice de ese ajuste:
According to RFC 2487 this MUST NOT be applied in case of a publicly-referenced SMTP server. Instead, this should be used on dedicated servers, for example submission (port 587).1
RFC 2487 es el número viejo de RFC 3207, y esa regla es la razón de que la mayoría de los hosts MX de internet sigan aceptando correo en claro. Yo se la aplico igualmente a un MX de referencia pública. A propósito. El artículo que abre la serie explica por qué creo que la regla está mal, y el coste es real: un remitente cuyo servidor no sabe hacer TLS no puede llegarte. En 2026 ese es un servidor del que no quieres correo.
Sin texto plano a la salida
Esta es la mitad que la mayoría de las guías hacen mal, porque el ajuste obvio no hace lo que su nombre sugiere.
Postfix tiene un nivel de seguridad dane. Lo pones y esperarías que el correo saliera autenticado. No lo hace. Esto es lo que postconf(5) dice que hace cuando el otro extremo no publica nada:
When DNSSEC-validated TLSA records are not found the effective tls security level is “may”.2
may es TLS oportunista, que el TLS_README describe sin rodeos: «Otherwise, messages are sent in the clear.»3 La mayor parte de internet no publica registros TLSA. Así que smtp_tls_security_level = dane por sí solo significa DANE para el puñado de dominios que lo hacen, y texto en claro para todos los demás cuyo servidor no ofrezca STARTTLS. Oportunista, con un nombre mejor.
El arreglo es hacer de encrypt el suelo y subir cada destino por encima de él cuando publica algo mejor. postfix-tlspol es quien sube. Es un pequeño servidor de políticas, con licencia MIT, al que Postfix pregunta por cada dominio destinatario sobre un socketmap. Busca registros TLSA usables en una zona firmada primero y MTA-STS después, y responde con la política que Postfix debe usar:
smtp_dns_support_level = dnssec
smtp_tls_security_level = encrypt
smtp_tls_mandatory_protocols = >=TLSv1.2
smtp_tls_policy_maps = socketmap:inet:[fd89:50::25]:8642:QUERYwithTLSRPT
Su README es específico sobre lo que devuelve: «Verify TLSA records for correctness and supported parameters, only then the dane-only policy (Mandatory DANE) will be returned.»4 Preguntado por los tres dominios del laboratorio, dijo exactamente eso:
level=INFO msg="Evaluated policy" origin=network domain=alpha.test policy=dane-only ttl=214
level=INFO msg="Evaluated policy" origin=network domain=bravo.test policy=dane-only ttl=198
level=INFO msg="No policy found" origin=network domain=plain.test ttl=1800
Sin política para plain.test, Postfix recae en el suelo encrypt, y plain.test no ofrece STARTTLS. El correo espera:
postfix/smtp[194]: 78598130DA54: to=<someone@plain.test>, relay=mail.plain.test[fd89:50::22]:25,
delay=0.02, delays=0.01/0.01/0.01/0, dsn=4.7.4,
status=deferred (TLS is required, but was not offered by host mail.plain.test[fd89:50::22])
Y el caso que demuestra que DANE hace algo. Al principio del montaje, Stalwart en bravo.test seguía sirviendo su propio certificado de arranque mientras la zona publicaba el registro TLSA del de verdad. Un servidor con un certificado perfectamente bueno, solo que no el que el dueño del dominio avaló:
postfix/smtp[196]: server certificate verification failed for mail.bravo.test[fd89:50::21]:25:
num=65:no matching DANE TLSA records
postfix/smtp[196]: 79160130DA56: to=<someone@bravo.test>, relay=mail.bravo.test[10.89.50.21]:25,
delay=5.9, delays=0/0.01/5.9/0, dsn=4.7.5, status=deferred (Server certificate not verified)
Aplazar es la respuesta correcta en ambos casos. Un mensaje aplazado se reintenta durante días, lo que da al otro lado tiempo para arreglar su parte, y no se pierde nada. Un mensaje enviado en claro no se puede des-enviar. Como tal, esperar es siempre el fallo más barato.
El resolver es parte de la cadena de confianza
smtp_dns_support_level = dnssec hace que Postfix pida a su resolver respuestas validadas, y luego se crea el bit AD del resolver, la bandera que dice «comprobé las firmas». Postfix no valida nada por sí mismo. Confía. Sea cual sea el resolver nombrado en /etc/resolv.conf, es ahora quien decide si tu correo está autenticado.
Encontrarás consejos que dicen que glibc tira ese bit a la basura a menos que resolv.conf lleve options trust-ad. Lo probé, y en este Postfix no cambia nada: DANE casó con la opción y sin ella. El código explica por qué. Postfix pone la bandera él mismo cada vez que pide DNSSEC:
if (DNS_WANT_DNSSEC_VALIDATION(flags))
flags |= (RES_USE_EDNS0 | RES_TRUSTAD);
Eso es de src/dns/dns_lookup.c.5 Así que lo que importa no es una opción del resolver. Es a qué resolver apuntas. Debe validar, y el camino hasta él debe ser uno en el que confíes, lo que en la práctica significa que corre en el propio servidor de correo o en una red que nadie más pueda alcanzar. Un resolver público al otro lado de internet te pondrá el AD encantado. Y también cualquiera que se siente entre tú y él.
DANE: publica la clave, no el certificado
Un registro TLSA fija lo que un servidor emisor debería ver cuando conecta. El que hay que usar es 3 1 1, y cada uno de los tres números es una elección:
| Campo | Valor | Significado | Por qué este |
|---|---|---|---|
| Uso | 3, DANE-EE | casa con el propio certificado del servidor | ninguna CA en la cadena en la que confiar o que pueda fallar |
| Selector | 1, SPKI | aplica hash solo a la clave pública | sobrevive a cada renovación sobre la misma clave |
| Coincidencia | 1, SHA-256 | un resumen, no la clave completa | todo cliente DANE debe soportarlo |
RFC 7672 da la razón de esa combinación en una línea:
this record type works for all clients and need not change across certificate renewals with the same key.6
El hash es una sola tubería, y aquí está contra el certificado del laboratorio y el registro que sirve la zona firmada:
$ openssl x509 -in mail.alpha.test.crt -noout -pubkey \
| openssl pkey -pubin -outform DER | sha256sum
093a80cac907a52eb41d17df20163c95f2e15d6702c76f65174d7fc32460c69f -
$ dig +dnssec _25._tcp.mail.alpha.test TLSA
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
_25._tcp.mail.alpha.test. 165 IN TLSA 3 1 1 093A80CAC907A52EB41D17DF20163C95F2E15D6702C76F65174D7FC3 2460C69F
El laboratorio demostró la parte del «need not change» por accidente. A mitad de camino, las comprobaciones de certificados más estrictas de Python 3.13 rechazaron los certificados del laboratorio por faltarles un authority key identifier, así que se reemitieron todos. Mismas claves privadas, certificados nuevos. Ni un registro TLSA cambió, y ni un remitente DANE se enteró.
Ahora la trampa. La mayoría de la gente renueva sus certificados con certbot, de forma automática, cada pocas semanas. De la propia documentación de certbot:
Not reusing private keys is the default behavior of Certbot.7
Así que la renovación por defecto hace una clave nueva cada vez. El registro TLSA deja de casar, y cada remitente que comprueba DANE empieza a aplazar tu correo el día de la renovación. Nada en tu lado se rompe. Nada te avisa. Corre certbot con --reuse-key y el registro sobrevive a cada certificado que le pongas detrás. Cuando la clave sí tiene que cambiar, hay un orden para ello:
El paso dos es el que la gente se salta. El resultado es correo aplazado hasta que el registro viejo caduca de cada caché, y viene exactamente de los remitentes que hacen la seguridad bien.
Nadie retransmite sin un login
Retransmitir es mandar correo más allá, hacia un dominio del que no eres el destino. Un relay abierto es como el spam se lava a través de la reputación de otro. La vieja forma de cerrar uno era confiar en un rango de direcciones, de modo que cualquier cosa desde la red de la oficina podía retransmitir. Eso es confianza por ubicación. En una red con WiFi de invitados, usuarios de VPN y algún portátil comprometido, la ubicación vale muy poco.
Así que no se confía en ninguna dirección salvo la propia máquina:
mynetworks = 127.0.0.0/8 [::1]/128
smtpd_relay_restrictions = reject_unauth_destination
El puerto 25 es para el correo que llega para este dominio y nada más. Nunca retransmite, para nadie:
<** 554 5.7.1 <someone@plain.test>: Relay access denied
Los usuarios envían por submission: el puerto 587 con STARTTLS, o el 465 con TLS desde el primer byte. Ambos quieren un login antes de hacer nada. Dovecot comprueba las contraseñas, con todos los protocolos de correo apagados para que no haga nada más, y Postfix le pregunta por un socket. El listener de submission en master.cf:
submission inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_tls_auth_only=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_restrictions=reject_sender_login_mismatch
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
smtpd_tls_auth_only significa que el servidor ni siquiera ofrece AUTH hasta que TLS está levantado, así que una contraseña nunca cruza el cable en claro. Líneas AUTH en la respuesta a un EHLO en texto plano: cero. Un cliente que conecta al 587 e intenta enviar sin hacer login no llega a ningún sitio:
<** 554 5.7.1 <unknown[fd89:50::50]>: Client host rejected: Access denied
Un login es dueño de sus direcciones
Un login demuestra quién eres. No dice nada sobre con qué direcciones puedes enviar. Sin ese vínculo, cualquier usuario del servidor puede enviar como el director financiero, y el correo se firmará y se entregará como si fuera real. Postfix ata las dos cosas con un mapa de dirección a dueño:
alice@alpha.test alice@alpha.test
bob@alpha.test bob@alpha.test
postmaster@alpha.test alice@alpha.test
reject_sender_login_mismatch en los puertos de submission lo hace cumplir contra el remitente del sobre. Con sesión iniciada como alice, intentando enviar como bob:
<** 553 5.7.1 <bob@alpha.test>: Sender address rejected: not owned by user alice@alpha.test
Eso cubre el sobre. No cubre la cabecera From:, que es la dirección que el destinatario ve de verdad, y postconf(5) lo dice en sus notas sobre esta misma restricción:
to enforce that the From: header address matches the envelope sender (MAIL FROM) address, use an external filter such as a Milter8
Rspamd ya es el milter aquí, así que la comprobación de la cabecera es una regla Lua corta que compara el usuario con sesión iniciada con la dirección From:, más un rechazo forzado sobre el símbolo que levanta:
rspamd_config.AUTH_FROM_NOT_LOGIN = {
callback = function(task)
local user = task:get_user()
if not user then return false end
local from = task:get_from('mime')
if not from or not from[1] or not from[1].addr then return true, 'no From address' end
if string.lower(from[1].addr) ~= string.lower(user) then
return true, from[1].addr .. ' is not ' .. user
end
return false
end,
score = 0.0,
description = 'Authenticated user sent with a From: address that is not their own',
group = 'headers',
}
Alice enviando con MAIL FROM:<alice@alpha.test> y From: bob@alpha.test:
<** 554 5.7.1 You may only send as your own address
Un nombre para mostrar está bien, y un cambio de mayúsculas también. Alice <ALICE@alpha.test> pasa, porque la comparación es sobre la dirección e ignora mayúsculas y minúsculas.
La comprobación que se apaga sola
Aquí está el primero de los ajustes que no hacen nada en silencio. La forma obvia de impedir que los de fuera falsifiquen tu propio dominio en el puerto 25 es poner ahí también las comprobaciones de login-mismatch. Postfix acepta la configuración, arranca, y registra esto:
warning: restriction `reject_authenticated_sender_login_mismatch' ignored: no SASL support
warning: restriction `reject_unauthenticated_sender_login_mismatch' ignored: no SASL support
El puerto 25 tiene SASL apagado, con toda la razón, porque ahí nadie hace login. Así que la comprobación se salta. El correo se acepta. La única evidencia es un aviso en un log que nadie lee, y sin la comprobación, cualquiera en internet podría enviar MAIL FROM:<alice@alpha.test> a este servidor y hacer que se entregue a los colegas de alice como si lo hubiera escrito ella.
El arreglo es rechazar nuestros propios dominios por su nombre en el puerto 25, porque el correo nuestro nunca llega ahí de forma legítima:
smtpd_sender_restrictions = reject_non_fqdn_sender, reject_unknown_sender_domain,
check_sender_access texthash:/etc/postfix/own_domains
con own_domains conteniendo una línea, alpha.test REJECT Our own domain: send through submission with a login. Probado desde fuera:
<** 554 5.7.1 <alice@alpha.test>: Sender address rejected: Our own domain: send through submission with a login
Dominios de remitente que no existen
Si el dominio en MAIL FROM no existe, nunca se le puede devolver nada, y nunca vino nada legítimo de él. reject_unknown_sender_domain rechaza el correo cuando ese dominio no tiene «no DNS MX and no DNS A record».9 El código de respuesta por defecto de Postfix para eso es 450, un fallo temporal, que invita al falsificador a probar otra vez más tarde. Yo lo cambié a 550.
postconf(5) es tajante sobre ese ajuste: «Do not change this unless you have a complete understanding of RFC 5321.»10 De acuerdo. Aquí está el razonamiento. Un 4xx le dice al remitente que encole y reintente, lo cual es correcto cuando la respuesta podría cambiar. Un dominio sin registros ahora no va a criar ninguno en los próximos cinco minutos. Postfix separa eso del caso en que la respuesta aún no se conoce: un fallo de DNS lo maneja unknown_address_tempfail_action, que sigue aplazando. Cambiar el código cambia solo el caso que tiene una respuesta.
El laboratorio probó los tres, más el que le importa a la regla de IPv6. El texto de postconf dice «A record», y un dominio cuya única dirección es un registro AAAA no debería tratarse como ausente:
| Dominio del remitente | Lo que dice el DNS | Respuesta |
|---|---|---|
nosuch-lab.co.uk | NXDOMAIN | 550 5.1.8 <someone@nosuch-lab.co.uk>: Sender address rejected: Domain not found |
v6only-lab.co.uk | AAAA solo, sin A, sin MX | aceptado |
dnsfail-lab.co.uk | SERVFAIL | 450 4.1.8 … Sender address rejected: Domain not found, reintentado luego |
sender.test | MX, A, AAAA | aceptado |
En la práctica «A record» cubre AAAA, y un dominio solo-IPv6 está a salvo. El rechazo llega en MAIL FROM, antes de que el remitente haya enviado un solo byte del cuerpo. Postfix retiene un rechazo de MAIL FROM hasta el primer RCPT TO por defecto, así que la respuesta llega un paso más tarde de lo que esperarías, que es por lo que el diagrama de etapas la muestra ahí.
SPF, DKIM y DMARC, con dientes
Estos tres se cubren como es debido en el artículo que abre la serie. En una línea cada uno: SPF dice qué servidores pueden enviar por un dominio, DKIM firma el mensaje para que los cambios se noten, y DMARC dice qué hacer cuando fallan y si casan con el dominio From: visible. La regla de la casa es rechazar ante un fallo duro de SPF y ante una firma DKIM que está presente y no verifica. El correo sin firmar no es un fallo de DKIM. DMARC p=reject en el dominio del remitente es lo que maneja eso.
Rspamd hace los tres en un solo milter, más la firma DKIM del correo saliente. Postfix le entrega cada mensaje, y si Rspamd no responde, el correo espera en vez de pasar sin comprobar:
smtpd_milters = inet:127.0.0.1:11332
non_smtpd_milters = $smtpd_milters
milter_default_action = tempfail
Rspamd trabaja con puntuaciones, y eso es lo primero que hay que entender de él. De serie, nada se rechaza porque SPF fallara. Nada. Cada resultado suma o resta unos pocos puntos, y el mensaje se rechaza cuando el total pasa de 15. El módulo force_actions es lo que convierte un solo resultado en una decisión:
rules {
SPF_FAIL_REJECT {
action = "reject";
expression = "R_SPF_FAIL";
message = "SPF hard fail: this host is not allowed to send for that domain";
}
DKIM_FAIL_REJECT {
action = "reject";
expression = "R_DKIM_REJECT";
message = "DKIM signature does not verify: the message was changed or forged";
}
DMARC_REJECT {
action = "reject";
expression = "DMARC_POLICY_REJECT";
message = "DMARC policy of the sender's domain says reject";
}
}
Cinco mensajes desde sender.test, enviados desde el cliente del laboratorio. El registro SPF permite ip6:fd89:50::50 y nada más, así que el mismo cliente enviando sobre IPv4 es un servidor no autorizado para ese dominio. Eso es también exactamente lo que parece un mensaje reenviado:
| Caso | Enviado como | SPF | DKIM | DMARC | Puntuación Rspamd | Respuesta |
|---|---|---|---|---|---|---|
| A | firmado, desde la dirección IPv6 listada | pasa | pasa | pasa | 3.59 de 15 | aceptado |
| B | firmado, luego se cambiaron los datos bancarios | pasa | falla | pasa | 2.30 de 15 | 554 5.7.1 DKIM signature does not verify |
| C | sin firmar, desde IPv4 | falla | ninguna | rechazo | 5.00 de 15 | 554 5.7.1 SPF hard fail |
| D | firmado e intacto, desde IPv4 | falla | pasa | pasa | 2.30 de 15 | 554 5.7.1 SPF hard fail |
| E | sin firmar, desde la dirección IPv6 listada | pasa | ninguna | pasa | 3.80 de 15 | aceptado |
Mira el caso B. Es una factura con el código de banco y el número de cuenta cambiados después de firmarse. Fraude de facturas, dicho de otro modo. SPF pasó, porque vino del servidor correcto. DMARC pasó, porque SPF pasó y se alineó con el dominio From:. Por sus puntuaciones Rspamd le dio 2.30 de 15 y lo habría entregado. Lo único que lo paró fue el rechazo forzado sobre la firma rota. DMARC por sí solo deja pasar un mensaje manipulado mientras sobreviva una de las dos comprobaciones, y eso conviene saberlo antes de llamar protegido a un dominio porque su DMARC diga reject.
El caso D es el coste, y hay que decirlo sin rodeos. Un mensaje correctamente firmado e intacto se rechaza porque vino de un servidor que no está en el registro SPF. Eso es lo que hace el reenvío: el reenviador manda tu mensaje más allá desde su propia dirección. Rechazar solo por SPF significa que el correo reenviado desde dominios con -all rebota. SRS en el reenviador lo arregla, y también lo haría confiar en DKIM por encima de SPF, pero la regla de la casa es la regla de la casa, y el coste queda por escrito.
El valor por defecto que confía en toda red privada
El segundo ajuste que no hace nada en silencio. Rspamd se salta SPF y algunas otras comprobaciones para las direcciones que considera locales, y la lista que viene de serie en options.inc es esta:
local_addrs = [192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, fd00::/8, 169.254.0.0/16, fe80::/10];
Todos los rangos privados, ambas familias. Si tu MX se sienta detrás de un balanceador de carga, un NAT o un proxy en una dirección privada, SPF se salta en silencio para todo ello. En el laboratorio, que es todo direccionamiento privado, SPF nunca falló hasta que se cambió esto.
El arreglo obvio no funciona. Rspamd tiene dos directorios para ajustes locales, y su FAQ deletrea la diferencia: local.d «Merges with defaults. Collections and lists are combined», mientras que override.d «Replaces defaults.»11 Una lista más estrecha en local.d/options.inc se combina con la que viene de serie, lo que significa que no estrecha nada. Tiene que ir en override.d/options.inc:
local_addrs = [127.0.0.0/8, ::1];
y rspamadm configdump options es como compruebas con qué acabó Rspamd de verdad, en vez de lo que crees que escribiste.
El greylisting también está apagado en esta construcción. Envía un rechazo temporal a los remitentes desconocidos y los acepta en el reintento, lo que sí para algo de spam, pero el greylisting de Rspamd responde antes de que SPF y DMARC hayan dicho lo suyo, así que mientras pruebas ves un aplazamiento donde esperabas un veredicto.12 Las pruebas de arriba solo tienen sentido con él apagado.
Tus propios rebotes, rechazados por tu propio DMARC
La última trampa apareció cuando Stalwart en el laboratorio rechazó un rebote de este servidor. Postfix envía los rebotes con un remitente de sobre vacío y From: MAILER-DAEMON@mail.alpha.test. Sin remitente de sobre, la única comprobación de SPF que queda es sobre el nombre que el servidor dio en HELO, mail.alpha.test, que no tenía registro SPF. El rebote tampoco estaba firmado. Así que DMARC recayó en la política de alpha.test, encontró p=reject, y el servidor receptor hizo exactamente lo que el dominio le dijo:
smtp.dmarc-fail … domain = "alpha.test", policy = "reject", result = DMARC check failed
(dmarc.fail) { causedBy = Policy not aligned }
La propia política del dominio estaba tirando sus propios fallos de entrega. Cada nombre de host MX recibe su propio registro SPF, lo que lo arregla:
mail IN TXT "v=spf1 a -all"
Seis ajustes que parecen terminados
Las trampas de arriba comparten una forma. Cada una de ellas es un ajuste que parece terminado, no produce ningún error, y no hace nada de nada.
| Parece terminado | Lo que pasa en realidad | Hallado por |
|---|---|---|
smtp_tls_security_level = dane | texto en claro allí donde no hay TLSA publicado | leyendo postconf(5) |
| comprobaciones de login-mismatch en el puerto 25 | saltadas, un aviso en el log | enviando una falsificación |
local_addrs estrechado en local.d | fusionado con el valor por defecto, nada estrechado | rspamadm configdump |
DMARC p=reject en el remitente | el correo manipulado pasa si SPF pasa | caso B |
| renovaciones de certbot | clave nueva, TLSA deja de casar | leyendo la documentación de certbot |
DMARC p=reject en tu propio dominio | tus propios rebotes rechazados | un rebote rechazado |
Dos de ellas están en la documentación, si lees el párrafo correcto de la página correcta. Las otras cuatro aparecieron solo enviando el mensaje que la regla debía parar y mirando lo que volvía. Releer la configuración y darle el visto bueno no encontró ninguna. Si te llevas una sola costumbre de este artículo, llévate esa, porque cada línea de aquí se puede copiar a un servidor y seguir sin funcionar.
Los valores por defecto son la decisión de otro
Cada rechazo de este artículo es un ajuste que Postfix tiene desde hace años. Incluso DANE, lo más nuevo que hay aquí, está en él desde la 2.11.13 Nada de esto es nuevo, nada de esto es caro, y el servidor de debajo es el mismo software libre que fue siempre. La distancia entre un servidor de correo que deja pasar una factura alterada y uno que la rechaza en DATA es una página de configuración y una tarde de pruebas.
¿Y por qué tan poca parte de internet funciona así? Porque los valores por defecto deciden, y los valores por defecto se pusieron para asegurar que el correo llegara en una internet donde TLS era la excepción. Fueron una decisión razonable entonces. Déjalos en paz ahora y habrás entregado tus decisiones de seguridad a una elección que alguien hizo en 2002, para una red que ya no existe, sobre un correo que no era el tuyo.
Correr un servidor de correo es una decisión de hacerse responsable de lo que pasa por él. Esos ajustes son la decisión puesta por escrito. Los valores por defecto son lo que te queda cuando nadie la tomó.
postconf(5): smtpd_tls_security_level — el nivel
encrypt: «According to RFC 2487 this MUST NOT be applied in case of a publicly-referenced SMTP server.» ↩︎postconf(5): smtp_tls_security_level — el nivel
dane: «When DNSSEC-validated TLSA records are not found the effective tls security level is “may”.» ↩︎Postfix TLS_README: Opportunistic TLS — «The SMTP transaction is encrypted if the STARTTLS ESMTP feature is supported by the server. Otherwise, messages are sent in the clear.» ↩︎
postfix-tlspol on GitHub — servidor de políticas MTA-STS y DANE para Postfix, con licencia MIT: «Verify TLSA records for correctness and supported parameters, only then the dane-only policy (Mandatory DANE) will be returned.» Aquí se usa la versión 1.14.4. ↩︎
Postfix source, src/dns/dns_lookup.c — «Automatically turns on the RES_TRUSTAD flag on systems that support this flag», y el código que lo hace cuando se pide la validación DNSSEC. ↩︎
RFC 7672 §3.1.1 — SMTP security via opportunistic DANE TLS. Los registros TLSA de los servidores SMTP «should, in most cases, be “DANE-EE(3) SPKI(1) SHA2-256(1)” records». ↩︎
Certbot user guide, command-line options —
--reuse-key: «When renewing, use the same private key as the existing certificate. (default: False)», y bajo--no-reuse-key: «Not reusing private keys is the default behavior of Certbot.» ↩︎postconf(5): reject_known_sender_login_mismatch — «to enforce that the From: header address matches the envelope sender (MAIL FROM) address, use an external filter such as a Milter». ↩︎
postconf(5): reject_unknown_sender_domain — rechaza cuando el dominio de MAIL FROM tiene «no DNS MX and no DNS A record», o un MX malformado. ↩︎
postconf(5): unknown_address_reject_code — por defecto 450: «Do not change this unless you have a complete understanding of RFC 5321.» ↩︎
Rspamd FAQ: the difference between local.d and override.d — local.d «Merges with defaults. Collections and lists are combined»; override.d «Replaces defaults. Entire sections are overwritten.» ↩︎
Rspamd greylisting module — «The greylisting module triggers a soft reject action, which is intended to be interpreted by the MTA as a temporary rejection». ↩︎
Postfix TLS_README: DANE — «Postfix ≥ 2.11 supports the new dane and dane-only security levels». ↩︎