SSH Hardening · Sin Password, Solo Keys
La última capa del hardening base. SSH es el único servicio que acepta conexiones entrantes desde fuera de la VM. Tiene que estar blindado. Sin root login, sin autenticación por contraseña, puerto no estándar y fail2ban para fuerza bruta.
¿Por qué tanto rebuild por SSH?
SSH es el único servicio que escucha fuera de la VM. Si alguien quiere entrar, ese es el camino. Y los bots lo saben. Escaneos automáticos al puerto 22 corren 24/7 probando combinaciones de usuario/contraseña. Así que: sin contraseña que adivinar, sin root al que acceder, y los intentos fallidos se bloquean solos.
Lo que se cambia en sshd_config
Cinco líneas y se acabó. Cada una se valida con sshd -t
antes de tocar el archivo. Si algo queda mal, Ansible revierte
al estado anterior.
PermitRootLogin no default: yes Chau root por SSH. Obliga a entrar como usuario normal y escalar con sudo. Es la que más impacto tiene de las cinco.
PasswordAuthentication no default: yes Solo claves SSH, nada de passwords. No hay contraseña que adivinar: el ataque de fuerza bruta queda descartado de entrada.
Port 2222 default: 22 No es seguridad por oscuridad, es reducir el ruido. Los bots escanean el 22 las 24 horas. Moverlo al 2222 saca los falsos positivos de los logs y baja el tráfico basura.
MaxAuthTries 3 default: 6 3 intentos y se corta la conexión. Con fail2ban, a los 5 fallos se banea la IP entera por una hora. No da para probar combinaciones.
LoginGraceTime 30 default: 120 30 segundos para autenticarte o se cierra la conexión. Contra conexiones colgadas que consumen recursos y ataques de timing.
El rol en Ansible
Modifica sshd_config, registra el puerto 2222 en
SELinux y reinicia el servicio solo si es necesario. Los
handlers de Ansible evitan reinicios sin motivo.
Defaults
ssh_port: 2222
ssh_password_auth: false
ssh_max_auth_tries: 3
ssh_login_grace_time: "30" Tasks
- name: Instalar el servicio SSH
ansible.builtin.dnf:
name: openssh-server
state: present
- name: Deshabilitar login root
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitRootLogin"
line: "PermitRootLogin no"
validate: "sshd -t -f %s"
- name: Configurar autenticación por password
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PasswordAuthentication"
line: "PasswordAuthentication {{ 'yes' if ssh_password_auth else 'no' }}"
validate: "sshd -t -f %s"
- name: Configurar MaxAuthTries
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?MaxAuthTries"
line: "MaxAuthTries {{ ssh_max_auth_tries }}"
validate: "sshd -t -f %s"
- name: Configurar LoginGraceTime
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?LoginGraceTime"
line: "LoginGraceTime {{ ssh_login_grace_time }}"
validate: "sshd -t -f %s"
- name: Cambiar puerto SSH (solo si es diferente de 22)
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?Port "
line: "Port {{ ssh_port }}"
validate: "sshd -t -f %s"
when: ssh_port != 22
notify: Restart sshd
- name: Configurar SELinux para el nuevo puerto SSH
ansible.builtin.command:
cmd: semanage port -a -t ssh_port_t -p tcp {{ ssh_port }}
when:
- ssh_port != 22
- selinux_ssh_port_check.rc != 0
Dos cosas a notar. El validate: "sshd -t -f %s"
prueba la sintaxis antes de guardar. Si algo se rompe, Ansible
revierte. Y la task de SELinux usa semanage port
para que el puerto 2222 quede registrado como ssh_port_t,
que es lo que se configuró en el rol anterior.
Handler
- name: Restart sshd
ansible.builtin.systemd:
name: sshd
state: restarted
Solo se dispara si el puerto cambió (when: ssh_port != 22).
Si ya era 2222 de una ejecución anterior, sshd no se reinicia:
y no se cortan sesiones SSH activas.
Conectarse después
Terminado el hardening, solo hay una forma de entrar: clave SSH en el puerto 2222. Ni password, ni root. Punto.
Para simplificar, se puede agregar al ~/.ssh/config de la
máquina local:
Host rocky-lab
HostName 192.168.64.10
Port 2222
User agustinm
IdentityFile ~/.ssh/id_ed25519
Con esta configuración, conectarse es simplemente
ssh rocky-lab.
~/.ssh/authorized_keys de la
VM. Si el playbook se ejecuta desde fuera de la VM y la key
no está configurada, la conexión SSH se pierde al terminar
el playbook. Sin forma de recuperarla remotamente.
Chequear que anda
Playbook terminado → verificar. Cada directiva tiene su comando.
Cómo juega con fail2ban
SSH corta la conexión a los 3 intentos. fail2ban banea la IP entera tras 5 fallos en 10 minutos. Juntos: un bot no tiene chance.
La configuración de fail2ban se cubre en detalle en la Fase 8
(Seguridad para Producción). Para la Fase 1, el rol de fail2ban
ya está incluido en site.yml y funciona con los
valores por defecto.
Siguiente: Rocky Linux vs Ubuntu
Con el hardening completo aplicado, la última página de la Fase 1 compara las decisiones de seguridad entre Rocky Linux (RHEL) y Ubuntu. El porqué de cada elección y cómo posiciona el proyecto.