Use case
I have a bunch of APIs that I want them exposed on Internet in a controlled and secure fashion.On the same time, for business reasons, I might want them published on Internet on multiple custom domains, like api.client1.com, api.client2.com and so on. Due to fact that a Premium Tier Azure APIM is pretty expensive, the use case requires the use of a single Azure APIM.
For exposing APIs on Internet, in a secure, controlled and protected fashion, there is the option to run Azure APIM in a VNET internal integration mode, meaning that APIs published on APIM are accessible only for requests coming from within the VNET.
For those APIs to also be available on Internet, Microsoft reference architecture states that an Application Gateway should be placed in from of Azure API Gateway, to handle incoming requests from Internet and forward them to the internal Azure APIM (VNET integrated). Also, another advantage of using an Application Gateway in from of APIM is that it has WAF and can protect the APIs from DDOS attacks, SQL injections and other OWASP issues. Last, but not least, Application Gateway allows multi path routes creation (like api.client1.com/backend, api.client1.com/frontend, allowing or denying requests to published APIs). Also, using an Application Gateway allows having more than one HTTPS listener, one for each custom domain we want to publish APIs for.
Microsoft reference architecture:
Having the above picture in mind, we can see that Azure APIM is running in internal VNET integration mode, with published APIs accessible from within the VNET. For outside access (from Internet), an Application Gateway is placed in front of it, allowing traffic to flow from Internet to APIs.
The solution
Now, let’s see how the proposed setup looks like:
First we have the VNET in which APIM is integrated to run, in internal mode.
- Two subnets, one for APIM, one for Application Gateway
- One Azure Private DNS Zone. As once integrated with a VNET, in internal mode, APIM doesn’t have anymore DNS resolution and also APIM does not answer by it’s IP address, only by name, we need a private DNS zone for name resolution inside VNET
- Application Gateway with 2 ore more HTTPS listeners, one for each domain we want to publish APIs for (also we need multiple HTTPS listeners for APIM Developer Portal and Management APIs)
- Application Gateway provides WAF and API protection against OWASP vulnerabilities
- APIs to be published
In this configuration, all the calls going to APIM service pass trough Application Gateway (also, internal call, from within VNET can be made to pass trough Application Gateway using the private front-end). Application Gateway routes call using IP addresses but APIM responds only to the host names. Since there will be only one virtual IP for all endpoints exposed by APIM, we will use the Azure Private DNS Zone to make appropriate DNS A records for each endpoint (api, developer portal, etc).
The setup
First we will create a VNET with three subnets:
- appgw-subnet, for Application Gateway
- apim-subnet, for APIM
- div-subnet, for other services, such a test VM
Once the VNET and subnets created, we can proceed to APIM creation, from the portal (I advise to stick with the Developer Tier for this example, as Premium Tier is pretty expensive and the rest of tiers don’t have VNET integration).
Next,we need to setup the custom domain for APIM. Default, it is created with <name you provided on creation>.azure-api.net. Even if we can still use it like this and set up custom domains only at Application Gateway level, it is better to use a custom internal domain. like api.xyz.com.
For this, we need three SSL certificates, following domains:
- api.xyz.com
- developer-portal.xyz.com
- api-management.xyz.com
When setting up a custom domain for APIM, we need SSL certificates. You can use SSL App Service Certificates from Azure (but they are not free, are issued by GoDaddy and costs about 52 Euro/year) or a service like LetsEncrypt or SSLForFree. I’ve also tried self signed certificates, but the setup is not working, with some errors on APIM level (don’t know why, didn’t dig into this).
If you use Azure App Service Certificates:
- Buy a certificate from Azure Portal (search in the search bar for SSL Certificates and choose the App Service Certificate option)
- You will have to buy a certificate for each APIM endpoint (Gateway, Developer Portal, Management API and probably SCM)
- After buying, store the certificate in a Key Vault and verify it, using the wizard provided by Azure
- Now, for using this certificate in APIM and Application Gateway, we need the certificate PFX file and a password.
- The following PowerShell script will extract the PFX and will generate the password:
$appServiceCertificateName = "<Your certificate name>" $resourceGroupName = "<Resource group name, where you saved the certificate" $azureLoginEmailId = (Get-AzureRmADUser -DisplayName "<Your Azure login display name>").UserPrincipalName $subscriptionId = "<Your Azure Subscription ID>" #login to Azure Login-AzureRmAccount Set-AzureRmContext -SubscriptionId $subscriptionId #Get the KeyVault Resource Url and KeyVault Secret Name were the certificate is stored $ascResource = Get-AzureRmResource -ResourceName $appServiceCertificateName -ResourceGroupName $resourceGroupName -ResourceType "Microsoft.CertificateRegistration/certificateOrders" -ApiVersion "2015-08-01" $keyVaultId = "<Your key vault ID, where you stored the certificate>" $keyVaultSecretName = "<KeyVault secret name, corresponding to the certificate>" $certificateProperties=Get-Member -InputObject $ascResource.Properties.certificates[0] -MemberType NoteProperty $certificateName = $certificateProperties[0].Name $keyVaultId = $ascResource.Properties.certificates[0].$certificateName.KeyVaultId $keyVaultSecretName = $ascResource.Properties.certificates[0].$certificateName.KeyVaultSecretName #Split the resource URL of KeyVault and get KeyVaultName and KeyVaultResourceGroupName $keyVaultIdParts = $keyVaultId.Split("/") $keyVaultName = $keyVaultIdParts[$keyVaultIdParts.Length - 1] $keyVaultResourceGroupName = $keyVaultIdParts[$keyVaultIdParts.Length - 5] #Only users who can set the access policy and has the the right RBAC permissions can set the access policy on KeyVault, if the command fails contact the owner of the KeyVault Set-AzureRmKeyVaultAccessPolicy -ResourceGroupName $keyVaultResourceGroupName -VaultName $keyVaultName -UserPrincipalName $azureLoginEmailId -PermissionsToSecrets get #Getting the secret from the KeyVault $secret = Get-AzureKeyVaultSecret -VaultName $keyVaultName -Name $keyVaultSecretName $pfxCertObject=New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @([Convert]::FromBase64String($secret.SecretValueText),"", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) $pfxPassword = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 50 | % {[char]$_}) $currentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath [Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath [io.file]::WriteAllBytes(".\appservicecertificate.pfx", $pfxCertObject.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $pfxPassword)) Write-Host "Created an App Service Certificate copy at: $currentDirectory\appservicecertificate.pfx" Write-Warning "For security reasons, do not store the PFX password. Use it directly from the console as required." Write-Host "PFX password: $pfxPassword"
- After you fill in the blanks in the script and run it (in a PowerShell console, with Run As Administrator), you will have a PFX file and a password for it. Save the password from the console, in a text file, we will use it a bit later.
- Repeat the above steps for each custom domain, for all the APIM endpoints you want to expose and save PFX files and passwords
If you use SSLForFree or LetsEncrypt
- After you obtain a certificate from SSLForFree, you can download it. The download has three files: 2 .crt files and a .key file. We need a .PFX file and a password.
- Using a command line tool, like OpenSSL, following commands are needed:
For creating the PFX file from .crt and .key file: openssl.exe pkcs12 -export -out api-xyz-com.pfx -inkey private.key -in certificate.crt and do that for each custom domain you need For creating .cer file from .crt file (it will be needed in Application Gateway) openssl.exe x509 -inform pem -in certificate.crt -outform der -out api-xyz-com.cer
Now we have the certificates and .PFX files needed and you can proceed and configure custom domains for APIM. I will not detail this task, I assume it’s clear how this is done, using Azure Portal (link to MS docs here).
Next step, now that Azure APIM has been configured for custom domains, is to create a Private DNS Zone and link it to the initially created VNET. Again, the exact steps are beyond this article, you can find here relevant MS documentation.
One thing bear in mind, the private DNS Zone must be created for the same domain you used in APIM for custom domain. Let’s say that if you APIM Gateway endpoint is api.xyz.com, domain is xyz.com. So you will have to create the Private DNS Zone for xyz.com domain. And don’t forget to link the Private DNS zone to VNET after it has been created.
So far, VNET, along with its subnets, is created, SSL certificates are present, APIM is created and configured with custom domains.
Next step is to integrate APIM with the VNET, in internal mode.
This is done by going to Azure Portal -> Your APIM -> Deployment and infrastructure -> Virtual Network, from where you can choose the before created VNET and apim-subnet, as subnet. After hitting Save, it will take some time until done, for Development Tier.
Once APIM joined with VNET, now it’s time to create DNS A records in the private DNS zone. For this, first we need the Private IP of APIM, once joined to VNET.
For this, go to Azure Portal -> Your resource group -> Your APIM -> Properties and copy from there the Private Virtual IP
Now, go to the created Private DNS Zone and create A records for each APIM endpoint you want to use (Gateway, Developer Portal, Management API). The end result should look like this:
Now, APIM endpoint are accessible from within the VNET under the same custom domain. You can deploy a VM in the VNET and then try to access an API exposed by APIM using api.xyz.com as a hostname. If ain’t working, then something is wrong with the Private DNS zone.
Remember that if you change VNET network configuration after you joined APIM to VNET (like changing DNS servers, switching to custom provided instead of Azure’s) then you have to submit a call to Apply Network Configuration Updates of APIM Management API. Details in this link and in this one.
Status: we have the VNET and subnets configured, APIM up and running and configured for custom domains, Private DNS zone configured and linked to VNET, APIM also joined to VNET. Next, setup the Application Gateway.
First, create a Public IP in the resource group you are working in. Choose it as Standard because Application Gateway is also Standard Tier.
Next, start the Application Gateway creation and give it a name and choose it as Standard V2 (or WAF V2, WAF can be anyway enabled later).
In the next step, Frontends, select Frontend IP Address Type as Public and select from the dropdown the above created Public IP.
In the backends step, select “Add a backend pool” and fill in the wizard like below.
Remark: If you have configured the Private DNS zone and linked it to VNET, then APIM endpoints are accessible by host names from Application Gateway (because Application Gateway is in the same VNET as APIM and uses the Private DNS zone for name resolution). If you decide not to use Private DNS zone then you have to bear in mind that APIM backend will be accessible only by IP, so in the backend configuration window, above, instead of domain name you will have to fill in the APIM Private Virtual IP.
Another consequence of not using Private DNS zones will be that from other VMs or Azure components in the VNET, APIM endpoints will not be accessible by hostnames (there is no name resolution), only by Private Virtual IP, but in the same time, APIM is not answering requests coming to it’s IP address, only to hostnames. A way to circumvent this is to use the hosts file (on VMs with Linux or Windows) or to build your own custom DNS server and use it as DNS server for the entire VNET.
References on this topic: here and here.
Once backends step is completed, go to the next one, Configuration and add a routing rule.
- Fill in a name for the routing rule
- Fill in a name for the listener
- Select “Public” for the Frontend IP
- Protocol HTTPS, because we want our APIs to be available on Internet with HTTPS access only
- Once selected HTTPS, a certificate must now be uploaded to AppGW to be able to perform it’s SSL wizardry. Upload the same pfx file you used for the custom domain configured in APIM (let’s say that for Gateway endpoint you uploaded in APIM a pfx certificate file named api.xyz.com.pfx, for the same domain. The same file will be uploaded here, in AppGW)
- Fill in a name for the certificate and the password (same password used in APIM custom domains)
- Listener type will be selected as multi site, because we want to expose on Internet not only api.xyz.com but also developer-portal.xyz.com and management-api.xyz.com
- Next click on “Backend targets” tab
- Select “Backend pool” as Target Type and select and the backend created earlier from “Backend target” dropdown
- For HTTP settings click on “Add new”
- Fill in a name for HTTP Setting
- Select “Backend protocol” as HTTP (we will change it later)
- On “Override with new host name” select “Yes” and:
- If you have configured Private DNS zone, then select “Pick host name from backend target”
- Else, select “Override …” and enter specific endpoint address. If you don’t have a form of name resolution on VNET and backend is configured with APIM Private Virtual IP and because APIM responds only to hostnames, not IP here we have to specify a hostname to be “mocked” by Application Gateway when it makes the request to APIM
- Select “yes” for the custom probe. We will come back later to this to fully configure it
Now click “Save” and then finish the wizard and create the Application Gateway.
After Application Gateway has been created, open it in Azure Portal and go to Health Probes section and open the probe created by default (should be only one after Application Gateway creation).
In the “Path” setting, enter the following address:
/status-0123456789abcdef
This is a special link where Azure APIM can be interrogated if it’s healthy or not, by AppGw. Also, make sure that “Host” setting is correct, pointing to the correct APIM endpoint. Test and save and that’s about it so far.
Last thing that have to be done is to create a DNS A record in Azure Public DNS zones (or wherever you are hosting your domain) and point it to the public IP of the Application Gateway. See example below.
Right now we have a functional setup, the APIs exposed in APIM are now available on Internet, on a custom domain, api.xyz.com. There still are two problems.
1. No end to end SSL yet
We don’t have yet end to end SSL. Right now, SSL communication is enforced between the end user and Application Gateway. For the path between Application Gateway and APIM, we haven’t yet configured SSL, just plain HTTP. If you want to test now your API by calling it from Internet, you first have to set it up to answer to both HTTP and HTTPS (by default, it’s HTTPS only), from APIM (see below picture).
You can make now a test using curl and call the default API provided by APIM, using syntax:
curl -v -X GET "https://api.xyz.com/echo/resource?param1=sample¶m2=10" -H "Ocp-Apim-Subscription-Key: your api subscription key"
I will get back to end to end SLL topic a bit later.
We have only one custom domain on Internet
Right now, we are exposing the APIM default example API on Internet under api.xyz.com. But what if we need to expose the same API, from the same APIM instance on another custom domain, api.abc.com?
Well, this is just a matter of creating another HTTPS listener, for the second domain, at Application Gateway level and bind it to the same HTTP setting and backend created for the initial domain. The only thing, besides listener, that must be created is a second Rule, where you actually bind the listener to the same backend and HTTP setting created earlier.
So proceed by creating a new SSL certificate for the second domain (api.abc.com, let’s say) and pfx, crt, and cer files, as we did in this initial setup.
Next, open the Application Gateway in Azure Portal, go to Listeners and create a new listener. Follow the same steps, regarding certificate and other settings as in the beginning of this article. What is different, for the second domain is that when choosing Listener type, Multisite you have to enter the host name for the second domain (api.abc.com)
Now, the only next thing that has to be created for the second domain to work is a routing rule.
From the Rules menu, choose to create a new routing rule.
In the “Listener” tab, select from dropdown the newly created listener (the one for the second domain). In the “Backend targets” tab, select as backend target the same backend pool created for the first domain and the same HTTP setting created for the first domain.
Basically, for the second domain, we will associate, at AppGw level, the same backend and HTTP setting from the first domain (I assume that we are doing now work for the same APIM endpoint, api. So api.abc.com will use the same settings as api.xyz.com, meaning it will go the same backend APIM endpoint).
Last step, for this new domain, add a DNS A record in Azure Public DNS zones (or wherever you are hosting your doamin) pointing it to the public IP of AppGw.
And so on, for each domain for the same APIM endpoint. If I remember correctly, Application Gateway V2 supports up to 100 SSL certificates (and listeners).
For other APIM endpoint (developer portal, management api) the procedure is quite similar. For each endpoint, a new backend must be created in AppGw, pointing to the correct APIM endpoint (one for developer-portal.xyz.com, one for api-management.xyz.com) and a new HTTP setting for each APIM endpoint.
An then create a new HTTPS listener for each endpoint (developer-portal.xyz.com, developer-portal.abc.com) and the routing rules to match each listener with the correct backend and HTTP settings.
End to end SSL
So far, the setup is working only partially on SSL. We have SSL only from the end user to the Application Gateway. For the path between AppGw and APIM, we haven’t configure SSL, we are still on plain HTTP.
For configuring SSL, we have to whitelist the SSL certificates from APIM custom domain (api.xyz.com) at AppGw level. The trick here is that on HTTPS setting, AppGw wants you to load a .cer file as a certificate, not a pfx file as we did for the listener.
As the .cer file obtained in the beginning of this article from the pfx file, using OpenSSL command is invalid for Application Gateway (don’t know why, it will spill out an error when trying to save HTTPS setting), we have to stick to Microsoft way of doing this.
From File Explorer, right click on the pfx file used to configure custom domain on APIM (in this example, api.xyz.com) and select “Install PFX”. You will need the password for the certificate and make sure that you mark “Mark this key as exportable” option.
From this step forward, please follow Microsoft guidelines of how to extract the needed .cer file, depending of what type of Application Gateway you use (V1 or V2). Guideline is here.
After you have obtained the .cer file,let’s go back to Application Gateway, in Azure portal and create a new HTTP setting.
When you create a new HTTP setting, this time select HTTPS in “Backend protocol” option, load the .cer file as certificate, set “Yes” for “Override with new host name” and select “Override with specific domain name” and use your domain. In this example domain is api.xyz.com. For the moment,leave “No” selected for “Use custom probe”, we will create it immediate afterwards. Click “Save” and way to go (remember the name you have given for this new HTTP seeting).
Next, go to “Health probe” and create new probe. This new probe is mostly identical with the first created, with some differences.
- First, select HTTPS for “Protocol”
- Path, don’t forget to add the specific path ( /status-0123456789abcdef )
- In the last option, HTTP setting, select the HTTP setting name you have saved for the above created HTTP setting.
- Click “Save”
Now, go back to the created HTTP setting and in “Custom probe” select “Yes” and select from the dropdown the name of the https probe created earlier.
Now, last step is to change the first created routing rule, to use the HTTPS setting we have created now, instead the old HTTP one.
Go to “Rules” and select to edit the routing rule. Once opened, in the “Backend targets” tab, in HTTP settings, select from the dropdown the second HTTP setting, created for HTTPS. Click save and that is about all.
To check that the new HTTPS setup is working, go to “Backend healh” and there everything should be green.
Conclusions
Now we have SSL working from the user who is calling the API up to APIM, so data in transit is protected by SSL.
Also, we have at least two different Internet visible domains (api.xyz.com and api.abc.com) who are pointing to the same API, exposed by the same APIM instance.
Having an Application Gateway in front of APIM, we can enable WAF and have the APIs fully protected against various attacks and OWASP vulnerabilities. (with some caveats, see the warnings in documentation).
We can also expose the other APIM endpoints (developer portal, management api, SCM) using the same procedure and also it can be done for multiple Internet visible domains.
If you want further protection for APIs, you can segregate them in external APIs (available from Internet) and internal APIs (available only from within the VNET) using paths like api.xyz.com/external/api1 and api.xyz.com/internal/api2 and configure Application Gateway to route only calls that have “external” in their URL path and to drop all calls that have “internal” in the path, making sure that no request originating from Internet can reach your internal APIs.
References
When I first started to dig into this setup, following links (blogs and Microsoft documentation) were useful to fully understand how AppGw and APIM are working toghether.
- https://medium.com/azure-architects/azure-api-management-and-application-gateway-integration-a31fde80f3db
- http://thewindowsupdate.com/2020/03/20/integrating-api-management-with-app-gateway/
- https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-integrate-internal-vnet-appgateway
- https://tointegrationandbeyond.com/blogs/index.php/2019/05/27/azure-application-gateway-with-an-internal-apim/
- https://docs.microsoft.com/en-us/azure/application-gateway/certificates-for-backend-authentication
- https://docs.microsoft.com/en-us/azure/api-management/api-management-using-with-internal-vnet
- https://docs.microsoft.com/en-us/azure/api-management/api-management-using-with-vnet
- https://docs.microsoft.com/en-us/rest/api/apimanagement/2019-12-01/ApiManagementService/ApplyNetworkConfigurationUpdates
- https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-name-resolution-for-vms-and-role-instances#name-resolution-that-uses-your-own-dns-server
- https://github.com/Azure/azure-quickstart-templates/tree/master/301-dns-forwarder/
- https://francescomolfese.it/en/2018/07/azure-application-gateway-come-monitorarlo-con-log-analytics/
- https://techcommunity.microsoft.com/t5/azure-paas-developer-blog/integrating-api-management-with-app-gateway/ba-p/1241650
- https://docs.microsoft.com/en-us/azure/dns/private-dns-getstarted-portal
- https://docs.microsoft.com/en-us/azure/api-management/configure-custom-domain
- http://blog.repsaj.nl/index.php/2019/08/azure-application-gateway-certificate-gotchas/
- https://medium.com/@brentrobinson5/automating-certificate-management-with-azure-and-lets-encrypt-fee6729e2b78
- https://www.domstamand.com/end-to-end-ssl-solution-using-web-apps-and-azure-application-gateway-multisite-hosting/