Trying to implement Rate Limiting Policy on API Management in Azure - azure

I'm trying to setup a policy within Azure APIM where I can rate limit calls to the API, but also whitelist a range of IP's so they're not stopped by the Rate Limit Policy.
Ideally I want to limit the amount of calls per IP to 60 times within 60secs, unless it's from a whitelisted IP address (We use a set range to pull info to an external dependency which would exceed the rate, so would need them to still do that)
So far, I've tried the following (The IP's and backend-id have been changed for here :) ):
<set-backend-service id="apim-generated-policy" backend-id="name-of-my-function-app" />
<rate-limit-by-key calls="60" renewal-period="60" counter-key="#(context.Request.IpAddress)" increment-condition="#(context.Response.StatusCode == 204 ^ context.Response.StatusCode == 404)" remaining-calls-variable-name="remainingCallsPerIP" />
<ip-filter action="allow">
<address-range from="10.0.0.0" to="10.0.0.254"/>
<address-range from="10.1.0.0" to="10.1.0.254"/>
<address-range from="10.2.0.0" to="10.2.0.254"/>
<address>10.20.30.40</address>
</ip-filter>
I have the 'rate-limit-by-key calls' portion working on another project - but the issue is when I try to apply an IP filter/whitelist into it.
What I'm finding is that the rate limit isn't applying using the above code, but it's applying a rate limit, but only if you're part of that IP filter.
I reckon I need to try slip in an IF module somehow so that if you're part of that IP range, it would ignore the calls? Would that be the best way of creating the policy, or is there another way?

Managed to fiddle around with it, and I've answered my own question.
If anyone needs it, the answer is:
<policies>
<inbound>
<base />
<set-backend-service id="apim-generated-policy" backend-id="name-of-my-function-app" />
<rate-limit-by-key calls="100" renewal-period="60" counter-key="#(context.Request.Ip)" >
<whitelist>
<add ip="10.0.0.0/24"/>
<add ip="10.1.0.0/24"/>
<add ip="10.2.0.0/24"/>
<add ip="20.77.50.57"/>
</whitelist>
<on-exceeded>
<return-response>
<set-status code="429" reason="Too Many Requests" />
</return-response>
</on-exceeded>
</rate-limit-by-key>
</inbound>
</policies>

Related

How to dynamically update the backend/outbound URL in Azure APIM

we are using AIPM with AKS.I am trying to dynamically redirect the target URL based on the availability. For e.g say I have couple of backend url configured with load balancer. and when I hit the load balancer I want to to go to endpoint 1 or endpoint 2 based on the availability
I am tryin to do something similar as mentioned here in the HA proxy https://www.haproxy.com/blog/failover-and-worst-case-management-with-haproxy/. Since we already using APIM I am assuming it would be a overkill to have HA proxy just for this unless the feature is not available. while I can understand we can rewrite the URL or set the backend dynamically I am not sure if APIM can decide whether the URL is up and if not it can fallback to other URL's mentioned in the config something available in the link above.
What my current understand is that I can use the policy and if there is an error I can use an error policy where I can redirect. but that is for one condition. Any suggestion or pointers would be helpful.
You can react on the response code from your backend API and set a new backend url. This might not be perfect but it will get you started. In this example I assume that you get a 500 response from your backend if it's "down":
<backend>
<retry condition="#(context.Response.StatusCode == 500)" count="2" interval="1" first-fast-retry="true">
<choose>
<when condition="#(context.Response != null && context.Response.StatusCode == 500)">
<set-backend-service base-url="https://my-other-service.azurewebsites.net" />
</when>
<otherwise />
</choose>
<forward-request />
</retry>
</backend>

Azure API Management refresh cached response

