sábado, 23 de octubre de 2021

Integración de servicios con Kerberos - PDNS

Aprovechando un rato libre, es momento de continuar con la implementación de un servidor de nombres y autenticación basado en OpenLDAP y Kerberos. 

En las entradas anteriores de esta serie, fuimos añadiendo servicios sobre la base de un servidor OpenLDAP y llegamos a un punto donde teníamos un servidor de nombres implementado mediante un servidor OpenLDAP, un servidor de autenticación implementado mediante Kerberos y un servidor DNS, usando PDNS. Configuramos Kerberos y el servidor DNS para que usaran OpenLDAP como backend, para lo cual creamos DNs específicos dedicados a cada servicio. Estos DNs permiten a cada servicio realizar consultas o modificaciones, en caso de ser necesario, de la información contenida en el servidor OpenLDAP.

Hoy veremos como integrar un servicio, en este caso PDNS, para que utilice tickets de Kerberos para poder realizar consultas al servidor OpenLDAP en vez de emplear un par DN/contraseña. El objetivo de esta integración consiste en aumentar la seguridad de la infraestructura, además de asegurar que no hay contraseñas de servicio, aunque sean encriptadas, en los ficheros de configuración de los servicios.

Para realizar esta integración es importante recordar, de forma resumida, el flujo de autenticación de Kerberos, teniendo en cuenta que las dos entidades implicadas en este caso son dos servicios que se ejecutarán en el mismo host o en dos hosts separados y por tanto, uno de los servicios actuará como cliente:

  1. El servicio cliente, en este caso PDNS, ha sido configurado para autenticarse mediante el uso de GSSAPI, con lo que envía la clave almacenada en su keytab al KDC (Key Distribution Center) solicitando un TGT (Ticket Granting Ticket).
  2. El KDC valida la clave enviada por el servicio remoto y genera el TGT correspondiente.
  3. El servicio PDNS recibe el TGT, lo que le permitirá solicitar tickets para acceder a otros servicios.
  4. Empleando el TGT, el cliente solicita al KDC un ticket de servicio para poder consultar el servidor OpenLDAP.
  5. El KDC recibe la solicitud y tras validarla, devuelve el ticket de servicio correspondiente al cliente.
  6. El servicio cliente consulta al servicio remoto empleando el ticket de servicio proporcionado por el KDC.

Este flujo de validación que acabamos de ver implica los siguientes puntos:

  • En el dominio o reino Kerberos existente debemos crear los principales necesarios para ambos servicios, tanto el del cliente como el del servidor.
  • El servicio cliente debe disponer de la clave necesaria en un keytab accesible por el mismo.
  • El servicio cliente debe configurarse para emplear GSSAPI.
  • El servicio servidor debe estar configurado para permitir el acceso mediante Kerberos y disponer de un keytab.
  • El servicio servidor debe configurarse para acepta GSSAPI como mecanismo de autenticación.

Para implementar todo esto, vayamos punto por punto y empecemos por saber que es un keytab. De forma simple, un keytab es un fichero que almacena claves de kerberos para uno o más principales. En concreto un fichero keytab almacenará la fecha de escritura de la entrada en el fichero, el nombre del principal, un número de versión de la clave que representa la entrada, un tipo de encriptación y la propia clave.

Conviene recordar que un principal o Service Principal Name (SPN) es una entrada en un reino Kerberos, tanto de usuarios como de servicios, con un formato como el siguiente:

Servicio/Nombre de Host@REINO Kerberos

Servicio/Nombre de Host.Dominio@REINO Kerberos

Nombre de Usuario@Reino Kerberos

Los dos primeros tipos suelen emplearse para representar servicios, mientras que el último se utiliza para representar usuarios. Por tanto, en nuestro caso, es necesario que creemos cuatro SPNs nuevos, dos para el servicio LDAP y otros dos para el servicio PDNS, a partir de los cuales podremos crear los keytabs correspondientes.

Teniendo en cuenta lo expuesto hasta aquí, creamos el principal correspondiente para cada servidor LDAP de nuestra infraestructura y los exportamos a un keytab:

Creación principal - Servidor ldap1

Creación principal - Servidor ldap2

