Skip to content

About

💰 Terraform module for Azure API Management: Developer-tier-by-default, secure posture explicit, the estate's MCP gateway layer

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Terraform AzureRM API Management

Azure API Management instances with cheap-and-secure defaults: Developer_1 unless you say otherwise (every capability, development pricing, no SLA), a system-assigned identity on by default, legacy protocols and weak ciphers explicitly off, and tenant access (the direct management API) off until asked for. The first layer of the estate's MCP gateway architecture: the instance this module creates is what fronts MCP servers and REST-as-MCP tools.

CI Release Terraform Registry License


Design decisions worth knowing

  • One way to do custom hostnames. Domains ride the standalone custom-domain resource (the custom_domain object), never the service's hostname_configuration block, which is deliberately not exposed: the standalone resource changes domains without a full service update, and two ways to say the same thing is how drift arguments start.
  • The security posture is explicit. The security block is always emitted with every legacy protocol and weak cipher off by default, so the secure state is visible in plan and state rather than implied by an absent block.
  • Instances key by name in a map, with the placement arguments (resource_group_id, location, tags) at module level per the estate convention; the resource group name parses from its id.
  • Premium surface is present but not defaulted: zones and additional_locations type-check and pass through, they just cost money this module refuses to imply.

Cost and lifecycle notes

  • Developer tier bills by the hour at development rates and carries no SLA; it is the right tier for everything short of production, and it is this module's default on purpose.
  • An APIM create takes 30 to 45 minutes; plan CI accordingly (the self-test here is an hour-scale run by nature). Deletes soft-delete the name: the examples' provider features set purge_soft_delete_on_destroy = true so repeated apply-destroy cycles do not collide.
  • The instance's public_ip_addresses output is what firewalled backends allow-list; an advisory check warns when public access is disabled without vnet integration, which locks everything out.

Security scan exceptions

CI runs a trivy config scan that gates on HIGH and CRITICAL findings. Waivers live in .trivyignore.yaml, and every waiver is recorded here. There are currently none.

ID Where Justification

Requirements

Name Version
terraform >= 1.9.0, < 2.0.0
azurerm >= 4.0.0, < 5.0.0

Providers

Name Version
azurerm 4.81.0

Modules

No modules.

Resources

Name Type
azurerm_api_management.this resource
azurerm_api_management_custom_domain.this resource

Inputs

Name Description Type Default Required
api_managements API Management instances to create, keyed by instance name. The full azurerm_api_management
surface, typed, with secure-and-cheap defaults: sku_name defaults to Developer_1 (the no-SLA
development tier; every paid capability, none of the production cost), a system-assigned identity
is on by default, tenant access (the direct management API) is off by default, and the security
object's legacy protocol and cipher toggles all default off.

Custom hostnames are managed through the custom_domain object (the standalone custom-domain
resource, which updates domains without a full service update); the service-level
hostname_configuration block is deliberately not exposed, so there is exactly one way to do it.

