En esta serie de artículos, exploraremos cómo desplegar infraestructura en Azure utilizando Terraform y Terragrunt. Empezaremos con un caso de uso fundamental: acceder a secretos almacenados en Azure Key Vault mediante Terraform.
Introducción a Azure Key Vault
Azure Key Vault es un servicio de administración de claves y secretos en la nube de Microsoft Azure. Permite almacenar y controlar el acceso a las credenciales, claves de cifrado y otros secretos utilizados por las aplicaciones y servicios en la nube.
Estructura del Proyecto
Antes de profundizar en el código, es esencial entender la estructura del proyecto:
├── env
│ ├── code
│ │ └── appservice
│ │ └── app
│ │ └── front
│ │ ├── backend.tf
│ │ ├── data.tf
│ │ ├── endpoint.tf
│ │ ├── locals.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── dev
│ │ ├── appservice
│ │ │ └── app
│ │ │ └── front
│ │ │ ├── terragrunt.hcl
│ │ │ └── vars.tfvars
│ │ └── varEnvironment.hcl
│ └── modules
│ └── tf
│ └── spn
│ ├── data.tf
│ ├── main.tf
│ ├── output.tf
│ ├── provider.tf
│ └── variables.tf
├── terragrunt.hcl
├── varCommon.hcl
└── varDataState.hcl
La carpeta env
contiene la configuración específica del entorno, mientras que code
alberga los archivos de configuración de Terraform para el despliegue de la aplicación web. La carpeta dev
contiene archivos de configuración específicos del entorno de desarrollo. terragrunt.hcl
, varCommon.hcl
, y varDataState.hcl
son archivos de configuración para Terragrunt y variables comunes.
En el directorio modules/tf/spn
, encontramos la configuración específica para nuestro módulo Terraform.
Configuración del Módulo Terraform
Ahora, veamos cómo está configurado nuestro módulo Terraform para acceder a Azure Key Vault:
data.tf
data "azurerm_key_vault" "kv" {
name = var.kv_name
resource_group_name = var.kv_resource_group
provider = azurerm.kv
}
- data: Esta palabra clave indica que estamos obteniendo datos de un recurso existente en Azure, en este caso, un Azure Key Vault.
- «azurerm_key_vault»: Es el tipo de recurso que estamos consultando, en este caso, un Azure Key Vault.
- «kv»: Es el nombre arbitrario que le estamos dando a este bloque de datos. Puedes usar este nombre más tarde para referirte a los datos recuperados en otras partes de tu configuración.
- name: Es el nombre del Azure Key Vault que queremos consultar. Esta es una variable que se espera que esté definida en otra parte de tu configuración, y aquí estamos usando
var.kv_name
para hacer referencia a esa variable. - resource_group_name: Es el nombre del grupo de recursos en el que se encuentra el Azure Key Vault que queremos consultar. Al igual que con el nombre del Key Vault, esta es una variable (
var.kv_resource_group
) que se espera que esté definida en otra parte de tu configuración. - provider: Aquí estamos especificando el proveedor de recursos de Azure que proporcionará los datos del Key Vault. En este caso, estamos usando
azurerm.kv
, lo que significa que estamos utilizando el proveedor de recursos de Azure para Key Vault. Este proveedor debe estar configurado correctamente en tu archivo de configuración Terraform.
main.tf
resource "azuread_application" "kv_app" {
display_name = var.kv_sp_display_name
}
resource "azuread_service_principal" "kv_sp" {
application_id = azuread_application.kv_app.application_id
}
resource "azuread_service_principal_password" "kv_sp_password" {
service_principal_id = azuread_service_principal.kv_sp.id
end_date_relative = "8760h" # 1 year
}
resource "random_password" "kv_sp_password" {
length = 16
special = true
}
resource "azurerm_key_vault_access_policy" "kv_policy" {
key_vault_id = data.azurerm_key_vault.kv.id
tenant_id = var.tenant_id
object_id = azuread_service_principal.kv_sp.object_id
secret_permissions = [
"Get",
"List",
]
}
- resource «azuread_application» «kv_app»: Aquí estamos creando una nueva aplicación en Azure Active Directory (AD). Esta aplicación será utilizada para autenticar el servicio principal que queremos asociar con el Azure Key Vault. El nombre
kv_app
es el identificador que utilizaremos más adelante para referirnos a esta aplicación en otras partes de nuestro código. - display_name = var.kv_sp_display_name: Establecemos el nombre de visualización de la aplicación utilizando una variable llamada
kv_sp_display_name
. Esta variable debería contener el nombre que deseamos asignar a la aplicación en Azure AD. - resource «azuread_service_principal» «kv_sp»: Ahora creamos un servicio principal en Azure AD asociado a la aplicación que acabamos de crear. Esto nos permite autenticar la aplicación y otorgarle permisos en Azure.
- application_id = azuread_application.kv_app.application_id: Aquí estamos obteniendo el ID de la aplicación recién creada (
kv_app
) para asociarlo con el servicio principal. Esto establece la relación entre la aplicación y el servicio principal. - resource «azuread_service_principal_password» «kv_sp_password»: Estamos generando una contraseña para el servicio principal que acabamos de crear. Esta contraseña será utilizada para autenticarse en Azure y acceder a los recursos protegidos.
- service_principal_id = azuread_service_principal.kv_sp.id: Asociamos la contraseña generada con el servicio principal que creamos anteriormente. Esto asegura que la contraseña esté vinculada al servicio principal correcto.
- end_date_relative = «8760h» # 1 year: Establecemos la fecha de vencimiento de la contraseña. En este caso, estamos configurando la contraseña para que expire después de un año (
8760
horas). - resource «random_password» «kv_sp_password»: Aquí generamos una contraseña aleatoria para el servicio principal. Esta contraseña se utilizará como la credencial de acceso para el servicio principal.
- length = 16: Especificamos la longitud de la contraseña generada. En este caso, la contraseña tendrá una longitud de 16 caracteres.
- special = true: Indicamos que la contraseña generada debe incluir caracteres especiales.
- resource «azurerm_key_vault_access_policy» «kv_policy»: Finalmente, creamos una política de acceso para el Azure Key Vault. Esta política define quién puede acceder al Key Vault y qué permisos tienen.
- key_vault_id = data.azurerm_key_vault.kv.id: Especificamos el ID del Key Vault al que queremos aplicar esta política de acceso. Este ID se obtiene utilizando el bloque de datos
data.azurerm_key_vault
que vimos anteriormente. - tenant_id = var.tenant_id: Definimos el ID del inquilino de Azure AD que tiene permiso para acceder al Key Vault. Este es un parámetro que esperamos que se proporcione a través de una variable llamada
tenant_id
. - object_id = azuread_service_principal.kv_sp.object_id: Establecemos el ID del objeto del servicio principal que tiene acceso al Key Vault. Esto vincula la política de acceso al servicio principal que creamos anteriormente.
- secret_permissions = [«Get», «List»]: Especificamos los permisos que se otorgan al servicio principal para trabajar con secretos en el Key Vault. En este caso, el servicio principal puede obtener y listar secretos.
output.tf
output "kv_client_id" {
value = azuread_service_principal.kv_sp.application_id
}
output "kv_client_secret" {
value = azuread_service_principal_password.kv_sp_password.value
}
output «kv_client_id»: Aquí estamos definiendo un bloque de salida (output) con el nombre «kv_client_id». Esto significa que queremos generar un valor específico que podamos utilizar fuera de este módulo o configuración. Este valor se utilizará para representar el ID del cliente (client ID) del servicio principal asociado al Azure Key Vault.
value = azuread_service_principal.kv_sp.application_id: Estamos asignando un valor a este output. El valor que queremos asignar es el ID de aplicación (application ID) del servicio principal asociado al Azure Key Vault. Esta línea especifica que queremos que el valor de «kv_client_id» sea igual al ID de aplicación de ese servicio principal.
output «kv_client_secret»: Similar al primer output, aquí estamos definiendo otro bloque de salida llamado «kv_client_secret». Este output generará un valor que representará el secreto del cliente (client secret) asociado al Azure Key Vault.
value = azuread_service_principal_password.kv_sp_password.value: Estamos asignando un valor a este output. El valor que queremos asignar es la contraseña generada para el servicio principal. Esto garantiza que el secreto del cliente sea accesible fuera de este módulo o configuración.
provider.tf
provider "azurerm" {
alias = "kv"
subscription_id = var.kv_subscription_id
tenant_id = var.tenant_id
features {}
}
provider "azuread" {
tenant_id = var.tenant_id
}
- provider «azurerm»: Aquí estamos declarando un proveedor de recursos para Azure en nuestra configuración de Terraform. Este proveedor nos permite interactuar con los recursos de Azure.
- alias = «kv»: Establecemos un alias para este proveedor, que en este caso es «kv». Esto nos permite diferenciar entre diferentes proveedores de Azure si tenemos varios en nuestra configuración.
- subscription_id = var.kv_subscription_id: Especificamos la ID de la suscripción de Azure que queremos utilizar para desplegar nuestros recursos. Esta ID se espera que esté definida en una variable llamada
kv_subscription_id
. - tenant_id = var.tenant_id: Establecemos el ID del inquilino (tenant) de Azure Active Directory (Azure AD) con el que queremos autenticarnos para realizar operaciones en Azure. Esta ID también se espera que esté definida en una variable llamada
tenant_id
. - features {}: Aquí podemos especificar características adicionales del proveedor, pero en este caso no se están especificando opciones adicionales.
- provider «azuread»: Declaración de otro proveedor, pero en este caso para Azure Active Directory (Azure AD). Este proveedor nos permite interactuar con los recursos relacionados con la gestión de identidades en Azure AD.
- tenant_id = var.tenant_id: Al igual que con el proveedor de recursos de Azure, aquí especificamos el ID del inquilino de Azure AD con el que queremos autenticarnos. Se espera que este ID esté definido en una variable llamada
tenant_id
.
variables.tf
variable "kv_sp_display_name" {
description = "Display name for the Key Vault Service Principal"
type = string
}
variable "tenant_id" {
description = "Tenant ID"
type = string
}
variable "kv_subscription_id" {
description = "Subscription ID where the Key Vault is located"
type = string
}
variable "kv_name" {
description = "Name of the Key Vault"
type = string
}
variable "kv_resource_group" {
description = "Resource group of the Key Vault"
type = string
}
- variable «kv_sp_display_name»: Aquí estamos declarando una variable llamada «kv_sp_display_name». Esta variable se utilizará para almacenar el nombre de visualización (display name) del servicio principal del Key Vault.
- description = «Display name for the Key Vault Service Principal»: Proporcionamos una descripción para esta variable, lo que facilita la comprensión de su propósito. En este caso, la descripción indica que esta variable almacena el nombre de visualización del servicio principal del Key Vault.
- type = string: Establecemos el tipo de datos que puede contener esta variable. En este caso, la variable «kv_sp_display_name» solo puede contener valores de tipo cadena (string).
- variable «tenant_id»: Declaración de otra variable llamada «tenant_id». Esta variable se utilizará para almacenar el ID del inquilino (tenant) de Azure Active Directory (Azure AD).
- description = «Tenant ID»: Proporcionamos una descripción para la variable «tenant_id», indicando que se utiliza para almacenar el ID del inquilino de Azure AD.
- type = string: Al igual que antes, especificamos que esta variable solo puede contener valores de tipo cadena (string).
- variable «kv_subscription_id»: Declaramos otra variable llamada «kv_subscription_id». Esta variable se utilizará para almacenar el ID de la suscripción de Azure donde se encuentra el Key Vault.
- description = «Subscription ID where the Key Vault is located»: Proporcionamos una descripción para la variable «kv_subscription_id», indicando que se utiliza para almacenar el ID de la suscripción de Azure donde se encuentra el Key Vault.
- type = string: Al igual que las otras variables, especificamos que esta variable solo puede contener valores de tipo cadena (string).
- variable «kv_name»: Declaración de otra variable llamada «kv_name». Esta variable se utilizará para almacenar el nombre del Key Vault.
- description = «Name of the Key Vault»: Proporcionamos una descripción para la variable «kv_name», indicando que se utiliza para almacenar el nombre del Key Vault.
- type = string: Especificamos que esta variable solo puede contener valores de tipo cadena (string).
- variable «kv_resource_group»: Declaramos otra variable llamada «kv_resource_group». Esta variable se utilizará para almacenar el nombre del grupo de recursos (resource group) donde se encuentra el Key Vault.
- description = «Resource group of the Key Vault»: Proporcionamos una descripción para la variable «kv_resource_group», indicando que se utiliza para almacenar el nombre del grupo de recursos donde se encuentra el Key Vault.
- type = string: Especificamos que esta variable solo puede contener valores de tipo cadena (string).
Uso del Módulo para Leer Secretos en Azure Key Vault
Una vez que hemos creado nuestro módulo para gestionar la creación de un servicio principal y la configuración de permisos en Azure Key Vault, podemos integrarlo fácilmente en otros proyectos de Terraform. A continuación, te mostraremos cómo puedes utilizar este módulo en un ejemplo práctico.
Supongamos que tienes una aplicación web en Azure App Service que necesita acceder a secretos almacenados en un Azure Key Vault. Vamos a ver cómo podemos utilizar nuestro módulo para lograr esto.
Utilizando el Módulo en el Proyecto de la Aplicación Web
Ahora, dentro de la configuración de tu aplicación web en Azure App Service (ubicada en env/code/appservice/app/front
), agrega el siguiente código:
main.tf
module "kv_service_principal" {
source = "../../../../modules/tf/spn"
kv_sp_display_name = "KeyVaultServicePrincipal"
tenant_id = var.tenant_id
kv_subscription_id = var.kv_subscription_id
kv_name = var.kv_name
kv_resource_group = var.kv_resource_group
}
module "kv_service_principal"
: Estamos declarando un módulo llamado «kv_service_principal», que nos permitirá configurar un servicio principal en Azure AD para acceder al Key Vault.source = "../../../../modules/tf/spn"
: Esta línea especifica la ubicación del módulo que estamos utilizando. En este caso, el módulo se encuentra en la ruta relativa../../../../modules/tf/spn
. Esto significa que Terraform buscará el módulo en el directoriomodules/tf/spn
en relación con el directorio actual.kv_sp_display_name = "OpsGPTKeyVaultServicePrincipal"
: Aquí establecemos el nombre de visualización (display name) del servicio principal que se creará. Este nombre se utilizará para identificar el servicio principal en Azure AD.tenant_id = var.tenant_id
: Pasamos el ID del inquilino (tenant ID) como un argumento al módulo. Este ID se utiliza para identificar la instancia de Azure AD a la que pertenece nuestra aplicación.kv_subscription_id = var.kv_subscription_id
: Pasamos el ID de la suscripción de Azure como un argumento al módulo. Este ID se utiliza para identificar la suscripción de Azure en la que se encuentra el Key Vault.kv_name = var.kv_name
: Pasamos el nombre del Key Vault como un argumento al módulo. Este es el nombre del Key Vault al que queremos que nuestro servicio principal acceda.kv_resource_group = var.kv_resource_group
: Pasamos el nombre del grupo de recursos donde se encuentra el Key Vault como un argumento al módulo. Esto asegura que el servicio principal tenga acceso al Key Vault en el grupo de recursos adecuado.
Recursos de la Web App:
- AZURE_STORAGE_CONNECTION_STRING: Es el nombre de la variable de entorno que se está configurando. En este caso, parece ser la cadena de conexión a un servicio de almacenamiento en Azure, posiblemente para acceder a un contenedor de Blob Storage u otro recurso de almacenamiento.
- =: Indica que se está asignando un valor a la variable de entorno.
- data.azurerm_key_vault_secret.kv_storage_connection_string.value: Esta es la parte clave de la línea. Veámosla en detalle:
- data.azurerm_key_vault_secret: Estamos accediendo a un recurso de tipo
azurerm_key_vault_secret
, que es un secreto almacenado en un Azure Key Vault. - .kv_storage_connection_string: Esto sugiere que estamos accediendo a un secreto específico dentro del Key Vault, posiblemente un valor de conexión relacionado con el almacenamiento.
- .value: Esto indica que queremos acceder al valor real del secreto, en lugar de solo a su metadato.
data.tf
data "azurerm_key_vault" "kv" {
name = var.kv_name
resource_group_name = var.kv_resource_group
provider = azurerm.kv
}
data "azurerm_key_vault_secret" "kv_storage_connection_string" {
name = "REDORBITA-STORAGE-CONNECTION-STRING"
key_vault_id = data.azurerm_key_vault.kv.id
provider = azurerm.kv
}
Primer bloque de datos (data «azurerm_key_vault» «kv»)
data "azurerm_key_vault" "kv"
: Este bloque define un recurso de tipoazurerm_key_vault
, que es un recurso de datos. Los recursos de datos se utilizan para consultar información existente en lugar de crear o modificar recursos como lo hacen los bloques de recursos normales.name = var.kv_name
: Aquí especificamos el nombre del Key Vault que queremos consultar. El valor proviene de una variable llamadakv_name
, que probablemente se haya definido en otra parte del código o se haya proporcionado como entrada al script.resource_group_name = var.kv_resource_group
: Esto indica en qué grupo de recursos se encuentra el Key Vault que estamos consultando. Al igual que con el nombre, el valor proviene de una variable llamadakv_resource_group
.provider = azurerm.kv
: Estamos especificando el proveedor de recursos para este recurso de datos. Esto establece que Terraform debe usar el proveedorazurerm
(que debe estar configurado previamente) y específicamente el recursokv
dentro de ese proveedor para obtener información sobre el Key Vault.
Segundo bloque de datos (data «azurerm_key_vault_secret» «kv_storage_connection_string»)
data "azurerm_key_vault_secret" "kv_storage_connection_string"
: Este bloque define un recurso de tipoazurerm_key_vault_secret
, que también es un recurso de datos. Este bloque se utiliza para recuperar un secreto específico almacenado dentro del Key Vault.name = "REDORBITA-STORAGE-CONNECTION-STRING"
: Aquí especificamos el nombre del secreto que queremos recuperar del Key Vault. En este caso, estamos buscando un secreto con el nombre «REDORBITA-STORAGE-CONNECTION-STRING». Este nombre debe coincidir exactamente con el nombre del secreto en el Key Vault.key_vault_id = data.azurerm_key_vault.kv.id
: Esto especifica el ID del Key Vault del que queremos recuperar el secreto. Utilizamos la información obtenida del primer bloque de datos (data "azurerm_key_vault" "kv"
) para obtener el ID del Key Vault.provider = azurerm.kv
: Al igual que en el primer bloque de datos, estamos especificando el proveedor de recursos para este recurso de datos. Estamos utilizando el mismo proveedorazurerm
y el mismo recursokv
para acceder al Key Vault.
output.tf
output "client_id" {
value = module.kv_service_principal.kv_client_id
}
output "client_secret" {
value = module.kv_service_principal.kv_client_secret
}
output "client_id"
: Este es un bloque de salida en Terraform. Se utiliza para definir valores que serán mostrados como resultado cuando se ejecute Terraform. En este caso, se está definiendo un valor llamado «client_id».value = module.kv_service_principal.kv_client_id
: Aquí se especifica el valor que se asociará con la salida «client_id». Terraform accede al valor del módulokv_service_principal
utilizandomodule.kv_service_principal
y luego accede al atributokv_client_id
de ese módulo.output "client_secret"
: Similar al primer bloque de salida, este define otro valor de salida llamado «client_secret».value = module.kv_service_principal.kv_client_secret
: Al igual que en el primer caso, aquí Terraform accede al valor del módulokv_service_principal
utilizandomodule.kv_service_principal
y luego accede al atributokv_client_secret
de ese módulo.
provider.tf
provider "azurerm" {
alias = "kv"
subscription_id = var.kv_subscription_id
tenant_id = var.tenant_id
features {}
}
provider "azurerm"
: Este es un bloque de proveedor en Terraform. Se utiliza para configurar un proveedor específico para interactuar con los recursos de Azure.alias = "kv"
: Con esta línea, se está asignando un alias al proveedor. Los alias se utilizan para distinguir entre diferentes configuraciones del mismo proveedor. En este caso, el alias «kv» se utilizará para referirse a este proveedor específico en otros lugares del código.subscription_id = var.kv_subscription_id
: Aquí se especifica la ID de la suscripción de Azure que se utilizará para interactuar con los recursos. El valor proviene de una variable llamadakv_subscription_id
.tenant_id = var.tenant_id
: Esto establece el ID del inquilino de Azure que se utilizará para autenticar las solicitudes al proveedor de Azure. El valor proviene de una variable llamadatenant_id
.features {}
: En este caso, no se están configurando características específicas del proveedor, por lo que este bloque está vacío. Sin embargo, se pueden configurar características adicionales del proveedor dentro de este bloque si es necesario.
variables.tf
variable "tenant_id" {
description = "Tenant ID"
type = string
}
variable "kv_subscription_id" {
description = "Subscription ID where the Key Vault is located"
type = string
}
variable "kv_name" {
description = "Name of the Key Vault"
type = string
}
variable "kv_resource_group" {
description = "Resource group of the Key Vault"
type = string
}
variable "tenant_id"
: Este es un bloque de declaración de variable en Terraform. Se utiliza para definir una variable que puede ser utilizada en el código Terraform. En este caso, la variable se llama «tenant_id».description = "Tenant ID"
: Aquí se proporciona una descripción para la variable. La descripción es útil para documentar el propósito o el uso de la variable.type = string
: Esta línea especifica el tipo de la variable. En este caso, la variable «tenant_id» es de tipo string, lo que significa que solo aceptará valores de cadena de texto.variable "kv_subscription_id"
,variable "kv_name"
,variable "kv_resource_group"
: Estos son bloques de declaración de variables adicionales. Cada uno sigue el mismo formato que el bloque anterior, pero define variables con nombres diferentes y posiblemente usos diferentes en el código.description
: Cada variable tiene una descripción que documenta su propósito.type
: Al igual que antes, se especifica el tipo de cada variable. En este caso, todas las variables son de tipo string.
varCommon.hcl
locals {
kv_name = "KV-REDORBITA-01"
kv_resource_group = "RG-REDORBITA-01"
kv_subscription_id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
tenant_id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
}
inputs = {
kv_name = local.kv_name
kv_resource_group = local.kv_resource_group
kv_subscription_id = local.kv_subscription_id
tenant_id = local.tenant_id
}
- Bloque locals: Aquí se define un bloque locals, que se utiliza para definir variables locales en Terragrunt. Estas variables locales son específicas de este archivo y pueden ser utilizadas dentro del mismo.
Definición de variables locales:
- kv_name: Esta variable local contiene el nombre del Key Vault.
- kv_resource_group: Contiene el nombre del grupo de recursos donde está ubicado el Key Vault.
- kv_subscription_id: Esta variable almacena el ID de suscripción de Azure donde se encuentra el Key Vault.
- tenant_id: Almacena el ID del inquilino de Azure.
- Bloque inputs: Aquí se define un bloque inputs, que especifica los valores de entrada para el módulo o configuración de Terraform. Estos valores son proporcionados al módulo o configuración principal para su uso.
Definición de los valores de entrada:
- kv_name, kv_resource_group, kv_subscription_id, tenant_id: Cada uno de estos valores de entrada se asigna a las variables locales definidas anteriormente.
terragrunt.hcl
###############################
# KeyVault Module #
###############################
kv_name = "${include.varCommon.inputs.kv_name}"
kv_resource_group = "${include.varCommon.inputs.kv_resource_group}"
kv_subscription_id = "${include.varCommon.inputs.kv_subscription_id}"
tenant_id = "${include.varCommon.inputs.tenant_id}"
- Asignación de valores a través de interpolación: En este archivo, los valores de las variables locales definidas en varCommon.hcl se asignan a las variables de entrada del módulo KeyVault. Se utiliza la interpolación «${}» para insertar los valores de las variables locales.
:wq!