When dealing with resources in Azure that can be called when the right credentials are known by a calling client, it can be easy to simply grant direct access to the cloud resource itself. In the case of Azure Functions the common scenario is to give the client/caller/user a Function URL with a Function key associated with the function for authorisation. But it really does not have to be this way, the client that has been given a Function URL may act maliciously for example and create an unlimited number of requests to the Azure Function. In my case back in Overhead Part 1, I was giving the away the full Function URL to the client (in the form of a Twilio webhook). In essence, the link between my Twilio Environment and Azure Environment was as follows:
A better way would be not to give the FunctionUrl to the client at all. The client app does not necessarily need to be aware that we are using Azure Functions to fetch the data it wants, thus creating more abstraction. Enter Azure API Management!.
Azure API Management acts as a first line front door that our users and client apps call into to interact with our processing resources in Azure such as our Functions that can serve back results (if need be), back through API Management and back to the calling client again. The diagram would now look like following with some changes to key components discussed further below:
In my implementation of API Management as structured above, I used the Developer Tier(explained later**) for API Management and I used the Ocp-Apim-Subscription Key as part of verifying the calling client (the authentication processes with APIM are beyond scope here but an ideal way is to use Client certificate authentication). This Ocp-Apim-Subscription Key can be given to our users/client apps to use as part of their requests that are sent to Azure APIM rather than sending requests to the Azure Function directly. The Ocp-Apim-Subscription Key is sent specifically as an HTTP header in the request to APIM. In my Twilio environment, having an extra HTTP header to send to APIM meant I had to utilise a Twilio Function (Twilio's serverless functions) in order to make this request. The default config for a SMS Webhook call as used in Overhead Part 1 in the Twilio dashboard does not allow the adding of extra HTTP headers. Using Twilio Functions with Node.js, we can write the function like this, and customise our HTTP POST call to APIM with the headers we need:
I added an axios dependency to create the custom http client instance to have my custom header in place. Then I added the qs dependency to easily construct the form data that is sent as the payload to the endpoint. Dependencies can be added in the Twilio dashboard here:
After saving the Twilio function code and deploying it using the Deploy All button in Twilio, I configured my new HandleSMS/process function as the function that is triggered when the Twilio number receives an SMS:
In the Twilio dashboard, the wizard here allows a Function which will actually just set the Twilio Function's URL as the webhook endpoint after saving.
In Azure, the APIM instance I created has the Azure Function added as a backend service that can be consumed by callers with the ocp-apim-subscription key. This can be done in the portal by by going to Azure APIM Instance > APIs > Create from Azure Resource > Function App. When you locate your Function App, remember that the only selectable Functions from your Function App will be Functions that have an HTTP trigger as the input trigger. In my case, for the purpose of limiting the call rate of a caller we can add a 'rate limit by key' policy on the inbound requests as shown below:
We can add the rate limit by choosing '+Add policy' and using the GUI or opening the Inbound Processing panel Editor shown as '</>' in Inbound processing and adding a limit of 10 calls per 30 seconds for example:
<policies> <inbound> <base /> <set-backend-service id="apim-generated-policy" backend-id="[your azure function name is autopopulated here]" /> <rate-limit-by-key calls="10" renewal-period="30" counter-key="@(context.Subscription?.Key ?? "anonymous")" /> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies>
After saving, this setup now effectively means that calling into the Function through the new configured APIM instance now stops too many calls. This can be tested with the APIM Request URL that can be found in the portal from APIM Instance > APIs > [target API] > [target Post operation] > Test > Request URL:
With the valid APIM details (Request URL, Host and ocp apim sub key) we can decide to 'misbehave' here by continuously sending in requests in Postman for example (shown below):
As seen with Postman above, when we call into the APIM Request URL to get to the Function (with the ocp apim key as header 'Ocp-Apim-Subscription-Key', and a 'body' property as form-data), the APIM instance now applies rules to incoming traffic, such as rate limiting, or any other policies that we may configure to make sure that requests to our backend services have been sanitised as needed. This behaviour enforced by APIM applies to my Twilio Function in kind and prevents it from calling into the Azure Function too many times and helps minimise and soften it's access to the Azure Function.
**Note, the rate limiting policy is only available from the Developer tier of APIM and higher. The Developer tier of APIM costs at least £0.05 per hour at the time of writing.