Cómo desplegar un servidor PostgreSQL Flexible en Azure con Terraform y Azure Key Vault

RMAG news

En este post, quiero compartir cómo desarrollé un módulo de Terraform para desplegar un servidor PostgreSQL flexible en Azure, integrando la gestión de secretos con Azure Key Vault. Este módulo permite implementar de manera eficiente y repetible un servidor PostgreSQL, almacenando de forma segura las credenciales en Key Vault y configurando reglas de firewall para acceso seguro.

Recursos de Azure utilizados

El módulo desplegará los siguientes recursos de Azure:

Azure Key Vault : Servicio para gestionar y mantener de manera segura secretos, claves y certificados.

Servidor PostgreSQL Flexible : Servicio de base de datos PostgreSQL administrado por Azure.

Reglas de Firewall para PostgreSQL : Configuraciones para controlar el acceso a la base de datos.

Este módulo es ideal para aquellos que desean implementar un servidor PostgreSQL seguro y bien configurado en Azure, aprovechando las capacidades de Azure Key Vault para la gestión de credenciales.

Contenido de main.tf

El archivo main.tf es el corazón del módulo, donde se definen los recursos principales que vamos a crear.

Configuración del Proveedor de Azure

Primero, configuramos el proveedor de Azure, que es esencial para gestionar cualquier recurso en Azure con Terraform.

provider “azurerm” {
features {}
}

Generación de una Contraseña Aleatoria

Utilizamos el recurso random_password para generar una contraseña segura y aleatoria para el administrador del servidor PostgreSQL. Esto garantiza que la contraseña cumpla con los requisitos de seguridad al incluir caracteres especiales, mayúsculas y minúsculas.

resource “random_password” “password” {
length = 16
special = true
upper = true
lower = true
override_special = “-“
}

Almacenamiento de Credenciales en Azure Key Vault

Para garantizar la seguridad de las credenciales del servidor PostgreSQL, almacenamos el nombre de usuario y la contraseña en Azure Key Vault. Este enfoque permite una gestión segura y centralizada de las credenciales.

resource “azurerm_key_vault_secret” “postgresql_username” {
count = var.create_resource ? 1 : 0

name = “POSTGRESQL-USERNAME”
value = var.administrator_login
key_vault_id = data.azurerm_key_vault.existing.id
}

resource “azurerm_key_vault_secret” “postgresql_password” {
count = var.create_resource ? 1 : 0

name = “POSTGRESQL-PASSWORD”
value = random_password.password.result
key_vault_id = data.azurerm_key_vault.existing.id
}

Configuración de las Reglas de Firewall

Configuramos las reglas de firewall para permitir el acceso público al servidor PostgreSQL, definiendo las direcciones IP de inicio y fin que tienen permitido conectarse.

resource “azurerm_postgresql_flexible_server_firewall_rule” “public_access” {
count = length(azurerm_postgresql_flexible_server.postgresql) > 0 ? 1 : 0
depends_on = [azurerm_postgresql_flexible_server.postgresql]

name = “AllowAll”
server_id = length(azurerm_postgresql_flexible_server.postgresql) > 0 ? azurerm_postgresql_flexible_server.postgresql[0].id : “”
start_ip_address = var.start_ip_address
end_ip_address = var.end_ip_address
}

Despliegue del Servidor PostgreSQL Flexible

Finalmente, definimos el recurso para desplegar el servidor PostgreSQL flexible. Aquí especificamos parámetros importantes como el nombre, la ubicación, la versión de PostgreSQL, el SKU, las etiquetas, la zona de disponibilidad, el almacenamiento, la retención de backups y las credenciales del administrador.

resource “azurerm_postgresql_flexible_server” “postgresql” {
count = var.create_resource && length(azurerm_key_vault_secret.postgresql_username) > 0 && length(azurerm_key_vault_secret.postgresql_password) > 0 ? 1 : 0

name = var.name
resource_group_name = var.resource_group_name
location = var.location
version = var.version_postgresql
sku_name = var.sku_name
tags = var.tags

# Zone
zone = var.zone

# Storage
storage_mb = var.storage_mb
storage_tier = var.storage_tier

# Backup
backup_retention_days = var.backup_retention_days
geo_redundant_backup_enabled = var.geo_redundant_backup_enabled

# Administrator Login
administrator_login = azurerm_key_vault_secret.postgresql_username[0].value
administrator_password = azurerm_key_vault_secret.postgresql_password[0].value
}

Contenido de data.tf

En este archivo, obtenemos el ID del Key Vault existente para utilizarlo en el almacenamiento de secretos.

data “azurerm_key_vault” “existing” {
name = var.key_vault_name
resource_group_name = var.resource_group_name
}

Contenido de outputs.tf

Este archivo define las salidas que nos interesan del módulo, como el ID y el nombre del servidor PostgreSQL, y el ID del Key Vault.

output “postgresql_id” {
value = length(azurerm_postgresql_flexible_server.postgresql) > 0 ? azurerm_postgresql_flexible_server.postgresql[0].id : “”
description = “El ID del servidor de PostgreSQL.”
}

output “postgresql_name” {
value = length(azurerm_postgresql_flexible_server.postgresql) > 0 ? azurerm_postgresql_flexible_server.postgresql[0].name : “”
description = “El nombre del servidor de PostgreSQL.”
}

