Skip to content

Commit

Permalink
[TT-12990] fix upstream endpoint RL not considering endpoint method (#…
Browse files Browse the repository at this point in the history
…6651)

### **User description**
<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-12990"
title="TT-12990" target="_blank">TT-12990</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
<td>API endpoint upstream rate limiting is not considering endpoint
method</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Bug"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium"
/>
        Bug
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Dev</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
      <td>-</td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

<!-- Provide a general summary of your changes in the Title above -->

## Description

This PR fixes a bug where upstream endpoint rate limit middleware
doesn't consider endpoint method while generating redis key
## Related Issue
https://tyktech.atlassian.net/browse/TT-12990

## 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)
- [ ] 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**
Bug fix, Tests


___

### **Description**
- Fixed a bug in the rate limit middleware where the HTTP method was not
considered in the rate limit key generation, potentially causing
incorrect rate limiting.
- Enhanced the rate limit tests to include HTTP method consideration,
ensuring that rate limits are correctly applied per method.
- Refactored test functions to support method-specific rate limits and
added a regression test to verify the fix.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>mw_api_rate_limit.go</strong><dd><code>Include HTTP
method in rate limit key generation</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/mw_api_rate_limit.go

<li>Added <code>fmt</code> package import.<br> <li> Modified rate limit
key generation to include HTTP method.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6651/files#diff-46326b04f936c839922e970db5c2924156cc797070948f3dc9c589d04661d6d2">+2/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>per_api_limit_test.go</strong><dd><code>Enhance rate
limit tests to include HTTP method</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

tests/rate/per_api_limit_test.go

<li>Added HTTP method consideration in rate limit tests.<br> <li>
Refactored test functions to support method-specific rate limits.<br>
<li> Added regression test for per-method rate limiting.<br>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6651/files#diff-81981a7ab606e7274913a4cf3030c12ef9d6856f2862420b6b069909f8175bd7">+61/-24</a>&nbsp;
</td>

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

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
  • Loading branch information
jeffy-mathew authored Oct 18, 2024
1 parent 33db0e2 commit cf5aeb0
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 25 deletions.
3 changes: 2 additions & 1 deletion gateway/mw_api_rate_limit.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gateway

import (
"fmt"
"net/http"

"strconv"
Expand Down Expand Up @@ -54,7 +55,7 @@ func (k *RateLimitForAPI) getSession(r *http.Request) *user.SessionState {
if ok {
if limits := spec.RateLimit; limits.Valid() {
// track per-endpoint with a hash of the path
keyname := k.keyName + "-" + storage.HashStr(limits.Path)
keyname := k.keyName + "-" + storage.HashStr(fmt.Sprintf("%s:%s", limits.Method, limits.Path))

session := &user.SessionState{
Rate: limits.Rate,
Expand Down
86 changes: 62 additions & 24 deletions tests/rate/per_api_limit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ package rate_test

import (
"encoding/json"
"fmt"
"net/http"
"testing"

"github.com/TykTechnologies/tyk/apidef"

"github.com/stretchr/testify/assert"

. "github.com/TykTechnologies/tyk/gateway"
"github.com/TykTechnologies/tyk/internal/uuid"
"github.com/TykTechnologies/tyk/test"
)

func buildPathRateLimitAPI(tb testing.TB, gw *Gateway, pathName string, rate, per int64) {
func buildPathRateLimitAPI(tb testing.TB, gw *Gateway, per int64, rateLimits []apidef.RateLimitMeta) {
tb.Helper()

gw.BuildAndLoadAPI(func(spec *APISpec) {
Expand All @@ -23,39 +25,34 @@ func buildPathRateLimitAPI(tb testing.TB, gw *Gateway, pathName string, rate, pe
spec.GlobalRateLimit.Per = float64(per)

version := spec.VersionData.Versions["v1"]
versionJSON := []byte(fmt.Sprintf(`{
"use_extended_paths": true,
"extended_paths": {
"rate_limit": [{
"method": "GET",
"rate": %d,
"per": %d
}]
}
}`, rate, per))
err := json.Unmarshal(versionJSON, &version)
assert.NoError(tb, err)

version.ExtendedPaths.RateLimit[0].Path = pathName
version.UseExtendedPaths = true
version.ExtendedPaths.RateLimit = rateLimits
spec.VersionData.Versions["v1"] = version

})
}

func testRateLimit(tb testing.TB, ts *Test, testPath string, want int) {
func testRateLimit(tb testing.TB, ts *Test, testPath string, testMethod string, want int) {
tb.Helper()

// single request
_, _ = ts.Run(tb, test.TestCase{
Path: "/ratelimit" + testPath,
BodyMatch: fmt.Sprintf(`"Url":"%s"`, testPath),
Path: "/ratelimit" + testPath,
Method: testMethod,
BodyMatchFunc: func(bytes []byte) bool {
res := map[string]any{}
err := json.Unmarshal(bytes, &res)
assert.NoError(tb, err)
return assert.Equal(tb, testPath, res["Url"]) && assert.Equal(tb, testMethod, res["Method"])
},
})

// and 50 more
var ok, failed int = 1, 0
for i := 0; i < 50; i++ {
res, err := ts.Run(tb, test.TestCase{
Path: "/ratelimit" + testPath,
Path: "/ratelimit" + testPath,
Method: testMethod,
})

assert.NoError(tb, err)
Expand All @@ -82,8 +79,16 @@ func TestPerAPILimit(t *testing.T) {
forPath := "/" + uuid.New()
testPath := "/miss"

buildPathRateLimitAPI(t, ts.Gw, forPath, 30, 60)
testRateLimit(t, ts, testPath, 15)
rateLimits := []apidef.RateLimitMeta{
{
Method: http.MethodGet,
Path: forPath,
Rate: 30,
Per: 60,
},
}
buildPathRateLimitAPI(t, ts.Gw, 60, rateLimits)
testRateLimit(t, ts, testPath, http.MethodGet, 15)
})

t.Run("hit per-endpoint rate limit", func(t *testing.T) {
Expand All @@ -93,7 +98,40 @@ func TestPerAPILimit(t *testing.T) {
forPath := "/" + uuid.New()
testPath := forPath

buildPathRateLimitAPI(t, ts.Gw, forPath, 30, 60)
testRateLimit(t, ts, testPath, 30)
rateLimits := []apidef.RateLimitMeta{
{
Method: http.MethodGet,
Path: forPath,
Rate: 30,
Per: 60,
},
}
buildPathRateLimitAPI(t, ts.Gw, 60, rateLimits)
testRateLimit(t, ts, testPath, http.MethodGet, 30)
})

t.Run("[TT-12990][regression] hit per-endpoint per-method rate limit", func(t *testing.T) {
ts := StartTest(nil)
defer ts.Close()

forPath := "/anything/" + uuid.New()
testPath := forPath
rateLimits := []apidef.RateLimitMeta{
{
Method: http.MethodGet,
Path: forPath,
Rate: 20,
Per: 60,
},
{
Method: http.MethodPost,
Path: forPath,
Rate: 30,
Per: 60,
},
}
buildPathRateLimitAPI(t, ts.Gw, 60, rateLimits)
testRateLimit(t, ts, testPath, http.MethodGet, 20)
testRateLimit(t, ts, testPath, http.MethodPost, 30)
})
}

0 comments on commit cf5aeb0

Please sign in to comment.