Esta pagina se ve mejor con JavaScript habilitado

Autenticación SSH mediante certificados

 ·  🎃 kr0m

Utilizar certificados mediante ssh nos puede resultar útil cuando un desarrollador pierde su key y hay que regenerarla, no tendremos que entrar en todos los servidores para instalar la nueva key, simplemente generaremos otra, la firmaremos con la CA, añadiremos la antigua a la lista de revocadas y distribuiremos la lista de revocadas a los servidores.

OpenSSH soporta autenticación mediante certificados desde la versión 5.4 :

Add support for certificate authentication of users and hosts using a new, minimal OpenSSH certificate format (not X.509).
Certificates contain a public key, identity information and some validity constraints and are signed with a standard SSH public key using ssh-keygen(1).
CA keys may be marked as trusted in authorized_keys or via a TrustedUserCAKeys option in sshd_config(5) (for user authentication),
or in known_hosts (for host authentication).

Creamos un usuario para gestionar la CA en el servidor CA:

groupadd -g 3000 sshca
useradd -m -u 3000 -g sshca -G sshca -c “SSH Certificate Authority Signing User” -s /bin/bash -d /home/sshca sshca
su -i -u sshca

Creamos la CA:

umask 77
mkdir ~/my-ca
cd ~/my-ca
ssh-keygen -C CA -f ca
chmod 0644 ca.pub

Copiamos la ca.pub a los servidores ssh:

scp CA:~/my-ca/ca.pub SSHSERVER:/etc/ssh/ca.pub

Editamos la config en el server ssh para que permita la autenticación mediante pubkey-certificado:

vi /etc/ssh/sshd_config

TrustedUserCAKeys /etc/ssh/ca.pub

Reiniciamos el servicio:

/etc/init.d/ssh restart

Generamos en la CA el certificado del cliente en base a su pubkey, la sintaxis es de la siguiente forma:

ssh-keygen -s ca -I ID_USER -n SSHUSER -V +VALIDEZ -z SERIAL USER_PUBKEY.pub

Un ejemplo de un certificado válido por una semana para el usuario kr0m sería así:

ssh-keygen -s ca -I kr0m -n root -V +1w -z 1 id_rsa.pub

Signed user key id_rsa-cert.pub: id "kr0m" serial 1 for root valid from 2019-05-20T22:41:00 to 2019-05-27T22:42:20

NOTA: Un intervalo de tiempo válido puede ser desde ahora hasta X tiempo o un rango concreto, desde X hasta Y separados por “;”.

Podemos consultar los datos del certificado con:

ssh-keygen -Lf id_rsa-cert.pub

id_rsa-cert.pub:
 Type: ssh-rsa-cert-v01@openssh.com user certificate
 Public key: RSA-CERT SHA256:oV14spSAEcsEWiXREUYvBgVEyEumRMBQGtwbZXXF6ZE
 Signing CA: RSA SHA256:xhAgl+pmJ4Y5jZgAM1hGCFgIwbidV7xRKphbxtfDvqE
 Key ID: "kr0m"
 Serial: 1
 Valid: from 2019-05-20T22:41:00 to 2019-05-27T22:42:20
 Principals: 
 root
 Critical Options: (none)
 Extensions: 
 permit-X11-forwarding
 permit-agent-forwarding
 permit-port-forwarding
 permit-pty
 permit-user-rc

Le pasamos el certificado al cliente:

scp CA:~/my-ca/id_rsa-cert.pub CLIENT:~/.ssh/id_rsa-cert.pub

Probamos a conectar:

chmod 600 .ssh/id_rsa-cert.pub
ssh root@A.B.C.D -p 22

En los logs del server podemos ver el registro del acceso:

May 20 22:46:04 rsaserver sshd[715]: Accepted publickey for root from x.x.x.x port 50102 ssh2: RSA-CERT ID kr0m (serial 1) CA RSA SHA256:xhAgl+pmJ4Y5jZgAM1hGCFgIwbidV7xRKphbxtfDvqE

Si intentamos conectar después de la semana en los logs veríamos:

May 20 23:00:33 rsaserver sshd[1619]: error: Certificate invalid: expired

La autenticación por certifcados nos permite definir “principals” estos actúan como tokens de acceso, de este modo podemos definir zonas de acceso. Esto resulta útil para restringir el acceso a ciertos servidores.

Configuramos los servidores ssh:

vi /etc/ssh/sshd_config

AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u

Definimos los principals:

mkdir /etc/ssh/auth_principals
echo -e ‘zone-webservers\nroot-everywhere’ > /etc/ssh/auth_principals/root

Reiniciamos el servicio:

/etc/init.d/ssh restart

La idea es tener un principal genérico que utilizarán los sysadmins(root-everywhere) y uno mas específico para los usuarios.
Para tener acceso a este servidor nuestro certificado debe haber sido emitido con el principal zone-webservers o root-everywhere.
Si intentamos conectar con el certificado sin el principal aparecerá el siguiente error:

May 20 23:02:34 rsaserver sshd[935]: error: Certificate does not contain an authorized principal

Regeneramos el certificado en la CA indicando el principal:

ssh-keygen -s ca -I kr0m -n zone-webservers -V +1w -z 2 id_rsa.pub

Actualizamos el certificado en el cliente y conectamos:

scp CA:~/my-ca/id_rsa-cert.pub CLIENT:~/.ssh/id_rsa-cert.pub
ssh root@A.B.C.D -p 22

Podemos hacer que el principal sea dinámico ejecutando en el servidor ssh un script/binario para obtenerlo, por ejemplo consultando un activedirectory:

vi /etc/ssh/sshd_config

AuthorizedPrincipalsCommand /usr/bin/adquery user –P %u
AuthorizedPrincipalsCommandUser root

Las variables soportadas en la configuración de los principals son:

AuthorizedPrincipalsCommand accepts the tokens %%, %F, %f, %h, %i, %K, %k, %s, %T, %t, %U, %u.
AuthorizedPrincipalsFile accepts the tokens %%, %h, %U, %u.
%% A literal `%'.
%F The fingerprint of the CA key.
%f The fingerprint of the key or certificate.
%h The home directory of the user.
%i The key ID in the certificate.
%K The base64-encoded CA key.
%k The base64-encoded key or certificate for authentication.
%s The serial number of the certificate.
%T The type of the CA key.
%t The key or certificate type.
%U The numeric user ID of the target user.
%u The username.

Podemos comprobar mediante tcpdump que no hay conexión alguna entre el servidor ssh a la CA en el proceso de login:
En la CA:

tcpdump -ni eth0 host A.B.C.D

Realizamos la conexión al servidor ssh desde el cliente:

En el tcpdump no aparece nada de tráfico, esto es bueno ya que la CA no será un PoF si nos atacan por fuerza bruta el servicio ssh.


Pero por otro lado para revocar certificados tendremos que distribuir el fichero de revoked keys a cada servidor ssh de forma independiente, este fichero se puede distribuir vía pssh, ansible, puppet, chef, cron…

En mi caso la mejor opción es crontabear en cada server un wget del fichero de keys revocadas y si hace falta aplicar la revocación de forma inmediata ejecutar mediante pssh el wget de forma manual.

Para revocar un certificado habrá que configurar los servidores ssh para que consulten la lista de keys revocadas y revocarla en la CA.
En el servidor ssh:

echo “RevokedKeys /etc/ssh/revoked_keys” » /etc/ssh/sshd_config
touch /etc/ssh/revoked_keys

Reiniciamos el servicio:

/etc/init.d/ssh restart

En la CA revocamos la key del usuario:

ssh-keygen -k -f revoked_keys id_rsa.pub –> La primera vez generamos el fichero
ssh-keygen -k -u -f revoked_keys id_rsa.pub –> Append al fichero existente

Podemos comprobar si la key está en la lista de revocadas:

ssh-keygen -Qf revoked_keys id_rsa.pub

id_rsa.pub (kr0m@DirtyCow): REVOKED

NOTA: Si revocamos una key por accidente no tendremos mas remedio que regenerar las keys ssh y el certificado asociado a ellas.

Distribuimos el fichero de keys revocadas a todos los servidores ssh:

scp CA:~/my-ca/revoked_keys SSHSERVER:/etc/ssh/revoked_keys

Comprobamos que el cliente ya no puede conectar:

En los logs veremos:

May 20 23:49:57 rsaserver sshd[1306]: error: Authentication key RSA SHA256:oV14spSAEcsEWiXREUYvBgVEyEumRMBQGtwbZXXF6ZE revoked by file /etc/ssh/revoked_keys

NOTA: Las keys revocadas son independientes de si utilizamos certs o pubkeys, sin habilitar los certs podemos hacer uso del baneo de keys.

Lo recomendable es generar los certificados con caducidad X y además ir revocando las keys si un desarrollador abandona el proyecto, así en la lista de revocados solo tenemos que mantener los desarrolladores despedidos en los últimos X días.

Si queremos que solo sea posible hacer login mediante pubkeys-certs:

vi /etc/ssh/sshd_config

PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no

Reiniciamos el servicio:

/etc/init.d/ssh restart

NOTA: No hay forma de configurarlo solo con certificados y sin autenticación por pubkey.


Los conceptos clave que debemos extraer de este artículo son:

  • Distribuir la pubkey de la CA a los servidores ssh.
  • Distribuir la lista de keys revocadas cuando un desarrollador abandona el proyecto.
  • Cada departamento debe tener un principal diferente, de este modo podemos utilizar la misma CA para todos los certificados de todos los usuarios.
  • Crontabear como root un script que compruebe el contenido de los authorized_keys de todos los usuarios del sistema, de este modo si el servidor fuese comprometido lo detectaríamos de forma rápida y sencilla, este script también podría meter las keys encontradas en el fichero de keys revocadas por si algún desarrollador intenta darse acceso mediante authorized_keys

El servidor de la CA es un punto critico de seguridad, un atacante con acceso a la CA podría generarse un certificado con el que acceder a todos los servidores de nuestra infraestructura, la única manera de detectar estos accesos sería revisando los logs de los servidores finales.

Si te ha gustado el artículo puedes invitarme a un RedBull aquí