Managing Azure Private Link with Terraform

Learn how to set up and manage Azure Private Link using Terraform, including private endpoints and service connections

Managing Azure Private Link with Terraform

Azure Private Link provides secure and private connectivity to Azure PaaS services, customer-owned services, and Microsoft partner services. This guide demonstrates how to set up and manage Private Link using Terraform.

Video Tutorial

Learn more about managing Azure Private Link with Terraform in this comprehensive video tutorial:

Prerequisites

  • Azure CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Resource group created
  • Understanding of networking concepts

Project Structure

terraform-azure-privatelink/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── privatelink/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── configs/
    └── services.json

Create modules/privatelink/main.tf:

# Virtual Network
resource "azurerm_virtual_network" "main" {
  name                = "${var.project_name}-vnet"
  address_space       = ["10.0.0.0/16"]
  location            = var.location
  resource_group_name = var.resource_group_name

  tags = var.tags
}

# Subnet for Private Endpoints
resource "azurerm_subnet" "endpoint" {
  name                                           = "endpoint-subnet"
  resource_group_name                            = var.resource_group_name
  virtual_network_name                           = azurerm_virtual_network.main.name
  address_prefixes                               = ["10.0.1.0/24"]
  enforce_private_link_endpoint_network_policies = true
}

# Private DNS Zone
resource "azurerm_private_dns_zone" "blob" {
  name                = "privatelink.blob.core.windows.net"
  resource_group_name = var.resource_group_name

  tags = var.tags
}

resource "azurerm_private_dns_zone" "sql" {
  name                = "privatelink.database.windows.net"
  resource_group_name = var.resource_group_name

  tags = var.tags
}

resource "azurerm_private_dns_zone" "keyvault" {
  name                = "privatelink.vaultcore.azure.net"
  resource_group_name = var.resource_group_name

  tags = var.tags
}

# Virtual Network Links
resource "azurerm_private_dns_zone_virtual_network_link" "blob" {
  name                  = "${var.project_name}-blob-link"
  resource_group_name   = var.resource_group_name
  private_dns_zone_name = azurerm_private_dns_zone.blob.name
  virtual_network_id    = azurerm_virtual_network.main.id
  registration_enabled  = false

  tags = var.tags
}

resource "azurerm_private_dns_zone_virtual_network_link" "sql" {
  name                  = "${var.project_name}-sql-link"
  resource_group_name   = var.resource_group_name
  private_dns_zone_name = azurerm_private_dns_zone.sql.name
  virtual_network_id    = azurerm_virtual_network.main.id
  registration_enabled  = false

  tags = var.tags
}

resource "azurerm_private_dns_zone_virtual_network_link" "keyvault" {
  name                  = "${var.project_name}-kv-link"
  resource_group_name   = var.resource_group_name
  private_dns_zone_name = azurerm_private_dns_zone.keyvault.name
  virtual_network_id    = azurerm_virtual_network.main.id
  registration_enabled  = false

  tags = var.tags
}

# Storage Account Private Endpoint
resource "azurerm_private_endpoint" "blob" {
  name                = "${var.project_name}-blob-pe"
  location            = var.location
  resource_group_name = var.resource_group_name
  subnet_id           = azurerm_subnet.endpoint.id

  private_service_connection {
    name                           = "${var.project_name}-blob-connection"
    private_connection_resource_id = var.storage_account_id
    is_manual_connection          = false
    subresource_names            = ["blob"]
  }

  private_dns_zone_group {
    name                 = "blob-group"
    private_dns_zone_ids = [azurerm_private_dns_zone.blob.id]
  }

  tags = var.tags
}

# SQL Server Private Endpoint
resource "azurerm_private_endpoint" "sql" {
  name                = "${var.project_name}-sql-pe"
  location            = var.location
  resource_group_name = var.resource_group_name
  subnet_id           = azurerm_subnet.endpoint.id

  private_service_connection {
    name                           = "${var.project_name}-sql-connection"
    private_connection_resource_id = var.sql_server_id
    is_manual_connection          = false
    subresource_names            = ["sqlServer"]
  }

  private_dns_zone_group {
    name                 = "sql-group"
    private_dns_zone_ids = [azurerm_private_dns_zone.sql.id]
  }

  tags = var.tags
}

# Key Vault Private Endpoint
resource "azurerm_private_endpoint" "keyvault" {
  name                = "${var.project_name}-kv-pe"
  location            = var.location
  resource_group_name = var.resource_group_name
  subnet_id           = azurerm_subnet.endpoint.id

  private_service_connection {
    name                           = "${var.project_name}-kv-connection"
    private_connection_resource_id = var.key_vault_id
    is_manual_connection          = false
    subresource_names            = ["vault"]
  }

  private_dns_zone_group {
    name                 = "keyvault-group"
    private_dns_zone_ids = [azurerm_private_dns_zone.keyvault.id]
  }

  tags = var.tags
}

