Apply a custom domain to Azure API Management

Last Update: 1/12/2020
Azure
API Management

Azure API Management is a fast way to create a consistent, modern API gateway for existing back-end services 1. This article describes how to apply a custom domain to API Management. In addition, we will explain how to enable SSL with your own certificate. All resources are deployed with ARM Template 3 or Azure CLI, so that you could construct CI/CD pipelines with them.

1. Deploy Key Vault

First, deploy Key Vault to hold the certificates.

keyvault.json
{
  "$schema": "https://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "keyVaultName": {
      "type": "string",
      "metadata": {
        "description": "Key Vault Name"
      }
    },
    "apiMgmtName": {
      "type": "string",
      "metadata": {
        "description": "API Management Name"
      }
    },
    "commanderObjectId": {
      "type": "securestring",
      "metadata": {
        "description": "Object id of azure cli command executor."
      }
    }
  },
  "variables": {},
  "resources": [
    {
      "name": "[parameters('apiMgmtName')]",
      "type": "Microsoft.ApiManagement/service",
      "apiVersion": "2019-01-01",
      "properties": {
        "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com",
        "hostnameConfigurations": [],
        "publisherEmail": "ch241.sample@example.com",
        "publisherName": "mark241"
      },
      "sku": {
        "name": "Developer"
      },
      "identity": {
        "type": "SystemAssigned"
      },
      "location": "[resourceGroup().location]"
    },
    {
      "name": "[parameters('keyVaultName')]",
      "type": "Microsoft.KeyVault/vaults",
      "apiVersion": "2018-02-14",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.ApiManagement/service', parameters('apiMgmtName'))]"
      ],
      "properties": {
        "tenantId": "[subscription().tenantId]",
        "sku": {
          "family": "A",
          "name": "standard"
        },
        "accessPolicies": [
          {
            "tenantId": "[subscription().tenantId]",
            "objectId": "[reference(resourceId('Microsoft.ApiManagement/service', parameters('apiMgmtName')), '2019-01-01', 'Full').identity.principalId]",
            "permissions": {
              "keys": [],
              "secrets": ["get"],
              "certificates": ["get"],
              "storage": []
            }
          },
          {
            "tenantId": "[subscription().tenantId]",
            "objectId": "[parameters('commanderObjectId')]",
            "permissions": {
              "keys": [],
              "secrets": [],
              "certificates": ["import"],
              "storage": []
            }
          }
        ],
        "enabledForDeployment": false,
        "enabledForDiskEncryption": false,
        "enabledForTemplateDeployment": false
      },
      "resources": []
    }
  ]
}

The Template above contains Key Vault and API Management resources. First, let's take a look at API Management.

apimgmt
{
  "name": "[parameters('apiMgmtName')]",
  "type": "Microsoft.ApiManagement/service",
  "apiVersion": "2019-01-01",
  "properties": {
    "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com",
    "hostnameConfigurations": [],
    "publisherEmail": "ch241.sample@example.com",
    "publisherName": "mark241"
  },
  "sku": {
    "name": "Developer"
  },
  "identity": {
    "type": "SystemAssigned"
  },
  "location": "[resourceGroup().location]"
}

Here's a brief explanation of why we created API Management first. In the Key Vault deployment described below, you grant API Management read access to Key Vault. This is for API Management to get the certificate stored in Key Vault. API Management and its Managed Id must exist before this authorization can be granted. For more information about Managed Id, please refer to the official document 4. It is an ID for identifying API Management, and authority management is performed using this ID.

managedId
"identity": {
    "type": "SystemAssigned"
  }

In the template above part, the Managed Id for API Management has been generated. See the official documentation 2 for other details.

Next, let's look at Key Vault resources.

keyvault
{
  "name": "[parameters('keyVaultName')]",
  "type": "Microsoft.KeyVault/vaults",
  "apiVersion": "2018-02-14",
  "location": "[resourceGroup().location]",
  "dependsOn": [
    "[resourceId('Microsoft.ApiManagement/service', parameters('apiMgmtName'))]"
  ],
  "properties": {
    "tenantId": "[subscription().tenantId]",
    "sku": {
      "family": "A",
      "name": "standard"
    },
    "accessPolicies": [
      {
        "tenantId": "[subscription().tenantId]",
        "objectId": "[reference(resourceId('Microsoft.ApiManagement/service', parameters('apiMgmtName')), '2019-01-01', 'Full').identity.principalId]",
        "permissions": {
          "keys": [],
          "secrets": ["get"],
          "certificates": ["get"],
          "storage": []
        }
      },
      {
        "tenantId": "[subscription().tenantId]",
        "objectId": "[parameters('commanderObjectId')]",
        "permissions": {
          "keys": [],
          "secrets": [],
          "certificates": ["import"],
          "storage": []
        }
      }
    ],
    "enabledForDeployment": false,
    "enabledForDiskEncryption": false,
    "enabledForTemplateDeployment": false
  },
  "resources": []
}

The key point of Key Vault resources is to manage access rights with accessPolicy. You will see two targets listed in the accessPolicy.

apimgmt-grant
{
  "tenantId": "[subscription().tenantId]",
  "objectId": "[reference(resourceId('Microsoft.ApiManagement/service', parameters('apiMgmtName')), '2019-01-01', 'Full').identity.principalId]",
  "permissions": {
    "keys": [],
    "secrets": ["get"],
    "certificates": ["get"],
    "storage": []
  }
}

