Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

☁️ Azure API Management Gateway Terraform Module

Provisions a self-hosted API Management gateway with its API assignments, trusted certificate authorities, and host-name configurations — legacy TLS off by default. Targets hashicorp/azurerm ~> 4.0.

Terraform azurerm Module Version Type Resources


🧩 Overview

  • 🌉 The self-hosted gateway (azurerm_api_management_gateway) with required physical location_data.
  • 🔗 API assignments (..._gateway_api), 🔐 certificate authorities (..._gateway_certificate_authority), and 🌐 host-name configurations (..._gateway_host_name_configuration) — keyed maps.
  • 🔒 Host-name configs disable TLS 1.0/1.1 by default.

💡 Why it matters: Self-hosted gateways extend API Management to on-prem/edge. Their host-name configurations are a common place legacy TLS creeps back in; this module defaults TLS 1.0/1.1 off so a hardened listener is what you get without extra input.

❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!

🗺️ Where this fits in the family

flowchart LR
  apim["terraform-azurerm-<br/>api-management"]
  gw["terraform-azurerm-<br/>api-management-gateway"]
  ch["gateway api / cert-authority / host-name (children)"]
  apim -->|"id (api_management_id)"| gw
  gw -->|"id / name"| ch
  classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
  classDef target fill:#004578,stroke:#002d4d,color:#ffffff;
  classDef sib fill:#eef3f8,stroke:#b8c4d0,color:#1b1b1b;
  class gw me;
  class apim target;
  class ch sib;
Loading

🧬 What this module builds

flowchart TD
  core["name · api_management_id · location_data"]
  this["azurerm_api_management_gateway.this"]
  a["gateway_api (for_each)"]
  c["certificate_authority (for_each)"]
  h["host_name_configuration (for_each)"]
  core --> this
  this --> a
  this --> c
  this --> h
  classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
  classDef child fill:#eef3f8,stroke:#b8c4d0,color:#1b1b1b;
  class this me;
  class a,c,h child;
Loading

Resource inventory

Resource Count Role
azurerm_api_management_gateway.this 1 The self-hosted gateway (keystone).
azurerm_api_management_gateway_api.api 0..N API assignments (for_each).
azurerm_api_management_gateway_certificate_authority.certificate_authority 0..N Trusted CAs (for_each).
azurerm_api_management_gateway_host_name_configuration.host_name_configuration 0..N Host-name listeners (for_each).

✅ Provider / Versions

Requirement Value
Terraform >= 1.12.0
Provider hashicorp/azurerm ~> 4.0
Provider block None in this module — the caller configures provider "azurerm" { features {} }.

Schema notes that bite (verified against the live provider schema):

  • 🔒 name and api_management_id are effectively immutable.
  • 🧩 The gateway references the service by id; CA and host-name children reference the service by id plus the gateway by name.
  • 🔐 TLS 1.0/1.1 default to disabled on host-name configurations.
  • 🚫 No Azure resource tags — the tail is timeouts only.

🔑 Required Azure RBAC Roles / Permissions

  • Contributor on the API Management service's resource group (or a least-privilege role with write on Microsoft.ApiManagement/service/gateways/*).

Azure Prerequisites

  • An existing API Management service (the api-management module).
  • An API Management certificate (azurerm_api_management_certificate) for any host-name configuration. It may itself reference a Key Vault secret, but a Key Vault ID cannot be passed here.
  • The caller configures the provider "azurerm" { features {} } block, auth, and subscription.

📁 Module Structure

terraform-azurerm-api-management-gateway/
├── providers.tf · variables.tf · main.tf · outputs.tf
├── README.md · SCOPE.md · LICENSE · .gitignore

⚙️ Quick Start

provider "azurerm" {
  features {}
}

module "apim" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management.git?ref=v1.0.0"
  name                = "apim-contoso-prod"
  resource_group_name = "rg-apim-eastus"
  location            = "eastus"
  publisher_name      = "Contoso Platform"
  publisher_email     = "platform@contoso.example"
  sku_name            = "Developer_1"
}

module "orders_api" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-api.git?ref=v1.0.0"
  name                = "orders"
  resource_group_name = "rg-apim-eastus"
  api_management_name = module.apim.name
  display_name        = "Orders API"
  path                = "orders"
}

module "catalog_api" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-api.git?ref=v1.0.0"
  name                = "catalog"
  resource_group_name = "rg-apim-eastus"
  api_management_name = module.apim.name
  display_name        = "Catalog API"
  path                = "catalog"
}

module "edge_gateway" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"

  name              = "edge-eastus"
  api_management_id = module.apim.id
  description       = "On-prem edge gateway"

  location_data = { name = "Contoso HQ", city = "Redmond", region = "WA" }
}

🔌 Cross-Module Contract

Consumes

