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
- Seguridad Centralizada : Permite almacenar secretos en un lugar seguro, controlando quién puede acceder a ellos.
- Gestión de Certificados : Facilita la emisión y gestión de certificados SSL/TLS.
- Integración con Otros Servicios : Se integra fácilmente con otros servicios de Azure y aplicaciones externas.
- 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:
- main.tf : Contiene la definición de los recursos principales.
- locals.tf : Define variables locales.
- outputs.tf : Define los outputs del módulo.
- variables.tf : Define las variables que se utilizarán para configurar el módulo.
- terraform.tfvars : Contiene los valores de las variables que se aplicarán.
- key_vault.tftest.hcl : Contiene los tests para validar nuestro módulo.
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
}
}
}
}
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 }
}
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 : ""
}
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'."
}
}
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"
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?
-
Definición de comandos : Cada archivo de prueba define una serie de comandos de Terraform que se ejecutarán, como
apply
ydestroy
. -
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. - Variables de prueba : Es posible definir variables específicas para los tests, asegurando que las pruebas se ejecuten en un entorno controlado.
- 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"
}
}
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.