Sécurisez vos secret avec HashiCorp Vault : avec le PaaS GCP

Introduction

HashiCorp Vault est un outil de gestion des secrets populaire qui permet de centraliser et de sécuriser les secrets sensibles tels que les mots de passe, les jetons d’API, les certificats et toute sorte de données confidentielles. C’est donc un outil d’exploitation qui devient très rapidement indispensable. À titre personnel, j’en ai en particulier besoin pour mon article sur la gestion des clés SSH.

Cependant même si l’éditeur de la solution le conseil de l’installer sur une plateforme IaaS (un cluster de machine), pour des raisons de coût je pense qu’on peut obtenir un très bon compromis sur une installation de type PaaS. * IaaS apporterait la possibilité d’isoler au maximum les secrets du reste de l’infrastructure et comme beaucoup d’autres infrastructures sont dépendantes de ce service, cela permet aussi de la relancer en premier en cas de gros crash. * PaaS lui évite les contraintes de gestion des mises à jour des serveurs, les pannes matérielles et offre une tarification à l’utilisation qui est très intéressante pour de petites infrastructures.

Il n’y a cependant pas beaucoup de documentation selon moi sur l’utilisation de Hashicorp Vault sur du PaaS. Voici ici une aide sur comment déployer la solution Hashicorp sur une infrastructure PaaS :

En résumé, utiliser ces services permet de n’être facturé qu’as l’utilisation. Lectures de mot de passe au gestionnaire de secrets, décodage de flux, entraînera une facturation, mais si l’infrastructure “dort” elle ne coûtera rien.

Le schéma ci-dessous resume comment le service peut fonctionner sur du PaaS : image

Voici maintenant comment installer HashiCorp Vault sur Cloud Run en utilisant un backend de stockage Cloud Storage (GCS) et un chiffrement Cloud Key Management Service (KMS) sur GCP.

Prérequis

Avant de commencer, vous devez avoir les éléments suivants :

Étape 1 : Créer un service de compte

Pour que notre application puisse interagir avec les APIs dans GCP (accès aux objets du bucket, KMS, …). Il sera utilisé par notre service CloudRUN et nous lui donnerons les droits en respectant scrupuleusement le principe des moindres-privilèges.

# service_account.tf

# Create a new service account
resource "google_service_account" "vault_sa" {
  account_id = "vault-${var.environment}-kapable-info-sa"
}

Étape 2 : Confirgurer une Clé KMS

On initialise maintenant une clé KMS qui sera elle utilisé par hashicorp pour chiffrer les secrets avant de les déposer dans le bucket. Cette clé appelé “seal”, dans le verbiage Vault est une sécurité permettant de ne pas avoir “en clair” les données dans le bucket.

L’utilisation d’un service comme KMS pour ce chiffrement permet de ne pas avoir a “imprimer”/”stocker” des “unseal-key”, ces chaines de caractères demandés au lancement de Vault pour décoder les données présente dans Vault.

Voici le code terraform permettant de générer une tel clé dans GCP et de la rendre accessible a notre service-account préalablement généré.

# kms.tf

# Generate random string for each ressource name
resource "random_id" "vault_kms" {
  byte_length = 8
}

# A keyring to store our keys
resource "google_kms_key_ring" "vault" {
  name     = "${var.environment}-vault-${random_id.vault_kms.hex}"
  location = "europe-west1"
  project  = var.project
}

# The crypto-key ressources
resource "google_kms_crypto_key" "vault-key" {
  name     = "${var.environment}-vault-${random_id.vault_kms.hex}"
  key_ring = google_kms_key_ring.vault.id

  version_template {
    algorithm        = "GOOGLE_SYMMETRIC_ENCRYPTION"
    protection_level = "SOFTWARE"
  }
  # If we lost this key, then we won't be able to read our data
  lifecycle {
    prevent_destroy = true
  }
}

# Allow our application service account to use this key
data "google_iam_policy" "seal" {
  binding {
    role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
    members = [
      "serviceAccount:${google_service_account.vault_sa.email}",
    ]
  }
}
# Atach policy to our key
resource "google_kms_key_ring_iam_policy" "seal" {
  key_ring_id = google_kms_key_ring.vault.id
  policy_data = data.google_iam_policy.seal.policy_data
}

Étape 3 : Configurer un bucket de stockage

Voici mon code standard pour la création d’un bucket de stockage

# bucket.tf

# Define vars
locals {
  buckets = {
    vault = { name = "vault" }
  }
}

# Generate random string for each ressource name
resource "random_id" "vault_bucket" {
  byte_length = 8
}

# Create buckets
resource "google_storage_bucket" "buckets" {
  for_each      = local.buckets
  name          = "${var.environment}-data-${each.key}-${random_id.vault_bucket.hex}"
  location      = "europe-west1"
  force_destroy = true

  public_access_prevention = "enforced"
}

# Allow our application service account to use this bucket
data "google_iam_policy" "data" {
  binding {
    role = "roles/storage.admin"
    members = [
      "serviceAccount:${google_service_account.vault_sa.email}",
    ]
  }
}

# Atach policy to our key
resource "google_storage_bucket_iam_policy" "policy" {
  for_each    = local.buckets
  bucket      = google_storage_bucket.buckets[each.key].name
  policy_data = data.google_iam_policy.data.policy_data
}

Étape 4 : Générer un fichier de configuration pour le container

Voici le fichier de configuration qui sera utilisé par le service Vault :

# vault-server.hcl

default_max_request_duration = "90s"
disable_clustering           = false
disable_mlock                = true
ui                           = true

log_file = "/dev/stdout"

listener "tcp" {
  address = "[::]:8200"
  cluster_address = "[::]:8201"
  tls_disable = "true"
}

# Utilisation du KMS Vault
seal "gcpckms" {
  key_ring   = "${key_ring}"
  crypto_key = "${crypto_key}"
  region     = "${region}"
}

# Utilisation du stockage GCS
storage "gcs" {
  ha_enabled = "true"
}

On injecte ensuite le fichier ci-dessus dans le service GCP Secret Manager :

# config.tf

# Generate random string for each ressource name
resource "random_id" "config" {
  byte_length = 8
}

# Secret to store config file
resource "google_secret_manager_secret" "vault_config" {
  secret_id = "${var.environment}-vault-config-${random_id.vault-config.hex}"

  labels = {
    app         = "vault"
    environment = var.environment
  }

  replication {
    user_managed {
      replicas {
        location = "europe-west1"
      }
    }
  }
}

# Inject config into our secret and complete config-file with KMS values
resource "google_secret_manager_secret_version" "vault_config" {
  secret = google_secret_manager_secret.vault_config.id

  secret_data = templatefile("${path.module}/vault-server.hcl", {
    key_ring   = google_kms_key_ring.vault.name
    crypto_key = google_kms_crypto_key.vault-key.name
    region     = google_kms_key_ring.vault.location
  })
}

# Atach policy to our secret
data "google_iam_policy" "vault_config" {
  binding {
    role = "roles/secretmanager.secretAccessor"
    members = [
      "serviceAccount:${google_service_account.vault_sa.email}",
    ]
  }
}
resource "google_secret_manager_secret_iam_policy" "vault_config" {
  project     = var.project
  secret_id   = google_secret_manager_secret.vault_config.id
  policy_data = data.google_iam_policy.vault_config.policy_data
}

Étape 5 : Configurer le lancement de notre container

Maintenant il est possible d’enfin lancer notre container pour cela on utilise un module perso et on prend soin de surcharger l’entrypoint du container. En effet par défaut le container utilise un mode “dev” pour se lancer.

module "vault_service" {
  source         = "../modules/http-container"
  project        = var.project
  fqdn           = var.environment != "prod" ? "vault-${var.environment}.kapable.info" : "vault.kapable.info"
  docker_image   = "hashicorp/vault"
  allways_on     = false
  container_port = 8200
  cpus           = 2
  memory         = 2000
  command        = ["/bin/vault"]
  args           = ["server", "-config", "/etc/vault/config.hcl"]
  container_env = {
    SKIP_SETCAP           = 1
    GOOGLE_PROJECT        = var.project
    GOOGLE_STORAGE_BUCKET = google_storage_bucket.buckets["vault"].name
    VAULT_LOG_LEVEL       = "info"
    SKIP_CHOWN            = 1
    VAULT_ADDR            = "http://127.0.0.1"
    VAULT_API_ADDR        = "http://127.0.0.1"
 }
  volumes = {
    config = {
      secret_id      = google_secret_manager_secret.vault_config.id
      secret_version = google_secret_manager_secret_version.vault_config.version
      secret_file    = "config.hcl"
      mount_path     = "/etc/vault"
    }
   }
  depends_on = [google_kms_key_ring_iam_policy.seal]
}

Étape 6 : Initialiser le Vault

Conclusion

Ce guide vous a montré comment déployer et configurer Hashicorp Vault sur Cloud Run en utilisant un backend de stockage Cloud Storage (GCS) et un chiffrement Cloud Key Management Service (KMS) sur GCP.

Avantages de cette approche :

Points importants à retenir:

En conclusion, l’hébergement de Hashicorp Vault sur Cloud Run avec un backend de stockage GCS et un chiffrement KMS sur GCP offre une solution simple, évolutive et sécurisée pour la gestion des secrets de vos applications.