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 CACon step-ca
Un cliente se encuentra un servidor nuevoEnseña una huella, tecleas yesComprueba el certificado de host contra la CA, no dice nada
Se reconstruye un servidorTodos los clientes avisan de que la clave cambióFirma la clave nueva, los clientes ni se enteran
Un usuario recibe accesoSu clave va a authorized_keys en cada servidorLa CA firma su clave, todos los servidores la aceptan
Un usuario se vaBuscar y borrar su clave en cada servidorDejar de firmar; su certificado está muerto en menos de 16 horas
Qué guarda cada servidorUn authorized_keys por usuarioUna clave pública de CA
Quién firma qué, y quién confía en quéstep-caclave CA de hostclave CA de usuarioClientecert. de usuario, 16 hconfía en la CA de hostServidorcert. de host, 30 díasconfía en la CA de usuariofirma la clave de usuariofirma la clave de hostssh, sin preguntascada uno compruebael cert. del otro
La CA firma los dos lados. El cliente solo tiene que confiar en la clave de la CA de host, y el servidor solo en la clave de la CA de usuario. Ninguno guarda una lista de las claves del otro.

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...
ClaveFirmaQuién confía en ella
CA de hostLa clave de host de cada servidorTodos los clientes, en known_hosts
CA de usuarioLa clave de inicio de sesión de cada personaTodos 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:

CampoValor
Tipoecdsa-sha2-nistp256-cert-v01@openssh.com host certificate
ID de clavenas.home.example
Principalesnas.home.example
Válido30 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
CampoValor
Tipouser certificate
Principalesdamien
Válido16 horas
Extensionespermit-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:

PruebaResultado
Clave firmada con su certificado, como damienlogged in as damien, salida 0
La misma clave, sin el certificadoPermission denied (publickey,password,keyboard-interactive)
Certificado usado para entrar como rootPermission denied: el certificado solo nombra a damien
Cliente sin la línea de la CA de hostHost 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 serieVálido hasta
Firmado151663258693294169972026-11-08 17:35:30
Renovado66789577897973167682026-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

CasoQué pasaQué hacer
CA caídaLos certificados existentes siguen funcionando; nadie consigue uno nuevoEjecutar la CA en algo que no se caiga; los usuarios tienen 16 horas de margen
Caduca el certificado de hostLos clientes rechazan el host, como si fuera un desconocidoUn temporizador de renovación, y sshd recargado después
Un usuario tiene que irse hoy, no mañanaSu certificado funciona hasta que caducaRevokedKeys en sshd_config acepta una lista de claves revocadas2
Nombre de host que no está en los principalesEl cliente rechaza el hostFirmar todos los nombres a los que responde
CA SSH no creada al configurarNo hay forma de añadirlaUna CA nueva, configurada con SSH activado
Roban las claves SSH de la CAQuien las tenga puede entrar en cualquier sitio y ser cualquier hostTener 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.


  1. smallstep/certificates — docker/entrypoint.sh — DOCKER_STEPCA_INIT_SSH=true añade --ssh a step ca init. ↩︎

  2. 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». ↩︎ ↩︎ ↩︎ ↩︎

  3. OpenBSD manual — sshd(8), SSH_KNOWN_HOSTS FILE FORMAT — la marca “@cert-authority” «to indicate that the line contains a certification authority (CA) key». ↩︎

  4. 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.» ↩︎

  5. Smallstep — step ssh renew — el comando usado para renovar el certificado de host. ↩︎