Saltar a contenido

Proteger SSH

SSH es la puerta de entrada principal a un servidor remoto. Por defecto, muchas imágenes cloud vienen con acceso root habilitado y autenticación por contraseña — dos vectores de ataque que conviene cerrar cuanto antes.

Enfoque por niveles

Esta guía construye la configuración de SSH por niveles: cada uno añade unas pocas líneas al mismo fichero y sube un escalón de seguridad. Puedes parar en el nivel que quieras y retomarlo más tarde — o saltarte los que no te aporten.

Todo se configura en un único fichero aparte (00-hardening.conf), sin tocar sshd_config principal. Así se mantienen las actualizaciones del sistema limpias y la config propia, separada.

Nivel Qué añade ¿Aplicarlo?
1 Cerrar root y limitar usuarios Siempre, desde el primer acceso
2 Clave pública y límites de login Obligatorio en producción (al desplegar tu clave)
3 Banner legal Opcional
4 Desactivar funcionalidades Recomendado; omite lo que necesites

Nivel 1 — Cerrar root

Aplícalo siempre, desde el primer acceso al servidor: nadie entra como root y solo el usuario hosting puede conectar.

  1. Crear el drop-in de hardening:

    sudo vim /etc/ssh/sshd_config.d/00-hardening.conf
    

    Contenido:

    PermitRootLogin no
    PasswordAuthentication yes
    AllowUsers hosting
    

     

    Puerto 22 se mantiene — cambiarlo no aporta seguridad real y es trivialmente detectable.

    PasswordAuthentication yes es el punto de partida, para no perder el acceso al servidor. En cuanto tengas tu clave pública desplegada, aplica el Nivel 2 para desactivarla.

    Por qué 00- y no 99-

    En sshd_config.d/ los ficheros de Include se leen en orden alfabético y, a diferencia de sysctl.d/ o limits.d/, gana el primer valor encontrado, no el último. Muchas imágenes cloud (DigitalOcean, Hetzner...) traen un 50-cloud-init.conf con PasswordAuthentication yes explícito — si tu fichero se llamara 99-hardening.conf se leería después y tu no se ignoraría en silencio (sshd -T seguiría diciendo yes). Llamándolo 00- te aseguras de que se lee el primero y gana siempre.

  2. Validar la sintaxis antes de recargar:

    sudo sshd -t
    
  3. Aplicar cambios (no corta la sesión actual, a diferencia de restart):

    sudo service sshd reload
    
  4. Antes de cerrar la sesión actual, abrir una terminal nueva y confirmar que entras con hosting:

    ssh hosting@<ip-servidor>
    

Nivel 2 — Clave pública

Obligatorio en producción. Desactiva las contraseñas y deja solo la clave pública, con límites en el proceso de login. Solo puedes saltarlo mientras sigas conectando con PasswordAuthentication yes del Nivel 1.

  1. En tu máquina local (no en el servidor), generar el par de claves si todavía no tienes una (si ya tienes ~/.ssh/id_ed25519 de otro proyecto, puedes reutilizarla y saltar al paso 2):

    ssh-keygen -t ed25519 -C "hosting@<nombre-servidor>"
    

    Acepta la ruta por defecto (~/.ssh/id_ed25519) pulsando Enter y pon una passphrase (recomendado; te la pedirá el agente SSH, no en cada conexión, una vez lo configures).

  2. En tu máquina local, copiar la clave pública al servidor (del Nivel 1, hosting ya puede conectar con contraseña):

    ssh-copy-id hosting@<ip-servidor>
    

    Si ssh-copy-id no está disponible (por ejemplo en Windows sin WSL), hazlo a mano:

    ssh hosting@<ip-servidor> \
     'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys' \
     < ~/.ssh/id_ed25519.pub
    
  3. En tu máquina local, confirmar que entras con la clave (sin que pida contraseña):

    ssh -o PasswordAuthentication=no hosting@<ip-servidor>
    

    Si esto falla, no sigas con el resto de este nivel — revisa la copia de la clave antes de desactivar PasswordAuthentication en el servidor o te quedas fuera.

  4. En el servidor, crear el grupo dedicado y añadir a hosting:

    sudo groupadd sshusers
    sudo usermod -aG sshusers hosting
    
  5. Añadir al drop-in ya creado:

    sudo vim /etc/ssh/sshd_config.d/00-hardening.conf
    

    Añadir las líneas:

    # Nivel 2 - clave pública
    AllowGroups sshusers
    ClientAliveCountMax 2
    ClientAliveInterval 300
    LoginGraceTime 30
    MaxAuthTries 3
    PasswordAuthentication no
    PubkeyAuthentication yes
    

     

    Cambia el PasswordAuthentication yes del Nivel 1 por el no anterior — no añadas un segundo PasswordAuthentication: dentro de un mismo fichero gana el primer valor encontrado.

  6. Validar la sintaxis antes de recargar (evita quedarte fuera si hay un error):

    sudo sshd -t
    
  7. Recargar (no corta las sesiones activas, a diferencia de restart):

    sudo service sshd reload
    
  8. Antes de cerrar la sesión actual, abrir una terminal nueva y confirmar que entras con clave, sin contraseña:

    ssh hosting@<ip-servidor>
    

