Je suppose que pour vous connecter via SSH, vous avez l’habitude de copier votre clef sur vos serveurs. C’est ok pour vos serveurs persos mais si vous avez tout un parc de serveurs et d’utlisateurs à gérer, cela peut vite devenir problématique. Vous pouvez bien sûr utiliser un outil comme ansible pour pousser les clefs publiques de vos utilisateurs, j’ai écrit un rôle pour ça et qui prend également en compte l’authentification 2FA mais ce n’est pas le sujet.

On va aborder aujourd’hui une manière beaucoup plus sexy de gérer les connexions de vos utilisateurs sur votre parc de serveurs. La connexion par certificat, gérée par Hashicorp Vault.

Comment ça fonctionne ?

Au lieu de copier les clefs publiques de tous vos utilisateurs sur vos serveurs, vous allez juste copier un certificat généré par Vault et dire au daemon SSH de faire confiance à ce certificat.

Et lorsque vous voudrez vous connecter sur vos serveurs, il faudra préalablement demander à Vault de signer votre clef SSH avec ledit certificat. Cette signature vous permettra de vous authentifier, car le daemon SSH fera confiance au certificat de Vault.

L’avantage de cette solution est que vous n’aurez plus à copier (ou supprimer) les clefs de vos utilisateurs manuellement sur vos serveurs. Vous aurez juste à copier une seule fois le certificat fourni par vault et c’est tout.

La signature de clef ayant une durée de validité très courte (c’est configurable), vos utilisateurs auront à demander une signature avant chaque tentative de connexion. Pour révoquer l’accès de vos utilisateurs, il suffira de révoquer leur certificat Vault.

Et si pour une raison X ou Y, Vault n’est pas joignable ? Je ne pourrai plus me connecter sur mes serveurs ?

Si, vous pourrez, vous avez toujours la possibilité de copier les clefs de vos utilisateurs de confiance, à l’ancienne :p

Voyons maintenant comment mettre tout ça en place.

Mise en place

Je pars du principe ici que vous avez déjà un serveur Vault à disposition. Si ce n’est pas le cas et que vous voulez juste tester, je vous renvoie à la documentation de Vault.

Activation du secret engine SSH pour la signature

Vault dispose de plusieurs secrets engines. Je vous invite à aller faire un tour sur la documentation pour en savoir plus.

Nous allons tout d’abord activer le secret engine ssh sur le path ssh-client-signer:

vault secrets enable -path=ssh-client-signer ssh

Créez ensuite un nouveau couple de clefs SSH pour la signature

ssh-keygen -a 50 -t ed25519 -f ssh-ca-signer -q -N ""

Cette commande va vous générer 2 fichiers:

  • une clef privée: ssh-ca-signer
  • et sa clef publique: ssh-ca-signer.pub

Créez le certificat vault qui va signer les clefs SSH de vos utilisateurs

Ce certificat se base sur les clefs ssh-ca-signer que vous avez créé précédemment, et fait partie de la configuration de votre secret engine ssh monté sur le path ssh-client-signer:

vault write ssh-client-signer/config/ca \
    private_key="$(cat ssh-ca-signer)" \
    public_key="$(cat ssh-ca-signer.pub)"

Exportez le certificat public

vault read -field=public_key ssh-client-signer/config/ca > trusted-user-ca-keys.pem

C’est ce certificat que vous allez devoir copier sur tous vos serveurs.

Placez ce fichier sur votre serveur dans /etc/ssh/trusted-user-ca-keys.pem et ajoutez à votre fichier de configuration /etc/ssh/sshd_config:

TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem

C’est donc cette ligne qui va dire à votre serveur SSH: Laisse entrer les utilisateurs dont la clef a été signée avec ce certificat.

Il faut bien entendu relancer votre service ssh afin que ce soit pris en compte: systemctl restart sshd.service

Je vous laisse le soin de scripter ça dans un playbook ansible ou autre :-)

Création de rôles dans Vault

Afin de gérer vos utilisateurs et leur connexion SSH à vos serveurs, nous allons maintenant créer des rôles dans le path ssh-client-signer.

rôle ssh-all-user

Voici par exemple un rôle ssh-all-user qui va permettre à vos utilisateurs de se connecter avec n’importe quel utilisateur, grâce au paramètre "allowed_users": "*":

vault write ssh-client-signer/roles/ssh-all-users -<<"EOH"
{
  "algorithm_signer": "default",
  "allow_user_certificates": true,
  "allowed_users": "*",
  "allowed_extensions": "permit-pty,permit-port-forwarding,permit-user-rc,permit-X11-forwarding,permit-agent-forwarding",
  "default_extensions": {
    "permit-pty": ""
  },
  "key_type": "ca",
  "ttl": "1m0s"
}
EOH

rôle ssh-ubuntu-user

Un autre exemple de création de rôle qui n’autorisera que les connexions en tant qu’utilisateur dont le login est ubuntu, grâce au paramètre "allowed_users": "ubuntu":

vault write ssh-client-signer/roles/ssh-ubuntu-user -<<"EOH"
{
  "algorithm_signer": "default",
  "allow_user_certificates": true,
  "allowed_users": "ubuntu",
  "allowed_extensions": "permit-pty,permit-port-forwarding,permit-user-rc,permit-X11-forwarding,permit-agent-forwarding",
  "default_extensions": {
    "permit-pty": ""
  },
  "key_type": "ca",
  "ttl": "1m0s"
}
EOH

A noter que j’ai défini dans la commande ci-dessus le ttl à une minute ("ttl": "1m0s"). Ce qui signifie que tout certificat signé par vault sera valide une minute. Au dela d’une minute, vous ne pourrez plus vous authentifier et il faudra redemander un certificat. Libre à vous d’augmenter ou diminuer ce ttl.

Je pense que vous avez compris le principe. Encore une fois, je vous renvoie à la documentation du secret engine SSH si vous souhaitez en savoir plus concernant les différentes options disponibles.

Création des policy

Vous devez donner les autorisation create et update sur les roles ssh que nous venons de créer. Ce sont des policy.

Pour le rôle ssh-all-users:

vault policy write ssh-all-users - << EOF
path "ssh-client-signer/sign/ssh-all-users" {
  capabilities = ["create", "update"]
}
EOF

Pour le rôle ssh-ubuntu-user:

vault policy write ssh-ubuntu-user - << EOF
path "ssh-client-signer/sign/ssh-ubuntu-user" {
  capabilities = ["create", "update"]
}
EOF

Si vous voulez en savoir plus, allez lire la documentation :-)

Création des tokens utilisateurs

Pour pouvoir signer leurs clefs, vous devez donner à vos utilisateurs un token vault afin qu’ils soient en mesure de demander un certificat avant chaque connexion.

Vous pouvez définir plusieurs policy pour un token donné:

vault token create -field token -policy=ssh-all-users  -policy=ssh-ubuntu-user

Donnez ensuite ce token à votre utilisateur afin qu’il puisse signer sa clef SSH personnelle et s’authentifier sur vos serveurs.

Je vous renvoie à la documentation si vous souhaitez en savoir plus sur comment gérer les tokens de Vault.

Mise en place côté client.

Ça y est, tout à été configuré côté serveur SSH et côté Vault, votre administrateur vous a donné un token pour signer votre clef avec Vault, mais concrètement comment allez vous vous connecter ?

connexion avec vault ssh

Vous devez tout d’abord avoir configuré le client vault sur votre poste avec le token donné par votre administrateur.

Il vous faut également un couple de clef SSH personnel :-) J’ai personnellement l’habitude d’utiliser des clefs elliptiques ed25519 qui se trouvent par défaut dans ~/.ssh/id_ed25519, il faudra le dire à vault car par défault, il va chercher des clefs RSA (par défaut ~/.ssh/rsa).

Admettons donc que vous souhaitez vous connecter en tant qu’utilisateur ubuntu sur le serveur dev.ubuntu.com, et que votre admin a tout correctement configuré, il vous suffit de vous connecter avec cette commande:

vault ssh \
  -mode=ca \
  -role=ssh-ubuntu-user \
  -mount-point=ssh-client-signer \
  -private-key-path=~/.ssh/id_ed25519 \
  -public-key-path=~/.ssh/id_ed25519.pub \
  -valid-principals=ubuntu \
  ubuntu@dev.ubuntu.com

Si vous souhaitez plutôt vous connecter en tant qu’utilisateur admin, vous utiliserez plutôt le rôle ssh-all-users, il faudra également spécifier le bon principal avec -valid-principals=admin, ce qui donne:

vault ssh \
  -mode=ca \
  -role=ssh-all-users \
  -mount-point=ssh-client-signer \
  -private-key-path=~/.ssh/id_ed25519 \
  -public-key-path=~/.ssh/id_ed25519.pub \
  -valid-principals=admin \
  admin@dev.ubuntu.com

Méthode alternative

Vous pouvez également demander à Vault de vous afficher votre certificat que vous sauvegarderez par exemple dans un fichier signed-cert.pub

vault write \
  -field=signed_key \
  ssh-client-signer/sign/ssh-all-users \
  public_key=@$HOME/.ssh/id_ed25519.pub \
  valid_principals=ubuntu > signed-cert.pub

Vous pouvez afficher les informations de ce certificat (on voit que mon certificat est valide 30 secondes):

ssh-keygen -Lf signed-cert.pub
signed-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:h6QTOpFCtIJoNzuT5HgrVp3lksq7F3xbGgKOtvNkkX4
        Signing CA: ED25519 SHA256:6wUjUVmnrHg+3aqwYjDXMgMOF49o3VqFNVYrY7H0Bkg (using ssh-ed25519)
        Key ID: "vault-token-87a4133a9142b48268373b93e4782b569de592cabb177c5b1a028eb6f364917e"
        Serial: 8428675053565845939
        Valid: from 2022-10-05T15:00:04 to 2022-10-05T15:01:34
        Principals: 
                ubuntu
        Critical Options: (none)
        Extensions: 
                permit-pty

Pour vous connecter, vous devez à la fois spécifier votre clef ssh + votre certificat:

ssh -i signed-cert.pub -i ~/.ssh/id_ed25519 ubuntu@dev.ubuntu.com

Autres manières de faire

Toutes les commandes ci-dessous permettent de demander un certificat signé:

vault write \
  -field=signed_key \
  ssh-client-signer/sign/ssh-all-users \
  public_key=@$HOME/.ssh/id_ed25519.pub \
  valid_principals=ubuntu > signed-cert.pub

vault write -field=signed_key ssh-client-signer/sign/ssh-all-users -<<"EOH" > signed-cert.pub
{
  "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFLbVuUe9JVpHfd4l50sGQoCtZvXuYvNuVpMRheT8+5G",
  "valid_principals": "ubuntu"
}
EOH

vault write -field=signed_key ssh-client-signer/sign/ssh-all-users -<<"EOH" > signed-cert.pub{                                            
  "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFLbVuUe9JVpHfd4l50sGQoCtZvXuYvNuVpMRheT8+5G",                                                                        
  "valid_principals": "ubuntu",
  "extensions": {
    "permit-pty": "",
    "permit-port-forwarding": ""
  }
}
EOH

Aller encore plus loin

Nous avons vu comment signer une clef SSH d’un utilisateur ce qui est déjà bien mais pour encore plus de sécurité, vous pouvez en plus activer la signature des clefs SSH du serveur sur lequel vous allez vous connecter.

Lorsque cette fonctionnalité est activée, votre client SSH va vérifier que le serveur distant est bien celui qu’il prétend être, ce qui évite de se connecter accidentellement sur une machine compromise.

Les commandes ci-dessous sont à executer idéalement depuis le serveur distant.

Activation du secret engine SSH pour l’hôte distant

Nous allons activer le secret engine SSH sur un autre path: ssh-host-signer

vault secrets enable -path=ssh-host-signer ssh

Créez ensuite un nouveau couple de clefs SSH

ssh-keygen -a 50 -t ed25519 -f ssh-ca-host -q -N ""

Créez le certificat pour signer les requêtes clientes

vault write ssh-host-signer/config/ca \
    private_key="$(cat ssh-ca-host)" \
    public_key="$(cat ssh-ca-host.pub)"

Augmentez le TTL du certificat

vault secrets tune -max-lease-ttl=87600h ssh-host-signer

Créez un role pour signer les clefs de votre serveur

vault write ssh-host-signer/roles/hostrole \
    key_type=ca \
    algorithm_signer=default \
    ttl=87600h \
    allow_host_certificates=true \
    allowed_domains="localdomain,example.com" \
    allow_subdomains=true

Signez la clef publique de votre serveur

vault write ssh-host-signer/sign/hostrole \
    cert_type=host \
    public_key=@/etc/ssh/ssh_host_ecdsa_key.pub

Exportez le certificat signé

vault write -field=signed_key ssh-host-signer/sign/hostrole \
    cert_type=host \
    public_key=@/etc/ssh/ssh_host_ecdsa_key.pub > /etc/ssh/ssh_host_ecdsa_key-cert.pub

Fixez les permissions

chmod 0640 /etc/ssh/ssh_host_ecdsa_key-cert.pub

Mettez à jour votre fichier de configuration SSH

Modifiez /etc/ssh/sshd_config en ajoutant ces lignes:

HostKey /etc/ssh/ssh_host_ecdsa_key
HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub

Puis relancez le service SSH.