DEV Community

Cover image for Cómo utilizar el módulo de Terraform para Azure Key Vault
Daniel J. Saldaña
Daniel J. Saldaña

Posted on • Originally published at danieljsaldana.dev on

Cómo utilizar el módulo de Terraform para Azure Key Vault

En este post, te explicaré cómo usar un módulo de Terraform para desplegar un Azure Key Vault con varios recursos relacionados. Este módulo está diseñado para ser reutilizable y configurable según tus necesidades. A continuación, te detallaré los archivos necesarios y cómo estructurarlos.

¿Qué es Azure Key Vault?

Azure Key Vault es un servicio de Microsoft Azure diseñado para almacenar y acceder a secretos de manera segura. Los secretos pueden ser contraseñas, claves API, certificados y otros datos sensibles. Key Vault proporciona una manera segura y centralizada para gestionar estos secretos, reduciendo el riesgo de exposición no autorizada.

Beneficios de usar Azure Key Vault

  1. Seguridad Centralizada : Permite almacenar secretos en un lugar seguro, controlando quién puede acceder a ellos.
  2. Gestión de Certificados : Facilita la emisión y gestión de certificados SSL/TLS.
  3. Integración con Otros Servicios : Se integra fácilmente con otros servicios de Azure y aplicaciones externas.
  4. Auditoría y Supervisión : Proporciona capacidades de auditoría y monitoreo para rastrear el acceso y uso de los secretos.

Estructura del proyecto

Nuestro proyecto de Terraform constará de los siguientes archivos:

  1. main.tf : Contiene la definición de los recursos principales.
  2. locals.tf : Define variables locales.
  3. outputs.tf : Define los outputs del módulo.
  4. variables.tf : Define las variables que se utilizarán para configurar el módulo.
  5. terraform.tfvars : Contiene los valores de las variables que se aplicarán.
  6. key_vault.tftest.hcl : Contiene los tests para validar nuestro módulo.

Estructura del proyecto de Terraform

Archivo main.tf

El archivo main.tf es el corazón de nuestro módulo, donde definimos los recursos principales que queremos gestionar, incluyendo Key Vault, secretos, claves y configuraciones de diagnóstico. Este archivo debe incluirse en la raíz de tu proyecto de Terraform.

Contenido del archivo main.tf:

provider "azurerm" {
  features {}
  # Asegúrate de configurar la autenticación de Azure,
  # puede ser mediante variables de entorno, archivos de configuración, etc.
}

data "azurerm_client_config" "current" {}

resource "random_string" "random" {
  for_each = local.secrets_without_value
  length = 16
  special = true
}

resource "random_string" "secrets_random" {
  for_each = local.secrets_without_value
  length = 16
  special = true
  keepers = {
    key = each.key
  }
}

resource "azurerm_key_vault" "vault" {
  count = var.create_resource ? 1 : 0

  name = var.name
  location = var.location
  resource_group_name = var.resource_group_name
  tenant_id = data.azurerm_client_config.current.tenant_id
  sku_name = var.sku_name
  soft_delete_retention_days = 7
  tags = var.tags

  access_policy {
    tenant_id = data.azurerm_client_config.current.tenant_id
    object_id = local.current_user_id_terraform

    key_permissions = var.key_permissions
    secret_permissions = var.secret_permissions
  }

  access_policy {
    tenant_id = data.azurerm_client_config.current.tenant_id
    object_id = local.current_user_id

    key_permissions = var.key_permissions
    secret_permissions = var.secret_permissions
  }
}

resource "azurerm_key_vault_key" "key" {
  for_each = length(var.keys) > 0 ? var.keys : {}

  name = each.key
  key_type = each.value.key_type
  key_size = each.value.key_size
  key_opts = each.value.key_ops
  key_vault_id = azurerm_key_vault.vault[0].id
}

resource "azurerm_key_vault_secret" "secret" {
  for_each = var.secrets

  name = each.key
  value = coalesce(each.value.value, lookup(random_string.secrets_random, each.key, { result = "" }).result)
  key_vault_id = azurerm_key_vault.vault[0].id
}