Exportación keytab servidor ldap1.

Exportación keytab servidor ldap2.

Ahora realizamos el mismo proceso de creación de SPNs y exportación de los keytab correspondientes para el servicio PDNS, que actuará como cliente del servicio OpenLDAP. ¿Como hacemos esto? Pues como se ve a continuación:

Creación principal - Servidor PDNS1.

Creación principal - Servidor PDNS2.

Ahora, al igual que en el caso de los principales de OpenLDAP, exportamos la clave del principal recién creado a un fichero para su uso por parte de PDNS. Esto debe hacerse desde el servidor master, ya que al realizar la exportación se realiza un modificación en las entradas correspondientes en OpenLDAP y por tanto es necesario que el KDC realice operaciones de escritura:

Exportación keytab servidor PDNS1.

Exportación keytab servidor PDNS2.

Una vez que tenemos listos los keytabs de todos los servicios, pasamos a realizar la configuración de los mismos para que se utilice Kerberos como mecanismo de autenticación entre ellos.

Para empezar de forma sencilla, comenzamos con el servicio PDNS. PDNS permite configurar el tipo de validación a utilizar con el backend LDAP con las siguientes opciones de configuración:

  • ldap-bindmethod, permite especificar las opciones simple, el método básico con un DN y una password o gssapi, para el uso de Kerberos.
  • ldap-krb5-keytab, especifica la ruta al fichero que contiene la clave que se usará para la validación.
  • ldap-krb5-ccache, especifica la ruta completa al fichero que almacenará la cache de credenciales de Kerberos.

Por tanto, la configuración necesaria del servidor PDNS implica añadir las siguientes lineas al fichero /etc/pdns/pdns.conf:

Configuración de PDNS para usar Kerberos.

Como es lógico, utilizaremos el keytab específico para cada servidor en cada caso y es muy importante tener en cuenta los permisos de acceso a dicho fichero si configuramos el servicio para que utilice un usuario diferente a root, que es lo más recomendable.

A continuación, configuramos OpenLDAP para que utilice el fichero keytab al arrancar, para lo cual es necesario cambiar el fichero de configuración correspondiente que dependerá de la distribución utilizada. En este caso tenemos lo siguiente para CentOS y Debian:

Configuración keytab OpenLDAP en CentOS

Configuración keytab OpenLDAP en Debian.

Al reiniciar el servicio OpenLDAP este utilizará el keytab especificado. Como es lógico, es muy importante que tenga los permisos correctos y el propietario del fichero sea el usuario que ejecuta el servicio slapd.

Ahora es necesario que pasemos a configurar OpenLDAP para que soporte mecanismos de validación adicionales. Por tanto, antes de continuar, comprobemos los mecanismos de autenticación soportados por el servidor OpenLDAP. Para esto basta con realizar una consulta como la siguiente:
 
Comprobación de mecanismos SASL soportados.

Como se puede apreciar en la imagen anterior, todavía no hay ninguno disponible, así que tenemos que añadirlos, empezando por el plugin correspondiente de SASL para GSSAPI ya que todos los mecanismos de autenticación adicionales o externos, como Kerberos en este caso, emplean la biblioteca SASL y los plugins proporcionados por dicha biblioteca. Para instalar el plugin, por ejemplo en CentOS, solo es necesario hacer lo siguiente:

Instalación de plugin GSSAPI de SASL.

Con el plugin instalado, debemos cambiar la configuración existente de la biblioteca SASL para incluir los nuevos mecanismos a utilizar por parte de OpenLDAP.

En una entrada anterior de esta serie, concretamente en esta, configuramos OpenLDAP para realizar autenticación passthrough y usar Kerberos para validar usuarios. Como vimos entonces, OpenLDAP utilizaba un servicio de autenticación externo, delegando el proceso de autenticación al proceso saslauthd, para todas aquellas entradas cuyo atributo userPassword tuvieran el formato {SASL}principal@REINO. El contenido del fichero que, por tanto, debemos tener inicialmente es parecido a este:

Configuración SASL para autenticación passthrough.
 
