One of the Azure services I frequently find myself working with is API Management.
API Management is an excellent service for abstracting your back-end services and presenting a set of APIs via a single HTTPS endpoint.
There are 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 APIs externally whilst keeping all other APIs internal?
Microsoft has 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 APIs externally
The challenge with this blueprint is that whilst it works well, the documentation isn’t particularly comprehensive and omits several vital 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.
Architecture Overview
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.
Certificates
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 needed 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.crt
openssl.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.cer
openssl.exe x509 -inform pem -in portalcertificate.crt -outform der -out portalcert.cer
Deployment script
The PowerShell deployment script is based on MS’s script, but I’ve added the missing steps to deploy the path-based routing rules, cleaned up the variable names, etc.
I’ve also added lots of comments to the script, so I won’t go into too much additional detail here. 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 relatively slow. Expect it to run for at least 60min.
This is the extra code needed to create the routing rules -
The full script can be found on GitHub here.
Once deployed, there are a couple of additional tasks to ensure DNS resolution works correctly.
DNS Resolution
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
External DNS
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 to 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.net
portal.carbideconsulting.co.uk 9b04903c-e8f6–43a7–9ea9-b7ad42923c3d.cloudapp.net
As I use GoDaddy for my DNS management, 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 likely going to need a better solution for internal DNS resolution. Creating an Azure private DNS zone for the internal VNET typically works well.
Create the API’s
I’ve created two 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 backend for these APIs, I’ve enabled mocking for testing purposes -
That’s it; everything’s set up and ready to test!
Testing
There are two scenarios we want to test -
Accessing the APIs across the Internet via App Gateway
external APIs should succeed
internal APIs should fail
2. Accessing the APIs internally
both external and internal APIs should succeed
I’ll use Postman for testing the APIs as I like its ability to log in and sync API queries across all of my Postman installations.
External Access
Using Postman on my laptop, I get the following results when hitting API-M via the Application Gateway across the Internet.
External API’s —
Internal APIs —
As expected, the external API works but attempting to access the internal API fails… result!
Internal Access
Using Postman from the virtual machine I provisioned earlier, I now repeat the same tests. Remember that on this VM, DNS is correctly resolving to the private ip of API-M —
(Please ignore the timeouts, as API-M doesn’t respond to ping requests).
External APIs —
Internal APIs —
Because we’re on the internal network and able to hit API-M directly, the internal API now also succeeds.
Conclusion
There are 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 APIs through the Application Gateway.
We’ve used the example of routing external APIs 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 you found this post useful, but please reach out if you’d like further clarification.
Comments