Rotaste claves SSH. Todos asintieron. Los tickets se cerraron. Entonces un contratista que “definitivamente perdió el acceso” vuelve a iniciar sesión desde una IP de una cafetería que nunca habías visto.
Así es como falla la rotación de claves en el mundo real: no en la capa criptográfica, sino en la humana—copiar/pegar, deriva, bastiones olvidados y esa caja heredada debajo de un escritorio que “no se puede reiniciar”.
Debian 13 facilita ejecutar una pila OpenSSH sensata. Lo difícil es revocar el acceso limpiamente y demostrarlo. Este expediente es una guía de producción:
cómo inventariar dónde viven las claves, rotar sin tiempo de inactividad, revocar de forma precisa y evitar que la proliferación de claves vuelva a crecer como malas hierbas en un estacionamiento descuidado.
Decida qué entiende por «revocado»
“Rotamos claves SSH” es una frase que puede significar tres cosas diferentes, y solo una de ellas satisface operativamente.
- Reemplazo de clave: emitiste nuevas claves y dijiste a la gente que las use. Las claves antiguas pueden seguir funcionando. Esto no es revocación; es una sugerencia educada.
-
Eliminación de clave: quitaste claves públicas antiguas de
authorized_keys(y de cualquier otro lugar donde pudieran autenticarse). Esto es revocación,
pero solo para las rutas que recuerdas. -
Invalidación de acceso: eliminaste/bloqueaste claves, cerraste sesiones supervivientes cuando procede, auditaste rutas alternativas (bastiones, cuentas compartidas,
reenvío de agente, hosts de salto), y puedes probar que las claves antiguas ya no autentican. Esto es lo que quieres.
En Debian 13, OpenSSH es capaz, aburrido y estricto si se lo permites. Usa eso. Tu enemigo no es la matemática. Tu enemigo es
la proliferación de claves: la replicación gradual e invisible de claves en lugares aleatorios porque la forma más rápida de “arreglar el acceso” es añadir otra línea
a otro authorized_keys.
Un modelo mental útil: trata las claves SSH como credenciales con ciclo de vida, no como “archivos que los desarrolladores guardan en su home”.
Ciclo de vida significa inventario, emisión, rotación, caducidad, revocación y revisión. Si falta cualquiera de esos, no tienes un sistema; tienes folclore.
Una cita que tengo en una nota adhesiva, porque es el trabajo:
La esperanza no es una estrategia.
— Gen. Gordon R. Sullivan
Broma #1: Las claves SSH no “caducan” solas. Son como los aguacates: si no las vigilas, o están inmaduras para siempre o de repente se convierten en un incidente de seguridad.
Hechos interesantes e historia (por qué existe el desorden actual)
Un poco de contexto te ayuda a diagnosticar por qué tu flota parece gestionada por un comité de mapaches.
Aquí hay hechos concretos que suelen importar en la limpieza post-rotación.
- SSH1 vs SSH2: SSH2 reemplazó a SSH1 a principios de los 2000 porque SSH1 tenía debilidades de seguridad. “SSH” hoy prácticamente significa SSH2, pero supuestos heredados permanecen en scripts y appliances antiguos.
-
Las claves DSA fueron desaprobadas:
ssh-dss(DSA) cayó en desuso y está deshabilitado por defecto en OpenSSH moderno por restricciones de algoritmo y política. Si aún ves claves DSA enauthorized_keys, estás viendo capas arqueológicas. - Ed25519 se convirtió en la recomendación por defecto: Las claves Ed25519 son compactas, rápidas y en general preferidas sobre RSA en muchos entornos. Los proyectos de rotación suelen aprovechar para actualizar algoritmos.
-
authorized_keysfue diseñado para la simplicidad: Es solo un archivo con líneas. Esa simplicidad facilita la proliferación de claves:
las herramientas de distribución son opcionales, el copiar/pegar siempre está disponible. - Existen certificados, pero la mayoría de organizaciones no los usa: OpenSSH soporta certificados de usuario firmados por CA, lo que reduce drásticamente la proliferación, pero requieren algo de diseño inicial. Muchos equipos posponen ese diseño indefinidamente.
- La revocación no es simétrica: Con claves públicas crudas, revocas quitándolas en todas partes. Con certificados, puedes revocar por número de serie o ID de clave usando una lista de revocación, pero necesitas la infraestructura de CA.
- El reenvío de agente popularizó “simplemente saltar por el bastión”: También difuminó los límites de credenciales. Cuando la revocación falla, el reenvío de agente suele estar en la lista de sospechosos.
- StrictModes existe por una razón: SSH se niega a usar archivos de clave con permisos inseguros porque intenta protegerte de ti mismo. Cuando la gente “soluciona” errores de permiso desactivando las comprobaciones, suele crear un radio de daño mayor.
- Los registros SSH son útiles pero poco amables: OpenSSH registra lo que pasó, no cómo te sientes al respecto. Las recuperaciones más rápidas vienen de equipos que saben exactamente qué líneas de log mapean a qué rutas de autenticación.
Guía rápida de diagnóstico
Cuando alguien dice “la clave antigua sigue funcionando” o “la clave nueva no funciona”, no empieces a editar archivos a ciegas.
Encuentra el cuello de botella en tres comprobaciones.
1) Identifica dónde se toma la decisión de autenticación
¿El usuario se autentica directamente en el host objetivo, a través de un bastión, mediante un wrapper con forced command, o vía una cuenta de gestión de configuración?
Si erras aquí, “revocarás” en el lugar equivocado y nada cambiará.
2) Confirma qué clave se está ofreciendo realmente
El cliente SSH ofrecerá felizmente un pequeño desfile de claves a menos que le digas lo contrario. La gente a menudo cree que está probando la clave nueva mientras su agente
ofrece una antigua primero y esa tiene éxito.
3) Valida la fuente de claves en el servidor
En Debian 13, las claves pueden venir de ~/.ssh/authorized_keys, pero también de rutas de AuthorizedKeysFile, o de AuthorizedKeysCommand (LDAP/HTTP/lo que sea), o de un authorized_keys de una cuenta compartida que habías olvidado que existía.
Solo después de fijar estas tres cosas empiezas a eliminar líneas, desplegar cambios de configuración y terminar sesiones.
Tareas prácticas (comandos, salidas, decisiones)
A continuación hay tareas de producción que puedes ejecutar en Debian 13 (o en tu estación de administración) que te llevan de “rotamos claves” a “revocamos acceso y lo demostramos”.
Cada tarea incluye: un comando, qué significa una salida típica y la decisión que tomas luego.
Task 1 — Verify OpenSSH server version (know what features you have)
cr0x@server:~$ sshd -V
OpenSSH_9.7p1 Debian-3, OpenSSL 3.3.1 4 Jun 2024
Meaning: You’re on a modern OpenSSH with good defaults and certificate support.
Decision: Prefer Ed25519 keys or OpenSSH user certificates; avoid clinging to legacy algorithms “for compatibility” without proof.
Task 2 — List active SSH listeners (avoid revoking on the wrong daemon)
cr0x@server:~$ ss -ltnp | grep ssh
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=3))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=812,fd=4))
Meaning: sshd is listening on 22 on IPv4 and IPv6.
Decision: If you expected a bastion port (e.g., 2222) or only IPv4, reconcile that now. “Revoked on port 22” won’t help if users connect elsewhere.
Task 3 — Confirm sshd authentication sources (AuthorizedKeysFile / Command)
cr0x@server:~$ sshd -T | egrep 'authorizedkeys(file|command)|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication'
pubkeyauthentication yes
authorizedkeysfile .ssh/authorized_keys .ssh/authorized_keys2
authorizedkeyscommand none
passwordauthentication no
kbdinteractiveauthentication no
Meaning: You’re on a modern OpenSSH with good defaults and certificate support.
Decision: Prefer Ed25519 keys or OpenSSH user certificates; avoid clinging to legacy algorithms “for compatibility” without proof.
Task 4 — Inventory local users with home directories (find where keys can exist)
cr0x@server:~$ getent passwd | awk -F: '$6 ~ /^\/home\// {print $1 "\t" $6}'
alice /home/alice
buildbot /home/buildbot
deploy /home/deploy
Meaning: These users likely have ~/.ssh directories.
Decision: Scope the key audit. Don’t forget service accounts like deploy—they are where “temporary” keys go to live forever.
Task 5 — Find every authorized_keys on the host (including odd paths)
cr0x@server:~$ sudo find / -xdev -type f -name 'authorized_keys' -o -name 'authorized_keys2' 2>/dev/null
/home/alice/.ssh/authorized_keys
/home/deploy/.ssh/authorized_keys
/root/.ssh/authorized_keys
Meaning: Three key entry points exist locally.
Decision: If you revoke a contractor, check all of them. Root is frequently forgotten and frequently abused.
Task 6 — Count keys and spot obvious drift (comments matter)
cr0x@server:~$ sudo awk 'NF && $1 !~ /^#/ {print FILENAME ":" NR ":" $0}' /home/*/.ssh/authorized_keys /root/.ssh/authorized_keys | head -n 6
/home/alice/.ssh/authorized_keys:1:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... alice@laptop
/home/alice/.ssh/authorized_keys:2:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... old-admin
/home/deploy/.ssh/authorized_keys:1:command="/usr/local/bin/deploy-wrap" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... ci@runner
/root/.ssh/authorized_keys:1:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... breakglass@vault
Meaning: You have mixed algorithms and at least one “old-admin” key. You also have a forced command key for deploy.
Decision: Treat any line without a useful comment as suspect and require re-issuance. For deploy keys, verify the forced command is doing what you think.
Task 7 — Fingerprint a known-bad public key (so you can search for it reliably)
cr0x@server:~$ ssh-keygen -lf /tmp/contractor_id_ed25519.pub
256 SHA256:Wm3xNq7nq0M9m+eOQmR0f0sY0p+9QH8Zq4xkYw1o9X8 contractor@mbp (ED25519)
Meaning: This is the stable fingerprint you can grep for when comments lie.
Decision: Use fingerprints as your canonical identifier in tickets and audits. Comments are hints, not identity.
Task 8 — Search for the fingerprint across authorized_keys (find sprawl)
cr0x@server:~$ sudo grep -R --line-number --fixed-strings 'AAAAC3NzaC1lZDI1NTE5AAAAI' /home/*/.ssh/authorized_keys /root/.ssh/authorized_keys
/home/alice/.ssh/authorized_keys:7:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... contractor@mbp
/root/.ssh/authorized_keys:4:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... contractor@mbp
Meaning: The key exists in multiple places (including root).
Decision: Revoke in all locations, and open a follow-up incident: “Why did a contractor key land in root’s authorized_keys?”
Task 9 — Remove the key safely (surgical edit, keep audit trail)
cr0x@server:~$ sudo cp -a /root/.ssh/authorized_keys /root/.ssh/authorized_keys.bak.$(date +%F)
cr0x@server:~$ sudo sed -i '\#contractor@mbp#d' /root/.ssh/authorized_keys
cr0x@server:~$ sudo ssh-keygen -lf /root/.ssh/authorized_keys | head -n 3
256 SHA256:... breakglass@vault (ED25519)
Meaning: You took a backup, removed the contractor line by comment match, and verified remaining entries parse.
Decision: If comments are unreliable, delete by matching the key blob substring instead. Always keep a dated backup for forensics and rollback.
Task 10 — Reload sshd (don’t restart if you can avoid it)
cr0x@server:~$ sudo systemctl reload ssh
cr0x@server:~$ systemctl is-active ssh
active
Meaning: Config and key file changes are effective; sshd stayed up.
Decision: Prefer reload over restart to avoid dropping connections on busy bastions—unless you changed something that needs restart (rare).
Task 11 — Prove the old key is rejected (server-side log evidence)
cr0x@server:~$ sudo journalctl -u ssh -S "10 minutes ago" | tail -n 6
Dec 31 09:12:41 server sshd[22418]: Connection from 203.0.113.44 port 51221 on 10.0.0.10 port 22 rdomain ""
Dec 31 09:12:41 server sshd[22418]: Failed publickey for alice from 203.0.113.44 port 51221 ssh2: ED25519 SHA256:Wm3xNq7nq0M9m+eOQmR0f0sY0p+9QH8Zq4xkYw1o9X8
Dec 31 09:12:43 server sshd[22418]: Connection closed by authenticating user alice 203.0.113.44 port 51221 [preauth]
Meaning: The exact fingerprint failed. That’s your proof.
Decision: Mark the key revoked for this host. Repeat on other hosts, or—better—automate fleet-wide searches so you don’t “trust sampling.”
Task 12 — Confirm which key the client is offering (stop testing the wrong thing)
cr0x@server:~$ ssh -vvv -i ~/.ssh/id_ed25519_new alice@server 2>&1 | egrep 'Offering public key|Server accepts key|Authentications that can continue'
debug1: Offering public key: /home/alice/.ssh/id_ed25519_new ED25519 SHA256:9aZkNf...
debug1: Authentications that can continue: publickey
debug1: Server accepts key: /home/alice/.ssh/id_ed25519_new ED25519 SHA256:9aZkNf...
debug1: Authentication succeeded (publickey).
Meaning: The new key is used and accepted.
Decision: If it still fails, the issue is likely server-side restrictions (command=, from=, principals, permissions) or you’re hitting a different host/path.
Task 13 — List active SSH sessions (revocation doesn’t kill existing shells)
cr0x@server:~$ who
alice pts/1 2025-12-31 09:03 (203.0.113.44)
deploy pts/2 2025-12-31 08:50 (10.0.2.15)
Meaning: Users have active sessions already authenticated.
Decision: If the key was compromised, you likely need to terminate sessions. If it’s routine rotation, you may let sessions drain to avoid disruption.
Task 14 — Terminate a compromised session (safely, with intent)
cr0x@server:~$ ps -ft pts/1
UID PID PPID C STIME TTY TIME CMD
alice 22391 22388 0 09:03 pts/1 00:00:00 -bash
cr0x@server:~$ sudo kill -HUP 22391
cr0x@server:~$ ps -p 22391
PID TTY TIME CMD
Meaning: The shell exited and the session is gone.
Decision: Use targeted kills, not “restart ssh.” Restarting the daemon doesn’t necessarily kill sessions and creates collateral damage.
Task 15 — Validate file permissions (avoid “it works on one host” nonsense)
cr0x@server:~$ sudo namei -l /home/alice/.ssh/authorized_keys
f: /home/alice/.ssh/authorized_keys
drwxr-xr-x root root /
drwxr-xr-x root root home
drwxr-x--- alice alice alice
drwx------ alice alice .ssh
-rw------- alice alice authorized_keys
Meaning: Permissions are strict enough; sshd should accept the file.
Decision: If you see group/world-writable directories or files, fix them instead of disabling StrictModes.
Task 16 — Detect key reuse across accounts (one key, many doors)
cr0x@server:~$ sudo awk 'NF && $1 !~ /^#/ {print $2}' /home/*/.ssh/authorized_keys /root/.ssh/authorized_keys | sort | uniq -c | sort -nr | head
2 AAAAC3NzaC1lZDI1NTE5AAAAI...
1 AAAAB3NzaC1yc2EAAAADAQABAAABAQ...
Meaning: One key blob appears multiple times across the system.
Decision: Ban key reuse across accounts. It destroys attribution and makes revocation harder than it needs to be.
Task 17 — Check for ssh-agent forwarding usage (it changes the threat model)
cr0x@server:~$ sudo grep -R --line-number 'ForwardAgent' /etc/ssh/ssh_config /etc/ssh/ssh_config.d 2>/dev/null
/etc/ssh/ssh_config.d/20-bastion.conf:3: ForwardAgent yes
Meaning: Agent forwarding is enabled for something (likely bastion hops).
Decision: If you’re revoking due to compromise, treat forwarded agent paths as suspect. Consider turning it off and using certificates or ProxyJump patterns.
Task 18 — Validate sshd config before deploying (prevent self-inflicted lockouts)
cr0x@server:~$ sudo sshd -t
cr0x@server:~$ echo $?
0
Meaning: Config syntax is valid.
Decision: Never reload sshd without sshd -t in automation. The only thing worse than key sprawl is locking out the on-call.
Estrategias de revocación que realmente funcionan
Strategy A: Raw public keys in authorized_keys (the default reality)
Este es el mundo en el que vive la mayoría de los equipos: archivos authorized_keys por usuario dispersos en hosts.
La revocación aquí es contundente: elimina la clave en todas partes donde aparezca, incluidas cuentas compartidas y root (si la permites).
El trabajo no es “eliminar una línea”. El trabajo es “cerrar todas las puertas que esa línea abre”.
Si usas claves crudas, aplica estas políticas:
- Una persona → un par de claves por dispositivo (portátil, estación de trabajo, runner de CI). No compartir claves. No «clave del equipo».
- Cada línea de clave debe ser atribuible: el comentario incluye propietario y propósito, y registras la huella en tu IAM o sistema de tickets.
- Cada clave tiene un propietario y una fecha de revisión. Si no puedes nombrar al propietario, no puedes justificar el riesgo.
- No ediciones manuales en hosts críticos: si un host importa, las claves se gestionan por gestión de configuración (o por un comando de clave centralizado).
La parte difícil es la deriva: claves copiadas a lugares no planificados, claves olvidadas tras offboarding y claves
incrustadas en imágenes/AMIs/contenedores. Por eso el inventario debe ser repetible.
Strategy B: Centralize keys with AuthorizedKeysCommand (good for large fleets)
Con AuthorizedKeysCommand, sshd ejecuta un comando para obtener claves en tiempo de inicio de sesión. Ese comando puede consultar LDAP, una caché local,
un gestor de secretos o un archivo simple generado desde tu fuente de verdad.
Ventaja operativa: La revocación es inmediata y centralizada. Ya no hay “¿actualizamos esa caja?”.
Desventaja operativa: Has introducido una dependencia en tu ruta de login. Si es lento, está caído o mal configurado, nadie inicia sesión.
Si eliges esta vía, trátala como un servicio de producción:
- Cachea resultados localmente con TTL; protege los permisos de la caché.
- Tener una ruta estática de emergencia (clave break-glass en root, con controles estrictos).
- Instrumenta y alerta sobre fallos y latencia del comando.
- Limita la tasa y endurece el parsing de entrada. sshd ejecutará tu comando muchas veces.
Strategy C: Move to OpenSSH user certificates (the anti-sprawl option)
Los certificados son cómo detienes la proliferación de claves en lugar de estar siempre cortándola.
Los usuarios siguen teniendo una clave privada, pero se autentican con un certificado de corta duración firmado por tu CA SSH.
Los servidores confían en la CA, no en cada clave pública individual.
Qué cambia:
- En servidores: despliegas la clave pública de la CA una vez (o rara vez). Esa es la ancla de confianza.
- En clientes: los usuarios obtienen un cert (idealmente de corta duración) desde un servicio de firma tras SSO/MFA.
- Revocación: puedes revocar dejando de emitir, expirando certificados rápidamente y (si es necesario) usando listas de revocación para certs específicos.
Opinión: Si tienes más que un puñado de servidores y rotas claves más de una vez al año, los certificados se pagan solos.
Convertirán la revocación de “buscar líneas” a “dejar de emitir; esperar el TTL”.
Endurecimiento de sshd en Debian 13 después de la rotación
Los proyectos de rotación son una oportunidad: estás tocando sshd de todos modos, así que arregla los problemas estructurales que permitieron la proliferación.
La meta no es “paranoia máxima”. La meta es control de acceso predecible.
Haz explícitas las rutas de autenticación
Si rotaste claves por compromiso o por política, considera imponer esa política en sshd para no retroceder silenciosamente.
Por ejemplo, deshabilita password y keyboard-interactive si realmente no los usas; de lo contrario, los atacantes simplemente tomarán la otra puerta.
Ya viste en Task 3 cómo verificarlo.
Deja de permitir que cuentas aleatorias sean puntos de entrada remotos
La forma más rápida de acabar en el infierno de la proliferación de claves es permitir que muchas cuentas acepten claves arbitrarias.
Prefiere menos cuentas de login y usa sudo con políticas estrictas para privilegios. Aún mejor, usa cuentas por persona y evita cuentas compartidas por completo.
Cuando no puedas evitar una cuenta compartida (runners CI, usuarios deploy), conténla:
- Forzar un comando para claves de CI (como en Task 6).
- Restringir IPs origen con
from="10.0.2.15"en authorized_keys. - Deshabilitar PTY, reenvío de puertos y reenvío X11 para accesos no humanos.
Usa bloques Match para bastiones y usuarios especiales
No “lo pongas globalmente” y esperes que funcione. Los bastiones son distintos. Los usuarios de CI son distintos. Root es distinto.
OpenSSH en Debian soporta reglas granulares Match. Son legibles cuando las mantienes cortas.
Break-glass no es opcional; es controlado
Necesitas una forma de entrar cuando tu servicio de claves o despliegue de configuración falle. Pero break-glass debe ser:
usado raramente, fuertemente registrado y rotado rápidamente.
Una clave de emergencia almacenada cuidadosamente vence a cinco claves aleatorias “por si acaso” dispersas por ahí.
Broma #2: Una “clave SSH temporal” es lo más permanente en TI—justo hasta que vienen los auditores y de repente se convierte en “un malentendido”.
Tres micro-historias corporativas (todas lo bastante reales para doler)
Incidente causado por una suposición errónea: “Quitamos la clave, así que no puede iniciar sesión”
Una empresa SaaS de tamaño mediano rotó claves SSH tras el robo de un portátil. El equipo hizo lo obvio: eliminar la clave pública comprometida del
authorized_keys del ingeniero en producción. Usaron gestión de configuración, los cambios se desplegaron y el ticket del incidente se cerró.
Dos días después, un inicio de sesión sospechoso afectó a un host de base de datos. Los logs mostraban claramente autenticación por clave pública con la huella antigua.
Siguió el pánico, como suele pasar, y la primera ola de respuestas fue predecible: reiniciar sshd, rotar claves otra vez y bloquear la IP.
El inicio de sesión siguió sucediendo desde IPs nuevas.
El problema real fue pequeño y humillante: el host de base de datos no usaba claves por usuario en absoluto. Se accedía mediante una cuenta compartida heredada llamada dbmaint
que vivía fuera de la gestión de configuración porque fue creada “para un proveedor, hace años”.
Esa cuenta tenía su propio authorized_keys, y la clave comprometida también estaba ahí.
La suposición equivocada fue que “quitar la clave del usuario” equivalía a “quitar la clave del sistema”.
Las claves no se preocupan por la estructura organizativa. Se preocupan por archivos y rutas de confianza.
La solución no fue heroica: inventariar cada authorized_keys en cada host, eliminar la huella a nivel de flota y luego eliminar la cuenta compartida por completo. ¿La mejor parte? Tras la limpieza, las futuras revocaciones se volvieron aburridas, porque tenían la lista completa de dónde podían existir claves.
Optimización que salió mal: “Aceleraremos onboarding copiando claves en todas partes”
Un equipo de plataforma de una gran empresa tenía un problema de onboarding: los nuevos ingenieros necesitaban acceso a decenas de hosts y las aprobaciones tardaban.
Alguien propuso una “optimización simple”: un script que añade la clave pública del nuevo a la plantilla /etc/skel
de cada servidor y a un par de cuentas de servicio comunes, para que el acceso “simplemente funcionara”.
Funcionó, a corto plazo. El tiempo de onboarding mejoró. Menos tickets. Todos aplaudieron el script.
Luego llegó el primer offboarding de una ingeniera senior que se fue en malos términos.
Rotaron sus claves. Quitaron su clave de los lugares obvios. Ella aún tenía acceso.
El script había creado proliferación más rápido de lo que cualquier humano podía. Las claves estaban en homes creados desde /etc/skel, en cuentas compartidas
y en algunas cuentas “ayudantes ops” que la gente había creado con los años. Nadie conocía el conjunto completo. Nadie había querido crear un IAM paralelo.
Sin embargo, lo habían creado.
El efecto adverso fue costo operacional: la revocación requirió ahora una búsqueda forense a nivel de flota, y había que repetirla con cada salida.
Terminaron reemplazando el script con un proceso real de incorporación: distribución centralizada de claves, cuentas de login limitadas y migración hacia certificados de corta duración.
La lección: optimizar la rapidez del onboarding duplicando credenciales es como optimizar para incendios más rápidos almacenando gasolina en los pasillos.
Va bien hasta que necesitas detener algo con rapidez.
Práctica aburrida pero correcta que salvó el día: “Podemos probar quién puede iniciar sesión”
Un equipo de infra de fintech ejecutaba hosts Debian con una regla estricta: no ediciones manuales a authorized_keys en producción. Las claves se desplegaban
desde un repositorio Git mediante gestión de configuración. Cada línea de clave contenía propietario, propósito y fecha de revisión en el comentario.
También registraban huellas en el ticket de solicitud de acceso.
Una tarde, saltó una alerta: intentos repetidos de SSH fallidos seguidos por un inicio de sesión exitoso a un bastión. El inicio exitoso usó una huella
que no estaba en su repositorio. Eso no debía ocurrir.
Aquí es donde la disciplina aburrida paga: no debatieron. Ejecutaron una búsqueda en la flota por esa huella. Apareció solo en el bastión,
añadida manualmente en la última hora. La marca de tiempo del archivo y los logs de auditoría cuadraron con un error humano: alguien había pegado la clave de un proveedor durante una llamada de soporte.
Quitaron la clave, recargaron sshd, cerraron la sesión y compararon el authorized_keys del bastión con la versión gestionada por el repositorio.
La diferencia era exactamente una línea. Restauraron el archivo desde la gestión de configuración, abrieron un incidente de proceso y ajustaron el flujo de soporte.
Sin forenses dramáticos. Sin proyecto de auditoría de una semana. Solo contención rápida porque el sistema tenía una fuente de verdad y una regla contra cambios ad-hoc.
Errores comunes (síntoma → causa raíz → solución)
1) “La clave antigua sigue funcionando” después de la rotación
Síntoma: Una clave revocada continúa autenticando en algunos hosts.
Causa raíz: La clave existe en múltiples cuentas o múltiples fuentes de clave (cuentas compartidas, root, rutas alternativas de AuthorizedKeysFile, bastiones).
Solución: Obtén la huella de la clave y búscala en toda la flota en todas las ubicaciones de authorized_keys (y en cualquier fuente centralizada). Elimínala en todas partes. Verifica en logs.
2) “La clave nueva falla, pero funciona en otro servidor”
Síntoma: Mismo usuario, misma clave, resultado distinto según el host.
Causa raíz: Problemas de permisos (StrictModes), configuración sshd distinta, ruta home de usuario distinta o la línea de clave tiene restricciones (from=, command=) que no coinciden.
Solución: Ejecuta sshd -T en el host que falla, verifica permisos con namei -l y revisa restricciones en la línea de authorized_keys.
3) “Quitamos la clave pero la sesión se mantuvo activa”
Síntoma: El usuario sigue conectado y puede seguir operando.
Causa raíz: SSH verifica la clave en el momento de la autenticación; quitar claves no desautentica sesiones existentes.
Solución: Identifica sesiones con who / ss / ps y termínalas selectivamente cuando sea necesario.
4) “Nos dejamos fuera”
Síntoma: Tras recargar/reiniciar, nadie puede iniciar sesión.
Causa raíz: Configuración sshd mala, ruta de AuthorizedKeysFile errónea, ruptura de permisos o deshabilitar métodos de auth sin una clave probada.
Solución: Siempre ejecuta sshd -t antes de recargar, mantén una ruta de consola de emergencia y despliega cambios progresivamente con verificación.
5) “Contratista dado de baja, pero CI sigue fallando”
Síntoma: Tras limpiar claves, trabajos automatizados no pueden desplegar.
Causa raíz: CI usaba la clave personal del contratista (sí, pasa) o se eliminó inadvertidamente la clave de una cuenta de servicio.
Solución: CI debe usar su propia clave/cert dedicada con forced command y restricciones. Restaura el acceso de la cuenta de servicio y luego re-arquitecta.
6) “Revocamos la clave en hosts, pero el acceso sigue ocurriendo vía bastión”
Síntoma: El acceso directo falla pero el salto por bastión tiene éxito.
Causa raíz: La clave sigue siendo válida en el bastión, o el reenvío de agente permite usar otra clave en el host final.
Solución: Revoca primero en el bastión, revisa la configuración de reenvío de agente y fuerza uso explícito de IdentityFile durante pruebas de respuesta a incidentes.
7) “La auditoría dice que hay 400 claves; solo tenemos 60 empleados”
Síntoma: Los authorized_keys están hinchados con entradas desconocidas.
Causa raíz: Acumulación histórica: ex-empleados, acceso de proveedores, tareas puntuales, imágenes copiadas y reutilización de claves.
Solución: Establece una línea base: elimina claves sin propietario, exige re-solicitudes y migra a un modelo centralizado o a certificados.
Listas de verificación / plan paso a paso
Phase 0 — Decide the policy (one page, enforced)
- Algoritmos permitidos (preferir Ed25519; permitir RSA solo si es necesario y puedes nombrar los clientes).
- Reglas de propiedad de claves (no claves humanas compartidas; cuentas de servicio con claves dedicadas).
- Formato requerido de comentarios de clave (propietario + propósito + caducidad/revisión opcional).
- Dónde se almacenan y gestionan las claves (repositorio de gestión de configuración o fuente de AuthorizedKeysCommand).
- Método de acceso de emergencia (break-glass) y cadencia de rotación.
Phase 1 — Inventory (the part everyone wants to skip)
- Enumera todas las cuentas con capacidad de login (humanas y servicios).
- Enumera todas las fuentes de clave: rutas de
AuthorizedKeysFile, claves root, cuentas compartidas, cualquier comando de clave central. - Extrae huellas a una tabla de inventario (huella → usuario → host → archivo → comentario de línea).
- Marca claves desconocidas y duplicados (mismo blob de clave en múltiples cuentas).
Phase 2 — Rotate (introduce new keys without breaking access)
- Emite nuevas claves/certs para usuarios y servicios.
- Despliega nuevas claves públicas junto a las antiguas temporalmente (si no estuvieron comprometidas).
- Valida con pruebas controladas: un usuario, un host,
-iexplícito, confirmación en logs. - Despliegue gradual: bastión primero, luego objetivos de alto valor y luego el resto.
Phase 3 — Revoke (make old keys unusable and prove it)
- Elimina claves antiguas a nivel de flota, incluidos bastiones y cuentas compartidas.
- Recarga sshd tras la validación.
- Durante un compromiso: termina sesiones activas autenticadas con credenciales revocadas cuando sea factible.
- Recopila evidencia: líneas de log que muestren intentos de publickey fallidos con la huella revocada después del cambio.
Phase 4 — Prevent regrowth (stop key sprawl at the source)
- Prohíbe ediciones manuales en producción; haz cumplir mediante gestión de configuración y monitoriza la integridad de archivos.
- Reduce el número de cuentas que pueden aceptar logins SSH.
- Aplica restricciones a claves de servicio (forced commands, sin reenvío, límites de IP origen).
- Adopta certificados o lookup centralizado de claves si el tamaño de la flota lo justifica.
- Programa auditorías regulares (mensual/trimestral) y vincúlalas al offboarding.
Preguntas frecuentes
1) Si quitamos una clave de authorized_keys, ¿queda revocada inmediatamente?
Para nuevas conexiones, sí. Para sesiones existentes, no. SSH no vuelve a comprobar authorized_keys durante la sesión. Si la clave fue comprometida, termina sesiones deliberadamente.
2) ¿Cuál es la mejor manera única de evitar la proliferación de claves?
Deja de tratar authorized_keys como un bloc de notas. Usa una fuente de verdad (gestión de configuración o lookup centralizado) y prohíbe ediciones manuales en producción.
Si puedes, usa certificados OpenSSH de corta duración.
3) ¿Por qué necesitamos huellas si ya tenemos comentarios?
Porque los comentarios mienten, se copian o se pierden. Las huellas identifican el material clave real. En incidentes, tu rastro de auditoría debería referenciar huellas.
4) Rotamos claves, pero los usuarios aún se autentican con la clave antigua desde su agente. ¿Cómo es posible?
Su cliente SSH puede ofrecer múltiples claves automáticamente. Usa ssh -vvv -i para forzar la clave destinada durante pruebas y configura reglas de identidad por host en SSH config.
5) ¿Debemos deshabilitar el login root por SSH durante o después de la rotación?
Si puedes, sí—deshabilita el login directo de root y exige sudo desde cuentas nombradas. Si debes mantenerlo, restríngelo mucho y trata el authorized_keys de root como sagrado.
6) ¿Es seguro usar AuthorizedKeysCommand en producción?
Puede serlo, si lo tratas como una dependencia: cachea, monitoriza y ten un fallback break-glass. Si tu organización no puede mantener pequeños servicios de forma fiable, quédate con archivos gestionados por configuración.
7) ¿Cómo revocamos el acceso de un proveedor que usó una cuenta compartida?
Elimina su clave del authorized_keys de la cuenta compartida en todas partes, luego reemplaza el patrón: los proveedores deberían obtener acceso acotado en tiempo y con restricciones,
preferiblemente mediante bastión más certificados o cuentas por proveedor.
8) ¿Cómo demostramos a los auditores que una clave está revocada?
Muestra: (1) la huella de la clave, (2) evidencia de que fue removida de la(s) fuente(s) y (3) evidencia en logs de intentos de autenticación rechazados usando esa huella
después del cambio, en hosts representativos o en toda la flota cuando sea posible.
9) ¿Qué pasa con las claves incorporadas en imágenes o contenedores?
Si una imagen contiene contenido de authorized_keys, seguirás resucitando claves revocadas. Escanea imágenes, arregla la pipeline de build e invalida imágenes antiguas.
En otras palabras: deja de distribuir credenciales como artefactos.
10) ¿Debemos rotar también las claves de host?
Problema distinto. La rotación de claves de usuario revoca acceso de usuario; la rotación de claves de host cambia la identidad del servidor y afecta la confianza en known_hosts.
Rota claves de host cuando haya compromiso o por política, pero planifica las actualizaciones de confianza en los clientes.
Próximos pasos que puedes hacer esta semana
Si quieres una rotación que perdure, haz tres cosas en los próximos siete días:
-
Crea un inventario de huellas de cada
authorized_keysen una muestra representativa de hosts. Encontrarás sorpresas. Ese es el objetivo. - Elige un mecanismo de revocación operativo: authorized_keys gestionado por configuración, AuthorizedKeysCommand con caché, o (mejor) certificados de corta duración.
- Escribe la regla “no ediciones manuales” y hazla cumplir, empezando por bastiones y producción. La deriva es como las claves de hoy se convierten en el incidente de mañana.
La rotación de claves no es una ceremonia. Es un cambio de control de acceso con resultados medibles: las claves antiguas fallan, las nuevas funcionan y hay un rastro documental que leerías a las 3 a.m.
Hazlo una vez, hazlo bien y luego diseña el sistema para no volver a aprender la misma lección cada trimestre.