Errores de bind9 y logging

Desde que escribí "Logs, bind9 e IPv6" tenía pendiente echar un vistazo al resto de mensajes que bind9 estaba generando en los logs del sistema. Además de un montón de ruido inútil, el problema es que a través de estos mensajes se filtra cierta cantidad de información privada sobre los sitios que visitamos/accedemos (¡Hola NSA!), tanto consciente como inconscientemente1.

Mi proposito es por un lado reducir los mensajes que se están registrando en los logs, dejando únicamente aquellos que realmente merece la pena registrar, y por otro gestionar los logs de bind9 de forma separada de los del resto del sistema para poder establecer políticas de expiración diferentes. Afortunadamente bind9 tiene su propio sistema de logging interno, por lo que no tendremos que recurrir a jugar con la configuración de los logs del sistema.

Logging en bind9

La documentación oficial de bind9 describe la sintaxis para configurar el sistema de logging. Este sistema se basa en dos conceptos: los canales y las categorías. Los canales definen dónde se mandarán los mensajes de log: a un fichero, al syslog, a la salida estándar de error o a ningún sitio —null—. Además se pueden definir algunos detalles adicionales, como el nivel de gravedad (severity) mínimo de los mensajes, así como algunas opciones sobre campos a incluir en cada mensaje de log —como un timestamp o la propia severity—.

Las categorías por su parte sirven para agrupar los mensajes de bind9 que tienen similar causa o subsistema de origen. Por ejemplo tenemos categorías como queries, network, database, security, etc (para una lista completa consultad la documentación). El sistema de logging de bind9 permite definir qué categorías de mensajes se van a mandar a qué canales. Cada categoría puede mandarse a varios canales, y de hecho la configuración por defecto (que es la que se usa si no se indica nada) utiliza esto para mandar los mensajes de log tanto a los logs del sistema como a un canal especial de debug. Es lo mismo que si se hubiera escrito:

logging {
     category default { default_syslog; default_debug; };
     category unmatched { null; };
};

La categoría default es la "recoge-todo": toda categoría para la que no se definan canales usa los definidos en ésta. La categoría unmatched es también especial y sólo sirve para recoger los mensajes que por la razón que sea no tienen categoría. Como se puede observar, estos mensajes sin categoría se ignoran (se mandan al canal null). Los mensajes con categoría, como no hay definida ninguna otra categoría aparte de default, acaban todos enviándose a los canales por defecto default_syslog y default_debug. Estos dos canales están definidos por defecto así:

channel default_syslog {
    syslog daemon;
    severity info;
};

channel default_debug {
    file "named.run";
    severity dynamic;
};

El primer canal simplemente manda los mensajes de bind9 de gravedad info o mayor al syslog —rsyslog, journald o el que sea—. En el caso de rsyslog los mensajes acabarán normalmente en el fichero /var/log/syslog. El segundo canal es como he dicho especial, y sirve para hacer debugging de bind9. El nivel de la gravedad de los mensajes de este canal es variable (dynamic) y depende del valor (un número entre 1 y 99) pasado a named mediante la opcion -d de la línea de comandos cuando se lanzó el proceso. Este valor también se puede cambiar durante ejecución mediante los comando rndc trace <valor> y rndc notrace. El canal default_debug normalmente manda los mensajes de log a un fichero, pero si se le ha indicado la opción -f a named en la línea de comandos se usará en su lugar la salida de error estandar. Como systemd por defecto manda la stdout y la stderr de los demonios que arranca al syslog, y rsyslog almacena los mensajes daemon.* en /var/log/daemon.log, los mensajes de bind9 terminan apareciendo por duplicado en ambos ficheros de log.

Mi configuración va a ser diferente: no voy a mandar nada al syslog, sino que guardaré todo en ficheros controlados directamente por named. Lo primero de todo es crear el fichero de configuración de bind9 dónde escribiré la configuración de logging. He decidido ponerla en un fichero llamado /etc/bind/named.conf.logging separado del resto de los ficheros de configuración para que las actualizaciones de paquetes no me la pisen. Para que named lea este fichero hay que incluirlo desde /etc/named.conf:

