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ón | Significado |
|---|---|
-t | Códigos basados en el tiempo |
-d | Un código no se puede usar dos veces. Lo comprobamos: un segundo acceso con el mismo código falló. |
-f | Escribe el archivo sin preguntar |
-r 3 -R 30 | Como 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 3 | Acepta 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:nullokhace 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:
| Intento | Resultado |
|---|---|
| Clave y código correcto | Acceso concedido. El registro dice Accepted keyboard-interactive/pam for user. |
| Clave y código incorrecto, reutilizado o de hace cinco minutos | Permission denied (keyboard-interactive). |
| Sin clave | Permission denied (publickey). No se pide ningún código y no se acepta ninguna contraseña. |
| Clave y código de emergencia | Acceso 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-authenticatorpara reemplazar el secreto. - La consola del proveedor. No usa SSH. Desde ahí, borra
10-2fa.confy 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.