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

danieljsaldana

Daniel J. Saldaña

Posted on June 2, 2024

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

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 {}
}

Enter fullscreen mode Exit fullscreen mode

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 = "-"
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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."
}

Enter fullscreen mode Exit fullscreen mode

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."
  }
}

Enter fullscreen mode Exit fullscreen mode

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"

Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
danieljsaldana
Daniel J. Saldaña

Posted on June 2, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related