Protecting APIs with Access Policy Manager and custom iRules
The problem: Unprotected API - Vulnerable to Overload Without Rate-Limiting Enforcement
Our customer in the B2B sector is encountering a challenge with their public API. Despite having implemented a custom method for generating long-lived API keys, they find themselves unable to enforce rate-limiting effectively. This absence of rate-limiting mechanisms poses significant challenges, potentially resulting in the overloading of their system due to excessive requests or the exploitation of their API by unauthorized users. Without proper rate-limiting controls in place, the customer faces risks to both the performance and security of their API infrastructure, necessitating a solution to mitigate these concerns and ensure the smooth operation of their services for their clients. Our customers wants to offer two tiers of service level agreements (SLAs) - gold and standard. Complicating matters further, the API key, integral to authentication, is transmitted via a custom HTTP header.
The solution: BIG-IP APM and Custom iRules for Effective Rate-Limiting
My solution involves leveraging the API Protection feature of BIG-IP APM in conjunction with a custom iRule. By utilizing this combination, our customer can effectively extract the API Keys from HTTP requests and enforce rate limiting on specific API endpoints. As for now they only want to enforce rate limiting on the POST endpoints. This approach empowers the customer to secure their API while efficiently managing and controlling access to critical endpoints, ensuring optimal performance and safeguarding against abuse or overload.
With this iRule we can to extract the API key from the HTTP Requests and store it in a variable, that can later be used by the API Protection feature of the APM.
API Keys and the associated SLA level are stored in a Data Group of the type string.
# Enable (1) or disable (0) logging globally
when RULE_INIT {
set static::debug 1
}
# Access and analyze the HTTP header data for SLA value
when HTTP_REQUEST {
set sla [class lookup [HTTP::header value apikey] dg_apikeys]
if { $static::debug } {log local0. "Made it to HTTP_REQUEST event with SLA value $sla."}
}
# Evaluate SLA value during per-request access policy execution
when ACCESS_PER_REQUEST_AGENT_EVENT {
set id [ACCESS::perflow get perflow.irule_agent_id]
if { $id eq "read-sla" } {
if { $static::debug } {log local0. "Made it to iRule agent in perrequest policy with SLA value $sla."}
ACCESS::perflow set perflow.custom "$sla"
}
}
And this is how the Per Request Policy in the API Protection profile looks. It uses the value of the API Key (extracted with the help of the the iRule) and the Source IP of the client to enforce Rate Limiting on the POST endpoints, using two different SLAs.
In the APM log you should see the following message, once the client exceeds his quota defined in the SLA.
Apr 28 20:12:42 ltm-apm-16.mylab.local notice tmm[11094]: 01870075:5: (null):/Common: API request with weight (1) violated the quota in rate limiting config(/Common/demo_api_ratelimiting_auto_rate_limiting_standard).
Apr 28 20:12:42 ltm-apm-16.mylab.local notice tmm[11094]: 0187008d:5: /Common/demo_api_ratelimiting_ap:Common:6600283561834577940: Execution of per request access policy (/Common/demo_api_ratelimiting_prp) done with ending type (Reject)
Further reading:
You can find a more detailed write-up on my GitHub page: https://github.com/webserverdude/f5_APM_API_Protection
There you can find the Per Request Policy explained in all details.
The Data Group with for the iRule.
A demo API for testing my solution.
A Postman Collection for working with my demo API.