I'm using API Management with an external Azure Cache for Redis to cache responses that reach out to external services. I need to provide a mechanism for invalidating cached responses by refetching data from the server - in turn updating the cache with the new responses.
My current policy (shown below) receives up-to-date data from the server when the cached response expires or a new request with a Cache-Control: must-revalidate header is received. Again, when the header is received, I'd like to update the cached response with the new response. Am I missing anything either conceptually or in my policy?
<policies>
<inbound>
<base />
<set-backend-service id="apim-generated-policy" backend-id="func-myapp-dev-001" />
<set-variable name="mustRevalidate" value="#(context.Request.Headers.GetValueOrDefault("Cache-Control","").Contains("must-revalidate"))" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("mustRevalidate") == false)">
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" allow-private-response-caching="true" />
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-header name="Cached-At" exists-action="override">
<value>#(System.DateTime.Now.ToString())</value>
</set-header>
<cache-store duration="360" />
</outbound>
<on-error>
<base />
</on-error>
</policies>
The problem is that <cache-store> cannot live without <cache-lookup>. <cache-lookup> not only gets cached value from the cache but it is also kind of configuration for <cache-store>. And you can see that for yourself in the trace. When you call your API with Control-Cache: must-revalidate header, <cache-lookup> won't be triggered because of your <when> policy but also <cache-store> won't be triggered, unfortunately. In the logs you will see message:
cache-store (0.019 ms)
"Corresponding cache-lookup policy was not applied. Skipping cache-store."
So there you have it. Skipping <cache-lookup> will get you fresh response from the backend but won't cache it afterwards for future requests.
As for how to achieve what you want.
I've checked it twice but unfortunately <cache-lookup> does not have option to skip lookup based on parameter.
Also was thinking about different approach. I think this could work with use of <cache-lookup-value> policies. At the very beginning, based on your must-revalidate parameter if it is set to true you could remove your cached value. And then proceed normally. The hardest part would be to think of the mechanism that could generate the cache key based on the request. The same thing APIM is doing in cache-store. The key must be unique between different request but the same for the same request - exactly like caching works. The pattern that APIM applies for the cache keys, is something like this (you can check in the trace):
"cacheKey": "{apimName}.{apiId};{revision};{backendurl with all query parameters}"
example from my API:
"cacheKey": "my-apim.azure-api.net.1_weatherbit-io;rev=1.2432_get-current-city_id-city_id-key-key_4_https_api.weatherbit.io_443_/v2.0/current?city_id=4487042"
Once you have some smart generation of keys I think you are good to go, the final solution could look something like this:
<policies>
<inbound>
<set-variable name="cacheKey" value="#{
return ""; // here implement some smart key generation based on the request parameters from context.
}" />
<set-variable name="mustRevalidate" value="#(context.Request.Headers.GetValueOrDefault("Cache-Control","").Contains("must-revalidate"))" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("mustRevalidate") == true)">
<cache-remove-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" />
</when>
</choose>
<cache-lookup-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" variable-name="cachedResponse" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<JObject>("cachedResponse") != (JObject)null)">
<return-response>
<set-header name="Content-Type" exists-action="override">
<value>application/json; charset=utf-8</value>
</set-header>
<set-body>#{
return context.Variables.GetValueOrDefault<JObject>("cachedResponse").ToString();
}</set-body>
</return-response>
</when>
</choose>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<cache-store-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" value="#(context.Response.Body.As<JObject>(preserveContent: true))" duration="30" />
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Of course this caches only the response, if you want to cache headers as well you would have to implement mechanism of how to store it, and then when its time to return it inside <return-response>, how to parse it to headers and body. I hope you can see what is the downside of this solution, you need to implement everything yourself. You no longer can use the built-in functionality of <cache-lookup> and <cache-store>. But at the same time you have more freedom and can implement what you want. Anyway, good luck.
PS. <cache-store-value> policy has caching-type attribute. You can use it to change cache to external.

Azure API Management - determine if response was served from cache

