sábado, 20 de enero de 2024

Ansible - Autenticación, conexión y escalado de privilegios

En la entrada de hoy vamos a tratar los tipos o métodos de conexión que soporta Ansible, así como la autenticación y escalado de privilegios. Por defecto, Ansible siempre trabaja con OpenSSH y, si por algún motivo, nuestra máquina de control dispone de una versión antigua de OpenSSH que no soporta la característica ControlPersist, entonces empleará una implementación Python de OpenSSH llamada paramiko.

De forma muy sencilla, la característica ControlPersist de OpenssH está disponible desde la versión 3.9 y permite el multiplexado de conexiones. Mediante esta característica, varias sesiones SSH pueden compartir la misma conexión física lo cual, para entornos en los que estemos usando Ansible, presenta como principal ventaja, que se elimina la sobrecarga de crear nuevas conexiones y el negociado de las características de la conexión segura con lo que, en resumen, se consigue una mejora de rendimiento.

Ya vimos en el post anterior que, por defecto, Ansible usará siempre el usuario con el que estamos lanzando el comando o playbook, para establecer la conexión remota desde nuestra máquina de control. Podemos cambiar este comportamiento usando la opción de configuración remote_user, dentro de la sección defaults del fichero de configuración general de Ansible, o bien usando la variable ansible_user dentro del inventario, ya sea para un host específico a para un grupo de ellos.

Por tanto, y como regla general, es buena idea que especifiquemos el usuario de conexión en nuestro fichero de configuración, de una forma similar a la siguiente:

Fichero de configuración básico.
De esta manera nos aseguramos que siempre usaremos dicho usuario, para establecer conexión con los hosts que forman parte de nuestro inventario.

Ahora bien, aunque el método recomendado por Ansible es el uso de claves SSH, lo cual veremos en un momento, hasta ahora hemos estado conectándonos usando la passsword del usuario remoto. ¿Como le decimos a Ansible que debe solicitar la password del mismo? mediante las dos opciones que aparecen en todos los comandos ejecutados hasta ahora, que son -k y -K, las cuales, revisando la ayuda del comando ansible, se corresponden con la password de conexión y la password de escalado de privilegios:
 
-K, --ask-become-pass           ask for privilege escalation password
-k, --ask-pass        ask for connection password

Como es lógico, la password de conexión, es la que Ansible emplea cuando establece la conexión SSH con el host remoto y evidentemente, recibiremos un mensaje de error al intentar conectar con la password incorrecta.

Contraseña SSH incorrecta.

Si lanzamos manualmente comandos ad-hoc o playbooks, puede que resulte más seguro que, si no estamos empleando claves SSH, escribamos la password para realizar la conexión. Pero, como es lógico, esto impediría la ejecución de tareas desatendidas como, por ejemplo, aquellas que tengamos programadas en cron.

Una opción, que tiene importantes implicaciones de seguridad, es que fijemos la password como variable en el fichero de inventario. Por ejemplo, podemos hacer algo como lo siguiente:

Password SSH como variable.

Con esta configuración, ya podemos ejecutar comandos ad-hoc o lanzar playbooks sin necesidad de introducir la contraseña SSH cuando se solicite y, por tanto, podemos lanzar los comandos sin emplear la opción -k:

Ejecución sin password SSH.
 
Como vemos en la ejecución anterior, ahora Ansible solo solicita la password para el escalado de privilegios, usando para la conexión la especificada en el inventario.
 
Si optamos por no utilizar claves SSH, esta será la opción más cómoda, si no queremos introducir la password en cada ejecución, y totalmente requerida para el caso de tareas programadas. Aunque ya veremos más adelante como proteger esta información, es fundamental asegurar que, el fichero de inventario donde especifiquemos la password de conexión SSH, sea accesible solo por el usuario o usuarios requeridos.

Tras la conexión SSH, esta claro que queremos realizar alguna tarea en el host o hosts remotos. Esta tarea, por lo general, requerirá permisos de otro usuario para su ejecución. Como es lógico, si hablamos de tareas de administración, lo normal es que usemos el usuario root o un usuario con privilegios de administración para la ejecución de dichas tareas. En este punto es donde entra el escalado de privilegios, el cual, siguiendo la terminología utilizada por Ansible, se denomina become.

El uso del escalado de privilegios se puede configurar, de manera global, en el fichero ansible.cfg o al nivel de playbook o tarea. De forma general, podemos tener lo siguiente en el fichero de configuración ansible.cfg:

Configuración de become general.

Con la configuración definida dentro de la sección privilege_escalation, fijamos el comportamiento de ansible de forma general para todos los casos. En esta configuración estamos fijando lo siguiente:
  • become = true: Siempre se utilizará become, es decir, se realizará un cambio de usuario para el escalado de privilegios.
  • become_method = sudo: Especificamos que usaremos sudo para realizar el escalado de privilegios. Esto implica que sudo debe estar correctamente configurado en todas las máquinas remotas.
  • become_user = root: El usuario que se empleará para la ejecución de las tareas, es decir, el usuario que dispone de los permisos requeridos para realizar las tareas deseadas.