Input Type Source module
api_management_id string terraform-azurerm-api-management (its id)
gateway_apis[*].api_id string terraform-azurerm-api-management-api (its id)
host_name_configurations[*].certificate_id string terraform-azurerm-api-management-certificate output id — an API Management certificate ID (.../Microsoft.ApiManagement/service/<svc>/certificates/<name>), not a Key Vault ID

Emits

Output Description
id Gateway Resource ID (first).
name Gateway name.
gateway_api_ids / certificate_authority_ids / host_name_configuration_ids Maps of child key → Resource ID.

📚 Example Library

1 · Minimal gateway
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-eastus"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
}
2 · Full location metadata
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-hq"
  api_management_id = module.apim.id
  location_data     = { name = "Contoso HQ", city = "Redmond", district = "King", region = "WA" }
}
3 · Assign an API to the gateway
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-eastus"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
  gateway_apis      = { orders = { api_id = module.orders_api.id } }
}
4 · Assign several APIs
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-eastus"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
  gateway_apis = {
    orders  = { api_id = module.orders_api.id }
    catalog = { api_id = module.catalog_api.id }
  }
}
5 · Host-name configuration (hardened TLS)
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-eastus"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }

  host_name_configurations = {
    api = {
      host_name      = "api.contoso.com"
      certificate_id = var.gateway_cert_id
      http2_enabled  = true
      # tls10_enabled / tls11_enabled default to false
    }
  }
}

🔒 Legacy TLS stays off unless you explicitly set tls10_enabled/tls11_enabled = true.

6 · Require client certificates (mTLS)
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-eastus"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
  host_name_configurations = {
    api = {
      host_name                          = "api.contoso.com"
      certificate_id                     = var.gateway_cert_id
      request_client_certificate_enabled = true
    }
  }
}
7 · Trusted certificate authority
module "gw" {
  source                  = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name                    = "edge-eastus"
  api_management_id       = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
  certificate_authorities = { root = { is_trusted = true } }
}
8 · Legacy TLS (explicit opt-out)
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-legacy"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
  host_name_configurations = {
    api = {
      host_name      = "legacy.contoso.com"
      certificate_id = var.gateway_cert_id
      tls11_enabled  = true # ⚠️ re-enables a deprecated TLS version
    }
  }
}
9 · Custom timeouts
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-eastus"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
  timeouts          = { create = "30m" }
}
10 · Multiple host names
module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-eastus"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
  host_name_configurations = {
    api = { host_name = "api.contoso.com", certificate_id = var.api_cert_id }
    dev = { host_name = "dev.contoso.com", certificate_id = var.dev_cert_id }
  }
}
11 · for_each over multiple gateways
module "gw" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  for_each = toset(["edge-eastus", "edge-westus"])

  name              = each.key
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
}
12 · 🏗️ End-to-end composition
provider "azurerm" {
  features {}
}

module "gw" {
  source            = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
  name              = "edge-eastus"
  api_management_id = module.apim.id
  location_data = {
    name = "Contoso DC 1"
  }
  description       = "Edge gateway for the orders estate"

  gateway_apis            = { orders = { api_id = module.orders_api.id } }
  certificate_authorities = { root = { is_trusted = true } }
  host_name_configurations = {
    api = { host_name = "api.contoso.com", certificate_id = var.gateway_cert_id, http2_enabled = true }
  }
}

💡 The gateway references the APIM service id and the API id, so ordering is implicit.

📥 Inputs

Required: name, api_management_id. Optional: description, location_data (object), gateway_apis (map), certificate_authorities (map), host_name_configurations (map), timeouts.

Full input schemas
variable "host_name_configurations" {
  type = map(object({
    host_name                          = string
    certificate_id                     = string
    http2_enabled                      = optional(bool)
    request_client_certificate_enabled = optional(bool, false)
    tls10_enabled                      = optional(bool, false)
    tls11_enabled                      = optional(bool, false)
  }))
  default = {}
}
# gateway_apis = map({ api_id }); certificate_authorities = map({ is_trusted? }); see variables.tf.

🧾 Outputs