We are using a <cache-lookup> policy in the <inbound> block (with an external Redis). Is it possible to execute a policy - to be specific, I would like to write a <trace>when the cache lookup was a hit and the response is served from cache?
Today we have this:
<inbound>
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none" caching-type="external">
<vary-by-query-parameter>xyz</vary-by-query-parameter>
</cache-lookup>
Is something like this possible? (pseudo code)
<choose>
<when condition="cache-lookup == true">
<trace>
<message>This request is served from Redis cache!</message>
</trace>
</when>
</choose>
</inbound>
According to some research, it seems there isn't a variable or status for us to judge if the request is served from Redis cache or the backend server. The <cache-lookup> policy wraps the functions connect with Redis, I don't think we can know where the request is served from. And as you mentioned the code is pseudo, I don't think the code <when condition="cache-lookup == true"> can work.
For this requirement, I think you can just ask azure develop team for this feature on feedback page.

Retry request ends with "Content length mismatch"

My problem goes this way:
I have an Azure APIM, I have created an API and addeded the backend retry policy as below.
<backend>
<retry condition="#("{{Transient-ErrorCode}}".Contains(Convert.ToString(context.Response.StatusCode)))" count="3" interval="5" first-fast-retry="false">
<forward-request />
</retry>
</backend>
The server return success(statuscode: 200) for the first time, When it initiate the retry it encountered the following(I am retrying at its success also, for testing the retry is working fine.).
forward-request (1.326 ms)
{
"messages": [
"Content length mismatch",
"Content length mismatch"
]
}
Please help with your thoughts/experience on the same.
This is because request sent by client is not cached in memory by default in APIM, instead it's streamed right from client to backend. So when needs come to retry the request request payload is not there. I assume that you have problems only with requests that have body.
To resolve the issue you first need to cache request body:
<inbound>
<set-variable name="body" value="#(context.Request.Body.As<string>(preserveContent: true))" />
</inbound>
<backend>
<retry condition="#("{{Transient-ErrorCode}}".Contains(Convert.ToString(context.Response.StatusCode)))" count="3" interval="5" first-fast-retry="false">
<set-body>#((string)context.Variables["body"])</set-body>
<forward-request />
</retry>
</backend>
There appears to be an alternative simplification for including the body by using the attribute buffer-request-body on the forward-request policy:
<!-- no need to put body into variable in the inbound policy -->
<backend>
<retry condition="#("{{Transient-ErrorCode}}".Contains(Convert.ToString(context.Response.StatusCode)))" count="3" interval="5" first-fast-retry="false">
<!-- no need for the set-body policy -->
<forward-request buffer-request-body="true" />
</retry>
</backend>
See doco for forward-request's attributes

How to specify rewrite url for query string parameters in Azure API Management

I'm using the Azure API Management to transform the incoming query string into another query string.
My transformation code is:
<policies>
<inbound>
<rewrite-uri template="api/primes?a={a}&b={b}" />
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
When I try to save the edits, the error appears:
One or more fields contain incorrect values:
'=' is an unexpected token. The expected token is ';'. Line 15, position 50.
which refers to the equals symbol as in a={a}. How do I correct the template of the rewrite-uri? The input url is for example https://example.com/sum?a=7&b=5.
Try replacing:
<rewrite-uri template="api/primes?a={a}&b={b}" />
With:
<rewrite-uri template="api/primes?a={a}&b={b}" />
Find more details at https://azure.microsoft.com/en-us/blog/policy-expressions-in-azure-api-management/.
You only need to create "Query Parameters" in APIM instead of "Template parameters".
Then your rewrite uri doesn't need to include the Query parameters as APIM will add it to backend url automatically once it is provided via inbound.
<rewrite-uri template="api/primes" />
if request URL is something like this:
https://example.com/sum?a=7&b=5
then the HTTP request sent to backend would be like this:
GET backendapi/api/primes?a=7&b=5
and if request URL is without query strings like this:
https://example.com/sum
then the HTTP request sent to backend would be simply like this:
GET backendapi/api/primes

Resources