Table of Contents
Terraform Module for a Private Endpoint
Summary: This is a terraform module that I use to deploy a private endpoint for resources in Azure.
Date: 8 February 2025
Read the post to learn more about private endpoints and:
- How to depoloy a private endpoint using a terraform module
- Notice how we can provide the nic for the private endpoint with a custom name
- How we can assign an ip address to the private endpoint
About Private Endpoints
Private endpoints are a way to connect to a resource in Azure privately. This means that the resource is not exposed to the public internet. Instead, the resource is connected to a virtual network and can only be accessed from within that network. To accomplish this, a private endpoint is assigned a network interface card (NIC) that is connected to the virtual network. The network card gets assigned an IP address and a DNS name that can be used to connect to the resource. The private endpoint is connected to the resource.
Note the following tips should you start working with private endpoints:
- Not all azure resources support private endpoints
- Assigning a private endpoint to a resource does not automatically make the resource private. You need to configure the resource to only accept traffic from the private endpoint.
Terraform Module for a Private Endpoint
The module for the Private Endpoint is defined over three files:
- main.tf: Contains the resources for the private endpoint
- outputs.tf: Contains the output of the private endpoint
- variables.tf: Contains the input variables for the private endpoint
main.tf
Notice the following:
- The custom_network_interface_name is used to set a custom name for the network interface card. This option came available only recently.
- The private_connection_resource_id is the resource id of the resource that the private endpoint is connected to.
- The subresource_names to use is defined by microsoft. You can look them up here
- The private dns zone group name can be defined by your own naming conventions. The DNS zone group has a strong association between the private DNS zone and the private endpoint. It helps with managing the private DNS zone records when there's an update on the private endpoint. See here for more information.
- main.tf
resource "azurerm_private_endpoint" "private_endpoint" { name = var.name location = var.location resource_group_name = var.resource_group_name subnet_id = var.subnet_id tags = var.tags custom_network_interface_name = "nic-${var.name}" private_service_connection { name = "${var.name}-serviceconnection" private_connection_resource_id = var.private_connection_resource_id is_manual_connection = var.is_manual_connection subresource_names = try([var.subresource_name], null) request_message = try(var.request_message, null) } private_dns_zone_group { name = var.private_dns_zone_group_name private_dns_zone_ids = var.private_dns_zone_group_ids } dynamic "ip_configuration" { for_each = { for i, ip_config in var.ip_configurations : ip_config.private_ip_address => { index = i, member_name = ip_config.member_name } } content { name = "${var.name}-ipconfig-${ip_configuration.value.index}" private_ip_address = ip_configuration.key subresource_name = var.subresource_name member_name = ip_configuration.value.member_name } } lifecycle { ignore_changes = [ ] } }
outputs.tf
- outputs.tf
output "id" { description = "Specifies the resource id of the private endpoint." value = azurerm_private_endpoint.private_endpoint.id } output "nic_name" { description = "Specifies the name of the private endpoint nic." value = azurerm_private_endpoint.private_endpoint.custom_network_interface_name } output "private_dns_zone_group" { description = "Specifies the private dns zone group of the private endpoint." value = azurerm_private_endpoint.private_endpoint.private_dns_zone_group } output "private_dns_zone_configs" { description = "Specifies the private dns zone(s) configuration" value = azurerm_private_endpoint.private_endpoint.private_dns_zone_configs }
variables.tf
Note that most of the descriptions are copied from the terraform registry.
- variables.tf
variable "name" { description = "(Required) Specifies the name of the private endpoint. Changing this forces a new resource to be created." type = string } variable "resource_group_name" { description = "(Required) The name of the resource group. Changing this forces a new resource to be created." type = string } variable "private_connection_resource_id" { description = "(Required) Specifies the resource id of the private link service" type = string } variable "location" { description = "(Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created." type = string } variable "subnet_id" { description = "(Required) Specifies the resource id of the subnet" type = string } variable "private_dns_zone_group_name" { description = "(Required) Specifies the Name of the Private DNS Zone Group. Changing this forces a new private_dns_zone_group resource to be created." type = string } variable "private_dns_zone_group_ids" { description = "(Required) Specifies the list of Private DNS Zones to include within the private_dns_zone_group." type = list(string) } variable "ip_configurations" { description = "(Optional) Specifies the static IP addresses within the private endpoint's subnet to be used. Changing this forces a new resource to be created." default = [] type = list(object({ private_ip_address = string, member_name = string })) } variable "is_manual_connection" { description = "(Optional) Specifies whether the private endpoint connection requires manual approval from the remote resource owner." type = string default = false } variable "subresource_name" { description = "(Optional) Specifies a subresource name which the Private Endpoint is able to connect to." type = string default = null } variable "request_message" { description = "(Optional) Specifies a message passed to the owner of the remote resource when the private endpoint attempts to establish the connection to the remote resource." type = string default = null } variable "tags" { description = "(Optional) Specifies the tags of the network security group" default = {} }