resource "azurerm_storage_account" "diaglogs" {
  count = length(azurerm_key_vault.vault) > 0 ? 1 : 0
  depends_on = [azurerm_key_vault.vault]

  name = "${replace(substr(var.name, 0, 8), "-", "")}diaglogs"
  resource_group_name = var.resource_group_name
  location = var.location
  account_tier = var.account_tier
  account_replication_type = var.account_replication_type
}

resource "azurerm_monitor_diagnostic_setting" "monitor_diagnostic_setting" {
  count = length(azurerm_key_vault.vault) > 0 ? 1 : 0
  depends_on = [azurerm_storage_account.diaglogs, azurerm_key_vault.vault]

  name = "${substr(var.name, 0, 8)}-logs"
  target_resource_id = azurerm_key_vault.vault[0].id
  storage_account_id = azurerm_storage_account.diaglogs[0].id

  enabled_log {
    category = "AuditEvent"
  }

  metric {
    category = "AllMetrics"
    enabled = true
  }
}

resource "azurerm_storage_management_policy" "storage_management_policy" {
  depends_on = [azurerm_storage_account.diaglogs]
  count = length(azurerm_key_vault.vault) > 0 ? 1 : 0
  storage_account_id = azurerm_storage_account.diaglogs[0].id

  rule {
    name = "Delete blob older than 30 days"
    enabled = true

    filters {
      blob_types = ["blockBlob"]
    }

    actions {
      base_blob {
        delete_after_days_since_modification_greater_than = 30
      }
      snapshot {
        delete_after_days_since_creation_greater_than = 30
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Explicación de main.tf

  • Proveedor de Azure : Configura el proveedor de Azure (azurerm), que es necesario para interactuar con los recursos de Azure.
  • Datos del Cliente : Obtiene la configuración actual del cliente de Azure, necesaria para configurar los recursos.
  • Generación de Cadenas Aleatorias : Crea cadenas aleatorias para los secretos que no tienen valor especificado.
  • Azure Key Vault : Define el recurso Key Vault, incluyendo políticas de acceso y configuraciones de SKU.
  • Claves y Secretos : Configura las claves y secretos dentro del Key Vault.
  • Cuenta de Almacenamiento : Crea una cuenta de almacenamiento para almacenar los logs de diagnóstico.
  • Configuración de Diagnóstico : Configura la supervisión y los logs de diagnóstico para el Key Vault.
  • Política de Gestión de Almacenamiento : Define las políticas de gestión para la cuenta de almacenamiento, como la eliminación de blobs antiguos.

Archivo locals.tf

En locals.tf definimos las variables locales que se usarán dentro del módulo, permitiendo la reutilización de configuraciones y la simplificación del código.

Contenido del archivo locals.tf:

locals {
  current_user_id_terraform = coalesce(var.msi_id, data.azurerm_client_config.current.object_id)
  current_user_id = "e6df5784-df99-4c39-a857-d9362d7ed8e0"
}

locals {
  secrets_without_value = { for k, v in var.secrets : k => v if v.value == null }
}

Enter fullscreen mode Exit fullscreen mode

Explicación de locals.tf

  • ID de Usuario Actual : Obtiene el ID del usuario actual, que se usará en las políticas de acceso del Key Vault.
  • Secretos sin Valor : Filtra los secretos que no tienen valor especificado para generar valores aleatorios.

Archivo outputs.tf

En outputs.tf definimos las salidas del módulo, es decir, los valores que queremos obtener después de aplicar la configuración de Terraform, como el ID del Key Vault y los nombres de las claves y secretos.

Contenido del archivo outputs.tf:

output "azurerm_key_vault_name" {
  description = "El nombre del servicio de Key Vault."
  value = length(azurerm_key_vault.vault) > 0 ? azurerm_key_vault.vault[0].name : ""
}

output "azurerm_key_vault_id" {
  description = "El ID del servicio de Key Vault."
  value = length(azurerm_key_vault.vault) > 0 ? azurerm_key_vault.vault[0].id : ""
}

output "azurerm_key_vault_key_names" {
  description = "Los nombres de las claves almacenadas en el servicio de Key Vault."
  value = length(azurerm_key_vault_key.key) > 0 ? { for k, v in azurerm_key_vault_key.key : k => v.name } : {}
}

output "azurerm_key_vault_secret_names" {
  description = "Los nombres de los secretos almacenados en el servicio de Key Vault."
  value = length(azurerm_key_vault_secret.secret) > 0 ? { for k, v in azurerm_key_vault_secret.secret : k => v.name } : {}
}

output "azurerm_storage_account_name" {
  description = "El nombre de la cuenta de almacenamiento de diagnóstico."
  value = length(azurerm_storage_account.diaglogs) > 0 ? azurerm_storage_account.diaglogs[0].name : ""
}

output "azurerm_storage_account_id" {
  description = "El ID de la cuenta de almacenamiento de diagnóstico."
  value = length(azurerm_storage_account.diaglogs) > 0 ? azurerm_storage_account.diaglogs[0].id : ""
}

output "azurerm_storage_account_primary_blob_endpoint" {
  description = "El punto de conexión del blob de la cuenta de almacenamiento de diagnóstico."
  value = length(azurerm_storage_account.diaglogs) > 0 ? azurerm_storage_account.diaglogs[0].primary_blob_endpoint : ""
}

Enter fullscreen mode Exit fullscreen mode

Explicación de outputs.tf

  • Nombres y IDs de Recursos : Proporciona los nombres y IDs del Key Vault, las claves y los secretos para facilitar su uso en otras partes de tu infraestructura.
  • Detalles de la Cuenta de Almacenamiento : Proporciona información sobre la cuenta de almacenamiento de diagnóstico, como el nombre y el punto de conexión del blob.

Archivo variables.tf

El archivo variables.tf contiene la definición de todas las variables que pueden ser configuradas por el usuario del módulo, proporcionando flexibilidad y personalización.

Contenido del archivo variables.tf:

variable "create_resource" {
  type = bool

  validation {
    condition = var.create_resource == true || var.create_resource == false
    error_message = "El valor de create_resource debe ser 'true' o 'false'."
  }
}

variable "name" {
  type = string
  description = "El nombre del recurso de Azure Key Vault."

  validation {
    condition = length(var.name) > 0
    error_message = "El valor de name no puede estar vacío."
  }
}

variable "location" {
  type = string
  description = "La ubicación de Azure donde se desplegará el recurso."

  validation {
    condition = contains(["eastus", "eastus2", "westus", "westus2", "centralus", "northcentralus", "southcentralus", "northeurope", "westeurope", "eastasia", "southeastasia", "japaneast", "japanwest", "australiaeast", "australiasoutheast", "australiacentral", "brazilsouth", "southindia", "centralindia", "westindia", "canadacentral", "canadaeast", "uksouth", "ukwest", "francecentral", "francesouth", "koreacentral", "koreasouth", "germanywestcentral", "norwayeast", "switzerlandnorth", "uaenorth", "southafricanorth", "southafricawest", "usgovvirginia", "usgoveast", "usgovarizona", "usgovtexas", "usdodcentral", "usdodeast"], var.location)
    error_message = "El valor de location debe ser una de las siguientes: eastus, eastus2, westus, westus2, centralus, northcentralus, southcentralus, northeurope, westeurope, eastasia, southeastasia, japaneast, japanwest, australiaeast, australiasoutheast, australiacentral, brazilsouth, southindia, centralindia, westindia, canadacentral, canadaeast, uksouth, ukwest, francecentral, francesouth, koreacentral, koreasouth, germanywestcentral, norwayeast, switzerlandnorth, uaenorth, southafricanorth, southafricawest, usgovvirginia, usgoveast, usgovarizona, usgovtexas, usdodcentral, usdodeast."
  }
}

variable "resource_group_name" {
  type = string
  description = "El nombre del Grupo de Recursos donde se ubicará el servicio de Key Vault."

  validation {
    condition = length(var.resource_group_name) > 0
    error_message = "El valor de resource_group_name no puede estar vacío."
  }
}

variable "sku_name" {
  type = string
  description = "El SKU del servicio Key Vault. Por ejemplo, 'standard' para el nivel estándar."

  validation {
    condition = contains(["standard"], var.sku_name)
    error_message = "El valor de sku_name debe ser 'standard'."
  }
}

variable "tags" {
  type = map(string)
  description = "Un mapa de etiquetas para asignar al servicio de Key Vault."

  validation {
    condition = length(var.tags) > 0
    error_message = "El mapa de etiquetas no puede estar vacío."
  }
}

variable "key_permissions" {
  type = list(string)
  description = "Lista de permisos de clave."

  validation {
    condition = length(var.key_permissions) > 0
    error_message = "La lista de permisos de clave no puede estar vacía."
  }
}

variable "secret_permissions" {
  type = list(string)
  description = "Lista de permisos de secreto."

  validation {
    condition = length(var.secret_permissions) > 0
    error_message = "La lista de permisos de secreto no puede estar vacía."
  }
}

variable "msi_id" {
  type = string
  description = "El ID del identificador de servicio administrado (MSI) que se utilizará para acceder al Key Vault."
  default = null
}

variable "keys" {
  description = "Mapa de claves para crear"
  type = map(object({
    value = string
    key_type = string
    key_size = number
    key_ops = list(string)
  }))
}

variable "secrets" {
  description = "Mapa de secretos para crear"
  type = map(object({
    value = optional(string)
  }))
}

variable "account_tier" {
  type = string
  description = "El nivel de la cuenta de almacenamiento."

  validation {
    condition = contains(["Standard"], var.account_tier)
    error_message = "El valor de account_tier debe ser 'Standard'."
  }
}

variable "account_replication_type" {
  type = string
  description = "El tipo de replicación de la cuenta de almacenamiento."

  validation {
    condition = contains(["LRS", "GRS", "RAGRS", "ZRS"], var.account_replication_type)
    error_message = "El valor de account_replication_type debe ser 'LRS', 'GRS', 'RAGRS' o 'ZRS'."
  }
}

Enter fullscreen mode Exit fullscreen mode

Explicación de variables.tf

  • Variables de Configuración : Define las variables necesarias para configurar el Key Vault, incluyendo el nombre, ubicación, SKU, grupo de recursos y políticas de acceso.
  • Validación de Variables : Asegura que las variables tienen valores válidos antes de aplicarlas.

Archivo terraform.tfvars

En terraform.tfvars establecemos los valores específicos para las variables definidas en variables.tf, permitiendo una fácil modificación de los parámetros del módulo sin cambiar el código fuente.

Contenido del archivo terraform.tfvars:

# Voult
create_resource = true
name = "vault-danieljsaldana"
location = "francecentral"
resource_group_name = "danieljsaldana"
sku_name = "standard"
tags = {
  Project = "Daniel J. Saldaña"
  Tier = "Gratis"
  Environment = "Producción"
}

secret_permissions = ["Get", "List", "Set", "Delete", "Recover", "Backup", "Restore", "Purge"]
key_permissions = ["Get", "List", "Update", "Create", "Import", "Delete", "Recover", "Backup", "Restore", "Decrypt", "Encrypt", "UnwrapKey", "WrapKey", "Verify", "Sign", "Purge", "Release", "Rotate", "GetRotationPolicy", "SetRotationPolicy"]

keys = {
  "key" = {
    key_type = "RSA"
    key_size = 2048
    key_ops = ["encrypt", "decrypt", "sign", "verify"]
    value = "test"
  }
}

secrets = {
  "secrets" = {
    name = "secret"
    value = null
  },
  "secrets2" = {
    name = "secret2"
    value = "test"
  }
}

# Storage Diaglogs
account_tier = "Standard"
account_replication_type = "LRS"

Enter fullscreen mode Exit fullscreen mode

Explicación de terraform.tfvars

  • Valores de Variables : Proporciona los valores específicos que se aplicarán a las variables definidas en variables.tf, personalizando la implementación del Key Vault.

Test de Terraform

Los tests de Terraform son una herramienta esencial para asegurar que nuestra infraestructura se despliega y configura correctamente. Estos tests permiten validar que los recursos se crean según nuestras especificaciones y que los valores y configuraciones son los esperados.

Terraform cuenta con un framework de pruebas llamado "Terraform Test", que utiliza archivos de configuración .tftest.hcl. Estos archivos contienen una serie de comandos y aserciones que se ejecutan para verificar que los recursos se han creado y configurado correctamente.

¿Cómo funcionan los tests de Terraform?

  1. Definición de comandos : Cada archivo de prueba define una serie de comandos de Terraform que se ejecutarán, como apply y destroy.
  2. Aserciones : Dentro de cada comando, se pueden definir aserciones (assert) que verifican condiciones específicas. Si alguna de estas aserciones falla, la prueba se considera fallida.
  3. Variables de prueba : Es posible definir variables específicas para los tests, asegurando que las pruebas se ejecuten en un entorno controlado.
  4. Ejecución : Los tests se ejecutan utilizando la CLI de Terraform, y los resultados indican si las pruebas han pasado o fallado.

Archivo key_vault.tftest.hcl

El archivo key_vault.tftest.hcl contiene los tests para validar nuestro módulo de Terraform, asegurando que todos los recursos se creen y configuren correctamente según nuestras especificaciones.

Contenido del archivo key_vault.tftest.hcl:

provider "azurerm" {
  features {}
}

run "create_resource_group" {
  command = apply
  variables {
    name = "danieljsaldana_test"
    location = "francecentral"
    tags = {
      Environment = "Testing"
    }
  }

  module {
    source = "app.terraform.io/danieljsaldana/resource_group/modules"
    version = "1.0.10"
  }

  # Check that the resource group name is correct
  assert {
    condition = azurerm_resource_group.resource_group[0].name == "danieljsaldana_test"
    error_message = "Invalid resource group name"
  }

  # Check that the resource group location is correct
  assert {
    condition = azurerm_resource_group.resource_group[0].location == "francecentral"
    error_message = "Invalid location for the resource group"
  }

  # Optionally, if you want to check a specific tag
  assert {
    condition = azurerm_resource_group.resource_group[0].tags["Environment"] == "Testing"
    error_message = "Invalid or missing 'Environment' tag for the resource group"
  }
}

run "create_random_string" {
  command = apply
  variables {
    length = 3
    special = false
  }
}

run "create_key_vault" {
  command = apply
  variables {
    create_resource = true
    name = run.create_ramdom_string.random.result
    location = "francecentral"
    resource_group_name = run.create_resource_group.resource_group_name
    sku_name = "standard"
    tags = {
      Environment = "Testing"
    }
    key_permissions = ["get", "list"]
    secret_permissions = ["get", "list"]
    keys = {
      "key1" = {
        key_type = "RSA"
        key_size = 2048
        key_ops = ["encrypt", "decrypt"]
      }
    }
    secrets = {
      "secret1" = {}
    }
  }

  # Check that the key vault name is correct

  # Check that the key vault location is correct
  assert {
    condition = azurerm_key_vault.vault[0].location == "francecentral"
    error_message = "Invalid location for the key vault"
  }

  # Optionally, if you want to check a specific tag
  assert {
    condition = azurerm_key_vault.vault[0].tags["Environment"] == "Testing"
    error_message = "Invalid or missing 'Environment' tag for the key vault"
  }

  # Check that the key vault has the correct key permissions
  assert {
    condition = azurerm_key_vault.vault[0].access_policy[0].key_permissions == ["get", "list"]
    error_message = "Invalid key permissions for the key vault"
  }

  # Check that the key vault has the correct secret permissions
  assert {
    condition = azurerm_key_vault.vault[0].access_policy[0].secret_permissions == ["get", "list"]
    error_message = "Invalid secret permissions for the key vault"
  }
}

Enter fullscreen mode Exit fullscreen mode

Explicación de key_vault.tftest.hcl

  • Crear Grupo de Recursos : Aplica y valida la creación de un grupo de recursos en Azure.
  • Generar Cadena Aleatoria : Crea una cadena aleatoria y valida su longitud y características.
  • Crear Key Vault : Aplica y valida la creación de un Key Vault con sus políticas de acceso, claves y secretos.
  • Aserciones : Verifica que los recursos se han creado con los nombres, ubicaciones y permisos correctos, asegurando que la infraestructura cumple con las especificaciones.

Estos archivos en conjunto permiten la creación y gestión efectiva de un Azure Key Vault y sus recursos asociados mediante Terraform, proporcionando una infraestructura segura y bien configurada en Azure. Los tests de Terraform garantizan que todos los componentes se implementen correctamente y funcionen según lo esperado, lo que es crucial para mantener la estabilidad y seguridad de la infraestructura. Implementar esta configuración te permitirá manejar secretos y claves de manera eficiente y segura, aprovechando las capacidades avanzadas de Azure Key Vault.

Top comments (0)