La configuración anterior permite delegar el proceso de autenticación a un servicio externo, pero ahora necesitamos que OpenLDAP soporte otros mecanismos de autenticación, para que clientes puedan acceder usando otros mecanismos de autenticación. Esto requiere que configuremos adecuadamente la biblioteca SASL para OpenLDAP, añadiendo los mecanismos del plugin GSSAPI, lo que permitirá el uso de tickets Kerberos para realizar operaciones de lectura y escritura en el servidor. A partir del fichero anterior, la configuración quedaría del siguiente modo:

Configuración SASL incluyendo mecanismos adicionales.

Con esta configuración, y tras reiniciar el servidor OpenLDAP, podemos comprobar que se soportan todos los casos:

Búsqueda usando autenticación GSSAPI.

Búsqueda usando autenticación SIMPLE y passthrough.

Búsqueda usando autenticación SIMPLE y validación local.

Como podemos apreciar en los ejemplos anteriores, hemos realizado una operación de consulta al servidor OpenLDAP con las siguientes características:

  • En el primer caso, el usuario que realiza la operación debe obtener un ticket de Kerberos. Usando dicho ticket realiza la consulta al servidor OpenLDAP especificando como mecanismo de autenticación GSSAPI. Este proceso es el mismo que queremos configurar para PDNS.
  • En el segundo ejemplo, un usuario realiza una consulta empleando el método de autenticación SIMPLE, es decir, presentando un par DN-contraseña. Como ese usuario tiene delegada la autenticación (atributo userPassword: operator1@LAB.INT), OpenLDAP delega la validación del mismo al proceso saslauthd.
  • En el último caso, muy similar al anterior, comprobamos que la validación empleando em método de autenticación SIMPLE con un usuario cuya contraseña se encuentra en el propio servidor OpenLDAP, y que por tanto no se delega a ningún autenticador externo, también puede acceder correctamente al servidor OpenLDAP.
Por tanto, el servidor OpenLDAP ya está configurado para emplear los mecanismos necesarios para su integración con Kerberos y ahora, siguiendo con la configuración del mismo, tenemos que pasar a realizar un mapeo de dichas autenticaciones a DNs existentes. Pero, ¿que es exactamente esto de los mapeos de autenticación a DNs?
 
Para un servidor OpenLDAP, cualquier operación bind que se realice requiere de un DN. Con esto quiero decir que internamente, una vez que un usuario o una aplicación ha realizado un bind con el servidor OpenLDAP, lo que se espera es tener algo con el formato de un DN, por ejemplo cn=.... o uid=...., que identifique la entidad que ha realizado dicho bind. Esto es así porque, posteriormente existirán ACLs que permitirán o denegarán el acceso a ramas del árbol o a atributos, y las ACLs se basan en la comparación del DN que ha hecho el bind con la especificación de las reglas que las establecen.
 
Al emplear un procedimiento de autenticación externo, la operación de bind que se realiza con el servidor OpenLDAP no devuelve un DN existente en el servidor, sino algo como lo siguiente:
 
Bind con autenticación externa.

Al realizar una búsuqeda empleando un ticket de Kerberos, cuando la validación es correcta, OpenLDAP construye un DN como el que podemos ver en la imagen anterior. Básicamente el DN indica el reino del ticket Kerberos y el mecanismo de autenticación empleado. Como es lógico, con esta configuración básica podríamos validar entidades externas, como el servicio PDNS, pero para poder aplicar ACLs y controlar a que ramas tiene acceso cada uno de los servicios, lo mejor es convertir dicho DN a uno existente en el árbol de directorio. Es en este paso donde entra el mapeo de DNs, el cual se realiza con la confioguración de reglas de mapeo usando el atributo de configuración dinámica olcAuthzRegexp el cual, empleano expresiones regulares, nos permitirá convertir DNs específicos o conjuntos de los mismos, a otros DNs diferentes.
 
De forma muy básica, viendo el DN generado con una autenticación GSSAPI, podemos establecer un mapeo de autorizaciones muy simple usando una expresión como la siguiente:
 
 
Mapeo simple de autenticación GSSAPI.

Al aplicar este atributo en la rama de configuración dinámica y reiniciar el servicio slapd, ahora tenemos lo siguiente al realizar un bind empleando el mecanismo GSSAPI:

Mapeo de DN efectivo.

