In this blog post I'll deploy Azure API Management using the Terraform module. This will serve as a practical guide to creating an Azure API Management instance with custom domains and Key Vault self-signed certificate using Terraform code.

Before I delve into the code, it's crucial to understand the components involved:

  • Azure API Management (APIM): A full-featured platform that assists in API management and operations including access control, rate limits, traceability, and security.

  • Terraform: An open-source Infrastructure as Code (IaC) software tool that enables developers to define and provide data center infrastructure using a declarative configuration language.

  • Azure Key Vault: A service that safeguards cryptographic keys and secrets used by cloud services and applications.

Let's get started by breaking down the necessary Terraform code:

1. main.tf

The main.tf file is primarily responsible for creating the Azure API Management service and configuring custom domain names for different components.


resource "azurerm_api_management" "this" {
  name                = var.name
  location            = var.location
  resource_group_name = var.resource_group_name
  publisher_name      = var.publisher_name
  publisher_email     = var.publisher_email
  sku_name            = var.sku_name

  identity {
    type = "SystemAssigned"
  }

}
resource "azurerm_api_management_custom_domain" "apim_domain" {
  count             = var.requires_custom_host_name_configuration ? 1 : 0
  api_management_id = azurerm_api_management.this.id

  dynamic "developer_portal" {
    for_each = var.developer_portal_host_name != "" ? [1] : []
    content {
      host_name                    = var.developer_portal_host_name
      key_vault_id                 = azurerm_key_vault_certificate.example.secret_id
      negotiate_client_certificate = false

    }
  }

  dynamic "management" {
    for_each = var.management_host_name != "" ? [1] : []
    content {
      host_name                    = var.management_host_name
      key_vault_id                 = azurerm_key_vault_certificate.example.secret_id
      negotiate_client_certificate = false

    }
  }

  dynamic "gateway" {
    for_each = var.gateway_host_name != "" ? [1] : []
    content {
      host_name                    = var.gateway_host_name
      key_vault_id                 = azurerm_key_vault_certificate.example.secret_id
      negotiate_client_certificate = true

    }
  }

  depends_on = [azurerm_api_management.this, azurerm_key_vault_certificate.example]
}

The for_each construct is used in the azurerm_api_management_custom_domain block to conditionally create the configuration for custom domain names. The for_each takes an iterable collection and creates one instance for each item in the collection.

In the code, the iterable collection is an implicit array created based on whether the custom hostname variable (like var.developer_portal_host_name) is not empty (""). If the variable is not empty, an array with one item ([1]) is created, and hence one instance of the developer_portal block gets created. If the variable is empty, an empty array ([]) is created, and no instance of the developer_portal block gets created. The same applies to management and gateway blocks as well.

This approach provides flexibility because the custom hostname configuration for developer_portal, management, and gateway is optional, and these configurations are created only when the respective hostname is provided.

When it comes to dynamic blocks we are using for custom domains, In Terraform the dynamic block is a powerful tool that allows us to create blocks dynamically based on a complex variable, such as a list or a map. This is particularly useful when we want to conditionally create blocks based on the given input.

This means that the developer_portal block will only be created if we have provided a hostname for the developer portal. Otherwise, Terraform will not create a developer_portal block. This allows us to conditionally create the developer_portal block based on our requirements. The same logic applies to the management and gateway blocks.

The content keyword within the dynamic block is used to define the configuration for the block that will be dynamically created.

This dynamic approach brings two significant benefits:

  1. Flexibility: It allows us to make certain parts of our infrastructure optional. We can decide whether or not we want to create these parts based on the input variables.

  2. Code Reusability and Efficiency: It eliminates the need to duplicate code for similar resources and hence, leads to a more efficient and cleaner codebase. This is particularly useful when dealing with complex infrastructures with many optional components.

2. dependencies.tf

The dependencies.tf file contains resources that our main resources depend on - primarily Key Vault related resources.


data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "example" {
  name                     = var.key_vault_name
  location                 = var.location
  resource_group_name      = var.resource_group_name
  tenant_id                = data.azurerm_client_config.current.tenant_id # or use you actual AzureAD tenant ID  I.E. "00be2800-150-4100-9970-00a4eceaa43a" 
  purge_protection_enabled = false

  sku_name = "standard"
}

resource "azurerm_key_vault_certificate" "example" {
  name         = "example-certificate"
  key_vault_id = azurerm_key_vault.example.id

  certificate_policy {
    issuer_parameters {
      name = "Self"
    }

    key_properties {
      exportable = true
      key_size   = 2048
      key_type   = "RSA"
      reuse_key  = true
    }

    secret_properties {
      content_type = "application/x-pkcs12"
    }

    x509_certificate_properties {
      subject            = "CN=*.yourdomain.name"
      validity_in_months = 12

      key_usage = [
        "cRLSign",
        "dataEncipherment",
        "digitalSignature",
        "keyAgreement",
        "keyCertSign",
        "keyEncipherment",
      ]

      subject_alternative_names {
        dns_names = ["*.yourdomain.name"]
      }
    }
  }

}