The first is to grant API management get permission to obtain a certificate.

apimgmt-managedId
"objectId": "[reference(resourceId('Microsoft.ApiManagement/service', parameters('apiMgmtName')), '2019-01-01', 'Full').identity.principalId]"

With the above description, specify the Managed Id of API Management and grant the authority. Grant get to secrets and certificates.

permissions
"permissions": {
    "keys": [],
    "secrets": ["get"],
    "certificates": ["get"],
    "storage": []
  }

The second is permission to import the certificate into Key Vault.

command-grant
{
  "tenantId": "[subscription().tenantId]",
  "objectId": "[parameters('commanderObjectId')]",
  "permissions": {
    "keys": [],
    "secrets": [],
    "certificates": ["import"],
    "storage": []
  }
}

In this article we will use the Azure CLI to import the certificate into Key Vault. Therefore, you need to grant import permission to the Azure CLI executor (service principal or user). Specify the target service principal or user object ID in commanderObjectId, and grant import authority to certificates.

2. Import the certificate into Key Vault

Next, import the certificate into Key Vault. Here, let's use Azure CLI commands.

certificates-import
az keyvault certificate import --file $certFile --name $secretName --vault-name $keyVaultName --password $certPass
  • --file: Certificate file path. .pfx format.
  • --name: Key Vault secret resource name to store the certificate.
  • --vault-name: The name of the Key Vault that stores the certificate.
  • --password: Certificate password

Here, if you specify the Key Vault you created earlier, the certificate will be imported. Execute the above command with the authority of the executor who has given the import authority.

3. Deploy a custom domain for API Management

Finally, redeploy API Management.

apimgmt.json
{
  "$schema": "https://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "apiMgmtName": {
      "type": "string",
      "metadata": {
        "description": "Service name of API Management"
      }
    },
    "hostName": {
      "type": "string",
      "metadata": {
        "description": "Host name of API Management"
      }
    },
    "keyVaultName": {
      "type": "string",
      "metadata": {
        "description": "Key Vault name"
      }
    },
    "secretName": {
      "type": "string",
      "metadata": {
        "description": "Secret name"
      }
    }
  },
  "variables": {
    "keyVaultResourceId": "[resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretName'))]"
  },
  "resources": [
    {
      "name": "[parameters('apiMgmtName')]",
      "type": "Microsoft.ApiManagement/service",
      "apiVersion": "2019-01-01",
      "properties": {
        "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com",
        "hostnameConfigurations": [
          {
            "type": "Proxy",
            "hostName": "[parameters('hostName')]",
            "keyVaultId": "[reference(variables('keyVaultResourceId'), '2018-02-14').secretUriWithVersion]"
          }
        ],
        "publisherEmail": "ch241.sample@example.com",
        "publisherName": "mark241"
      },
      "sku": {
        "name": "Developer"
      },
      "identity": {
        "type": "SystemAssigned"
      },
      "location": "[resourceGroup().location]"
    }
  ]
}

This seems similar to what we deployed earlier, except for the hostnameConfigurations part. It was an empty array because we did not apply a custom domain earlier, but this time it contains the settings of the custom domain.

  • type: The type of service to apply custom domain. There are four types: Proxy, Portal, Scm, and Management.
  • hostName: custom domain FQDN
  • keyVaultId: Key Vault uri from which API Management get the certificate

Let's specify Proxy for type. This is the type when applying a custom domain to API Gateway ({api management name}.azure-api.net). keyVaultId can be obtained by the property secretUtiWithVersion as described above.

Overall Script

Finally, here is a script that deploys all the above templates. Please change them appropriately according to your CI / CD environment.

deploy-script
Param(
    [parameter(mandatory = $true)][String]$resourceGroup,
    [parameter(mandatory = $true)][String]$keyVaultName,
    [parameter(mandatory = $true)][String]$apiMgmtName,
    [parameter(mandatory = $true)][String]$objectId,
    [parameter(mandatory = $true)][String]$certFile,
    [parameter(mandatory = $true)][String]$certPass,
    [parameter(mandatory = $true)][String]$secretName,
    [parameter(mandatory = $true)][String]$hostName
)

# 1. Deploy Key Vault
try{
    az group deployment create --resource-group $resourceGroup --template-file ./keyvault.json --parameters keyVaultName=$keyVaultName apiMgmtName=$apiMgmtName commanderObjectId=$objectId
}
catch {
    $message = $_.Exception.message
    Write-Error "Failed to deploy key vault: ${message}"
}

# 2. Import a certificate
try{
    az keyvault certificate import --file $certFile --name $secretName --vault-name $keyVaultName --password $certPass
}
catch {
    $message = $_.Exception.message
    Write-Error "Failed to import certificate: ${message}"
}

# 3. Deploy API Management
try {
    az group deployment create --resource-group $resourceGroup --template-file ./apimgmt.json --parameters apiMgmtName=$apiMgmtName hostName=$hostName keyVaultName=$keyVaultName secretName=$secretName
}
catch {
    $message = $_.Exception.message
    Write-Error "Failed to deploy api management: ${message}"
}

Summary

This article described how to apply an API Management custom domain using ARM Template.

  1. Deploy Key Vault
  2. Import certificates to Key Vault
  3. Apply custom domain to API Management

We've successfully applied a custom domain using the steps above.

2019 Copyright Channel.241