Configurar servidores manualmente produce diferencias difíciles de detectar: paquetes, usuarios, permisos y parámetros de SSH pueden terminar distintos en cada nodo. Ansible reduce esa deriva describiendo el estado esperado en playbooks y aplicándolo por SSH, sin instalar un agente de Ansible en los servidores administrados.

El objetivo de esta guía es crear una línea base pequeña para Debian y Ubuntu, probarla en un servidor canario y extenderla gradualmente al resto de la flota.

Cuándo conviene aplicarlo

Este enfoque resulta útil para:

  • Flotas pequeñas o medianas.
  • Provisionamiento inicial.
  • Estandarización de seguridad básica.
  • Incorporación repetible de servidores.
  • Auditorías periódicas de configuración.

Cómo funciona Ansible

El flujo conecta cinco elementos:

  1. Inventario: define servidores, grupos y variables de conexión.
  2. Playbook: declara el estado esperado.
  3. Control node: ejecuta Ansible.
  4. SSH: transporta módulos y órdenes hacia los nodos.
  5. Verificación: confirma que todos alcanzaron el mismo estado.

Ansible normalmente no se instala en los nodos gestionados. En sistemas Linux, estos necesitan acceso SSH y un intérprete Python compatible para la mayoría de los módulos. Conceptos fundamentales de Ansible.

Requisitos previos

Necesitas:

  • Un control node con ansible-core o el paquete ansible.
  • Acceso SSH mediante llaves.
  • Un usuario remoto con permisos de sudo.
  • Python disponible en los servidores gestionados.
  • Inventarios separados para staging y producción.
  • Una consola alternativa para cambios que puedan afectar SSH.
  • Los playbooks almacenados en un sistema de control de versiones.

Comprueba la versión y la configuración cargada:

ansible --version
ansible-config dump --only-changed

Estructura inicial

Una estructura sencilla puede ser:

ansible-baseline/
├── inventories/
│   ├── staging/
│   │   └── hosts.ini
│   └── production/
│       └── hosts.ini
├── group_vars/
│   └── all.yml
└── hardening.yml

Separar staging y producción disminuye el riesgo de seleccionar servidores del entorno equivocado. Ansible también recomienda organizar los inventarios por función y entorno. Consejos de inventario.

Paso 1: crear el inventario

En inventories/staging/hosts.ini:

[web]
web1 ansible_host=10.0.0.11
web2 ansible_host=10.0.0.12

[web:vars]
ansible_user=admin

Los nombres web1 y web2 son alias de inventario. ansible_host contiene la dirección utilizada para la conexión.

Valida cómo Ansible interpreta el archivo:

ansible-inventory \
  -i inventories/staging/hosts.ini \
  --graph

También puedes inspeccionar un host concreto:

ansible-inventory \
  -i inventories/staging/hosts.ini \
  --host web1

No almacenes contraseñas ni claves privadas dentro del inventario.

Paso 2: comprobar SSH y privilegios

Prueba primero la conexión:

ansible web \
  -i inventories/staging/hosts.ini \
  -m ansible.builtin.ping

ansible.builtin.ping no envía paquetes ICMP. Comprueba que Ansible puede iniciar sesión, ejecutar Python en el nodo y recibir la respuesta pong. Documentación de ansible.builtin.ping.

Verifica después la elevación de privilegios:

ansible web \
  -i inventories/staging/hosts.ini \
  --become \
  -m ansible.builtin.command \
  -a 'whoami'

La salida debe mostrar root. Si sudo requiere contraseña, agrega --ask-become-pass o -K:

ansible web \
  -i inventories/staging/hosts.ini \
  --become \
  --ask-become-pass \
  -m ansible.builtin.command \
  -a 'whoami'

No guardes ansible_become_password en texto plano.

Paso 3: definir la línea base

Crea group_vars/all.yml:

---
base_packages:
  - ufw
  - fail2ban
  - unattended-upgrades

disable_ssh_password_auth: false

El valor false evita desactivar contraseñas durante el primer despliegue. Cámbialo únicamente cuando hayas probado el acceso mediante llaves en una sesión independiente.

Ahora crea hardening.yml:

---
- name: Aplicar línea base a servidores web
  hosts: web
  become: true
  gather_facts: true
  serial: 1

  pre_tasks:
    - name: Comprobar que el sistema pertenece a la familia Debian
      ansible.builtin.assert:
        that:
          - ansible_facts.os_family == "Debian"
        fail_msg: "Este playbook solo admite Debian y Ubuntu."

  tasks:
    - name: Instalar paquetes base
      ansible.builtin.apt:
        name: "{{ base_packages }}"
        state: present
        update_cache: true
        cache_valid_time: 3600

    - name: Aplicar configuración global de SSH
      ansible.builtin.blockinfile:
        path: /etc/ssh/sshd_config
        insertbefore: BOF
        marker: "# {mark} ANSIBLE MANAGED SSH BASELINE"
        block: |
          PermitRootLogin no
          {% if disable_ssh_password_auth | bool %}
          PasswordAuthentication no
          {% endif %}
        owner: root
        group: root
        mode: "0600"
        backup: true
        validate: "/usr/sbin/sshd -t -f %s"
      notify: Recargar SSH

  handlers:
    - name: Recargar SSH
      ansible.builtin.service:
        name: ssh
        state: reloaded

El playbook incorpora varias medidas importantes:

  • Usa nombres FQCN como ansible.builtin.apt.
  • Limita la ejecución a un servidor por lote con serial: 1.
  • Comprueba la familia del sistema operativo.
  • Conserva un respaldo de sshd_config.
  • Inserta la configuración antes de cualquier bloque Match.
  • Valida el archivo candidato antes de escribirlo.
  • Recarga SSH mediante un handler solo cuando existe un cambio.

blockinfile ejecuta el comando indicado en validate sobre un archivo temporal y solo reemplaza el destino cuando la validación finaliza correctamente. Documentación de blockinfile.

Instalar UFW no significa habilitarlo ni configurar sus reglas. La apertura del puerto SSH debe definirse y verificarse antes de activar una política restrictiva. Conviene gestionar el firewall en un playbook o rol independiente.

Paso 4: validar el playbook

Comprueba primero la sintaxis YAML y la resolución de módulos:

ansible-playbook \
  -i inventories/staging/hosts.ini \
  hardening.yml \
  --syntax-check

Revisa qué servidores seleccionará:

ansible-playbook \
  -i inventories/staging/hosts.ini \
  hardening.yml \
  --list-hosts

También puedes listar las tareas:

ansible-playbook \
  -i inventories/staging/hosts.ini \
  hardening.yml \
  --list-tasks

Estas comprobaciones no se conectan a los nodos ni modifican su configuración.

Paso 5: ejecutar check mode en un servidor

Simula el despliegue sobre web1:

ansible-playbook \
  -i inventories/staging/hosts.ini \
  hardening.yml \
  --check \
  --diff \
  --limit web1

El modo check intenta predecir cambios sin aplicarlos. No es una garantía absoluta: los módulos sin soporte para check mode pueden omitir tareas o no producir resultados completos. Check mode y diff mode.

--diff puede mostrar contenido sensible. No lo uses indiscriminadamente sobre plantillas, certificados o archivos que contengan contraseñas y tokens.

Paso 6: aplicar en el servidor canario

Mantén abierta una sesión SSH y asegúrate de contar con acceso a la consola del proveedor. Después ejecuta:

ansible-playbook \
  -i inventories/staging/hosts.ini \
  hardening.yml \
  --limit web1

El módulo validará la versión candidata de sshd_config mediante /usr/sbin/sshd -t -f %s. Si la validación falla, la tarea se detiene y el handler no recarga SSH.

La documentación de Ubuntu también recomienda ejecutar sshd -t antes de reiniciar o recargar el servicio, porque un error podría impedir nuevos accesos. Configuración segura de OpenSSH.

Paso 7: verificar el servidor canario

Sin cerrar la sesión existente, abre una conexión SSH nueva hacia web1. Luego comprueba:

ansible web1 \
  -i inventories/staging/hosts.ini \
  --become \
  -m ansible.builtin.command \
  -a '/usr/sbin/sshd -t'

Consulta la configuración efectiva:

ansible web1 \
  -i inventories/staging/hosts.ini \
  --become \
  -m ansible.builtin.command \
  -a '/usr/sbin/sshd -T'

En la salida debe aparecer:

permitrootlogin no

Si disable_ssh_password_auth está activado, también debe aparecer:

passwordauthentication no

Vuelve a ejecutar el playbook sobre el mismo nodo:

ansible-playbook \
  -i inventories/staging/hosts.ini \
  hardening.yml \
  --limit web1

Una línea base idempotente debería finalizar sin cambios adicionales, salvo tareas cuya naturaleza siempre reporte modificaciones.

Paso 8: extender el cambio a la flota

Cuando el servidor canario haya superado las pruebas:

ansible-playbook \
  -i inventories/staging/hosts.ini \
  hardening.yml

serial: 1 procesa un nodo completo antes de continuar con el siguiente. En flotas mayores puede utilizarse un número o porcentaje distinto. Ejecución por lotes con serial.

Repite el ciclo completo en producción:

sintaxis → hosts → check → canario → verificación → flota

Desactivar contraseñas SSH de forma segura

Antes de establecer:

disable_ssh_password_auth: true

confirma que:

  • La llave pública correcta está en authorized_keys.
  • El usuario puede iniciar una sesión nueva.
  • sudo funciona con ese usuario.
  • Existe acceso a consola fuera de SSH.
  • Ninguna automatización depende de una contraseña SSH.

No uses el cierre de la sesión actual como prueba: una conexión existente puede continuar funcionando aunque las nuevas autenticaciones fallen.

Protección de secretos

Ansible Vault permite cifrar variables y archivos almacenados junto al proyecto:

ansible-vault create group_vars/production/vault.yml

Vault protege los datos almacenados, pero no evita que una tarea los muestre durante la ejecución. Usa no_log: true en operaciones sensibles y evita --diff cuando el contenido pueda incluir secretos. Guía de Ansible Vault.

No almacenes dentro del repositorio:

  • Claves SSH privadas.
  • Contraseñas sin cifrar.
  • Tokens de API.
  • Contraseñas de Vault.
  • Copias de configuraciones que contengan secretos.

Ansible y su flujo

Problemas frecuentes

Permission denied

Comprueba:

  • ansible_user.
  • Dirección ansible_host.
  • Llave privada seleccionada.
  • Permisos de la llave local.
  • Presencia de la llave pública en authorized_keys.
  • Reglas de firewall y puerto SSH.

Para obtener más detalles:

ansible web1 \
  -i inventories/staging/hosts.ini \
  -m ansible.builtin.ping \
  -vvvv

sudo queda esperando una contraseña

Usa --ask-become-pass o configura una política de sudo restringida para la automatización. No deshabilites globalmente los controles de sudo solo para evitar el prompt.

ping devuelve un error de Python

ansible.builtin.ping necesita Python en el nodo remoto. En una imagen mínima puede ser necesario instalarlo primero mediante ansible.builtin.raw, que no depende de Python.

SSH no se recarga

Ejecuta en la sesión todavía abierta:

sudo /usr/sbin/sshd -t
sudo systemctl status ssh --no-pager
sudo journalctl -u ssh.service -n 100 --no-pager

No reinicies el servidor hasta corregir el archivo.

Check mode no muestra una tarea

Confirma que el módulo admita check mode. La simulación no ejecuta necesariamente módulos sin soporte ni reproduce dependencias basadas en resultados de tareas anteriores.

Buenas prácticas para producción

  • Usa un servidor canario y despliegues por lotes.
  • Versiona playbooks, inventarios no sensibles y variables.
  • Separa roles de paquetes, SSH, firewall y actualizaciones.
  • Utiliza nombres completos de módulos.
  • Asigna nombres descriptivos a plays y tareas.
  • Ejecuta --syntax-check y --list-hosts antes de --check.
  • Limita el objetivo con --limit en cambios riesgosos.
  • Protege secretos con Vault y no_log.
  • Revisa el resumen changed, failed y unreachable.
  • Ejecuta periódicamente el playbook para detectar y corregir deriva.

Una línea base útil debe ser pequeña, idempotente y comprobable. La automatización aporta consistencia, pero la seguridad depende de limitar el alcance, validar las configuraciones candidatas y conservar una ruta de recuperación.

Cloud Serversby Donweb

Todo el poder de la nube a tus proyectos y aplicaciones.
Alojamiento ultra rápido, escalable y con alta disponibilidad.

  • Performance que te sorprenderá
  • Rápida escalabilidad y sin limitaciones
  • Arquitectura de alta disponibilidad
  • Soporte experto y ejecutivos de cuenta
  • Pagos en tu moneda y facturación local

Descubre la mejor solución de Cloud Hosting