Es muy importante tener en cuenta que, si become es false, da igual el resto de opciones presentes de become, ya que no se hará el escalado de privilegios. Por ejemplo:

Fallo en la ejecución de la tarea remota. No se ha utilizado become.

Como se ve en la imagen anterior, aunque utilicemos la opción -K, para proprocionar la password para become, no se realiza el cambio al usuario root por la configuración que hemos fijado.

De la misma manera que hemos hecho con la password para la conexión SSH, también podemos especificar la password para el escalado de privilegios como una variable en el inventario:

Password para sudo como variable.
Ejecución sin especificar la password de sudo.
 
Con esta configuración, cualquier tarea programada funcionaría sin problemas ya que no sería necesaria intervención para introducir la clave. Pero, de nuevo, es muy importante controlar el acceso al fichero o ficheros de inventario que contengan las passwords necesarias.
 
Como ya hemos dicho al principio del post, Ansible recomienda el uso de claves SSH para establecer las conexiones con los hosts remotos. Como es evidente, esto pasa por crear el fichero authorized_keys correspondiente en el home del usuario remoto si este no existe. Una forma sencilla de copiar la clave pública de nuestra máquina de control, sería algo como lo siguiente:

Creando el fichero authorized_keys.

Con el comando ad-hoc anterior, copiamos la clave pública RSA del usuario que empleamos para conectarnos a los hosts remotos, usando el módulo copy de Ansible. Una vez hecho esto, podemos eliminar la variable ansible_password de nuestro inventario y lanzar comandos ad-hoc o ejecutar los playbooks sin necesidad de usar o especificar la password del usuario remoto:

Ejecución de comandos ad-hoc empleando claves SSH.
 
Como es lógico, el uso de claves SSH implica que, cuando creemos la plantilla o plantillas de nuestras máquinas, ya sean estas físicas o virtuales, sería recomendable que ya incluyeran el fichero authorized_keys necesario.

Para que nuestro entorno sea lo más seguro posible, lo recomendado para el caso de los ficheros que contienen nuestras claves SSH es:
  • Asegurar que los permisos de acceso a los ficheros son los correctos. En general, los permisos de los ficheros de clave SSH debería ser siempre 600 y el home del usuario empelado par ala conexión, debería tener permisos 700.
  • La clave SSH debería estar encriptada.

Respecto al último punto, si nuestra clave SSH esta encriptada, nos pasará algo como lo siguiente:

 

Usando una clave SSH encriptada.

Para evitar que nos pida la clave de desencriptación para cada conexión, podemos usar ssh-agent, cargar la clave y podremos trabajar de nuevo sin necesidad de introducir la contraseña necesaria:

Usando ssh-agent para cargar las claves encriptadas.
 
Este método es el recomendado por Ansible para el uso de claves SSH encriptadas, lo único que necesitamos tener en cuenta es que, si usamos diferentes claves para diferetens entornos o grupos de máquinas, tendremos que añadir la adecuada en cada momento.
 
Como último apunte de este post, es necesario que consideremos las implicaciones de seguridad relacionadas con el uso de Ansible. Es muy importante tener en cuenta que, el nodo principal de Ansible, es capaz de conectarse a toda nuestra infraestructura y realizar tareas administrativas, lo que la convierte en un sistema que debe ser protegido todo lo posible, con lo que, lo minimo recomendable es:
  • Restringir el acceso, tanto remoto como local, al grupo de administradores designados para el uso de Ansible.
  • Mantener el nodo de control actualizado y con todos los parches de seguridad instalados.
  • Configurar syslog para que registre toda la actividad del sistema, principalmente los accesos, tanto remotos como locales. Estos eventos deben enviarse a ficheros de log locales y a un servidor syslog centralizado.
Adicionalmente, en todas las máquinas controladas con Ansible, es muy importante asegurarnos de que todas las conexiones SSH que se realizan, se registran convenientemente en los logs del sistema. Para esto, es necesario que configuremos syslog para registrar los acceso SSH tanto en los ficheros de log locales como en un servidor syslog centralizado.
 
Como seguramente emplearemos sudo, la configuración de los sistemas controlados por Ansible que deberíamos plantearnos, debería cumplir al menos lo siguiente:
  • Emplear un usuario con permisos de administración diferente de root. Esto implica crear un usuario en el sistema, adicional al que usaremos para la conexión SSH.
  • Crear las reglas de sudo necesarias para dicho ususario, evitando usar una regla ALL para permitir la ejecución de todos los comandos.
  • Configurar syslog para que registre toda la actividad del sistema, principalmente los accesos, tanto remotos como locales, así como la ejecución de sudo. Estos eventos deben enviarse a ficheros de log locales y a un servidor syslog centralizado.
En próximos posts veremos como poder usar las contraseñas en nuestros ficheros, pero encriptar los mismos y desencriptarlos en el momento de lanzar nuestros comandos o playbooks.