One of the Azure services I frequently find myself working with is API Management.
API Management is a great service for abstracting your back-end services and presenting a set of API’s via a single HTTPs endpoint.
There’s a couple of common questions that organisations have with API Management -
- How do we protect the Internet-facing public endpoint of API Management?
- How can we selectively expose some API’s externally whilst keeping all other API’s internal?
Microsoft have a supported blueprint for this. The architecture has a couple of key components -
- API Management deployed in “internal” VNET mode
- Application Gateway (WAF) for exposing a subset of API’s externally
The challenge with this blueprint is that whilst it works well, the documentation isn’t particularly comprehensive and omits several key elements to get it up and running.
This post attempts to provide a clearer overview of this scenario and give some additional guidance along the way.
When I first started working with this scenario the first question I had was -
How are the API’s segregated so that only the ones deemed “external” are accessible via the Internet? Is it configured on API-M or the App Gateway?
It turns out the solution is a combination of both and is relatively simple -
- Within API-M, APIs are created with separate base URL’s i.e. /external and /internal
- Within Application Gateway, a path-based routing rule is created that redirects any API requests that contain /external to the API-M back-end
- The same routing rule drops requests to any other API requests including /internal
This is what the flow looks like…
Note: VNET integration is only provided in the Developer or Premium tier. Run with Developer for as long as you can as the Premium tier is relatively expensive. Hopefully MS will eventually offer this capability for the Basic and Standard tiers as well.
Whilst API-M provides the ability to white-list specific source IP addresses the benefit of this architecture is that non-permitted API requests are blocked at the perimeter of your network.
Now that we’ve looked at the high level architecture let’s deploy and test this configuration.
As we’re adding a custom domain to API-M and exposing it via App Gateway we need a -
- Public domain name that we can add DNS records to
- Public SSL certs for the domain
The deployment script assumes that you will have two separate certs i.e. -
- api.yourdomain.org — the cert for the API gateway
- portal.yourdomain.org — the cert for the developer portal
For testing purposes I use sslforfree.com for free certs. If you do the same there’s some additional work required to get the certificates into the file formats required by API-M and AppGW. Openssl is a handy cmd line utility for this.
Create .pfx file from the .crt and private.key -
openssl.exe pkcs12 -export -out apicert.pfx -inkey apiprivate.key -in apicertificate.crtopenssl.exe pkcs12 -export -out portalcert.pfx -inkey portalprivate.key -in portalcertificate.crt
Create .cer file from the .crt file -
openssl.exe x509 -inform pem -in apicertificate.crt -outform der -out apicert.ceropenssl.exe x509 -inform pem -in portalcertificate.crt -outform der -out portalcert.cer
The PowerShell deployment script is based is based on MS’s script but I’ve added the missing steps to deploy the path-based routing rules and cleaned up the variable names etc.
I’ve also added lots of comments into the script so I won’t go into too much additional detail. A few things worth calling out are -
- Adding a routing rule with path-based routing didn’t let me create the maps and apply the routing rule in one operation. Hence, the script deploys the App Gateway and then applies the “external” routing rules
- Deploying these resources is slow. Expect it to run for at least 60min.
This is the extra code needed for the routing rules -
The full script can be found on GitHub here.
Once deployed there’s a couple of additional tasks to ensure DNS resolution works correctly.
As API Management has been configured with a custom domain it will only respond to its new host address i.e. api.yourdomain.org or portal.yourdomain.org.
Depending on whether you’re hitting API Management internally or externally will determine which IP address you’re hitting -
- Externally: external DNS -> App Gateway public IP address
- Internally: internal DNS/hosts file -> API-M private IP address
For external DNS resolution to work we need to add CNAME records for api.yourdomain.org and portal.yourdomain.org to our DNS provider.
The CNAME record points at the A record of the Application Gateway’s public IP address. For example the records I’d create for my external personal domain are -
api.carbideconsulting.co.uk 9b04903c-e8f6–43a7–9ea9-b7ad42923c3d.cloudapp.netportal.carbideconsulting.co.uk 9b04903c-e8f6–43a7–9ea9-b7ad42923c3d.cloudapp.net
I use GoDaddy for my DNS management so the two records I’ve created are shown as -
You can find out your App Gateway’s public IP DNS name from its properties page —
Once configured, external requests to portal and api.carbideconsulting.co.uk resolve to the public IP address of the Application Gateway.
Internal DNS resolution
Internally, we want to hit the private IP of the API-M instance directly. For testing purposes, I’ve created a test virtual machine in the same VNET as API-M.
On the VM I’ve updated the hosts file with the following entries -
- 10.0.1.5 api.carbideconsulting.co.uk
- 10.0.1.5 portal.carbideconsulting.co.uk
10.0.1.5 is the internal IP of API-M. This IP address is found within API-M’s overview pane -
For production you’re going to want to a better solution for internal DNS resolution. Creating an Azure private DNS zone for the internal VNET could potentially work well.
Create the API’s
I’ve created two very simple API’s as shown below. The Internal API is identical to the External one except the URL is /internal/testapi1 —
As I don’t have an actual back-end for these API’s I’ve also enabled mocking for testing purposes -
That’s it... everything’s setup and ready to test!
There’s two scenarios we want to test -
- Accessing the API’s across the Internet via App Gateway
- external API’s should succeed
- internal API’s should fail
2. Accessing the API’s internally
- both external and internal API’s should succeed
I’ll use Postman for testing the API’s as I like its ability to login and sync API queries across all of my Postman installations.
Using Postman on a laptop across the Internet I get the following results when hitting API-M via the Application Gateway.
External API’s —
Internal API’s —
As expected the external API works but attempting to access the internal API fails… result!
Using Postman from the virtual machine I provisioned earlier I now repeat the same tests. Remember that on this VM DNS is resolving to the private IP of API-M —
(Ignore the timeouts as API-M doesn’t respond to ping).
External API’s —
Internal API’s —
Because we’re on the internal network and able to hit API-M directly the internal API now also succeeds.
There’s a few different pieces to get this architecture up and running. But the individual configuration of each component is relatively straightforward.
With this setup it’s possible to have a single API-M instance and only publish a subset of API’s through the Application Gateway.
We’ve used the example of routing external API’s through to a single API-M instance. But in theory, it’s possible to extend this architecture to support multiple API-M instances internally i.e. API requests to /developer could be routed through to a dedicated developer API-M instance.
I hope that you found this post useful but please reach out if you’d like any further clarification on anything.