Supongamos que un cliente le pide ayuda con la migración de script para desplegar un archivo sudoers centralizado en servidores RHEL y AIX.

Bueno, este es un escenario muy común y, con su ejemplo, puede demostrar el uso de las funciones avanzadas de Ansible, así como también cómo cambia el enfoque, desde un script que realiza una tarea determinada, hasta una descripción idempotente (sin hacer cambios) y supervisa el cumplimiento del estado de la instancia.
Toma el guión:
Hay 212 líneas de código, sin control de versión en el archivo sudoers. El cliente ya tiene un proceso que se ejecuta una vez por semana y verifica la suma de comprobación del archivo para garantizar la seguridad. Aunque el script contiene una referencia a Solaris, para este cliente no tuvimos que transferir este requisito.
Comencemos creando un rol y colocando el archivo sudoers en Git para el control de versiones. Entre otras cosas, esto nos permitirá deshacernos de la necesidad de montar volúmenes NFS.
Con las opciones de validación y copia de seguridad para los módulos de
copia y
plantilla , podemos eliminar la necesidad de escribir código para hacer una copia de seguridad y restaurar el archivo. En este caso, la validación se realiza antes de colocar el archivo en el punto de destino, y si la validación falla, el módulo arroja un error.
Para cada rol, necesitamos especificar tareas, plantillas y variables. Aquí está la estructura del archivo correspondiente:

