Integrating Azure Application Gateway with multiple custom domains with Azure APIM

Want to publish various APIs with multiple domain names while using the same Azure APIM instance? In this blog I am going to show how you can achieve this with Integrating an Azure Application Gateway with multiple custom domains with a single Azure APIM instance

Context

You may have an APIM instance deployed with an internal setup – in theory, the APIM instance is only accessible within your Virtual Network (vNET). You may want to access some APIs publicly and even access them using multiple custom domains.

Rather than deploying multiple APIM instances to cater for each domain, we can achieve this using an Azure Application Gateway to publicly expose the APIM instance on various domains or even subdomains. Other benefits of using an Azure Application Gateway with APIM is websocket support & the benefits of Application Gateway such as WAF protection.

High-Level Architecture

In the below diagram, i’ve shown at high-level of what will be deployed within this blog.

  • Virtual network with an Internal APIM instance, Virtual Machine (will be used to test internal APIM access)
  • Private DNS zone: thomasthornton.cloud
  • Application Gateway
  • Users that can access the APIM instance via three URLs:
    • api.thomasthornton.cloud
    • api.tamops.cloud
    • api.tt.cloud

As mentioned in the above, this setup and blog caters also for sub-domains, in the below diagram

  • Users can access the APIM instance via sub-domains of *.thomasthornton.cloud

Deployment & Setup

In this deployment, I will deploy the initial setup with Azure CLI & additional will be done via portal to show the various setup options required.

The below will create:

  • Resource Group
  • Virtual Network
  • Virtual Network with the 3 relevant subnets as per the above diagrams
  • Network Security Group & associated rules for apim & Virtual Machine access
  • Assigns Network Security Group to both apim & vm subnets
  • Virtual Machine
# Variables

#Resource Group
LOCATION="UKSOUTH"
RG_NAME="tamops-appgw-apim"

#Virtual Network
VNET_NAME="tamops-apim-vnet"
VNET_ADDRESS_PREFIX="10.0.0.0/16"
SUBNET_APPGW_NAME="appgw-subnet"
SUBNET_APPGW_PREFIX="10.0.0.0/24"
SUBNET_APIM_NAME="apim-subnet"
SUBNET_APIM_PREFIX="10.0.1.0/24"
SUBNET_VM_NAME="vm-subnet"
SUBNET_VM_PREFIX="10.0.2.0/24"

#Network Security Group
NSG_NAME="tamops-apim-nsg"
LOCATION_IP="1.2.3.4"

#Virtual Machine
VM_NAME="tamops-apim-vm"
VM_PIP_NAME="tamops-apim-vm-pip"


# Create Resource Group
az group create -l $LOCATION -n $RG_NAME

# Create Virtual Network
az network vnet create \
  --name $VNET_NAME \
  --resource-group $RG_NAME \
  --location $LOCATION \
  --address-prefix $VNET_ADDRESS_PREFIX \
  --subnet-name $SUBNET_APPGW_NAME \
  --subnet-prefix $SUBNET_APPGW_PREFIX

# Create Additional Subnets
az network vnet subnet create \
  --address-prefix $SUBNET_APIM_PREFIX \
  --name $SUBNET_APIM_NAME \
  --resource-group $RG_NAME \
  --vnet-name $VNET_NAME

az network vnet subnet create \
  --address-prefix $SUBNET_VM_PREFIX \
  --name $SUBNET_VM_NAME \
  --resource-group $RG_NAME \
  --vnet-name $VNET_NAME

# Create NSG
az network nsg create --resource-group $RG_NAME --name $NSG_NAME --location $LOCATION

# Create NSG rules
az network nsg rule create \
  --resource-group $RG_NAME \
  --nsg-name $NSG_NAME \
  --name RDP \
  --access Allow \
  --protocol Tcp \
  --direction Inbound \
  --priority 100 \
  --source-address-prefixes $LOCATION_IP \
  --source-port-range "*" \
  --destination-address-prefix "*" \
  --destination-port-range 3389

az network nsg rule create \
  --resource-group $RG_NAME \
  --nsg-name $NSG_NAME \
  --name apimanagement \
  --access Allow \
  --protocol Tcp \
  --direction Inbound \
  --priority 101 \
  --source-address-prefix ApiManagement \
  --source-port-range "*" \
  --destination-address-prefix "*" \
  --destination-port-range 3443

# Assign NSGs to subnets
az network vnet subnet update \
  --vnet-name $VNET_NAME \
  --name $SUBNET_APIM_NAME \
  --resource-group $RG_NAME \
  --network-security-group $NSG_NAME

az network vnet subnet update \
  --vnet-name $VNET_NAME \
  --name $SUBNET_VM_NAME \
  --resource-group $RG_NAME \
  --network-security-group $NSG_NAME