resource "azurerm_key_vault_access_policy" "this" {
  key_vault_id = azurerm_key_vault.example.id
  tenant_id    = data.azurerm_client_config.current.tenant_id # or update it with your real AzureAD tenant ID

  object_id = azurerm_api_management.this.identity[0].principal_id
#update necessary permsisions as required
  certificate_permissions = ["Get", "List", "Delete", "Recover", "Backup", "Restore", "Create", "Import", "Update", "ManageContacts", "ManageIssuers", "SetIssuers", "DeleteIssuers", "Purge"]

#if required, add one more policy for your build agent
resource "azurerm_key_vault_access_policy" "build_agent_policy" {
  key_vault_id = azurerm_key_vault.example.id

  tenant_id = data.azurerm_client_config.current.tenant_id
  object_id = data.azurerm_client_config.current.object.id
#update necessary permsisions as required
  certificate_permissions = ["Get", "List", "Delete", "Recover", "Backup", "Restore", "Create", "Import", "Update", "ManageContacts", "ManageIssuers", "SetIssuers", "DeleteIssuers", "Purge"]

  depends_on = [azurerm_api_management.this]

#note - for your build agent add one more block :
}

The azurerm_key_vault resource block creates a Key Vault instance in Azure. The Key Vault houses the certificate used by the Azure API Management for securing the custom domain names.

The azurerm_key_vault_certificate resource block generates a self-signed certificate and stores it in the Key Vault. The details about the certificate, such as key size, key type, subject, and other properties, are defined here.

azurerm_key_vault_access_policy sets the access policy for the Key Vault, granting specific permissions for keys, secrets, and certificates to the Azure API Management instance's managed identity.

 

3. variables.tf

The variables.tf file stores all the input variables that the main.tf and dependencies.tf will use.


variable "name" {
  description = "The name of the API Management Service"
  type        = string
}

variable "location" {
  description = "The Azure Region in which to create the resources"
  type        = string
}

variable "resource_group_name" {
  description = "The name of the resource group"
  type        = string
}

variable "publisher_name" {
  description = "The name of your organization"
  type        = string
}

variable "publisher_email" {
  description = "The email of your organization"
  type        = string
}

variable "sku_name" {
  description = "The name of the SKU used for API Management"
  type        = string
}

variable "requires_custom_host_name_configuration" {
  description = "Defines if custom hostname configuration is required"
  type        = bool
}

variable "developer_portal_host_name" {
  description = "The custom domain name for Developer Portal"
  type        = string
}

variable "management_host_name" {
  description = "The custom domain name for the management endpoint"
  type        = string
}

variable "gateway_host_name" {
  description = "The custom domain name for the Gateway"
  type        = string
}

variable "key_vault_name" {
  description = "The name of the Key Vault"
  type        = string
}
  • name: This variable represents the name of the API Management Service. The API Management service name is globally unique within Azure and cannot be changed once the service is created.

  • location: This variable represents the Azure region where your resources will be created. It's important to choose a location/region close to your services or users to reduce latency.

  • resource_group_name: This variable represents the name of the resource group where all your resources are stored. It's a container that holds related resources for an Azure solution.

  • publisher_name: This variable is the name of your organization. It is used in the Azure API Management portal to identify the publisher.

  • publisher_email: This variable is the email address associated with your organization. It is used in the Azure API Management portal for administrative purposes.

  • sku_name: This variable specifies the pricing tier of the Azure API Management service. It can be Developer (for development and test environments), Basic, Standard, or Premium.

  • requires_custom_host_name_configuration: This boolean variable indicates if custom hostname configuration is required or not. If set to true, custom hostnames will be configured for the developer portal, management endpoint, and the gateway.

  • developer_portal_host_name, management_host_name, gateway_host_name: These variables represent the custom domain names for the Developer Portal, the management endpoint, and the Gateway respectively. The developer portal is where developers can learn about your APIs, manage their accounts, and get analytics on their usage. The management endpoint is used for the administration of the API Management service. The Gateway is where your APIs are exposed to the world.

  • key_vault_name: This variable is the name of the Key Vault used to store the certificate used for the TLS binding of your custom domain names.

Conclusion

To conclude, utilizing Azure API Management with Terraform provides a highly scalable and flexible solution to manage and publish your APIs. The walkthrough in this article demonstrated how to effectively define and deploy Azure API Management infrastructure using a code-first approach, which enables us to take advantage of version control, collaboration, and other benefits of Infrastructure as Code (IaC).

I explored the main configuration file main.tf, where I set up the API Management Service and its identity. I also looked at setting up a custom domain for the service through azurerm_api_management_custom_domain. These constructs form the backbone of our API management infrastructure.

I also delved into the use of a Key Vault for storing and managing our certificates used in custom domain configuration, implemented via the azurerm_key_vault and azurerm_key_vault_certificate resources. The Key Vault access policy, created through azurerm_key_vault_access_policy, ensures that the API Management Service has the necessary permissions to access and utilize these certificates.

The variables.tf file is crucial, as it defines all the necessary variables that our configuration will use. These include settings for naming and location, publisher details, SKU name, custom hostname configuration requirements, and the Key Vault's name.

The use of for_each in our Terraform configuration for handling custom domain settings further provides us with the flexibility to conditionally create configurations depending on the input.

It's important to remember that Terraform offers us a declarative approach to defining our infrastructure, which means I simply declare what I want, and Terraform figures out how to achieve that state.

Finally, Terraform isn't only about the initial creation of resources; it's about the entire lifecycle of infrastructure management. With this Azure API Management deployment, any future updates to your setup can be handled gracefully using Terraform by modifying the configuration, reviewing the plan, and applying the changes.

In the age of digital transformation, automating your infrastructure deployment not only increases productivity but also reduces the risk of human error and enhances repeatability. The combination of Azure and Terraform offers a powerful toolset for managing your cloud resources in a structured and efficient manner.