Dans cet article je vais détailler comment utiliser Vault pour générer des certificats et les utiliser dans Kubernetes.
Je pars du principe que :

  • Vous avez un cluster Kubernetes opérationnel.
  • Cert-manager est déployé dans le cluster.
  • Vault est installé et opérationel.

Nous allons commencer par installer un CA dans Vault et préparer la configuration.

Création d’une CA dans Vault et configuration

Configuration de la CA

Je pars du principe que vous avez déjà une rootCA et que vous voulez créer une intermediate dans Vault.
Il y a plusieurs façon de faire voici par exemple la config terraform pour créer un backend PKI sur le path /pki.
Cela va aussi générer une CSR et va l’output sur le terminal après avoir lancé la commande terraform apply

resource "vault_mount" "pki" {
  path                      = "pki"
  type                      = "pki"
  description               = "Kubernetes CA"
  # 1 years
  default_lease_ttl_seconds = 31536000
  # 5 years
  max_lease_ttl_seconds = 157680000
}
resource "vault_pki_secret_backend_intermediate_cert_request" "vault_CA" {
  depends_on = [ vault_mount.pki ]
  backend = vault_mount.pki.path
  type = "internal"
  common_name = "Vault CA"
  country = "FR"
  province = "Lorraine"
  organization = "McTH"
  ou = "kube"
  format = "pem"
  private_key_format = "der"
  key_type = "rsa"
  key_bits = "4096"
  }
  output "ca_csr"  {
      value = vault_pki_secret_backend_intermediate_cert_request.vault_CA.csr
  }

Une fois que vous avez personnalisé cette configuration vous pouvez lancer :
terraform plan -out plan
Puis :
terraform apply plan

Petit rappel : pour Terraform avec Vault comme backend : il faut setter les variables d’environnement correspondante à votre Vault pour que ce dernier s’y connecte. Assurez-vous d’avoir les variables VAULT_TOKEN et VAULT_ADDR qui pointent sur votre Vault avec un utilisateur ayant suffisamment de droits.

Une fois que vous avez la CSR vous pouvez la signer avec votre Root certificate.
Il faut absolument ajouter l’option : authorityKeyIdentifier=keyid lors de la signature de votre CA.

Une fois votre CSR signée vous avez donc votre certiticat qu’il faut importer :

data "local_file" "kube_cert" {
    filename = "${path.module}/kube.pem"
}

resource "vault_pki_secret_backend_intermediate_set_signed" "kube_ca" { 
  backend = vault_mount.pki.path
  certificate = data.local_file.kube_cert.content
}

resource "vault_pki_secret_backend_config_urls" "config_urls" {
  backend              = vault_mount.pki.path
  issuing_certificates = ["https://vault.security.svc:8200/v1/pki/ca", "https://vault.domain.tld/v1/pki/ca"]
  crl_distribution_points = ["https://vault.security.svc:8200/v1/pki/crl","https://vault.domain.tld/v1/pki/crl"]
}

Avec cela vous avez une authorité de certification dans votre Vault fonctionnelle. Mais vous ne pouvez pas encore générer de certificat.
Il faut maintenant créer des roles afin de permettre cela.

Configuration des roles

Continuons cela avec Terraform.

resource "vault_pki_secret_backend_role" "cert-generator" {
  backend = vault_mount.pki.path
  name = "server-cert-for-kube"
  allowed_domains = [ "*.domain.tld" ]
  allow_subdomains = true
  allow_glob_domains = true
  allow_any_name = false
  enforce_hostnames = true
  allow_ip_sans = true
  server_flag = true
  client_flag = false 
  country = ["FR"]
  province = ["Lorraine"]
  organization = ["McTh"]
  ou = ["kube"]
  key_usage = ["DigitalSignature", "KeyAgreement", "KeyEncipherment"]
  # 2 years
  max_ttl = 63113904 
  # 30 days
  ttl = 2592000
  no_store = true
}

Cela va permettre de créer des certificats de type server. Je ne vais pas entrer dans les détails des options mais par défaut vos certificats auront une durée de vie de 30 jours et vous pourrez au max demander 2ans.

Il faut prévoir une dernière chose avant de passer à la configuration Kubernetes : une policy (nommée kube-cert-generator) afin d’autoriser un compte la génération de certificat dans Vault.

path "pki/issue/server-cert-for-kube" {
  capabilities = ["create", "update"]
}

path "pki/sign/server-cert-for-kube" {
  capabilities = ["create", "update"]
}

Cela permettra à un compte de service de générer et de signer des certificats dans Vault.

Configuration de Kubernetes dans Vault

