Search code examples
azureterraform

Azure Private Endpoint with Terraform - smart way to do mapping between Subresource and Private DNS Zone


Let me start with saying that I'm an absolute Terraform and IaC in general newbie, so whatever it described here might not be the best approach to things. Doing a lot of stuff based on different Terraform codes we have already in the organization, without doing much trainings/courses/etc. so a lot of self-learn approach is used.

What I'm attempting to do is create a generic Terraform module that I could use across different other modules within Azure. This is what I currently have:

data "azurerm_private_dns_zone" "dnszone" {
  provider            = azurerm.csvc
  name                = var.pdns_name
  resource_group_name = var.pdns_rg_name
}

resource "azurerm_private_endpoint" "private_endpoint" {
  for_each            = [var.subresource_names_map]
  name                = format("%s-%s-%s", "pe", var.private_connection_resource_name, each.value)
  location            = var.location
  resource_group_name = var.pe_resource_group_name
  subnet_id           = var.private_endpoint_subnet_id

  tags = merge({ StartDate = formatdate("DD-MM-YYYY", time_static.time.rfc3339) }, var.env_tags)

  private_service_connection {
    name                           = format("%s-%s-%s", var.private_connection_resource_name, each.value, "Conn")
    private_connection_resource_id = var.private_connection_resource_id
    is_manual_connection           = false
    subresource_names              = [each.value]
  }

  private_dns_zone_group {
    name                 = var.pdns_rg_name
    private_dns_zone_ids = [data.azurerm_private_dns_zone.dnszone.id]
  }
}

And I believe this is generally going to work for two scenarios:

  1. Resources that do have a 1 subresource per 1 DNS zone (good example could be Key Vault that have 1 subresource - vault going into a single private DNS zone privatelink.vaultcore.azure.net)
  2. Resources that do have more than 1 subresource going into 1 DNS zone, ex. Synapse Analytics have two subresources - Sql and SqlOnDemand that go into privatelink.sql.azuresynapse.net, since I could do a simple
pdns_name = "privatelink.sql.azuresynapse.net"
subresourece_names_map = ["Sql", "SqlOnDemand"]

What I'm thinking of doing is is solving a third scenario - one resource can have multiple subresources go into multiple private DNS zone (ex. Storage Account having overall 5 different subresources going into 5 different PDNSZ).

This would require having some sort of dictionary that would map subresources into different PDNSZ, however I think there are cases where a single subresource can be mapped into different PDNSZ (I think both Synapse and PostgreSQL use Sql as subresource, few resources use portal etc.). Right now I could achieve this by simply referencing PE module more than once and this is probably the safest, but not sure whether it's the most optimal code wise.

data "azurerm_client_config" "current" {}

resource "time_static" "time" {}
resource "azurerm_key_vault" "kv" {
  name                          = var.name
  location                      = var.location
  resource_group_name           = var.resource_group_name
  sku_name                      = var.sku_name
  enabled_for_disk_encryption   = true
  tenant_id                     = data.azurerm_client_config.current.tenant_id
  soft_delete_retention_days    = var.soft_delete_retention_days
  purge_protection_enabled      = true
  enable_rbac_authorization     = var.enable_rbac_authorization
  public_network_access_enabled = var.public_network_access_enabled

  tags = merge({ StartDate = formatdate("DD-MM-YYYY", time_static.time.rfc3339) }, var.env_tags)

  lifecycle {
    ignore_changes = [tags]
  }
}

module "private_endpoint" {
  source       = "../private_endpoint"
  pdns_rg_name = var.pdns_rg_name
  pdns_name    = var.pdns_name

  pe_resource_group_name           = var.pe_resource_group_name
  location                         = var.location
  private_endpoint_subnet_id       = var.private_endpoint_subnet_id
  private_connection_resource_id   = azurerm_key_vault.kv.id
  private_connection_resource_name = azurerm_key_vault.kv.name
  subresource_names_map            = var.subresource_names_map