Output Description Kind
id Resource ID of the API Management gateway registration Passthrough
name Name of the gateway registration, as created Passthrough
api_management_id Resource ID of the parent API Management service Passthrough
api_management_name Name of the parent API Management service, taken from the service ID Derived
resource_group_name Resource group holding the parent API Management service, taken from the service ID Derived
description The gateway's description as Azure holds it Passthrough
location_data_name The canonical location name recorded on the gateway, as Azure holds it Derived
location_data The full location metadata block as Azure holds it, with name, city, district and region Derived
location_data_is_required_and_is_metadata_only Constant true, and a correction worth carrying Constant
location_data_region_looks_like_an_azure_region_code True when location_data.region was given a value shaped like an Azure region code -- "eastus", "westus2", "centralus" and the like Derived
region_is_sent_to_azure_as_country_or_region Constant true Constant
gateway_api_ids Map of API-assignment key to the Resource ID of the assignment record Derived
gateway_api_names Map of API-assignment key to the API NAME the provider will actually use, derived the way the provider derives it: the last segment of api_id, cut at the first semicolon Derived
gateway_api_assignments_ignore_the_service_in_api_id Constant true, and the reason this module validates api_id more strictly than the provider does Constant
removing_an_api_assignment_does_not_delete_the_api Constant true Constant
certificate_authority_ids Map of certificate-authority key to the Resource ID of the trust record Derived
trusted_certificate_authority_names Names of the certificate authorities this gateway actually TRUSTS -- the entries with is_trusted set to true Derived
registered_but_untrusted_certificate_authority_names Names of certificate authorities registered on this gateway with is_trusted false Derived
trusted_certificate_authorities_with_no_host_name_requesting_client_certificates True when at least one certificate authority is trusted but no host-name configuration sets request_client_certificate_enabled, which makes the whole trust list inert Derived
host_name_configuration_ids Map of host-name-configuration key to the Resource ID of the record Derived
host_names Map of host-name-configuration key to the host name this gateway answers on, as Azure holds it Derived
host_name_certificate_ids Map of host-name-configuration key to the API Management certificate Resource ID serving it, as Azure holds it Derived
host_name_configurations_permitting_legacy_tls Names of the host-name configurations that allow TLS 1.0 or TLS 1.1 Derived
host_name_configurations_requesting_client_certificates Names of the host-name configurations that negotiate a client certificate from callers Derived
host_name_certificates_in_another_api_management_service Names of any host-name configurations whose certificate_id sits under a DIFFERENT API Management service than this gateway's Passthrough
certificate_id_must_be_an_api_management_certificate Constant true, and a correction to a widely held assumption about this field Constant
creating_this_resource_deploys_no_gateway Constant true, and the first thing to understand about this module Constant
requires_developer_or_premium_tier Constant true Constant
no_deployment_token_is_emitted_by_this_module Constant true, and deliberate Constant
access_token_authentication_expires_within_30_days Constant true, and an operational obligation this resource creates without recording Constant
entra_workload_identity_removes_the_token_entirely Constant true, and this module's recommendation against the default path Constant
fields_azure_returns_on_read The gateway fields the provider repopulates from the Azure API on every read, and therefore the only fields in which drift can be detected on the keystone Derived
this_resource_supports_no_azure_resource_tags Constant true, for the gateway and for all three of its child resources Constant

🧠 Architecture Notes

  • name and api_management_id are the gateway's fixed identity.
  • Mixed reference styles: the gateway uses api_management_id; the CA and host-name children use api_management_id + the gateway name; the API assignment uses gateway_id (the keystone id).
  • Secure TLS default: host-name configs disable TLS 1.0/1.1; enabling either is an explicit, reviewable change.
  • No secrets, no tags. Certificates are referenced by ID; the tail is timeouts only.
  • features {} dependence — configured by the caller.

🧱 Design Principles

Concern Secure default Opt-out
Legacy TLS 1.0 tls10_enabled = false set true
Legacy TLS 1.1 tls11_enabled = false set true
Client certificates request_client_certificate_enabled = false set true (to require mTLS)
Secrets none accepted or emitted —

Host-name listeners are hardened by default; relaxing TLS is explicit.

🚀 Runbook

terraform init -backend=false
terraform validate
terraform fmt -check
  • Pin by immutable tag (?ref=v1.0.0). Plan-only; a human applies from CI.

🧪 Testing

init -backend=false / validate / fmt -check confirm the config and schemas. Only plan/apply against a live service exercises the parent APIM existence, certificate validity, and gateway registration.

💬 Example Output

$ terraform output
id   = "/subscriptions/0000.../service/apim-contoso/gateways/edge-eastus"
name = "edge-eastus"
host_name_configuration_ids = { "api" = "/subscriptions/0000.../gateways/edge-eastus/hostnameConfigurations/api" }

🔍 Troubleshooting

Symptom Cause Fix
api_management_id must be the full Resource ID of an API Management SERVICE and nothing deeper... A name/partial ID passed. Pass the APIM service id.
Host-name config fails certificate_id is not an API Management certificate ID, or is inaccessible. Reference an azurerm_api_management_certificate in this service. A Key Vault certificate ID or vault.azure.net URL is rejected by the provider.
Clients rejected on TLS Legacy TLS disabled (default). Only enable tls10/11_enabled if a legacy client truly requires it.
provider not initialized / features error Caller missing features {}. Add the features {} block.

🔗 Related Docs


💙 "Infrastructure as Code should be standardized, consistent, and secure."