Una vez establecida toda la configuración necesaria, debemos pensar un momento en la secuencia de arranque del servicio PDNS. Al arrancar el servicio, este necesita solicitar un ticket a partir del keytab de servicio que hemos creado y establecido en su configuración. Al hacer esto, debe generar un fichero donde almacenará la caché de credenciales de Kerberos, usada para posteriormente poder realizar peticiones al servidor OpenLDAP. Pues bien, al menos en la version del servidor autoritativo de  PowerDNS que estoy utilizando, en concreto la 4.5.1, al arrancar el servicio tras realizar toda la configuración, este no es capaz de iniciarse y se recibe un error que indica que los permisos del fichero de caché de credenciales no son conrrectos:
 
Error del fichero de caché de credenciales.
 
Tras bastantes pruebas y analizar el arranque con strace, el error se produce sencillamente porque no es capaz de acceder al fichero que contiene la cache, el cual se está generando durante el arranque y antes de que el proceso cambie a ser del usuario pdns. Con la configuración que he establecido, el servicio cambia al usuario pdns al arrancar y según el análisis realizado, intenta acceder al archivo de credenciales que ya es del usuario root, recibiendo el error indicado. 

Si arrancamos el proceso manualmente, en vez de lanzar el servicio definido mediante systemctl, se recibe el siguiente error que es bastante más esclarecedor de lo que puede estar sucediendo:

Error de caché de credenciales durante el arranque de PDNS.

Este problema se debe, única y exclusivamente, a que el propietario del directorio de instalación de PDNS, que está en /etc, es el usuario root y no el propio usuario pdns. Por tanto, podemos cambiar el propietario de /etc para que sea del usuario pdns, pero es importante tener en cuenta que, quizás la actualización automática del paquete, pueda cambiar de nuevo dichos permisos. Con esta solución, la configuración de pdns quedaría del siguiente modo:

Configuración corregida de PDNS. Fichero /etc/pdns/pdns.conf.

Es muy importante asegurar que, en el fichero de descripción del servicio, no esta incluida la opción ProtectSystem=full, ya que esta evita que el proceso escriba en /etc con lo que es necesario cambiarla a false en caso de estar presente. Esto puede incluir que, al para el servicio usando systemctl, dicho fichero de caché de credenciales no se elimine, lo cual provocará un fallo en el siguiente arranque del servicio, con lo que es necesario modificar la descripción del servicio para borrar dicho fichero. Como referencia, una posible solución a estos problemas sería similar a la siguiente:

Modificaciones de la descripción del servicio pdns.

Con toda esta configuración, ya podemos arrancar el servicio pdns el cual, se valida usando Kerberos para poder realizar operaciones de lectura con el servidor OpenLDAP:

Servicio pdns arrancado.

Hasta aquí esta entrada y en cuanto saque otro rato, seguiremos integrando servicios con Kerberos para poder asegurar más nuestras infraestructuras.

jueves, 5 de agosto de 2021

Introducción a Ansible - Configuración básica y comandos ad-hoc

Aunque he publicado un par de posts sobre Ansible con anterioridad, creo que es buena idea hacer una pequeña serie para estudiar más detenidamente su funcionamiento. De esta manera veremos como podemos integrar Ansible en nuestra infraestructura y usarlo para configurar y desplegar sistemas.

Empecemos, por tanto, por la pregunta fundamental ¿que es exactamente Ansible? De forma sencilla y resumida es un software de gestión de configuración y despliegue de sistemas que permite la descripción de infraestructura como código. Esto quiere decir que mediante Ansible puedo fijar la configuración que debe tener un sistema, describiendo dicha configuración como código, para posteriormente aplicar dicha configuración a tantos sistemas como sea necesario. Esa descripción en código nos permitirá definir la plantilla que posteriormente aplicaremos a nuestros sistemas consiguiendo tener una infraestructura cuya configuración sea idéntica en todos los elementos que la forman.

Una de las ventajas de diseño de Ansible, es que solo necesita que los sistemas que vamos a controlar tengan instalado un servidor SSH, sudo debidamente configurado así como una versión más o menos moderna de Python. Gracias a esto podemos usar prácticamente cualquier sistema como consola central, en la cual estará instalado Ansible y desde donde lanzaremos las tareas para controlar toda la infraestructura.