  env_tags = var.env_tags
}

Solution

  • Azure Private Endpoint with Terraform - smart way to do mapping between Subresource and Private DNS Zone

    We need to make 3 main changes in the code configuration i.e., the way we map the resources by use of for_loop over this map and we also need to make changes according to code shared as mentioned below in the module private_dns_zone_group .

    configuration:

    module/private_end_point/main.tf:

     resource "azurerm_private_dns_zone" "dnszones" {
          for_each = { for dns in flatten([for dns in values(var.subresource_dns_map) : dns]) : dns => { name = dns, rg_name = var.pdns_rg_name } }
        
          name                = each.value.name
          resource_group_name = each.value.rg_name
        }
        
        resource "azurerm_private_endpoint" "private_endpoint" {
          for_each            = var.subresource_dns_map
          name                = format("%s-%s-%s", "pe", var.private_connection_resource_name, each.key)
          location            = var.location
          resource_group_name = var.pe_resource_group_name
          subnet_id           = var.private_endpoint_subnet_id
        
          tags = merge({ StartDate = formatdate("DD-MM-YYYY", timestamp()) }, var.env_tags)
        
          private_service_connection {
            name                           = format("%s-%s-%s", var.private_connection_resource_name, each.key, "Conn")
            private_connection_resource_id = var.private_connection_resource_id
            is_manual_connection           = false
            subresource_names              = [each.key]
          }
        
          dynamic "private_dns_zone_group" {
            for_each = [for dns_zone in each.value : {
              name = dns_zone
              id   = azurerm_private_dns_zone.dnszones[dns_zone].id
            }]
        
            content {
              name                 = private_dns_zone_group.value.name
              private_dns_zone_ids = [private_dns_zone_group.value.id]
            }
          }
        }
    

    main.tf:

        provider "azurerm" {
          features {}
        }
        
        resource "azurerm_resource_group" "example_rg" {
          name     = var.pdns_rg_name
          location = var.location
        }
        
        resource "azurerm_virtual_network" "example_vnet" {
          name                = "vksb-vnet"
          address_space       = ["10.0.0.0/16"]
          location            = azurerm_resource_group.example_rg.location
          resource_group_name = azurerm_resource_group.example_rg.name
        }
        
        resource "azurerm_subnet" "example_subnet" {
          name                 = "vksb-subnet"
          resource_group_name  = azurerm_resource_group.example_rg.name
          virtual_network_name = azurerm_virtual_network.example_vnet.name
          address_prefixes     = ["10.0.1.0/24"]
        }
        
        resource "azurerm_storage_account" "example_storage" {
          name                     = "vksbstorageacct"
          resource_group_name      = azurerm_resource_group.example_rg.name
          location                 = azurerm_resource_group.example_rg.location
          account_tier             = "Standard"
          account_replication_type = "LRS"
        }
        
        module "private_endpoint_example" {
          source = "./modules/private_endpoint"
        
          pdns_rg_name                    = azurerm_resource_group.example_rg.name
          pe_resource_group_name          = azurerm_resource_group.example_rg.name
          location                        = azurerm_resource_group.example_rg.location
          private_endpoint_subnet_id      = azurerm_subnet.example_subnet.id
          private_connection_resource_id  = azurerm_storage_account.example_storage.id
          private_connection_resource_name = azurerm_storage_account.example_storage.name
          subresource_dns_map = {
            "blob"       = ["privatelink.blob.core.windows.net"]
            "file"       = ["privatelink.file.core.windows.net"]
          }
        
          env_tags = {
            Environment = "dev"
            Project     = "example"
          }
    }
    

    deployment:

    enter image description here

    enter image description here

    enter image description here

    For more Info related to DNS zones provision using terraform you can refer to answer provided by me in SO link.

    Refer:

    https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_endpoint

    https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_dns_zone

    Azure Private Endpoint & DNS zones using Terraform | by Kirupakarans | Medium by kirupakarans