Skip to content

Commit

Permalink
[TT-12566/TT-12851] Add client endpoint rate limiter (#6462)
Browse files Browse the repository at this point in the history
### **User description**
<!-- Provide a general summary of your changes in the Title above -->

## Description
This PR adds endpoint level rate limiting to keys.
Merging this in policies would be added in a follow up PR.
Counter usage and increments would be as follows.
- Endpoint rate limits for `/API1/Endpoint1`, `/API1/Endpoint2` and
`/API2/Endpoint1` (using `API1Endpoint1_Counter`,
`API1Endpoint2_Counter`, `API2Endpoint1_Counter`),
- API rate limits for `/API1` and `/API3` (using `API1_Counter`,
`API3_Counter`), no per API limits specified for `API2`
 - Global rate limit for all other requests (using Global_Counter)

| Client request | Global_Counter | API1_Counter | API3_Counter |
API1Endpoint1_Counter | API1Endpoint2_Counter | API2Endpoint1_Counter |

|-----------------|----------------|--------------|--------------|-----------------------|-----------------------|-----------------------|
| /API1/Endpoint1 | | | | ++ | | |
| /API1/Endpoint2 | | | | | ++ | |
| /API1/Endpoint3 | | ++ | | | | |
| /API2/Endpoint1 | | | | | | ++ |
| /API2/Endpoint2 | ++ | | | | | |
| /API2/Endpoint3 | ++ | | | | | |
| /API3/Endpoint1 | | | ++ | | | |
| /API3/Endpoint2 | | | ++ | | | |
| /API3/Endpoint3 | | | ++ | | | |

## Related Issue
parent: https://tyktech.atlassian.net/browse/TT-12566
subtask: https://tyktech.atlassian.net/browse/TT-12851
## Motivation and Context

<!-- Why is this change required? What problem does it solve? -->

## How This Has Been Tested

<!-- Please describe in detail how you tested your changes -->
<!-- Include details of your testing environment, and the tests -->
<!-- you ran to see how your change affects other areas of the code,
etc. -->
<!-- This information is helpful for reviewers and QA. -->

## Screenshots (if appropriate)

## Types of changes

<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Refactoring or add test (improvements in base code or adds test
coverage to functionality)

## Checklist

<!-- Go over all the following points, and put an `x` in all the boxes
that apply -->
<!-- If there are no documentation updates required, mark the item as
checked. -->
<!-- Raise up any additional concerns not covered by the checklist. -->

- [ ] I ensured that the documentation is up to date
- [ ] I explained why this PR updates go.mod in detail with reasoning
why it's required
- [ ] I would like a code coverage CI quality gate exception and have
explained why


___

### **PR Type**
Enhancement, Tests


___

### **Description**
- Implemented endpoint-specific rate limiting logic in
`session_manager.go`.
- Added new test cases and refactored existing ones to support endpoint
rate limiting.
- Introduced new structures in `session.go` to handle endpoint-specific
rate limits.
- Enhanced test framework to support setup functions in test cases.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>mw_rate_limiting_test.go</strong><dd><code>Add and
refactor tests for endpoint rate limiting</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/mw_rate_limiting_test.go

<li>Added new test cases for endpoint rate limiting.<br> <li> Refactored
existing rate limit test cases into reusable structures.<br> <li>
Introduced <code>endpointRateLimitTestHelper</code> function for testing
endpoint <br>rate limits.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6462/files#diff-7cf2199231924147d538ba7ad576a48a3c0e691852077e147c9b2d86ba9b7c4d">+196/-51</a></td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>session_manager_test.go</strong><dd><code>Add tests for
endpoint rate limit info retrieval</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/session_manager_test.go

<li>Added tests for <code>getEndpointRateLimitInfo</code> function.<br>
<li> Verified correct rate limit info retrieval based on endpoint and
<br>method.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6462/files#diff-9674b884106ef21bfbe19418c5f640e530cac4fc0fbe85edc04ffe3190468874">+105/-0</a>&nbsp;
</td>

</tr>                    
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>session_manager.go</strong><dd><code>Implement
endpoint-specific rate limiting logic</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/session_manager.go

<li>Implemented endpoint-specific rate limiting logic.<br> <li> Added
support for endpoint rate limit key suffixes.<br> <li> Integrated
endpoint rate limit checks into existing rate limiting <br>flow.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6462/files#diff-e6b40a285464cd86736e970c4c0b320b44c75b18b363d38c200e9a9d36cdabb6">+62/-9</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>http.go</strong><dd><code>Add support for setup
functions in test cases</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

test/http.go

<li>Added support for <code>BeforeFn</code> in test cases to execute
setup functions <br>before tests.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6462/files#diff-a5530e34c740ce6fe2efe8dda5a356463c450696b39b97b91228f1be2491e05e">+4/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>session.go</strong><dd><code>Add endpoint-specific rate
limit structures and clone method</code></dd></summary>
<hr>

user/session.go

<li>Added <code>Clone</code> method to <code>APILimit</code> for deep
copying.<br> <li> Introduced <code>Endpoint</code>,
<code>EndpointMethod</code>, and <code>EndpointMethodRateLimit</code>
<br>structs for endpoint-specific rate limits.<br> <li> Updated
<code>AccessDefinition</code> to include <code>Endpoints</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6462/files#diff-95f1b81f7fbfef1d0372a02f3c2d557d32a3dd42e1a7d1626fdd209aaf7537f4">+50/-5</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
  • Loading branch information
jeffy-mathew authored Aug 21, 2024
1 parent 567f5fe commit c639e35
Show file tree
Hide file tree
Showing 15 changed files with 946 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .taskfiles/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ vars:
tags: '{{ .tags | default "goplugin dev" }}'
args: '{{ .args | default "-timeout=15m" }}'
buildArgs: -cover -tags "{{.tags}}"
testArgs: -failfast -coverpkg=./... {{.buildArgs}}
testArgs: -failfast -coverpkg=github.com/TykTechnologies/tyk/...,./... {{.buildArgs}}
python:
sh: python3 -c 'import sys; print("%d.%d" % (sys.version_info[0], sys.version_info[1]))'

Expand Down
6 changes: 4 additions & 2 deletions gateway/mw_api_rate_limit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,10 @@ func requestThrottlingTest(limiter string, testLevel string) func(t *testing.T)
} else if testLevel == "APILevel" {
a := p.AccessRights[spec.APIID]
a.Limit = user.APILimit{
Rate: rate,
Per: per,
RateLimit: user.RateLimit{
Rate: rate,
Per: per,
},
}

if requestThrottlingEnabled {
Expand Down
24 changes: 16 additions & 8 deletions gateway/mw_jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -933,8 +933,10 @@ func TestJWTScopeToPolicyMapping(t *testing.T) {
p.AccessRights = map[string]user.AccessDefinition{
"base-api": {
Limit: user.APILimit{
Rate: 111,
Per: 3600,
RateLimit: user.RateLimit{
Rate: 111,
Per: 3600,
},
QuotaMax: -1,
},
},
Expand All @@ -960,8 +962,10 @@ func TestJWTScopeToPolicyMapping(t *testing.T) {
p.AccessRights = map[string]user.AccessDefinition{
"api1": {
Limit: user.APILimit{
Rate: 100,
Per: 60,
RateLimit: user.RateLimit{
Rate: 100,
Per: 60,
},
QuotaMax: -1,
},
},
Expand All @@ -976,8 +980,10 @@ func TestJWTScopeToPolicyMapping(t *testing.T) {
p.AccessRights = map[string]user.AccessDefinition{
"api2": {
Limit: user.APILimit{
Rate: 500,
Per: 30,
RateLimit: user.RateLimit{
Rate: 500,
Per: 30,
},
QuotaMax: -1,
},
},
Expand Down Expand Up @@ -1191,8 +1197,10 @@ func TestJWTScopeToPolicyMapping(t *testing.T) {
p.AccessRights = map[string]user.AccessDefinition{
spec3.APIID: {
Limit: user.APILimit{
Rate: 500,
Per: 30,
RateLimit: user.RateLimit{
Rate: 500,
Per: 30,
},
QuotaMax: -1,
},
},
Expand Down
12 changes: 8 additions & 4 deletions gateway/mw_rate_limiting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,10 @@ func providerCustomRatelimitKey(t *testing.T, limiter string) {
APIID: spec.APIID,
APIName: spec.Name,
Limit: user.APILimit{
Rate: 3,
Per: 1000,
RateLimit: user.RateLimit{
Rate: 3,
Per: 1000,
},
},
},
}
Expand All @@ -317,8 +319,10 @@ func providerCustomRatelimitKey(t *testing.T, limiter string) {
APIID: spec.APIID,
APIName: spec.Name,
Limit: user.APILimit{
Rate: 3,
Per: 1000,
RateLimit: user.RateLimit{
Rate: 3,
Per: 1000,
},
},
},
}
Expand Down
4 changes: 4 additions & 0 deletions gateway/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type DBAccessDefinition struct {
DisableIntrospection bool `json:"disable_introspection"`
FieldAccessRights []user.FieldAccessDefinition `json:"field_access_rights"`
Limit *user.APILimit `json:"limit"`

// Endpoints contains endpoint rate limit settings.
Endpoints user.Endpoints `json:"endpoints,omitempty"`
}

func (d *DBAccessDefinition) ToRegularAD() user.AccessDefinition {
Expand All @@ -43,6 +46,7 @@ func (d *DBAccessDefinition) ToRegularAD() user.AccessDefinition {
AllowedTypes: d.AllowedTypes,
DisableIntrospection: d.DisableIntrospection,
FieldAccessRights: d.FieldAccessRights,
Endpoints: d.Endpoints,
}

if d.Limit != nil {
Expand Down
108 changes: 71 additions & 37 deletions gateway/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,10 @@ func (s *Test) TestPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesD
Limit: user.APILimit{
QuotaMax: 1000,
QuotaRenewalRate: 3600,
Rate: 20,
Per: 1,
RateLimit: user.RateLimit{
Rate: 20,
Per: 1,
},
},
}},
},
Expand All @@ -233,8 +235,10 @@ func (s *Test) TestPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesD
Limit: user.APILimit{
QuotaMax: 1000,
QuotaRenewalRate: 3600,
Rate: 20,
Per: 1,
RateLimit: user.RateLimit{
Rate: 20,
Per: 1,
},
},
}},
},
Expand All @@ -251,15 +255,19 @@ func (s *Test) TestPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesD
Limit: user.APILimit{
QuotaMax: 1000,
QuotaRenewalRate: 3600,
Rate: 20,
Per: 1,
RateLimit: user.RateLimit{
Rate: 20,
Per: 1,
},
},
},
"c": {
Limit: user.APILimit{
QuotaMax: -1,
Rate: 2000,
Per: 60,
RateLimit: user.RateLimit{
Rate: 2000,
Per: 60,
},
},
},
},
Expand All @@ -277,8 +285,10 @@ func (s *Test) TestPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesD
Limit: user.APILimit{
QuotaMax: 5000,
QuotaRenewalRate: 3600,
Rate: 200,
Per: 10,
RateLimit: user.RateLimit{
Rate: 200,
Per: 10,
},
},
},
},
Expand All @@ -299,8 +309,10 @@ func (s *Test) TestPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesD
Limit: user.APILimit{
QuotaMax: 5000,
QuotaRenewalRate: 3600,
Rate: 200,
Per: 10,
RateLimit: user.RateLimit{
Rate: 200,
Per: 10,
},
},
},
"e": {},
Expand Down Expand Up @@ -647,16 +659,16 @@ func (s *Test) TestPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesD
{
"Acl for a and rate for a,b", []string{"acl1", "rate-for-a-b"},
"", func(t *testing.T, s *user.SessionState) {
want := map[string]user.AccessDefinition{"a": {Limit: user.APILimit{Rate: 4, Per: 1}}}
want := map[string]user.AccessDefinition{"a": {Limit: user.APILimit{RateLimit: user.RateLimit{Rate: 4, Per: 1}}}}
assert.Equal(t, want, s.AccessRights)
}, nil,
},
{
"Acl for a,b and individual rate for a,b", []string{"acl-for-a-b", "rate-for-a", "rate-for-b"},
"", func(t *testing.T, s *user.SessionState) {
want := map[string]user.AccessDefinition{
"a": {Limit: user.APILimit{Rate: 4, Per: 1}},
"b": {Limit: user.APILimit{Rate: 2, Per: 1}},
"a": {Limit: user.APILimit{RateLimit: user.RateLimit{Rate: 4, Per: 1}}},
"b": {Limit: user.APILimit{RateLimit: user.RateLimit{Rate: 2, Per: 1}}},
}
assert.Equal(t, want, s.AccessRights)
}, nil,
Expand Down Expand Up @@ -699,16 +711,20 @@ func (s *Test) TestPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesD
Limit: user.APILimit{
QuotaMax: 1000,
QuotaRenewalRate: 3600,
Rate: 20,
Per: 1,
RateLimit: user.RateLimit{
Rate: 20,
Per: 1,
},
},
AllowanceScope: "d",
},
"c": {
Limit: user.APILimit{
QuotaMax: -1,
Rate: 2000,
Per: 60,
RateLimit: user.RateLimit{
Rate: 2000,
Per: 60,
},
},
AllowanceScope: "c",
},
Expand Down Expand Up @@ -742,17 +758,21 @@ func (s *Test) TestPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesD
"e": {
Limit: user.APILimit{
QuotaMax: -1,
Rate: 300,
Per: 1,
RateLimit: user.RateLimit{
Rate: 300,
Per: 1,
},
},
AllowanceScope: "per_api_with_limit_set_from_policy",
},
"d": {
Limit: user.APILimit{
QuotaMax: 5000,
QuotaRenewalRate: 3600,
Rate: 200,
Per: 10,
RateLimit: user.RateLimit{
Rate: 200,
Per: 10,
},
},
AllowanceScope: "d",
},
Expand Down Expand Up @@ -996,17 +1016,21 @@ func TestApplyPoliciesQuotaAPILimit(t *testing.T) {
Limit: user.APILimit{
QuotaMax: 100,
QuotaRenewalRate: 3600,
Rate: 1000,
Per: 1,
RateLimit: user.RateLimit{
Rate: 1000,
Per: 1,
},
},
},
"api2": {
Versions: []string{"v1"},
Limit: user.APILimit{
QuotaMax: 200,
QuotaRenewalRate: 3600,
Rate: 1000,
Per: 1,
RateLimit: user.RateLimit{
Rate: 1000,
Per: 1,
},
},
},
"api3": {
Expand Down Expand Up @@ -1121,8 +1145,10 @@ func TestApplyPoliciesQuotaAPILimit(t *testing.T) {
return false
}
api1LimitExpected := user.APILimit{
Rate: 1000,
Per: 1,
RateLimit: user.RateLimit{
Rate: 1000,
Per: 1,
},
QuotaMax: 100,
QuotaRenewalRate: 3600,
QuotaRenews: api1Limit.QuotaRenews,
Expand All @@ -1138,8 +1164,10 @@ func TestApplyPoliciesQuotaAPILimit(t *testing.T) {
return false
}
api2LimitExpected := user.APILimit{
Rate: 1000,
Per: 1,
RateLimit: user.RateLimit{
Rate: 1000,
Per: 1,
},
QuotaMax: 200,
QuotaRenewalRate: 3600,
QuotaRenews: api2Limit.QuotaRenews,
Expand All @@ -1155,8 +1183,10 @@ func TestApplyPoliciesQuotaAPILimit(t *testing.T) {
return false
}
api3LimitExpected := user.APILimit{
Rate: 1000,
Per: 1,
RateLimit: user.RateLimit{
Rate: 1000,
Per: 1,
},
QuotaMax: 50,
QuotaRenewalRate: 3600,
QuotaRenews: api3Limit.QuotaRenews,
Expand Down Expand Up @@ -1346,8 +1376,10 @@ func TestApplyMultiPolicies(t *testing.T) {
json.Unmarshal(data, &sessionData)

policy1Expected := user.APILimit{
Rate: 1000,
Per: 1,
RateLimit: user.RateLimit{
Rate: 1000,
Per: 1,
},
QuotaMax: 50,
QuotaRenewalRate: 3600,
QuotaRenews: sessionData.AccessRights["api1"].Limit.QuotaRenews,
Expand All @@ -1356,8 +1388,10 @@ func TestApplyMultiPolicies(t *testing.T) {
assert.Equal(t, policy1Expected, sessionData.AccessRights["api1"].Limit, "API1 limit do not match")

policy2Expected := user.APILimit{
Rate: 100,
Per: 1,
RateLimit: user.RateLimit{
Rate: 100,
Per: 1,
},
QuotaMax: 100,
QuotaRenewalRate: 3600,
QuotaRenews: sessionData.AccessRights["api2"].Limit.QuotaRenews,
Expand Down
Loading

0 comments on commit c639e35

Please sign in to comment.