Para no seguir con tanto rollo teórico, vamos a partir de un sistema donde instalaremos Ansible, ya sea de paquete o desde el repositorio de oficial de Github, realizando una configuración básica del mismo.

Por defecto, Ansible almacena toda su configuración en /etc/ansible siendo fundamental el fichero ansible.cfg y el fichero hosts, que contiene el inventario de sistemas bajo el control de Ansible.

Es importante que tengamos en cuenta que, cuando se ejecuta Ansible, este busca su fichero de configuración ansible.cfg de la siguiente manera:

  • Primero busca la variable de entorno Ansible_Config.
  • Luego busca el fichero ansible.cfg en el directorio actual.
  • Luego busca .ansible.cfg en el homedirectory del usuario que está ejecutando el comando ansible.
  • Por último busca en la ruta por defecto que es /etc/ansible/ansible.cfg.

Como lo ideal es usar siempre usuarios no privilegiados, vamos a crear un fichero ansible.cfg básico en el home de un usuario y dar de alta una serie de equipos, en mi caso varios contenedores, con los que realizar nuestras pruebas. El contenido inicial de estos ficheros serán similares a lo siguiente:

Fichero ansible.cfg básico.

Fichero de inventario inicial.

Como vemos en la imagen anterior, el fichero de configuración contiene secciones dentro de cada una de las cuales se establecen pares clave-valor definiendo las opciones que controlan el comportamiento de Ansible. En este caso simple, dentro de la sección defaults, establecemos la ruta y nombre del fichero que contiene el inventario de sistemas, la ruta al interprete de Python que queremos emplear en los sistemas que forman parte de nuestra infraestructura así como que no deseamos comprobar la clave SSH remota de cada uno de ellos en cada conexión. En la sección privilege_escalation, establecemos que método se empleará en los sistemas bajo control de Ansible para escalar a un usuario con privilegios, así como el nombre de dicho usuario.

En el caso del fichero de inventario, de momento, solo establecemos las direcciones IP de los sistemas bajo control de Ansible. Como podemos ver, podemos crear grupos para poder acceder a varios sistemas simultáneamente, siendo en este caso el grupo all el que contiene las direcciones IP de todos los sistemas.

Cada uno de estos sistemas debe tener, además de Python, un servidor SSH y sudo debidamente configurado para permitir la realización de operaciones privilegiadas con el usuario con el que nos conectemos de manera remota.

En mi caso, como ya he comentado, voy a utilizar varios contenedores basados en la última imagen disponible de Ubuntu en la cual añado un usuario no privilegiado, fijo su contraseña, instalo una serie de paquetes, entre ellos el servidor SSH, copio el fichero con la configuración necesaria del usuario no privilegiado para sudo y modifico el entrypoint de la imagen resultante, para que arranque el servidor SSH y lance un while infinito para asegurar que el contenedor permanece arrancado. El contenido del dockerfile para la construcción de la imagen, así como el script usado como entrypoint son los siguientes:

Dockerfile para la construcción de la imagen.

Script usado como entrypoint.

Con esto podemos construir una imagen y arrancar una serie de contenedores, cuyas direcciones IP serán las que hemos añadido al fichero de inventario, cuyo nombre es inventory.list en mi caso.

Estos contenedores sabemos que tienen un servidor SSH arrancado, una instalación de Python y un usuario no privilegiado, con una entrada específica que permite la ejecución de comandos como root pero solicitando contraseña en todos los casos. Este último punto es muy importante ya que, para poder enviar comandos u operaciones desde nuestra consola central de Ansible, será necesario que especifiquemos la contraseña del usuario, así como el nombre del mismo.

Una vez que tenemos establecida esta configuración, vamos a realizar una prueba sencilla de uso de Ansible lanzando lo que se conoce como comando ad-hoc. En Ansible, como ya veremos, podemos crear complejos ficheros de descripción de configuración y operaciones, denominados playbooks, o bien lanzar comandos individuales, denominados comandos ad-hoc. En este último caso, el primero que es recomendable lanzar, ya que nos permite comprobar si nuestra infraestructura está preparada para su control mediante Ansible, es el comando ad-hoc que emplea el módulo ping. Este comando solamente establece la conexión con el sistema o sistemas remotos y comprueba que existe una instalación funcional de Python.