sign_up, when enabled, requires its terms_of_service. delegation carries the portal sign-in and
subscription delegation endpoints. additional_locations and zones only mean anything on Premium.
map(object({
publisher_name = string
publisher_email = string

sku_name = optional(string, "Developer_1")

notification_sender_email = optional(string)
min_api_version = optional(string)
client_certificate_enabled = optional(bool)
gateway_disabled = optional(bool, false)
public_ip_address_id = optional(string)
public_network_access_enabled = optional(bool, true)
virtual_network_type = optional(string, "None")
virtual_network_subnet_id = optional(string)
zones = optional(list(string))
http2_enabled = optional(bool, false)
tenant_access_enabled = optional(bool, false)
sign_in_enabled = optional(bool)

identity = optional(object({
type = optional(string, "SystemAssigned")
identity_ids = optional(list(string), [])
}), {})

security = optional(object({
backend_ssl30_enabled = optional(bool, false)
backend_tls10_enabled = optional(bool, false)
backend_tls11_enabled = optional(bool, false)
frontend_ssl30_enabled = optional(bool, false)
frontend_tls10_enabled = optional(bool, false)
frontend_tls11_enabled = optional(bool, false)
triple_des_ciphers_enabled = optional(bool, false)
tls_ecdhe_ecdsa_with_aes128_cbc_sha_ciphers_enabled = optional(bool, false)
tls_ecdhe_ecdsa_with_aes256_cbc_sha_ciphers_enabled = optional(bool, false)
tls_ecdhe_rsa_with_aes128_cbc_sha_ciphers_enabled = optional(bool, false)
tls_ecdhe_rsa_with_aes256_cbc_sha_ciphers_enabled = optional(bool, false)
tls_rsa_with_aes128_cbc_sha256_ciphers_enabled = optional(bool, false)
tls_rsa_with_aes128_cbc_sha_ciphers_enabled = optional(bool, false)
tls_rsa_with_aes128_gcm_sha256_ciphers_enabled = optional(bool, false)
tls_rsa_with_aes256_gcm_sha384_ciphers_enabled = optional(bool, false)
tls_rsa_with_aes256_cbc_sha256_ciphers_enabled = optional(bool, false)
tls_rsa_with_aes256_cbc_sha_ciphers_enabled = optional(bool, false)
}), {})

sign_up = optional(object({
enabled = bool
terms_of_service = object({
enabled = bool
consent_required = bool
text = optional(string)
})
}))

delegation = optional(object({
subscriptions_enabled = optional(bool, false)
user_registration_enabled = optional(bool, false)
url = optional(string)
validation_key = optional(string)
}))

certificates = optional(list(object({
encoded_certificate = string
store_name = string
certificate_password = optional(string)
})), [])

additional_locations = optional(list(object({
location = string
capacity = optional(number)
zones = optional(list(string))
public_ip_address_id = optional(string)
gateway_disabled = optional(bool)
virtual_network_subnet_id = optional(string)
})), [])

custom_domain = optional(object({
gateway = optional(list(object({
host_name = string
certificate = optional(string)
certificate_password = optional(string)
key_vault_certificate_id = optional(string)
negotiate_client_certificate = optional(bool)
ssl_keyvault_identity_client_id = optional(string)
default_ssl_binding = optional(bool)
})), [])
management = optional(list(object({
host_name = string
certificate = optional(string)
certificate_password = optional(string)
key_vault_certificate_id = optional(string)
negotiate_client_certificate = optional(bool)
ssl_keyvault_identity_client_id = optional(string)
})), [])
portal = optional(list(object({
host_name = string
certificate = optional(string)
certificate_password = optional(string)
key_vault_certificate_id = optional(string)
negotiate_client_certificate = optional(bool)
ssl_keyvault_identity_client_id = optional(string)
})), [])
developer_portal = optional(list(object({
host_name = string
certificate = optional(string)
certificate_password = optional(string)
key_vault_certificate_id = optional(string)
negotiate_client_certificate = optional(bool)
ssl_keyvault_identity_client_id = optional(string)
})), [])
scm = optional(list(object({
host_name = string
certificate = optional(string)
certificate_password = optional(string)
key_vault_certificate_id = optional(string)
negotiate_client_certificate = optional(bool)
ssl_keyvault_identity_client_id = optional(string)
})), [])
}))
}))
n/a yes
location The location the API Management instances are deployed to. string n/a yes
resource_group_id Resource id of the resource group to deploy into; the name is parsed from it, per the estate's pass-ids principle. string n/a yes
tags Tags applied to every instance. map(string) {} no

Outputs

Name Description
custom_domain_ids Map of instance name to its custom-domain resource id, for instances that set one.
developer_portal_urls Map of instance name to its developer portal URL.
gateway_regional_urls Map of instance name to its regional gateway URL.
gateway_urls Map of instance name to its gateway base URL; MCP servers exposed through the instance live under it.
identity_principal_ids Map of instance name to the system-assigned identity's principal id (null when the instance is UserAssigned only).
ids Map of instance name to resource id.
ids_zipmap Map of instance name to {name, id} for easy composition.
management_api_urls Map of instance name to its management API URL.
portal_urls Map of instance name to its legacy publisher portal URL.
private_ip_addresses Map of instance name to its private IP addresses (vnet-integrated instances).
public_ip_addresses Map of instance name to its public IP addresses; the source addresses to allow-list on firewalled backends.
scm_urls Map of instance name to its SCM URL.

About

💰 Terraform module for Azure API Management: Developer-tier-by-default, secure posture explicit, the estate's MCP gateway layer

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages