Pré-requis

Il vous faudra :

  • Un vault
  • Un salt

Se connecter en SSH avec une clé publique signée par Vault

Vault SSH Secrets Engine

La première étape est d’activer le “SSH Secrets Engine” de votre Vault. C’est grâce à lui que vous pourrez signer plus tard votre clé publique SSH et ensuite vous connecter sur l’ensemble de serveurs associés à l’autorité de certification derrière cette signature. Ce n’est pas clair ? Bon. Se connecter avec vault ssh, comment ça marche ? Rien de très compliqué, la documentation est plutôt claire.

  • Monter le SSH Secrets Engine :
❯ vault secrets enable -path=ssh ssh
Successfully mounted 'ssh' at 'ssh'!
  • Il faut maintenant installer l’autorité de certification qui signera les clés SSH. Soit vous avez déjà un CA que vous pouvez injecter, soit vous demandez à Vault de vous en générer un. Pour l’exemple, je pars sur cette solution :
❯ vault write ssh/config/ca generate_signing_key=true
Key             Value
---             -----
public_key      ssh-rsa AAAAB3NzaC1yc2EA...
  • Il faut maintenant distribuer la clé publique de notre CA sur les serveurs à cibler par cette méthode d’authentification. Pour récupérer cette clé, la documentation propose deux solutions :

    • Via curl : curl -o /etc/ssh/trusted-user-ca-keys.pem http://mon_vault:8200/v1/ssh-client-signer/public_key
    • Via le cli vault : vault read -field=public_key ssh-client-signer/config/ca > /etc/ssh/trusted-user-ca-keys.pem

    L’idée est donc de créer le fichier /etc/ssh/trusted-user-ca-keys.pem qui contiendra la clé publique sur vos serveurs. Il faut ensuite rajouter dans votre sshd_config l’option suivante et redémarrer le service ssh :

# /etc/ssh/sshd_config
# ...
TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem
  • Il faut ensuite créer un rôle que vous allez ensuite utiliser pour signer votre clé SSH. Par exemple :
❯ vault write ssh/roles/remi -<<"EOH"
{
  "allow_user_certificates": true,
  "allowed_users": "*",
  "default_extensions": [
    {
      "permit-pty": "",
      "permit-port-forwarding": ""
}
  ],
  "key_type": "ca",
  "default_user": "remi",
  "ttl": "12h",
  "max_ttl": "24h"
}
EOH

Success! Data written to: ssh/roles/remi

Avec ce rôle, je vais pouvoir générer une clé signée, valide pour 12h et me connecter avec le user remi uniquement.

Attention au max_ttl : si vous n’indiquez rien dans votre rôle, c’est celui par défaut qui sera utilisé. Si vous voulez un ttl supérieur, ca ne fonctionnera pas !

J’en rajoute un identique en remplaçant le default_user par root. Je dois maintenant rajouter une policy et la lier à mon user pour pouvoir signer ma clé.

  • Je rajoute la policy ssh-remi suivante dans un fichier ssh-remi.hcl:
path "ssh/sign/root" {
 capabilities = [ "update" ]
}

path "ssh/sign/remi" {
 capabilities = [ "update" ]
}

Puis :

vault policy write ssh-remi ssh-remi.hcl
Success! Uploaded policy: ssh-remi
  • Que je lie ensuite à mon compte (à adapter en fonction de votre méthode d’authentification et de vos policies) :
vault write auth/userpass/users/remi policies="default,remi,ssh-remi"
Success! Data written to: auth/userpass/users/remi

Signature de la clé et connexion !

Tout est prêt côté Vault et sur vos serveurs. Avant de vous connecter il faut signer votre clé publique ssh. Cette étape sera à refaire à l’expiration de cette clé, dans mon exemple, toutes les 12h.

  • Pour signer ma clé, je dois me connecter à mon Vault :
❯ vault login -method=userpass username=remi
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                    Value
---                    -----
token                  c8e
token_accessor         4745
token_duration         8h
token_renewable        true
token_policies         [default remi ssh-remi]
token_meta_username    remi
  • Vérifiez que votre policy ssh se trouve bien dans vos policies. Ici j’ai rajouté ssh-remi que je retrouve bien dans mon token.
  • Je peux maintenant (enfin) signer ma clé ssh :
❯ vault write -field=signed_key ssh/sign/remi public_key=@$HOME/.ssh/id_rsa.pub > signed-remi.pub
  • \o/ Si on regarde dans le détail :
