Cómo añadir autenticación de dos factores a SSH con TOTP

Instala libpam-google-authenticator, ejecuta google-authenticator con cada usuario, añade auth required pam_google_authenticator.so a /etc/pam.d/sshd, y define AuthenticationMethods publickey,keyboard-interactive y KbdInteractiveAuthentication yes en la configuración del servidor SSH. Cada acceso necesitará entonces la clave y un código de seis dígitos. Mantén una sesión abierta mientras pruebas, porque un error puede dejarte fuera.

Actualizada el . Ejecutado de principio a fin con OpenSSH 10.0 y libpam-google-authenticator en Debian 13, en un contenedor desechable, con accesos reales y códigos generados con oathtool: códigos correctos, incorrectos, reutilizados y antiguos, códigos de emergencia, usuarios sin archivo de secreto y un modo de archivo incorrecto.

Antes de empezar

Mantén abierta una sesión en el servidor, en una ventana, hasta el final. Comprueba también que tienes una consola de tu proveedor de hosting. Configura primero el acceso con clave: mira cómo generar una clave.

Paso 1: instalar el módulo

sudo apt update
sudo apt install libpam-google-authenticator

A pesar del nombre, no necesita ninguna cuenta de Google y funciona con cualquier app TOTP. El servidor comprueba los códigos por sí mismo.

Paso 2: crear un secreto para cada usuario

Ejecútalo como el usuario que va a entrar, no con sudo:

google-authenticator -t -d -f -r 3 -R 30 -w 3
OpciónSignificado
-tCódigos basados en el tiempo
-dUn código no se puede usar dos veces. Lo comprobamos: un segundo acceso con el mismo código falló.
-fEscribe el archivo sin preguntar
-r 3 -R 30Como máximo 3 accesos cada 30 segundos. Tenlo presente cuando pruebes: los intentos repetidos y rápidos fallan incluso con un código correcto.
-w 3Acepta el código actual y los inmediatamente anterior y posterior, para tolerar una pequeña diferencia de reloj

Escanea el código QR con tu app y escribe un código para confirmar. Si no puedes escanearlo, escribe la clave secreta a mano en la app. La herramienta también imprime un enlace a una página de gráficos de Google y avisa de que abrirlo expone el secreto a Google, así que no lo abras. El comando imprime cinco códigos de emergencia. Guárdalos en un lugar distinto de este servidor y de tu teléfono. Cada uno sirve una vez en lugar de un código.

Esto crea ~/.google_authenticator con modo 400. Déjalo así: en nuestra prueba, con modo 644 los accesos fallaron incluso con un código correcto.

Paso 3: configurar PAM

Edita /etc/pam.d/sshd. Comenta la línea que pide la contraseña y añade dos líneas debajo:

# @include common-auth
auth required pam_google_authenticator.so nullok
auth required pam_permit.so
  • Por qué comentar common-auth: si la dejas, en nuestra prueba el acceso pedía el código y después también la contraseña del sistema.
  • Qué hace nullok: un usuario sin archivo de secreto puede entrar sin código, así que puedes ir usuario por usuario. Quítalo cuando todos estén configurados.
  • Por qué pam_permit.so: nullok hace que el módulo se abstenga, y PAM necesita que al menos un módulo tenga éxito. Sin esta línea, los usuarios sin archivo de secreto fueron rechazados en nuestra prueba. Los usuarios que sí tienen archivo siguen necesitando el código correcto.

Cuando quites nullok, puedes quitar también pam_permit.so. Los sistemas Red Hat usan una primera línea distinta, y el paquete viene de EPEL. Consulta la documentación de tu distribución.

Paso 4: configurar sshd

# /etc/ssh/sshd_config.d/10-2fa.conf
AuthenticationMethods publickey,keyboard-interactive
KbdInteractiveAuthentication yes
UsePAM yes

La coma significa que se exigen ambos, en ese orden. Usa un archivo de esta carpeta porque gana el primer valor leído, y el sshd_config principal de Debian tiene KbdInteractiveAuthentication no más abajo. Los sistemas antiguos llamaban a este ajuste ChallengeResponseAuthentication.

Revisa el archivo antes de recargar:

sudo sshd -t
sudo sshd -T | grep -iE '^(authenticationmethods|kbdinteractiveauthentication|usepam) '
sudo systemctl reload ssh

Si KbdInteractiveAuthentication está desactivado, sshd -t te lo dice, y no debes recargar:

Disabled method "keyboard-interactive" in AuthenticationMethods list "publickey,keyboard-interactive"
AuthenticationMethods cannot be satisfied by enabled authentication methods

Paso 5: probar desde una ventana nueva

ssh user@server
(user@server) Verification code:

Lo que obtuvimos en cada caso:

IntentoResultado
Clave y código correctoAcceso concedido. El registro dice Accepted keyboard-interactive/pam for user.
Clave y código incorrecto, reutilizado o de hace cinco minutosPermission denied (keyboard-interactive).
Sin clavePermission denied (publickey). No se pide ningún código y no se acepta ninguna contraseña.
Clave y código de emergenciaAcceso concedido una vez. Después el código se elimina del archivo y falla si se vuelve a usar.

Si los códigos correctos fallan, revisa primero el reloj. El servidor y el teléfono deben estar de acuerdo con la hora. Ejecuta timedatectl en el servidor y comprueba si el reloj está sincronizado.

Conserva una vía de vuelta

Ten al menos dos de estas antes de cerrar tu última sesión abierta:

  • Tus códigos de emergencia, guardados lejos del teléfono. Si pierdes el teléfono, entra con uno de ellos y vuelve a ejecutar google-authenticator para reemplazar el secreto.
  • La consola del proveedor. No usa SSH. Desde ahí, borra 10-2fa.conf y recarga.
  • Una excepción por dirección, para una red a la que solo tú puedas llegar, como una VPN:
# At the end of 10-2fa.conf
Match Address 10.8.0.0/24
    AuthenticationMethods publickey
  • Una cuenta de emergencia aparte, con una clave guardada sin conexión. Añade el mismo bloque con Match User breakglass. Esto también sirve para una tarea automatizada, si limitas su clave.

Comprueba una excepción con -C, porque un sshd -T a secas no muestra los bloques Match:

sudo sshd -T -C user=user,host=server,addr=10.8.0.5 | grep -i authenticationmethods

Cada excepción es una forma de saltarse el segundo factor, así que mantenlas pocas y acotadas.

Para limitar lo que puede ejecutar una clave de emergencia o de una tarea, consulta cómo limitar una clave SSH a un solo comando.

Escribir menos códigos

Con la conexión compartida, las sesiones nuevas reutilizan una conexión abierta y no vuelven a pedir un código.

Preguntas

¿Afectará a scp, sftp, rsync y a los scripts?

Sí. Todos entran de la misma forma y pedirán un código, así que una tarea sin nadie que lo escriba falla. Exime su cuenta con un bloque Match y limita lo que puede hacer su clave con las opciones de la guía sobre restringir claves.

¿Protege sudo?

No. Esto solo cambia /etc/pam.d/sshd. sudo tiene su propio archivo PAM y no se ve afectado.

Fuentes

Más en Proteger un servidor

Todas las guías