include "/etc/bind/named.conf.logging";
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";

Ahora pasemos a rellenar /etc/bind/named.conf.logging. Lo primero que voy a hacer es definir mi propio canal al que llamaré file_log:

logging {
    channel file_log {
        file "/var/log/bind9/named.log" versions 2 size 50k;
        severity info;
        print-time yes;
        print-severity yes;
        print-category yes;
    };
    category default { file_log; default_debug; };
    category unmatched { null; };
};

Este canal manda los mensajes de log a un fichero llamado /var/log/bind9/named.log. Es necesario crear dicho fichero de antemano, y su propietario y grupo deben ser bind:bind. La severity del nuevo canal es la misma que había por defecto (info) y además le he indicado que añada a cada mensaje de log la fecha/hora en que se produjo, la gravedad del mismo y la categoría a la que pertenece. En el caso de syslog la fecha/hora es redundante, pero aquí si no la activamos no sabremos cuando ocurrió cada mensaje. Los otros dos campos simplemente me ayudarán en mi investigación.

No es necesario incluir este nuevo fichero en las políticas de logrotate, ya que el propio bind9 permite declarar la política de rotación de logs del fichero de un canal. La palabra clave versions nos permite indicar cuántas rotaciones (ficheros) queremos tener como máximo, y si añadimos size se limitará el tamaño máximo que alcanzará el fichero de log antes de rotar (si no se rotará cada vez que se arranque o recargue named).

Pero hay un pequeño problema: named va a tratar de crear los distintos ficheros de rotación en el mismo directorio que el fichero de log, y normalmente lo hace ejecutándose como usuario bind. Si el directorio del fichero de log no permite escribir ficheros al usuario bind (como es el caso de /var/log que es propiedad de root), esto va a provocar errores de permisos cada vez que se intenten rotar los logs. Es por esta razón por la que arriba he puesto el fichero de log dentro de un subdirectorio /var/log/bind9/ cuyo propietario es bind:bind y en el que named pueda guardar y rotar los ficheros sin problemas.

Como mi objetivo es limitar la cantidad de información que pueda filtrarse a través de este fichero, he puesto una política muy agresiva de "olvido" (sólo dos ficheros de log de 50 KB cada uno como máximo). En teoría no debería ser mayor problema, ya que, una vez bien configuradas las categorías de mensajes, el número de líneas del fichero debería ser mínimo.

Errores y categorías

A continuación pondré una lista de distintos mensajes de log que he encontrado durante un pequeño periodo de pruebas. He ocultado los datos de dominios e IPs (se dice el pecado pero no el pecador. ;-)) y he incluido los campos de categoría y severity para el posterior análisis:

resolver: notice: DNS format error from XX.XX.XX.XX#53 resolving www.ejemplo.dom/AAAA for client 127.0.0.1#33333: Name ejemplo.dom (SOA) not subdomain of zone www.ejemplo.dom -- invalid response
lame-servers: info: error (FORMERR) resolving 'www.ejemplo.dom/AAAA/IN': XX.XX.XX.XX#53

lame-servers: info: error (unexpected RCODE REFUSED) resolving 'www.ejemplo.dom/AAAA/IN': XX.XX.XX.XX#53
lame-servers: info: error (unexpected RCODE SERVFAIL) resolving 'www.ejemplo.dom/A/IN': XX.XX.XX.XX#53
lame-servers: info: error (connection refused) resolving 'www.ejemplo.dom/A/IN': XX.XX.XX.XX#53
lame-servers: info: lame server resolving 'www.ejemplo.dom' (in 'ejemplo.dom'?): XX.XX.XX.XX#53

edns-disabled: info: success resolving 'www.ejemplo.dom/A' (in 'ejemplo.dom'?) after reducing the advertised EDNS UDP packet size to 512 octets
edns-disabled: info: success resolving 'www.ejemplo.dom/A' (in 'www.ejemplo.dom'?) after disabling EDNS

