Fase 1
Rocky Linux 9 · ARM64

Base Setup · Hardening RHEL

El punto de partida del lab. Un playbook de Ansible que toma una VM Rocky Linux 9 recién instalada y la deja hardenida, con SELinux enforcing, firewalld activo, SSH sin password y actualizaciones automáticas de seguridad.

01

¿Qué hace esta fase?

Un playbook de Ansible que toma una VM Rocky Linux 9 recién instalada y la deja hardenida. Idempotente: si algo ya está configurado, no lo toca. Si falla a la mitad, se vuelve a correr sin romper nada. Mismo código para el lab local y para Oracle Cloud. Solo cambia el inventario.

¿Por qué Ansible y no bash? Idempotencia. Un script de bash no sabe si algo ya está hecho: lo ejecuta igual y puede romper. Ansible verifica el estado antes de tocar nada. Y el mismo playbook sirve para dos inventarios diferentes.
02

system-base: lo primero que corre

Actualiza el sistema, instala los paquetes base y configura timezone/locale. Nada de magia: cuatro tasks explícitas.

Paquetes instalados

nano
Editar configs sin salir de la VM
curl
Descargar binarios, testear APIs
wget
Lo mismo que curl pero en scripts
git
Clonar el repo del proyecto
net-tools
netstat, ifconfig: lo de siempre
htop
top pero legible
bash-completion
Menos tipeo en la terminal
glibc-langpack-es
Locale es_AR: fechas y mensajes en español

Defaults

roles/system-base/defaults/main.yml
system_base_packages:
    - nano
    - curl
    - wget
    - git
    - net-tools
    - htop
    - bash-completion
    - glibc-langpack-es

system_timezone: America/Argentina/Buenos_Aires
system_locale: es_AR.utf8
Para usar otro timezone o locale, modificar las variables en defaults/main.yml o pasarlas como extra vars: ansible-playbook ... -e "system_timezone=Europe/Madrid". No hace falta tocar las tasks.

Tasks

roles/system-base/tasks/main.yml
- name: Actualizar todos los paquetes del sistema
  ansible.builtin.dnf:
      name: "*"
      state: latest
      update_cache: true

- name: Instalar paquetes base
  ansible.builtin.dnf:
      name: "{{ system_base_packages }}"
      state: present

- name: Configurar timezone
  ansible.builtin.command:
      cmd: timedatectl set-timezone {{ system_timezone }}
  changed_when: false

- name: Configurar locale
  ansible.builtin.command:
      cmd: localectl set-locale LANG={{ system_locale }}
  changed_when: false

Cuatro tareas simples. La primera hace un dnf update completo (equivalente a apt update && apt upgrade en Debian). Las siguientes instalan los paquetes de la lista, configuran la zona horaria y el locale del sistema.

03

Los 6 roles en orden

site.yml ejecuta los roles en secuencia. Cada uno depende del anterior. No se puede saltar.

playbooks/site.yml
---
# Playbook principal — Rocky Linux AIOps Lab

- name: Hardening base del sistema Rocky Linux 9
  hosts: rocky-aiops
  become: true
  roles:
      - system-base
      - firewalld
      - selinux
      - ssh-hardening
      - fail2ban
      - dnf-automatic
1.1
system-base
Actualización completa del sistema, paquetes esenciales, timezone y locale.
1.2
firewalld
Firewall con zonas. Puerto SSH custom (2222), k3s API (6443), HTTP/HTTPS.
1.3
selinux
SELinux en modo enforcing con booleanos para k3s. Nunca se desactiva.
1.4
ssh-hardening
Sin root login, sin password auth, puerto custom, MaxAuthTries limitado.
1.5
fail2ban
Protección contra fuerza bruta en SSH: baneo automático tras 5 intentos.
1.6
dnf-automatic
Actualizaciones automáticas de seguridad: solo parches críticos, sin reinicio.

El orden importa por dependencias. system-base instala paquetes que firewalld necesita. selinux corre después porque algunos booleanos requieren puertos ya abiertos. Y ssh-hardening va último para no lockearte fuera mientras el firewall se configura.

04

Ejecutar el Playbook

Dos modos de ejecución: dentro de la VM (lab local) o desde la terminal apuntando a la VM por SSH. El resultado es el mismo.

Correr dentro de la VM

Clonar el repo y ejecutar ansible-playbook adentro de la VM. Con ansible_connection: local no hace falta SSH.

Rocky Linux VM
$ git clone https://github.com/agusm2002/rocky-linux-aiops-lab.git
Cloning into 'rocky-linux-aiops-lab'...
$ cd rocky-linux-aiops-lab/ansible
$ ansible-playbook playbooks/site.yml
PLAY [Hardening base del sistema Rocky Linux 9] ***
ok: [rocky-aiops] Actualizar todos los paquetes
changed: [rocky-aiops] Instalar paquetes base
ok: [rocky-aiops] Configurar timezone
ok: [rocky-aiops] firewalld activo y habilitado
ok: [rocky-aiops] SELinux enforcing
changed: [rocky-aiops] Cambiar puerto SSH
PLAY RECAP ************************************
rocky-aiops : ok=12 changed=4 unreachable=0 failed=0

Correr desde la Mac (SSH)

Descomentar las líneas de SSH en el inventario y apuntar a la VM.

inventory/hosts.yml (modo SSH)
rocky-aiops:
    # ansible_host: localhost
    # ansible_connection: local
    ansible_host: 192.168.64.10
    ansible_port: 2222
    ansible_user: agustinm
    ansible_ssh_private_key_file: ~/.ssh/id_ed25519
    ansible_python_interpreter: /usr/bin/python3
Al ejecutar desde la máquina local por SSH, verificar que la VM tenga el puerto 2222 accesible y que la key SSH esté copiada en la VM antes de correr ssh-hardening. Ese rol deshabilita password auth. Sin key configurada, el acceso SSH se pierde.
05

Estructura de Ansible

Cada pieza tiene un propósito claro.

ansible.cfg
Configuración global
Define inventario, roles_path, deshabilita host key checking y activa pipelining para velocidad.
inventory/hosts.yml
Inventario local
Define rocky-aiops con ansible_connection: local para ejecutar los playbooks dentro de la VM.
inventory/oracle.yml
Inventario OCI
Define vm-bastion y vm-k3s con conexión SSH via ProxyCommand a través del bastion.
playbooks/site.yml
Playbook principal
Ejecuta los 6 roles de hardening en orden. Segundo play opcional para vault-agent.
roles/system-base/
Rol base
tasks/main.yml + defaults/main.yml: actualización, paquetes, timezone y locale.
06

Chequear que todo quedó bien

Después del playbook, un par de comandos confirman que cada capa de hardening está activa.

verify-base.sh
# SELinux debe estar Enforcing
$ getenforce
Enforcing
# firewalld debe estar corriendo
$ systemctl is-active firewalld
active
# SSH debe escuchar en 2222, no en 22
$ ss -tlnp | grep sshd
LISTEN 0 128 *:2222 *:* users:(("sshd",pid=891,fd=3))
# Root login debe estar deshabilitado
$ grep PermitRootLogin /etc/ssh/sshd_config
PermitRootLogin no
# fail2ban debe estar protegiendo SSH
$ fail2ban-client status sshd
Status for the jail 'sshd'
|- Filter: Currently 0 banned

Siguiente: SELinux

Con el hardening base aplicado, se profundiza en SELinux: la diferencia más grande entre RHEL y Ubuntu. Por qué está enforcing, qué son los booleanos y cómo convive con k3s.

SELinux en Detalle