El comando ad-hoc que lanzaremos será el siguiente el cual, además, nos devolverá error con todos los hosts:

ansible -m ping all

Este comando, como podemos ver, emplea el módulo ping, dado por la opción -m, sobre los hosts que forman parte del grupo all, el cual ya definimos en el inventario con las direcciones IP de todos los contenedores. El resultado de la ejecución de este comando es la siguiente:

Resultado de la ejecución del ping de Ansible.

Como vemos, el resultado es un error en todos los casos derivado del hecho de que no hemos especificado la contraseña para establecer la conexión SSH con cada uno de los contenedores. Para esto, solo es necesario añadir la opción -k al comando anterior, con lo que quedaría:

ansible -k -m ping all

siendo ahora el resultado:

Resultado de la ejecución del ping de Ansible.

Como podemos ver, el resultado ahora es muy diferente al anterior ya que, aunque de nuevo recibimos un error, podemos ver claramente que el problema es que no hemos proporcionado la password de sudo necesaria para el usuario. Recordemos que en el fichero de configuración de Ansible establecimos que debíamos escalar a un usuario con privilegios usando sudo, con lo que Ansible lanza el comando sudo con el usuario con el que se establece la conexión SSH. Para introducir la password necesaria para poder usar correctamente sudo, solo es necesario que añadamos la opción -K quedando el comando del siguiente modo:

ansible -kK -m ping all

siendo ahora el resultado el siguiente:

Resultado correcto del comando ping de Ansible.

La salida anterior confirma que los sistemas remotos están correctamente configurados para poder ser controlados por Ansible.

Ahora bien, una pregunta que queda es ¿con que usuario se está conectando Ansible cuando lanzamos los comandos? Por defecto y si no especificamos otra cosa, se conectará con el mismo usuario que esté ejecutando el comando, con lo que ese usuario debería existir en los sistemas remotos y además estar correctamente configurado para poder utilizar sudo.

Si por ejemplo usáramos el usuario root para lanzar un comando de Ansible, deberíamos especificar el usuario remoto con la opción -u, así como incluir las opciones necesarias para introducir la contraseña, quedando el comando del siguiente modo:

ansible -u develop -kK -m ping all

Comando ping con usuario correcto.

Comando ping como usuario root.




Otro punto más a tener en cuenta y que veremos con más detalle en próximas entradas, es que hemos visto que Ansible dispone de módulos que nos permiten realizar tareas específicas. En esta ocasión hemos utilizado el módulo ping, pero si quisieramos lanzar un comando ls -la de forma remota sobre uno de los sistemas usaríamos el módulo raw especificando como argumento de dicho módulo ls -la, quedando el comando más o menos así:

ansible -u develop -kK -m raw -a "ls -la" target

donde target será la dirección IP de un solo sistema o el nombre de un grupo definido en el inventario:

Ejecución de un comando ad-hoc con el módulo raw.
 
Este módulo permite el envío de comandos directamente, igual que haríamos con un comando enviado usando el cliente SSH del sistema.

Como veremos, hay una gran cantidad de módulos disponibles que nos permitirán realizar prácticamente cualquier tipo de tarea así que, para terminar esta entrada, veamos como comprobar instalar un paquete en todos los sistemas a la vez usando el módulo apt de gestión de paquetes en sistemas basados en Debian. El comando a usar es más o menos el siguiente:

ansible -u develop -kK -m apt -a "name=tcpdump state=present" all

Siendo el resultado de la ejecución el siguiente (no se muestra toda la salida):

Instalación de un paquete mediante un comando ad-hoc.
 
De este modo, con un solo comando ad-hoc, hemos instalado un paquete en varias máquinas al mismo tiempo y como veremos más adelante, estos comandos podrán pasar a formar parte de ficheros de descripción denominados playbooks, en los que cada tarea o paso realizará una acción en los sistemas objetivo controlados por Ansible.

sábado, 10 de abril de 2021

Servidor DNS con backend OpenLDAP

