Unified surface for multiple APIs
api-management

Direct access to APIs not reachable from the internet through Azure API Management.
July 16, 2020

The API Management service (APIM) in Azure became quite powerful since I last played with it a few years ago. The extent of custom scripting that can be done for instance with rewrite rules is quite high and this blogpost documents one use case where APIM rules enable the creation of unified API umbrella over a dynamically changing APIs.

Situation

Let's imagine a dynamic infrastructure where developers can provision (and tear down) test environments in the cloud to test the features they're working on. Each environment consists of several APIs and is protected by IP restrictions, so that only Azure Front Door is allowed to access.

Initial diagram

Goals

  1. Give developers direct access to APIs without the need to whitelist their IP addresses.
  2. Have the APIs protected with IP restrictions and not accessible directly from the internet.
  3. Have a single endpoint which accepts any path as a parameter and passes it on to the right environment and API.

Examples:

Implementation

The idea is to have one shared API Mangement instance and route developer requests dynamically to each environment and API.

Shared APIM instance

Dynamic API routing can be achieved in API Management with one API definition (per HTTP method) and a bit of URL rewriting magic in C#.

  1. Create API Management, but don't use the Consumption tier if you need to have static IP address, which can be used in IP restrictions.

  2. Remove the sample "Echo" API if it was created automatically (it's not needed).

  3. Create new API, select Blank API, fill required fields and leave the rest.

  4. Click Add operation.

  5. Choose display name and specify URL template like this: /{region}/{env}/{api}/*.

    image-20200716102615226

  6. Do the same for POST (and any other HTTP methods required).

  7. Go back to All operations.

  8. Click on the policy code editor button in the Inbound processing section.

    image-20200716103134225

  9. Define the inbound policy like this:

<inbound>
    <set-backend-service base-url="@($"http://{context.Request.MatchedParameters["env"]}-{context.Request.MatchedParameters["region"]}-{context.Request.MatchedParameters["api"]}.azurewebsites.net")" />
    <rewrite-uri template="@{
        var path = context.Request.OriginalUrl.Path;

        foreach(var param in context.Request.MatchedParameters)
        {
            var s = $"/{param.Value}";
            var i = path.IndexOf(s);
            if(i > -1)
            {
                path = path.Remove(i, s.Length);
            }
        }
        return path;
    }" />
    <base />
</inbound>

Code courtesy of the genius Sebastian Bader!

Every request coming to the frontend address will be transformed to the API URL and will contain full body of the original request.

The wildcard * parameter basically says "forward anyting from here and don't think about it". It has to be the last and doesn't have named parameter in the context. All transformation policies are desribed in the Docs.

The editor should now reflect the code:

image-20200717101550875

To easily test and verify that the transformation works, go to the Test tab, enter values for the parameters and click Send. Even if the request fails, you will still see what APIM did to the request and what was the resulting URL.

set-backend-service (0.013 ms)
{
    "message": "Backend service URL was changed.",
    "oldBackendServiceUrl": "",
    "newBackendServiceUrl": "http://test123-neu-api1.azurewebsites.net/",
    "request": {
        "url": "http://test123-neu-api1.azurewebsites.net/neu/test123/api1/api/books/123"
    }
}

rewrite-uri (0.005 ms)
{
    "message": "Expression was successfully evaluated.",
    "expression": "\n            var path = context.Request.OriginalUrl.Path;\n\n            foreach(var param in context.Request.MatchedParameters)\n            {\n                var s = $\"/{param.Value}\";\n                var i = path.IndexOf(s);\n                if(i > -1)\n                {\n                    path = path.Remove(i, s.Length);\n                }\n            }\n            return path;\n        ",
    "value": "/api/books/123"
}

rewrite-uri (0.006 ms)
{
    "message": "Updated request URL per specified rewrite template.",
    "request": {
        "url": "http://test123-neu-api1.azurewebsites.net/api/books/123"
    }
}

Firewall rules

Finally to punch a firewall hole just for APIM, go to the Overview tab and look for public Virtual IP (VIP) address - this address is assigned to your service only and will not change for its lifetime.

APIM virtual IP address

This address can be added to Access Restrictions in Web Apps networking to allow access.

Found something inaccurate or plain wrong? Was this content helpful to you? Let me know!

šŸ“§ codez@deedx.cz