Pour que Kubernetes puisse communiquer directement avec Vault via des service accounts il faut également activer le backend kubernetes.

Avant de se lancer directement dans la configuration Terraform il faut executer quelques commandes afin de récupérer certaines informations dans votre cluster Kubernetes :

NS="security" ## Namespace de Vault de préférence
export VAULT_SA_NAME=$(kubectl get sa vault-auth -n $NS -o jsonpath="{.secrets[*]['name']}")
export SA_JWT_TOKEN=$(kubectl get secret $VAULT_SA_NAME -n $NS -o jsonpath="{.data.token}" | base64 --decode; echo)
export SA_CA_CRT=$(kubectl get secret $VAULT_SA_NAME -n $NS -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo) 

export K8S_HOST="kubernetes.default.svc"

Cela implique que votre cluster contient un service account pour vault appelé vault-auth. Si ce n’est pas le cas voici :

cat <<EOF | kubectl apply -f -n security - 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault-auth
EOF

Vous pouvez directement lancer la commande Vault afin d’ajouter le backend d’auth plus facilement :

vault write auth/kubernetes/config \
        token_reviewer_jwt="$SA_JWT_TOKEN" \
        kubernetes_host="https://$K8S_HOST:443" \
        kubernetes_ca_cert="$SA_CA_CRT"

Ou si vous souhaitez continuer avec Terraform voici la configuration :

resource "vault_auth_backend" "kubernetes" {
  type = "kubernetes"
}
resource "vault_kubernetes_auth_backend_config" "config" {
  backend                = vault_auth_backend.kubernetes.path
  kubernetes_host        = "https://${K8S_HOST}:443"
  kubernetes_ca_cert     = "${SA_CA_CRT}"
  token_reviewer_jwt     = "${SA_JWT_TOKEN}"
  issuer                 = "api"
  disable_iss_validation = "true"
}

Pour finir il faut ajouter un role dans l’auth kubernetes afin d’autoriser un compte de service à se connecter à Vault :

resource "vault_kubernetes_auth_backend_role" "vault-issuer" {
  backend   = vault_auth_backend.kubernetes.path
  role_name = "vault-issuer"
  bound_service_account_names      = ["vault-issuer"]
  bound_service_account_namespaces = ["kube-system"]
  token_ttl                        = 3600
  token_policies                   = ["kube-cert-generator"]
  audience                         = "vault"
}

Avec tout cela Vault est complètement prêt pour générer des certificats signés par votre CA pour votre cluster.

Kubernetes

Je pars du principe que le cert-manager est installée et que vous avez un ingress controller d’installé (qui utilise les secrets pour les certificats).

Dans le namespace de votre ingress controller (kube-system de mon côté) vous devez créer un service account qui pourra se connecter à Vault (vault-issuer que nous avons créé précédemment) :

cat <<EOF | kubectl apply -f -n kube-system - 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault-issuer
EOF

Ensuite il faut créer un issuer dans cert-manager :

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: kube-issuer
  namespace: istio-system
spec:
  vault:
    path: pki/sign/server-cert-for-kube-dev
    server: https://vault.security.svc:8200
    caBundle: CAbase64
    auth:
      kubernetes:
        role: vault-issuer
        mountPath: /v1/auth/kubernetes
        secretRef:
          name: vault-issuer-token-ftdxh
          key: token

Ici je pars du principe que votre Vault est sécurisé en interne. S’il ne l’est pas vous pouvez enlever.

Si vous souhaitez par exemple ensuite générer un certificat pour test.domain.tld :

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: test.domain.tld
  namespace: kube-system
spec:
  commonName: "test.domain.tld"
  dnsNames:
  - "domain.tld"
  duration: 1440h
  renewBefore: 72h
  issuerRef:
    kind: Issuer
    name: kube-issuer
  secretName: test-domain
  usages:
  - digital signature
  - key encipherment
  - server auth

Ceci va générer un certificat dans cert-manager et ajouter le ca le cert et la key dans un secret test-domain.
Vous pourrez utiliser ce dernier dans votre ingress controller afin d’avoir un certificat valide.

En cas de problème avec votre certificat vous pouvez valider que tout est ok côté cert-manager en regardant les status dans kubernetes :

$ kubectl get issuer -n kube-system
  NAME          READY   AGE
  kube-issuer   True    12d

Ou encore :

kubectl get certificate -n kube-system
NAME                READY   SECRET              AGE
test.domain.tld     True    test.domain         11d

Ce n’est pas si simple d’utiliser Vault avec Kubernetes mais au final la génération de certificat automatique avec auto renew est vraiment une fonctionnalité intéressante et permet de simplifier ce process qui peut être assez lourd dans beaucoup d’organisations.