# Create VM
az network public-ip create \
  --resource-group $RG_NAME \
  --name $VM_PIP_NAME

az network nic create \
  --resource-group $RG_NAME \
  --name $VM_NAME \
  --vnet-name $VNET_NAME \
  --subnet $SUBNET_VM_NAME \
  --network-security-group $NSG_NAME \
  --public-ip-address $VM_PIP_NAME

az vm create \
  --resource-group $RG_NAME \
  --name $VM_NAME \
  --nics $VM_NAME \
  --image Win2019Datacenter \
  --admin-username tamops

Create APIM

With the initial deployment now complete, lets create APIM instance via Azure Portal

Create as below (only screenshots i’ve included are what I’ve added or changed – the rest of configuration has been kept default)

Note:- Pricing tier needs to be either Developer or Premium (to allow for internal vNET setup)

Allowing this to create, can take 30+ minutes

Generating self-signed root CA & self-signed certificate for api.thomasthornton.cloud

Time to generate a self-signed root CA & self-signed certificate for api.thomasthornton.cloud. I used this tutorial as part of this

Root CA creation

#Create Root CA
openssl ecparam -out rootthomasthorntoncloud.key -name prime256v1 -genkey
openssl req -new -sha256 -key rootthomasthorntoncloud.key -out rootthomasthorntoncloud.csr
openssl x509 -req -sha256 -days 365 -in rootthomasthorntoncloud.csr -signkey rootthomasthorntoncloud.key -out rootthomasthorntoncloud.crt

Successful output:

C:\Users\thomast\Downloads>openssl ecparam -out rootthomasthorntoncloud.key -name prime256v1 -genkey

C:\Users\thomast\Downloads>openssl req -new -sha256 -key rootthomasthorntoncloud.key -out rootthomasthorntoncloud.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:root.thomasthornton.cloud
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Create self-signed certificate for api.thomasthornton.cloud

# API.thomasthornton.cloud
openssl ecparam -out apithomasthorntoncloud.key -name prime256v1 -genkey
openssl req -new -sha256 -key apithomasthorntoncloud.key -out apithomasthorntoncloud.csr
openssl x509 -req -in apithomasthorntoncloud.csr -CA  rootthomasthorntoncloud.crt -CAkey rootthomasthorntoncloud.key -CAcreateserial -out apithomasthorntoncloud.crt -days 365 -sha256

Successful output:

C:\Users\thomast\Downloads>openssl ecparam -out apithomasthorntoncloud.key -name prime256v1 -genkey

C:\Users\thomast\Downloads>openssl req -new -sha256 -key apithomasthorntoncloud.key -out apithomasthorntoncloud.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:api.thomasthornton.cloud
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Create .pfx with this .crt & .key (create password as part of this)

C:\Users\thomast\Downloads>openssl pkcs12 -export -in apithomasthorntoncloud.crt -inkey apithomasthorntoncloud.key -out apithomasthorntoncloud.pfx
Enter Export Password:
Verifying - Enter Export Password:

Now update the APIM with new custom domain

Create Private DNS Zone

As shown in my initial diagram, a private dns zone is required as part of this implementation.

The below will:

  • Create DNS Zone: thomasthornton.cloud
  • Assign DNS zone
  • Add APIM private IP address to the DNS zone and a record: api.thomasthornton.cloud
RG_NAME="tamops-appgw-apim"
DNS_ZONE_NAME="thomasthornton.cloud"
VNET_ID="/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamops-appgw-apim/providers/Microsoft.Network/virtualNetworks/tamops-apim-vnet"
API_DNS="api"
API_IP_ADDRESS="10.0.1.5"

# Private DNS zone

az network private-dns zone create -g $RG_NAME -n $DNS_ZONE_NAME

az network private-dns link vnet create -g $RG_NAME -n linkzone -z $DNS_ZONE_NAME \
    -v $VNET_ID -e False

az network private-dns record-set a add-record -g $RG_NAME -z $DNS_ZONE_NAME \
    -n $API_DNS -a $API_IP_ADDRESS

Create mock api to test APIM connectivity

In APIM select APIs -> Add API & Http(manually define) – enter as below

For now, both URL schemes allowed (http & https)

Select the new API & remove subscription required inside settings option

Add a new mock policy using the below config -> select Frontend -> options -> Open API editor(json) & paste in below