Opcional. Aplícalo si quieres que quien se conecta lea un aviso legal antes de que se le pida la contraseña — tiene valor disuasorio frente a bots e intrusos y utilidad legal para una eventual reclamación. Sáltalo si no te aporta nada.

  1. Reemplazar el fichero por defecto y crear el banner:

    sudo rm /etc/issue.net
    sudo vim /etc/issue.net
    

    Contenido recomendado (aviso bilingüe, en texto plano):

    AVISO LEGAL / LEGAL NOTICE
    
    Este servidor es privado. Solo pueden acceder usuarios autorizados.
    Todo acceso queda monitorizado y registrado.
    
    This server is private. Only authorized users may connect.
    All access is monitored and logged.
    

     

    En muchas imágenes cloud /etc/issue.net es un symlink a /usr/lib/issue.net — el contenido lo trae la imagen. El rm lo elimina para crear un fichero propio.

    Escribe el contenido en texto plano: los códigos de escape que sí soporta el banner (\d fecha, \s sistema, \l tty) filtran información del servidor y restan efecto al aviso.

  2. Añadir la directiva al drop-in ya creado:

    sudo vim /etc/ssh/sshd_config.d/00-hardening.conf
    

    Añadir la línea:

    # Nivel 3 - banner legal
    Banner /etc/issue.net
    
  3. Validar la sintaxis antes de recargar:

    sudo sshd -t
    
  4. Recargar (no corta las sesiones activas):

    sudo service sshd reload
    

Banner no es MOTD

El banner se muestra antes de autenticarse, como primer aviso. No debe confundirse con el MOTD, que sale después del login y suele mostrar información del sistema.

Nivel 4 — Desactivar funcionalidades

Recomendado. El demonio ofrece funcionalidades que casi nadie usa y que amplían la superficie de ataque: reenvío de X, túneles TCP, sesiones ilimitadas... MaxStartups limita las conexiones simultáneas sin autenticar (anti-fuerza bruta), UseDNS no evita resolver la IP del cliente y LogLevel VERBOSE registra la huella de la clave usada para auditoría.

Omitir líneas concretas si las necesitas — el resto del bloque sigue aportando.

  1. Añadir las directivas al drop-in ya creado:

    sudo vim /etc/ssh/sshd_config.d/00-hardening.conf
    
    # Nivel 4 - desactivar funcionalidades
    AllowTcpForwarding no
    PermitEmptyPasswords no
    StrictModes yes
    X11Forwarding no
    
    # Rendimiento y auditoría
    LogLevel VERBOSE
    MaxSessions 10
    MaxStartups 10:30:60
    UseDNS no
    

    AllowTcpForwarding

    AllowTcpForwarding no corta también el port-forwarding y los túneles SOCKS. Si usas SSH para acceder a tu red local o crear túneles, no incluyas esta línea en tu configuración.

     

    StrictModes yes y PermitEmptyPasswords no ya son el comportamiento por defecto de OpenSSH; se ponen de forma explícita para que la config se autodocumente y no dependa de los valores por defecto de cada versión.

  2. Validar la sintaxis antes de recargar:

    sudo sshd -t
    
  3. Recargar (no corta las sesiones activas):

    sudo service sshd reload
    

Por qué no se tocan aquí los algoritmos criptográficos

Limitar KexAlgorithms, Ciphers o MACs depende de la versión de OpenSSH instalada y puede dejarte fuera del servidor. Aplícalo solo tras comprobar con sshd -T que tu cliente sigue siendo compatible — merece un apartado propio.

Referencia: configuración completa

Este es el estado del fichero al completar los cuatro niveles. Si has omitido algún nivel, deja fuera también su bloque.

# Nivel 1 - cerrar root
PermitRootLogin no
AllowUsers hosting

# Nivel 2 - clave pública
AllowGroups sshusers
ClientAliveCountMax 2
ClientAliveInterval 300
LoginGraceTime 30
MaxAuthTries 3
PasswordAuthentication no
PubkeyAuthentication yes

# Nivel 3 - banner legal
Banner /etc/issue.net

# Nivel 4 - desactivar funcionalidades
AllowTcpForwarding no
LogLevel VERBOSE
MaxSessions 10
MaxStartups 10:30:60
PermitEmptyPasswords no
StrictModes yes
UseDNS no
X11Forwarding no

Verificar

sudo sshd -T | grep passwordauth
sudo sshd -T | grep permitroot
sudo sshd -T | grep banner
sudo sshd -T | grep -E 'allowtcpforwarding|x11forwarding|maxsessions|maxstartups'

Con todos los niveles aplicados debe salir passwordauthentication no, permitrootlogin no, banner /etc/issue.net, allowtcpforwarding no, x11forwarding no, maxsessions 10 y maxstartups 10:30:60. Si has omitido algún nivel, solo faltarán sus líneas. Para comprobar el banner en vivo, conéctate y confirma que el aviso aparece antes de pedir la contraseña:

ssh hosting@<ip-servidor>

Y ahora qué

SSH está protegido. El siguiente paso es configurar el Firewall para abrir solo los puertos necesarios.