dnssec: info: validating @0x7fxxxxxxxxxx: www.ejemplo.dom A: no valid signature found
dnssec: info: validating @0x7fxxxxxxxxxx: www.ejemplo.dom AAAA: no valid signature found
dnssec: info:   validating @0x7fxxxxxxxxxx: ejemplo.dom SOA: no valid signature found
dnssec: info:   validating @0x7fxxxxxxxxxx: www.ejemplo.dom NSEC: no valid signature found

Estos son los mensajes más repetidos, especialmente los dos primeros que vienen en pareja. También he encontrado otros mucho menos frecuentes:

resolver: notice: DNS format error from XX.XX.XX.XX#53 resolving www.ejemplo.dom/AAAA for client 127.0.0.1#44444: non-improving referral
lame-servers: info: error (insecurity proof failed) resolving 'ejemplo.dom/DNSKEY/IN': XX.XX.XX.XX#53
lame-servers: info: error (no valid DS) resolving 'www.ejemplo.dom/A/IN': XX.XX.XX.XX#53
lame-servers: info: error (no valid RRSIG) resolving 'ejemplo.dom/DS/IN': XX.XX.XX.XX#53
dnssec: info: validating @0x7fxxxxxxxxxx: ejemplo.dom DNSKEY: got insecure response; parent indicates it should be secure

La severity de todos estos mensajes es info salvo para los dos mensajes de la categoría resolver que son notice, así que se podría simplemente indicar un severity warning; en el canal para descartar todos estos mensajes. Pero, y es aquí es cuando podemos empezar a intuir los inconvenientes del diseño del sistema de logging de bind9, así descartaríamos también cualquier otro mensaje de gravedad info o notice. Es por esa causa por la que se implementan las categorías, ya que éstas permiten una forma más específica de tratar los distintos mensajes.

Las categorías a las que pertenecen estos mensajes son: lame-servers, resolver, dnssec y edns-disabled. De ellas, lame-servers es la primera candidata a descartar, ya que es una categoría que reune precisamente el tipo de mensajes que genera bind9 cuando se encuentra con que otro servidor no está comportándose como debiera, e informa de ello. Como en estos casos es siempre el otro servidor está mal configurado, y raramente podemos hacer algo al respecto, estos mensajes son de los que no merece la pena informar al administrador y por lo tanto ignoraremos la categoría entera mandándola al canal null. Esta es una configuración muy común, pero por alguna razón —al menos en Debian— no viene por defecto.

Las otras 3 categorías en principio me van a requerir una investigación más profunda. He encontrado este bug #584557 en el BTS de Debian en el que que se solicita descartar las categorías resolver y edns-disabled:

Please suppress the "resolver" log category, on the grounds that it spams the system log a great deal, and just like "edns-disabled" and "lame-servers", refers to issues completely outside of the hability of the local nameserver operator to fix, AND extremely unlikely to get fixed even if reported.

[...]

Sideway referals are very common practice in the ARIN region, most people either don't care, or don't even understand that they introduce undue latency. Even clueful admins like the kernel.org people use them. I bet we have some of that in debian.org/debian.net as well.

As for the invalid response, a very common cause for that are broken load-balancer setups. Just like the ECN IP functionality, tracking this is a lost cause that will fix itself in a couple decades and will resist any efforts to accelerate the process (unless, say, someone from the top tier like Google decides to use a massive large hammer and face lawsuits over it).