❯ ssh-keygen -Lf signed-remi.pub
signed-remi.pub:
        Type: ssh-rsa-cert-v01@openssh.com user certificate
        Public key: RSA-CERT SHA256:koC21
        Signing CA: RSA SHA256:1oa
        Key ID: "vault-userpass-remi-928"
        Serial: 7658
        Valid: from 2018-05-23T22:07:03 to 2018-05-24T10:07:33
        Principals: 
                remi
        Critical Options: (none)
        Extensions: 
                permit-port-forwarding
                permit-pty
  • Je peux me connecter sur mon serveur avec cette clé :
❯ ssh -i signed-remi.pub remi@mon_serveur

Automatiser la signature et la connexion

Histoire de simplifier tout ça, voici le lien vers un petit script à adapter selon votre besoin pour vous connecter à un serveur et éventuellement faire la signature de votre clé publique ssh. C’est ici.

Connecter Salt à Vault

On va commencer avec quelques manips à faire côté Vault. L’idée est de créer un AppRole qui est la méthode à utiliser pour connecter une appli, un service à Vault. On pourra ensuite renseigner les identifiants dans la configuration de Salt et ainsi autoriser Salt a profiter de Vault en fonction de la policy utilisée.

AppRole

Je me base de nouveau sur la documentation pour créer mon nouvel AppRole :

  • Si ce n’est pas déjà activé, on active cette méthode d’authentification :
❯ vault auth enable approle
  • Rajoutez un rôle salt et adaptez à votre besoin cet exemple :
❯ vault write auth/approle/role/salt \
    secret_id_ttl=0 \
    token_ttl=10m \
    token_max_ttl=15m \
    secret_id_num_uses=0 \
    policies="default,salt" \
    period=0 \
    token_num_uses=0 \
    bind_secret_id=true
}
  • Il est possible de rajouter d’autre contrainte comme brider l’utilisation de cet approle à une adresse ip ou à un réseau. Plus de détails dans la documentation de l’api.
  • On va maintenant créer la policy salt. Deux choses à prévoir : Salt va générer des Token pour ses minions, il faut donc donner ce droit, puis, c’est le but de cet article, les droits pour créer et mettre à jour les mots de passe des serveurs. Ce qui donne ici :
path "pass_servers/*" {
  capabilities = ["create", "read", "update"]
}

path "auth/token/create" {
  capabilities = ["create", "update"]
}
  • Stockez ces règles dans un fichier et appliquez les. Exemple :
❯ vault policy write salt /root/vault_k8s/salt_policies.hcl

Success! Uploaded policy: salt
  • On va récupérer les informations nécessaires à Salt pour la connexion sur le Vault :
❯ vault read auth/approle/role/salt/role-id
role_id     db02de05-fa39-4855-059b-67221c5c2f63

❯ vault write -f auth/approle/role/salt/secret-id
secret_id               6a174c20-f6de-a53c-74d2-6018fcceff64
secret_id_accessor      c454f7e5-996e-7230-6074-6ef26b7bcf86
  • Attention la commande write -f génère un secret_id à chaque appel.

Configuration de Salt

  • Sur votre Salt Master, rajoutez un fichier dans /etc/salt/master.d avec pour contenu :
vault-home:
  driver: vault

vault:
  url: https://mon_vault
  verify: True
  auth:
    method: approle
    role_id: db02de05-fa39-4855-059b-67221c5c2f63
    secret_id: 6a174c20-f6de-a53c-74d2-6018fcceff64
  policies:
    - salt

peer_run:
  .*:
    - vault.generate_token
  • Si votre Vault à un certificat autosigné, il faudra l’installer sur votre serveur Salt ou passer l’option verify à False.
  • Relancez le service salt-master et salt-minion.

Tests

  • Vous devriez pouvoir créer et lire des secrets depuis Salt. Pour faire un test rapide, voici comment écrire un secret :
❯ salt 'mon_serveur' vault.write_secret pass_servers/test pass=coincoin
mon_serveur:
    True
  • Et pour lire ce secret :
❯ salt 'mon_serveur' vault.read_secret pass_servers/test 
mon_serveur:
    ----------
    pass:
        coincoin
  • C’est prêt !

Générer un mot de passe aléatoire et le stocker dans Vault

Voici un state Salt pour générer un mot de passe aléatoire et le stocker dans Vault.

Vous pouvez l’utiliser de différentes façons : à la demande avec par exemple un state.apply, via un reactor pour réagir à un évènement lors de la création d’un serveur par exemple, dans un schedule pour changer le mot de passe régulièrement.

{% set password = salt['random.get_str'](10) %}
{% set secret = salt['vault'].write_secret('pass_servers/'+grains['fqdn'],user='root',password=password, description='Généré par Salt le '+salt['system.get_system_date_time']()) %}
{% set hash_password = salt['shadow'].gen_password(password) %}

set_new_password:
  user.present:
    - name: root
      password: {{ hash_password }}

Et a sauvegarder ailleurs pour le jour où Vault sera HS ;-)