El archivo de script de roles (libro de jugadas),
sudoers.yml , tiene una estructura simple:
---
##
# Role playbook
##
- hosts: all
roles:
- sudoers
...
Las variables de rol se encuentran en el
archivo vars / main.yml . Este archivo contiene el archivo de suma de verificación e incluye / excluye directivas que se utilizarán para crear una lógica especial para omitir los hosts Lawson e incluir el archivo sudoers.d solo en los hosts hd.
Aquí está el contenido del
archivo vars / main.yml :
---
MD5FILE: /root/.sudoer.md5
EXCLUDE: la
INCLUDE: hd
...
Si usamos los
módulos copy y
lineinfile , entonces el rol no será idempotente. El módulo de copia instalará el archivo base, y lineinfile volverá a insertar incluir cada vez que se inicia. Dado que este rol se lanzará en
Ansible Tower , la idempotencia es imprescindible. Convertiremos el archivo a una plantilla jinja2.
En la primera línea, agregamos el siguiente comando para
controlar espacios y sangrías :
#jinja2: lstrip_blocks: True, trim_blocks: True
Tenga en cuenta que las versiones más nuevas del módulo de
plantilla incluyen parámetros para
trim_blocks (agregado en Ansible 2.4).
Aquí está el código que inserta la línea de
inclusión al final del archivo:
{% if ansible_hostname[0:2] == INCLUDE %}
#includedir /etc/sudoers.d
{% endif %}
Usamos la construcción condicional ({% if%}, {% endif%}) para el comando de shell que inserta una línea para hosts cuyos nombres comienzan con los caracteres "hd". Utilizamos hechos de Ansible y un filtro [0: 2] para analizar el nombre de host.
Ahora pasemos a las tareas. Primero, debe establecer un hecho para analizar el nombre de host. Usaremos el hecho "parhost" en la construcción condicional.
---
##
# Parse hostnames to grab 1st 2 characters
##
- name: "Parse hostname's 1st 2 characters"
set_fact: parhost={{ ansible_hostname[0:2] }}
No hay ningún parámetro
csum en el servidor de valores RHEL. Si es necesario, podemos usar otro hecho para indicar condicionalmente el nombre del archivo binario con la suma de verificación. Tenga en cuenta que es posible que se requiera código adicional si estas características son diferentes en AIX, Solaris y Linux.
Además, tenemos que resolver el problema con diferencias en los grupos raíz en AIX y RHEL.
##
# Conditionally set name of checksum binary
##
- name: "set checksum binary"
set_fact:
csbin: "{{ 'cksum' if (ansible_distribution == 'RedHat') else 'csum' }}"
##
# Conditionally set name of root group
##
- name: "set system group"
set_fact:
sysgroup: "{{ 'root' if (ansible_distribution == 'RedHat') else 'sys' }}"
El uso de bloques nos permite establecer la condición para toda la tarea. Utilizaremos la condición al final del bloque para excluir hosts "la".
##
# Enclose in block so we can use parhost to exclude hosts
##
- block:
El módulo de plantilla valida e instala el archivo. Arreglamos el resultado para que podamos determinar si la tarea ha cambiado. El uso del parámetro de validación en este módulo le permite verificar la validez del nuevo archivo sudoer antes de colocarlo en el host.
##
# Validate will prevent bad files, no need to revert
# Jinja2 template will add include line
##
- name: Ensure sudoers file
template:
src: sudoers.j2
dest: /etc/sudoers
owner: root
group: "{{ sysgroup }}"
mode: 0440
backup: yes
validate: /usr/sbin/visudo -cf %s
register: sudochg
Si se ha instalado una nueva plantilla, ejecute el script de shell para generar el archivo de suma de verificación. La construcción condicional actualiza el archivo de suma de verificación al instalar la plantilla sudoers, o si falta el archivo de suma de verificación. Dado que el proceso en ejecución también monitorea otros archivos, utilizamos el código de shell proporcionado en el script de origen:
- name: sudoers checksum
shell: "grep -v '/etc/sudoers' {{ MD5FILE }} > {{ MD5FILE }}.tmp ; {{ csbin }} /etc/sudoers >> {{ MD5FILE }} ; mv {{ MD5FILE }}.tmp {{ MD5FILE }}"
when: sudochg.changed or MD5STAT.exists == false
El módulo de archivo verifica la instalación de los permisos necesarios:
- name: Ensure MD5FILE permissions
file:
path: "{{ MD5FILE }}"
owner: root
group: "{{ sysgroup }}"
mode: 0600
state: file
Dado que el parámetro de copia de seguridad no proporciona ninguna opción para procesar copias de seguridad anteriores, tendremos que encargarnos de crear el código apropiado nosotros mismos. En el siguiente ejemplo, usamos el parámetro "registrarse" y el campo "stdout_lines" para esto.
##
# List and clean up backup files. Retain 3 copies.
##
- name: List /etc/sudoers.*~ files
shell: "ls -t /etc/sudoers*~ |tail -n +4"
register: LIST_SUDOERS
changed_when: false
- name: Cleanup /etc/sudoers.*~ files
file:
path: "{{ item }}"
state: absent
loop: "{{ LIST_SUDOERS.stdout_lines }}"
when: LIST_SUDOERS.stdout_lines != ""
Finalización de bloque:
##
# This conditional restricts what hosts this block runs on
##
when: parhost != EXCLUDE
...
El caso de uso previsto es desempeñar este papel en Ansible Tower. Ansible Tower Alerts se puede configurar para que, en caso de falla en la ejecución del trabajo, las alertas se envíen por correo electrónico, a Slack o de alguna otra manera. Este rol se ejecuta en Ansible, Ansible Engine o Ansible Tower.
Como resultado, eliminamos todo lo superfluo del script y creamos un rol completamente idempotente que puede proporcionar el estado deseado del archivo sudoers. El uso de SCM permite el control de versiones, proporciona una gestión de cambios y transparencia más eficientes. Los CI / CD con Jenkins u otras herramientas le permiten configurar pruebas automáticas de código Ansible para futuros cambios. El rol de auditor en Ansible Tower le permite monitorear y hacer cumplir los requisitos de la organización.
El código para trabajar con sumas de verificación podría eliminarse de la secuencia de comandos, pero para esto el cliente primero debería consultar a su servicio de seguridad. Si es necesario, el patrón de sudoers se puede proteger con Ansible Vault. Finalmente, el uso de grupos evita escribir lógica usando incluye y excluye.
→ Puede descargar el rol desde GitHub en este
enlace