Por qué certificados en vez de claves
SSH confía en el primer uso. Conéctate a una máquina nueva y te enseña una huella y te pregunta si estás seguro. Nadie la comprueba. Todo el mundo teclea yes. Es el mismo hábito que saltarse un aviso del navegador, y es justo lo que necesita que hagas una máquina puesta en medio.
La otra dirección no es mejor. Cada servidor guarda un archivo authorized_keys por usuario, y todos crecen. Las claves se añaden y nunca se quitan. Alguien se va, y su clave se queda en una máquina de la que nadie se acuerda hasta el día que importa. Reconstruye un servidor y todos los clientes gritan que la clave de host ha cambiado, así que la gente aprende a borrar también ese aviso.
Una autoridad de certificación SSH acaba con las dos cosas. La CA firma la clave de cada host, y los clientes confían en la CA, no en una lista de huellas. La CA firma la clave de cada usuario para unas pocas horas, y los servidores confían en la CA, no en una lista de claves. Nada que aceptar en la primera conexión, nada que limpiar cuando alguien se va, y un certificado de usuario que muere solo para mañana.
Esta es una configuración distinta del lado X.509 de step-ca que cubre Tu propia autoridad de certificación: step-ca en un contenedor, y por eso tiene su propia entrada. El mismo contenedor, otras claves, otros archivos en cada máquina. Todo lo de aquí se ejecutó con step-ca 0.30.2 y OpenSSH 10.0 en un pod de Podman.
| Sin CA | Con step-ca | |
|---|---|---|
| Un cliente se encuentra un servidor nuevo | Enseña una huella, tecleas yes | Comprueba el certificado de host contra la CA, no dice nada |
| Se reconstruye un servidor | Todos los clientes avisan de que la clave cambió | Firma la clave nueva, los clientes ni se enteran |
| Un usuario recibe acceso | Su clave va a authorized_keys en cada servidor | La CA firma su clave, todos los servidores la aceptan |
| Un usuario se va | Buscar y borrar su clave en cada servidor | Dejar de firmar; su certificado está muerto en menos de 16 horas |
| Qué guarda cada servidor | Un authorized_keys por usuario | Una clave pública de CA |
Activar la CA SSH
Las claves SSH se crean cuando se configura step-ca por primera vez, o nunca. Pon DOCKER_STEPCA_INIT_SSH=true en el primer arranque1, y el registro muestra dos claves más:
SSH Host CA Key: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy...
SSH User CA Key: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy...
| Clave | Firma | Quién confía en ella |
|---|---|---|
| CA de host | La clave de host de cada servidor | Todos los clientes, en known_hosts |
| CA de usuario | La clave de inicio de sesión de cada persona | Todos los servidores, en sshd_config |
También añade un aprovisionador sshpop, que es lo que deja que un host renueve su propio certificado más adelante. Si la CA ya está funcionando sin SSH, no hay interruptor que pulsar. Es una CA nueva.
Firmar el host
Coge la clave de host que ya tiene el servidor, fírmala, y devuélvele el certificado al servidor:
step ssh certificate --host --sign \
--provisioner admin --provisioner-password-file prov-pass \
nas.home.example ssh_host_ecdsa_key.pub
El orden importa. Con las opciones del aprovisionador después del nombre de host y del archivo de clave, step lo rechazó:
too many positional arguments were provided in 'step ssh certificate <key-id> <key-file>'
Pon todas las opciones primero y firma. El certificado, tal como lo lee ssh-keygen -L:
| Campo | Valor |
|---|---|
| Tipo | ecdsa-sha2-nistp256-cert-v01@openssh.com host certificate |
| ID de clave | nas.home.example |
| Principales | nas.home.example |
| Válido | 30 días, de 2026-10-09T18:34:30 a 2026-11-08T17:35:30 |
El principal es el nombre que tiene que usar un cliente para conectarse. Conéctate con un nombre que no esté en el certificado y el cliente no lo aceptará, así que firma todos los nombres a los que responde el host.
El servidor
Dos líneas en sshd_config2:
HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub
TrustedUserCAKeys /etc/ssh/ssh_user_ca_key.pub
HostCertificate entrega el certificado de host a los clientes, y su clave «must match a private host key already specified»2. TrustedUserCAKeys es la clave pública de la CA de usuario, «trusted to sign user certificates for authentication»2. Ejecuta sshd -t antes de reiniciar. Una errata en cualquiera de las dos rutas y sshd dice Could not load host certificate, que es mucho mejor descubrirlo ahora que desde una sesión que se ha quedado fuera.
En el servidor de pruebas no hay ningún authorized_keys. ls /home/damien/.ssh/authorized_keys devuelve No such file or directory, y los inicios de sesión de abajo funcionan igual.
El usuario
La CA crea el par de claves y lo firma de una vez:
step ssh certificate damien id_ecdsa \
--provisioner admin --provisioner-password-file prov-pass
| Campo | Valor |
|---|---|
| Tipo | user certificate |
| Principales | damien |
| Válido | 16 horas |
| Extensiones | permit-agent-forwarding, permit-port-forwarding, permit-X11-forwarding, permit-pty, permit-user-rc |
Dieciséis horas es justo la idea. Cubre una jornada de trabajo y muere por la noche. Revocar a un usuario es sobre todo cuestión de no firmarle mañana.
El cliente
Una línea en known_hosts sustituye todas las huellas que iría acumulando3:
@cert-authority *.home.example ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAy...
Eso dice: cualquier host bajo home.example cuyo certificado esté firmado por esta clave es auténtico. La marca @cert-authority es lo que la convierte en una línea de CA en vez de la clave de un host.
Lo que mostró la prueba
Las cuatro desde un cliente que no tenía nada más que el certificado de usuario y esa línea de known_hosts, con StrictHostKeyChecking=yes, para que no se pudiera aceptar nada sobre la marcha:
| Prueba | Resultado |
|---|---|
Clave firmada con su certificado, como damien | logged in as damien, salida 0 |
| La misma clave, sin el certificado | Permission denied (publickey,password,keyboard-interactive) |
Certificado usado para entrar como root | Permission denied: el certificado solo nombra a damien |
| Cliente sin la línea de la CA de host | Host key verification failed. |
La tercera es la que se le escapa a la gente. Un certificado no abre todas las cuentas de la máquina, solo los principales escritos en él. Una clave en el authorized_keys de alguien no puede hacer eso.
Renovar
Un host renueva su propio certificado con la clave que ya tiene. Ese es el aprovisionador sshpop, que «allows a client to renew, revoke, or rekey an SSH certificate using that certificate for authentication with the CA»4. Sin contraseña5:
step ssh renew --force ssh_host_ecdsa_key-cert.pub ssh_host_ecdsa_key
| Número de serie | Válido hasta | |
|---|---|---|
| Firmado | 15166325869329416997 | 2026-11-08 17:35:30 |
| Renovado | 6678957789797316768 | 2026-11-08 17:35:59 |
Un número de serie nuevo y 30 días nuevos. Ponlo en un temporizador semanal y el host no caduca nunca. Recarga sshd después, o el certificado viejo se queda en memoria.
Los usuarios renuevan pidiendo otra vez. Un step ssh certificate al día, o en el primer inicio de sesión del día, y ese es todo el trabajo.
Dónde se queda corto
| Caso | Qué pasa | Qué hacer |
|---|---|---|
| CA caída | Los certificados existentes siguen funcionando; nadie consigue uno nuevo | Ejecutar la CA en algo que no se caiga; los usuarios tienen 16 horas de margen |
| Caduca el certificado de host | Los clientes rechazan el host, como si fuera un desconocido | Un temporizador de renovación, y sshd recargado después |
| Un usuario tiene que irse hoy, no mañana | Su certificado funciona hasta que caduca | RevokedKeys en sshd_config acepta una lista de claves revocadas2 |
| Nombre de host que no está en los principales | El cliente rechaza el host | Firmar todos los nombres a los que responde |
| CA SSH no creada al configurar | No hay forma de añadirla | Una CA nueva, configurada con SSH activado |
| Roban las claves SSH de la CA | Quien las tenga puede entrar en cualquier sitio y ser cualquier host | Tener la CA en su propia máquina, con copia de seguridad, desconectada si puedes |
La última es el precio. Cada archivo authorized_keys era su propio riesgo pequeño. Ahora hay uno grande, en un solo sitio. Eso es mejor, porque un solo sitio se puede vigilar y blindar, pero solo si alguien lo hace.
Confianza donde se puede comprobar
La pregunta de la huella y el archivo authorized_keys tienen algo en común. Los dos le piden a una persona que tome una decisión de confianza sin información, cada día, para siempre. A la gente no se le da bien eso y nunca se le dará, así que dejan de decidir y empiezan a teclear yes.
Una CA lleva esa decisión a un solo sitio, una vez, donde se puede hacer bien. Esa es toda la idea, y es la misma que hay detrás de cualquier estándar que valga la pena: hacer lo cuidadoso una vez, ponerlo por escrito, y dejar de pedirle a la gente que lo haga de cabeza cien veces a la semana. La máquina comprueba la firma. Tú compruebas la CA. Es un reparto del trabajo que se sostiene.
smallstep/certificates — docker/entrypoint.sh —
DOCKER_STEPCA_INIT_SSH=trueañade--sshastep ca init. ↩︎OpenBSD manual — sshd_config(5) —
HostCertificate: «The certificate’s public key must match a private host key already specified by HostKey»;TrustedUserCAKeys: «public keys of certificate authorities that are trusted to sign user certificates for authentication»;RevokedKeys: «Keys listed in this file will be refused for public key authentication». ↩︎ ↩︎ ↩︎ ↩︎OpenBSD manual — sshd(8), SSH_KNOWN_HOSTS FILE FORMAT — la marca “@cert-authority” «to indicate that the line contains a certification authority (CA) key». ↩︎
Smallstep — Provisioners, SSHPOP — «An SSHPOP provisioner allows a client to renew, revoke, or rekey an SSH certificate using that certificate for authentication with the CA.» ↩︎
Smallstep — step ssh renew — el comando usado para renovar el certificado de host. ↩︎