Hoy continuamos con la serie sobre OpenLDAP y después de la anterior entrada sobre el bastionado básico de la arquitectura, pasamos a añadir una nueva pieza a la solución, concretamente un servidor DNS con backend LDAP.

Al añadir este nuevo elemento, la arquitectura de alto nivel quedaría más o menos así:

Relaciones entre los componentes de la solución.

Como podemos ver en el diagrama, el servidor DNS será un nuevo cliente LDAP que empleará el servidor OpenLDAP como backend para la información de zonas DNS. La principal ventaja de utilizar esta solución es que no necesitamos un servidor DNS que replique sus bases de datos, ya que la capacidad de replicación la proporciona directamente el servidor OpenLDAP.

Dentro de todas las posibles opciones de servidores DNS que puedan emplear un servidor OpenLDAP como backend, vamos a probar PowerDNS. Este servidor DNS dispone de un plugin específico para soportar servidores LDAP como backend y en general está disponible en cualquier distribución Linux. Otra característica adicional que puede ser intersante en ciertos despliegues, es que PowerDNS separa las funciones de servidor DNS autoritativo de las funciones del servidor DNS que realiza consultas recursivas. En el caso quue nos ocupa, usaremos el servidor autoritativo, ya que queremos crear un pequeño dominio DNS de pruebas, que nunca realizará consultas a servidores DNS externos.

En el caso de CentOS 7.8, PowerDNS se encuentra disponible en el repo EPEL, así que tras instalar y habilitar dicho repo, podemos instalar el servidor PowerDNS así como las herramientas y el backend LDAP:

Instalación de PowerDNS en CentOS.

De forma similar, podemos instalar PowerDNS en Debian empleando los comandos de gestión de paquetes de Debian.

Paquetes PowerDNS en Debian.

Al igual que hicimos al integrar Kerberos, es necesario ampliar el esquema de OpenLDAP para poder almacenar las clases de objeto y atributos requeridos por PowerDNS. Los ficheros de esquema correspondientes están incluidos con la instalación, encontrándose en la ruta /usr/shared/doc/pdns-backend-ldap-4.1.14 en el caso de CentOS mientras que, en el caso de Debian, se almacenan directamente en la ruta /etc/ldap/schema de instalación del servidor OpenLDAP:


Ficheros de esquema necesarios en CentOS.

Ficheros de esquema necesarios en Debian.

En el caso de CentOS hay dos ficheros de esquema diferentes, uno extiende el esquema del servidor incluyendo información necesaria para la integración de PowerDNS y el otro define los atributos y objetos necesarios para almacenar infomación de registros y zonas DNS. En el caso de Debian solo existe un fichero, el cual define  los atributos y objetos necesarios para almacenar infomación de registros y zonas DNS. Ya que estamos ampliando el esquema de ambos servidores, crearemos un único fichero de extensión de esquema y lo usaremos en ambos.

Para extender el esquema de un servidor OpenLDAP necesitamos crear una nueva entrada en el árbol de configuración cn=config, para lo cual es necesario que usemos el comando ldapadd para cargar el esquema. Es importante tener en cuenta que los ficheros proporcionados con las instalaciones, tanto en CentOS comno en Debian no están en formato LDIF, con lo que no podemos usar los ficheros directamente con el comando ldapadd. Es necesario corregir dichos ficheros, añadiendo las modificaciones necesarias para pasarlos a formato LDIF y así poder usarlos con los comandos ldapadd y ldapmodify. Tras un poco de vi, los ficheros quedarán más o menos así:

Modificación ficheros de schema PowerDNS.
 
Modificación ficheros schema PowerDNS.

Es muy importante tener en cuenta que, al estar dividido en dos ficheros, es necesario continuar los índices numéricos de cada atributo y clase de objeto para poder hacer la carga de forma correcta en el árbol de configuración dinámica.

Para cargar el primer fichero utilizaremos el comando ldapadd y para el segundo ldapmodify, ya que estaremos modificando una entrada ya existente. Los comandos a utilizar, teniendo en cuenta que ahora los servidores requiren TLS para las conexiones, serán más o menos así:

Carga de schema pdns-domaininfo

Carga de schema dnsdomain2