# Private Link Service
resource "azurerm_private_link_service" "main" {
  name                = "${var.project_name}-pls"
  location            = var.location
  resource_group_name = var.resource_group_name

  nat_ip_configuration {
    name                       = "primary"
    private_ip_address        = "10.0.1.17"
    private_ip_address_version = "IPv4"
    subnet_id                 = azurerm_subnet.service.id
    primary                   = true
  }

  load_balancer_frontend_ip_configuration_ids = [
    var.load_balancer_frontend_ip_configuration_id
  ]

  visibility_subscription_ids = [data.azurerm_client_config.current.subscription_id]
  auto_approval_subscription_ids = [data.azurerm_client_config.current.subscription_id]

  tags = var.tags
}

# Network Security Group
resource "azurerm_network_security_group" "endpoint" {
  name                = "${var.project_name}-endpoint-nsg"
  location            = var.location
  resource_group_name = var.resource_group_name

  security_rule {
    name                       = "AllowVnetInBound"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "VirtualNetwork"
    destination_address_prefix = "VirtualNetwork"
  }

  tags = var.tags
}

resource "azurerm_subnet_network_security_group_association" "endpoint" {
  subnet_id                 = azurerm_subnet.endpoint.id
  network_security_group_id = azurerm_network_security_group.endpoint.id
}

## Network Configuration

1. **Service Subnet**
```hcl
resource "azurerm_subnet" "service" {
  name                                          = "service-subnet"
  resource_group_name                           = var.resource_group_name
  virtual_network_name                          = azurerm_virtual_network.main.name
  address_prefixes                              = ["10.0.2.0/24"]
  enforce_private_link_service_network_policies = true
}

resource "azurerm_network_security_group" "service" {
  name                = "${var.project_name}-service-nsg"
  location            = var.location
  resource_group_name = var.resource_group_name

  security_rule {
    name                       = "AllowHttpsInBound"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  tags = var.tags
}

resource "azurerm_subnet_network_security_group_association" "service" {
  subnet_id                 = azurerm_subnet.service.id
  network_security_group_id = azurerm_network_security_group.service.id
}

Route Configuration

  1. Route Table
resource "azurerm_route_table" "endpoint" {
  name                = "${var.project_name}-endpoint-rt"
  location            = var.location
  resource_group_name = var.resource_group_name

  route {
    name                   = "ToInternet"
    address_prefix         = "0.0.0.0/0"
    next_hop_type         = "Internet"
  }

  tags = var.tags
}

resource "azurerm_subnet_route_table_association" "endpoint" {
  subnet_id      = azurerm_subnet.endpoint.id
  route_table_id = azurerm_route_table.endpoint.id
}

Monitoring Configuration

  1. Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "privatelink" {
  name                       = "${var.project_name}-diag"
  target_resource_id        = azurerm_private_link_service.main.id
  log_analytics_workspace_id = var.log_analytics_workspace_id

  metric {
    category = "AllMetrics"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }
}

resource "azurerm_monitor_metric_alert" "privatelink" {
  name                = "${var.project_name}-connection-alert"
  resource_group_name = var.resource_group_name
  scopes              = [azurerm_private_link_service.main.id]
  description         = "Alert when private link connections fail"

  criteria {
    metric_namespace = "Microsoft.Network/privateLinkServices"
    metric_name      = "PrivateEndpointConnectionsCount"
    aggregation      = "Average"
    operator         = "LessThan"
    threshold        = 1
  }

  action {
    action_group_id = var.action_group_id
  }
}

Best Practices

  1. Performance

    • Plan subnet sizing
    • Configure DNS properly
    • Optimize routing
    • Monitor latency
  2. Security

    • Use network policies
    • Configure NSGs
    • Implement monitoring
    • Control access
  3. High Availability

    • Use multiple endpoints
    • Configure failover
    • Monitor connections
    • Implement backup
  4. Cost Optimization

    • Plan endpoint usage
    • Monitor traffic
    • Optimize connections
    • Review configurations

Conclusion

You’ve learned how to set up and manage Azure Private Link using Terraform. This setup provides:

  • Private connectivity
  • Service isolation
  • DNS integration
  • Network security

Remember to:

  • Monitor connections
  • Review security
  • Update configurations
  • Maintain endpoints