No es la única referencia que he encontrado que atribuye esos mensajes de resolver y sus FORMERR asociados a problemas con load balancers mal configurados. De todas formas el bug (fusionado con el ##492901) sigue 6 años despues abierto, aunque puede deberse simplemente a que el mantenimiento del paquete bind9 en Debian parece centrado exclusivamente en cerrar agujeros de seguridad.

Los mensajes de edns-disabled por su parte parece que se deben a firewalls mal configurados que filtran los paquetes EDNS de más de 512 bytes. Los mensajes se pueden eliminar también de otra forma: reduciendo el tamaño de los paquetes EDNS recibidos a 512 bytes mediante la opción2 edns-udp-size 512;. Pero esta opción anula la razón de ser de la extensión EDNS (que es emplear paquetes UDP de hasta 4096 bytes) y haría que la resolución en general fuera más lenta. El manual de bind9 dice acerca de esta opción:

edns-udp-size

Sets the advertised EDNS UDP buffer size in bytes to control the size of packets received. Valid values are 512 to 4096 (values outside this range will be silently adjusted). The default value is 4096. The usual reason for setting edns-udp-size to a non-default value is to get UDP answers to pass through broken firewalls that block fragmented packets and/or block UDP packets that are greater than 512 bytes.

named will fallback to using 512 bytes if it get a series of timeout at the initial value. 512 bytes is not being offered to encourage sites to fix their firewalls. Small EDNS UDP sizes will result in the excessive use of TCP.

Creo que la decisión más inteligente en este caso es seguir usando EDNS con 4096 bytes, que es más óptimo, e ignorar los mensajes provenientes de servidores DNS tras firewalls mal configurados, que son —espero— una minoría.

La última categoría problemática es la de dnssec, que son los mensajes debidos al uso de las extensiones de seguridad de DNS DNSSEC. Este es un tema complejo y no parece que haya mucha gente que controle del mismo, así que parte de las respuestas que he encontrado consisten directamente en desactivar la validación DNSSEC. Del resto, la respuesta más repetida es la de usar la opción dnssec-validation auto; en vez de dnssec-validation yes;, pero resulta que, en el caso de Debian, esta opción ya viene en el fichero de opciones2. A partir de ahí, lo máximo que he conseguido averiguar es que los mensajes got insecure response; parent indicates it should be secure indica posiblemente una configuracion incorrecta del DNSSEC del servidor interrogado, y que el caso de los no valid signature found podría (y aquí las cosas no están muy claras) ser también por culpa de la fragmentación de paquetes UDP al pasar por firewalls "problemáticos".

Al final, lo que he decidido es ignorar también todos estos mensajes de categoría dnssec ya que estamos en la misma situación que con los lame-servers y demás: son mensajes sobre problemas que están más allá de nuestro control. Pero no me hace mucha gracia perder los mensajes de esta categoría con severity alta (errores serios de validación). Para esto necesito otro canal con una severity diferente, pero como dudo que bind9 permita a dos canales compartir el mismo fichero de log, necesito un destino distinto para el canal. No quiero usar otro fichero y tener que rotarlo, etc, así que al final, aunque había dicho de no utilizar el syslog, me he definido mi propio canal errors_syslog para recoger los mensajes graves (de severity warning o mayor) en él. El sistema de logging de bind9 no es tan flexible como pudiera parecer a primera vista, y en casos como éste queda en evidencia.

Podría hacer lo mismo con las otras tres categorías, pero he comprobado en el código fuente de bind9 (concretamente el de la versión 9.9.5 que uso) que los mensajes de categorias lame-servers y edns-disabled son todos de gravedad info y los de resolver son mayoritariamente notice o debug (hay alguna excepción que es warning). Por ello esas categorías las voy a ignorar totalmente3, y la versión final de mi named.conf.logging queda por lo tanto así:

logging {
    channel file_log {
            file "/var/log/bind9/named.log" versions 2 size 50k;
            severity info;
            print-time yes;
            print-severity yes;
            print-category yes;
        };
        channel errors_syslog {
            syslog daemon;
            severity warning;
        };
        category lame-servers { null; };
        category edns-disabled { null; };
        category resolver { null; };
        category dnssec { errors_syslog; };
        category default { file_log; errors_syslog; default_debug; };
        category unmatched { null; };
};

No obstante, lo vigilaré durante una temporada por si hay que hacer ajustes a posteriori.

:wq


  1. y es que normalmente no nos damos cuenta de la cantidad de conexiones que se están produciendo entre bambalinas cada vez que accedemos a un sitio: widgets, ads, trackers, herramientas analíticas, objetos incrustados y demás porquería 

  2. dentro de la sección options { ... };, que en Debian está en el fichero /etc/bind/named.conf.options 

  3. otra alternativa sería usar default_debug para que sólo se mostraran los mensajes cuando se activara el debug 

blogroll

social