Podemos comprobar que el esquema se ha ampliado correctamente comprobando que aparece dentro de la rama cn=schema del árbol de configuración dinámica cn=config:

 

Esquema del servidor OpenLDAP.

Una vez que hemos realizado la extensión del esquema de ambos servidores OpenLDAP correctamente, podemos empezar a configurar PDNS. El fichero principal de configuración es /etc/pdns/pdns.conf y en este fichero debemos establecer, al menos, las siguientes variables de configuración:

  • local-address, establece la dirección IP en la que escuchará el servicio PDNS.
  • launch, establece que backend empleará PDNS. En nuestro caso especificaremos ldap.
  • ldap-host, nos permite establecer la URL del servidor LDAP que actuará como backend.
  • ldap-starttls, para indicar que debe usarse StarTLS en las conexiones con el servidor LDAP.
  • ldap-bindmethod, esta opción puede tomar diferentes valores. De momento especificaremos simple para usar un DN específico para la conexión y consultas del servidor LDAP por parte de PDNS.
  • ldap-binddn y ldap-secret, DN y password usada para la conexión y consultas de PDNS con el servidor OpenLDAP.
  • ldap-basedn, DN a partir del cual PDNS realziará la búsqueda de entradas. 
  • ldap-method, esta opción puede tomar varios valores y básicamnte establece como se traslada una query DNS a la estructura de la zona dentro del servidor de directorio. La estableceremos en strict y luego analizaremos más detenidamente que significa.

Es importante resaltar que, al utilizar las bibliotecas LDAP cliente del sistema operativo, el fichero de configuración LDAP (/etc/ldap.conf o /etc/openldap/ldap.conf), debe estar correctamente configurado. Para poder iniciar correctamente la conexión StartTLS es muy importante la opción TLS_CACERT. Esta opción debe establecer la ruta completa al fichero que contenga el certificado de la CA que haya firmado el certificado usado por el servidor OpenLDAP.

El fichero de configuración de PowerDNS, en lo que respecta solamente a la configuración del backend LDAP, quedaría más o menos así:

Configuración PowerDNS para backend LDAP.

Una vez configuradas todas estas opciones, antes de arrancar el servicio PDNS, necesitamos construir la información básica de la zona DNS en el servidor OpenLDAP. Es en este punto donde necesitamos distinguir los diferente valores de la opción de configuración ldap-method, siendo el significado de cada uno de ellos el siguiente:

  • En modo simple podremos crear la estructura del árbol de directorio como deseemos. Cada consulta al servidor DNS se traducirá en una búsqueda del atributo associatedDomain de las entradas de la clase de objeto domainRelatedObject.
  • El modo strict es como el modo simple pero permite que PowerDNS devuelva las resoluciones inversas, las entradas PTR, a partir de las entradas directas, con lo que no es necesario crear entradas de tipo PTR.
  • Por último, el modo tree nos obliga a crear una estructura de directorio que coincida con nuestro dominio, es decir, una entrada A para un host como server1.lab.com se traduciría a un DN que sería dc=server1,dc=lab,dc=com,.... con lo que debemos crear las entradas teniendo en cuenta este modo de operación.

Al configurar la opción ldap-method en modo strict, podemos crear una unidad organizativa, que será la base de la zona o zonas de nuestro servidor DNS, añadiendo el objeto que define la zona y varias entradas de servidores, de una forma tan simple como la siguiente:

Estructura básica de zona DNS.

Con esta información básica ya creada en el servidor OpenLDAP, podemos arrancar el servicio pdns y comprobar que el servidor responde a las queries DNS que realicemos desde clientes.

Es importante tener en cuenta que PowerDNS considera que el servidor, con este tipo de configuración no debe ser configurado como master, ya que el backend es el encargado de realizar la replicación de los objetos de la zona DNS.

Con esto ya tenemos listo un servidor DNS integrado con el resto de la infraestructura basada en OpenLDAP. Los siguientes pasos a seguir, para continuar bastionando el sistema, será comenzar a usar Kerberos para las conexiones entre los diferentes elementos, lo cual nos permitirá eliminar contraseñas de ficheros de configuración, así como seguir aplicando opciones recomendadas de seguridad.