{
    "openapi": "3.0.1",
    "info": {
        "title": "Health Check",
        "description": "",
        "version": "1.0"
    },
    "servers": [{
        "url": "http://api.thomasthornton.cloud/testapi"
    }, {
        "url": "https://api.thomasthornton.cloud/testapi"
    }],
    "paths": {
        "/liveness": {
            "get": {
                "summary": "health",
                "description": "health",
                "operationId": "health",
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "Up"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/clone-61d2d/liveness": {
            "get": {
                "summary": "health (https)",
                "description": "health (https)",
                "operationId": "61d2d7378b2d0d754278af5b",
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "tamops"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "apiKeyHeader": {
                "type": "apiKey",
                "name": "Ocp-Apim-Subscription-Key",
                "in": "header"
            },
            "apiKeyQuery": {
                "type": "apiKey",
                "name": "subscription-key",
                "in": "query"
            }
        }
    },
    "security": [{
        "apiKeyHeader": []
    }, {
        "apiKeyQuery": []
    }]
}

Log into Virtual Machine & confirm APIM mock API is accessible as below:

Setup Application Gateway

As there has been a private dns zone configured, I will be assigning backend pool with FQDN (api.thomasthornton.cloud) of the APIM that was registered previously within the private DNS zone.

Now add a routing rule, with listener setup

Ensure you upload the certificate created previously that was uploaded to the apim instance.

Once complete ,add a new http setting (for now it will be http only)

Now deploy! Once complete, we now need to update the Health probes with path

/status-0123456789abcdef

This path can be used to determine if the APIM is healthy to receive traffic or not.

Once this has been completed, we’re ready to test connectivity from the Public Internet to Application Gateway -> APIM. For this, I have just updated my localhost file with the dns entry as below (take public IP from Application Gateway)

20.90.157.188 api.thomasthornton.cloud

Test connectivity from your web browser

It works 🙂 traffic flow has successfully been tested Application Gateway -> APIM

Though, the traffic flow is not full end to end ssl as from Application Gateway -> APIM is http, I will show can end to end SSL can be achieved later.

Add additional domains to Application Gateway

As mentioned at the start of this blog post, you may want to publish various APIs with multiple domain names while using the same Azure APIM instance? The initial setup has been completed, lets add additional domains to the application gateway; while still only accessing the same backend APIM instance.

To start, create additional self-signed certificates for the following domains:

  • api.tamops.cloud
  • api.tt.cloud
# API.tamops.cloud
openssl ecparam -out apitamopscloud.key -name prime256v1 -genkey
openssl req -new -sha256 -key apitamopscloud.key -out apitamopscloud.csr
openssl x509 -req -in apitamopscloud.csr -CA  rootthomasthorntoncloud.crt -CAkey rootthomasthorntoncloud.key -CAcreateserial -out apitamopscloud.crt -days 365 -sha256

# generate PFX
openssl pkcs12 -export -in apitamopscloud.crt -inkey apitamopscloud.key -out apitamopscloud.pfx


# API.tt.cloud
openssl ecparam -out apittcloud.key -name prime256v1 -genkey
openssl req -new -sha256 -key apittcloud.key -out apittcloud.csr
openssl x509 -req -in apittcloud.csr -CA  rootthomasthorntoncloud.crt -CAkey rootthomasthorntoncloud.key -CAcreateserial -out apittcloud.crt -days 365 -sha256

# generate PFX
openssl pkcs12 -export -in apittcloud.crt -inkey apittcloud.key -out apittcloud.pfx

Once created, the only additional change required is to add additional HTTPs listeners to the Application Gateway (for each, you will keep the same settings/configurations as per the original Application Gateway setup)

Follow the same process for each, upload the certificate each time!

Apply new routing rules for each of the new Listeners, (below shows only for api.tamops.cloud, perform same process for each additional domain)

Update localdns file with additional entries and test connectivity

20.90.157.188 api.thomasthornton.cloud
20.90.157.188 api.tamops.cloud
20.90.157.188 api.tt.cloud
Awesome! Now successfully accessed the same backend APIM instance with multiple different public domains!

Setup end-to-end SSL

As mentioned, the current setup is not configured for end-to-end SSL, this is the final step of the setup!

To enable full end-to-end SSL encryption, we need to

  • Configure an additional HTTPS setting & upload the root Cert to the Application Gateway
  • Create an https health probe on Application Gateway
  • Update each rule to use https setting rather than Http

Setup an additional HTTP setting (same as the HTTP setting setup but this time, include the root certificate that was created previously)

Create an additional Health probe as below for https

Update each rule with the new http setting: https

Once this has been setup, you can test each URL – also can review Backend health within Application Gateway settings

Complete!

Awesome, a length blog post but the setup has now been completed! What was covered:

  • Setup and configure APIM with custom DNS
  • Relevant network security configured with Network Security Group
  • Azure Private DNS setup and registration
  • Setup and configure Application Gateway to work with APIM
  • Configure multiple domains to be used to access to same backend APIM instance
  • End to end SSL termination setup

Some additional reading:

Repo containing code & diagrams

6 comments

Leave a Reply to Thomas Thornton Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s