output “key_vault_name” {
value = data.azurerm_key_vault.existing.id
description = “El ID del Key Vault existente.”
}

Contenido de variables.tf

Definimos las variables necesarias para nuestro módulo, asegurando flexibilidad y reutilización en diferentes entornos y configuraciones.

variable “create_resource” {
type = bool
default = true

validation {
condition = var.create_resource == true || var.create_resource == false
error_message = “El valor de create_resource debe ser verdadero o falso.”
}
}

variable “name” {
description = “Nombre del servidor de PostgreSQL.”

validation {
condition = length(var.name) > 0
error_message = “Se debe proporcionar un nombre para el servidor de PostgreSQL.”
}
}

variable “location” {
description = “Ubicación geográfica donde se desplegará el recurso.”

validation {
condition = length(var.location) > 0
error_message = “Se debe proporcionar una ubicación.”
}
}

variable “resource_group_name” {
type = string
description = “El nombre del grupo de recursos en el que se creará el servidor de PostgreSQL.”

validation {
condition = length(var.resource_group_name) > 0
error_message = “Se debe proporcionar un nombre para el grupo de recursos.”
}
}

variable “version_postgresql” {
type = number
description = “Versión del motor de PostgreSQL.”

validation {
condition = var.version_postgresql > 0
error_message = “La versión del motor de PostgreSQL debe ser mayor que 0.”
}
}

variable “sku_name” {
type = string
description = “Nombre del modelo de VM para el servidor.”

validation {
condition = length(var.sku_name) > 0
error_message = “Se debe proporcionar un nombre de modelo de VM.”
}
}

variable “tags” {
type = map(string)
description = “Un mapa de etiquetas para asignar al servidor.”

validation {
condition = length(var.tags) > 0
error_message = “Se deben proporcionar etiquetas para el servidor.”
}
}

variable “zone” {
type = number
description = “Zona de disponibilidad específica para el servidor.”
}

variable “storage_mb” {
type = number
description = “Tamaño de almacenamiento en MB para el servidor de PostgreSQL.”
}

variable “storage_tier” {
type = string
description = “Tipo de almacenamiento, por ejemplo, Premium SSD, Standard SSD, etc.”

validation {
condition = length(var.storage_tier) > 0
error_message = “Se debe proporcionar un tipo de almacenamiento.”
}
}

variable “backup_retention_days” {
type = number
description = “Número de días para retener los respaldos del servidor.”

validation {
condition = var.backup_retention_days > 0
error_message = “El número de días para retener los respaldos debe ser mayor que 0

.”
}
}

variable “geo_redundant_backup_enabled” {
type = bool
description = “Indica si el respaldo georreduntante está habilitado.”

validation {
condition = var.geo_redundant_backup_enabled == true || var.geo_redundant_backup_enabled == false
error_message = “El valor de geo_redundant_backup_enabled debe ser verdadero o falso.”
}
}

variable “administrator_login” {
type = string
description = “Nombre de usuario del administrador para el servidor de PostgreSQL.”

validation {
condition = length(var.administrator_login) > 0
error_message = “Se debe proporcionar un nombre de usuario para el administrador.”
}
}

variable “administrator_password” {
type = string
description = “Contraseña del administrador para el servidor de PostgreSQL.”
sensitive = true

validation {
condition = length(var.administrator_password) > 0
error_message = “Se debe proporcionar una contraseña para el administrador.”
}
}

variable “start_ip_address” {
description = “La dirección IP de inicio para la regla de firewall.”
type = string
}

variable “end_ip_address” {
description = “La dirección IP de fin para la regla de firewall.”
type = string
}

variable “key_vault_name” {
description = “El ID del Azure Key Vault.”
type = string

validation {
condition = length(var.key_vault_name) > 0
error_message = “Se debe proporcionar el ID del Azure Key Vault.”
}
}

Fichero de configuración terraform.tfvars

Ejemplo de configuración para utilizar este módulo:

# Postgresql
create_resource = true

name = “danieljsaldana-postgresql”
location = “francecentral”
resource_group_name = “danieljsaldana_dev”
version_postgresql = 16
sku_name = “B_Standard_B1ms”
tags = {
Project = “Daniel J. Saldaña”
Tier = “Gratis”
Environment = “Producción”
}

# Zone
zone = 1

# Storage
storage_mb = 32768
storage_tier = “P4”

# Backup
backup_retention_days = 7
geo_redundant_backup_enabled = true

# Administrator Login
administrator_login = “danieljsaldana”
administrator_password = “Password123”

# Firewall Rules
start_ip_address = “0.0.0.0”
end_ip_address = “255.255.255.255”

# Key Vault
key_vault_name = “vault-danieljsaldana”

Conclusión

Este módulo de Terraform facilita la gestión de recursos en Azure, asegurando una configuración coherente y repetible. Al utilizar variables y condicionales, podemos adaptarlo a diferentes entornos y necesidades específicas. Este módulo es especialmente útil para aquellos que desean implementar un servidor PostgreSQL seguro en Azure, integrando la gestión de secretos con Azure Key Vault. ¡Espero que este post te haya proporcionado una visión clara de cómo desarrollar un módulo de Terraform para Azure! ¡Gracias por leer!