8000 [RateLimiter][Security] Improve performance of login/request rate limiter by Seldaek · Pull Request #46110 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[RateLimiter][Security] Improve performance of login/request rate limiter #46110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign u 8000 p for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 24, 2022

Conversation

Seldaek
Copy link
Member
@Seldaek Seldaek commented Apr 19, 2022
Q A
Branch? 6.2
Bug fix? no
New feature? yes
Deprecations? no
Tickets Fix #40371
License MIT
Doc PR symfony/symfony-docs#...

The login rate limiter currently works this way:

  • consume 1 token on auth start (this READ + WRITES to cache backend / rate limiter storage), if this fails we fail the request as it exceeds the rate limit
  • if auth succeeded, clear the rate limit (this WRITES again)]
  • if auth failed, nothing further happens as we already consumed

So the happy path which is the most common (login succeeds) looks something like that on redis, as there are two rate limiter (global per IP and local per username+IP combo):

step1

1650362918.653657 [3 127.0.0.1:41584] "MGET" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6"
1650362918.654027 [3 127.0.0.1:41584] "MGET" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6"
1650362918.654294 [3 127.0.0.1:41584] "SETEX" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6" "119" "\x00\x00\x00\x02\x172Symfony\\Component\\RateLimiter\\Policy\\SlidingWindow\x14\x01\x11\"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00<login_per_ip-127.0.0.1\x0cA\xd8\x97\xa2\x98\xa9\xd8\xcf"
1650362918.654651 [3 127.0.0.1:41584] "MGET" "lIoZODSNyI:d97b05c8178d81ea0e2009445cb6465d5fcffa39"
1650362918.654877 [3 127.0.0.1:41584] "MGET" "lIoZODSNyI:d97b05c8178d81ea0e2009445cb6465d5fcffa39"
1650362918.655146 [3 127.0.0.1:41584] "SETEX" "lIoZODSNyI:d97b05c8178d81ea0e2009445cb6465d5fcffa39" "119" "\x00\x00\x00\x02\x172Symfony\\Component\\RateLimiter\\Policy\\SlidingWindow\x14\x01\x11\xcc\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00<login_per_ip_username-bd9913b290c5ecb5ebc0b925f736c15043ed9f214aa0bd9d980db7b9573b1a5f49943dc9a9fcccd22c9c0a630176ba7163df6cd32073cbe495fe94f7569c307a4b187e4917613982efbbe7ad2c3df785-127.0.0.1\x0cA\xd8\x97\xa2\x98\xa9\xe7\xb4"

step 2

1650362918.691968 [3 127.0.0.1:41584] "UNLINK" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6"
1650362918.692171 [3 127.0.0.1:41584] "UNLINK" "lIoZODSNyI:d97b05c8178d81ea0e2009445cb6465d5fcffa39"

This workflow is fine for form logins, but when rate limiting an API authenticator which works with a token, you end up doing a login on every request, so in that case it's less than optimal. This PR aims to improve that by changing the workflow to the following:

  • peek at token storage by consuming 0 tokens (4c2d670 allows doing this without writing to the cache to keep it to a simple READ from cache backend), if there are no tokens left available we fail the request as it is exceeding the rate limit
  • if auth success, nothing further happens
  • if auth fails, we then consume 1 token (which READ + WRITES to the cache)

Thanks to this, the happy path is much leaner on the cache backend:

1650374176.991495 [3 127.0.0.1:41926] "MGET" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6"
1650374176.991774 [3 127.0.0.1:41926] "MGET" "lIoZODSNyI:d97b05c8178d81ea0e2009445cb6465d5fcffa39"

And the login failed path consumes a token still of course so it looks a lot like the previous step1 but with an additional READ (that's the only trade-off this PR makes perf wise as far as I can tell).

1650373275.322053 [3 127.0.0.1:41906] "MGET" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6"
1650373275.322431 [3 127.0.0.1:41906] "MGET" "lIoZODSNyI:a4df4a2604acb8d5003ba9789f3af4727f50fee5"
1650373275.344146 [3 127.0.0.1:41906] "MGET" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6"
1650373275.344402 [3 127.0.0.1:41906] "MGET" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6"
1650373275.344666 [3 127.0.0.1:41906] "SETEX" "lIoZODSNyI:921c2aa99364ab8bbb0e4525c4297aa71c61d2c6" "62" "\x00\x00\x00\x02\x172Symfony\\Component\\RateLimiter\\Policy\\SlidingWindow\x14\x01\x11\"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00<login_per_ip-127.0.0.1\x0cA\xd8\x97\xac\xa7l1<"
1650373275.344966 [3 127.0.0.1:41906] "MGET" "lIoZODSNyI:a4df4a2604acb8d5003ba9789f3af4727f50fee5"
1650373275.345157 [3 127.0.0.1:41906] "MGET" "lIoZODSNyI:a4df4a2604acb8d5003ba9789f3af4727f50fee5"
1650373275.345378 [3 127.0.0.1:41906] "SETEX" "lIoZODSNyI:a4df4a2604acb8d5003ba9789f3af4727f50fee5" "62" "\x00\x00\x00\x02\x172Symfony\\Component\\RateLimiter\\Policy\\SlidingWindow\x14\x01\x11\xcd\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00<login_per_ip_username-bd9913b290c5ecb5ebc0b925f736c15043ed9f214aa0bd9d980db7b9573b1a5f49943dc9a9fcccd22c9c0a630176ba7163df6cd32073cbe495fe94f7569c307a4b187e4917613982efbbe7ad2c3df785z-127.0.0.1\x0cA\xd8\x97\xac\xa7l>\xc0"

@Seldaek Seldaek changed the title [RateLimiter][Security] Improve performance of login rate limiter [RateLimiter][Security] Improve performance of login/request rate limiter Apr 19, 2022
@Seldaek Seldaek force-pushed the login_rate_limit_peek branch 2 times, most recently from 297a22a to a4005d5 Compare April 19, 2022 14:01
Copy link
Member
@wouterj wouterj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! I had a quick look at the changes and read the PR description. Definitely sounds like a good way forward, and without any BC break/deprecations.

However, this really is a new feature and not a bugfix. That means that unfortunately, it won't be included in 6.1 (feature freeze was a month and a few days ago).

I'll have a deeper look when the development phase of 6.2 begins (especially for the TokenBucket implementation). I need to dig into the workings of the rate limiters again, and I prefer to focus on stabilizing 6.1 for the next 4 weeks.

@Seldaek
Copy link
Member Author
Seldaek commented May 5, 2022

Alright thanks for having a look :)

@chalasr chalasr modified the milestones: 6.1, 6.2 May 8, 2022
@fabpot
Copy link
Member
fabpot commented Jul 24, 2022

@wouterj Can you have a look at this one? I'd love to be able to merge it for 6.2.

@wouterj wouterj force-pushed the login_rate_limit_peek branch from d842b72 to 5c360db Compare July 24, 2022 10:23
@wouterj
Copy link
Member
wouterj commented Jul 24, 2022

I've checked the changes and added a test for all policies to make sure we don't break peeking behavior.

@Seldaek I've basically reverted the changes you did in f9229e6. Do you maybe know why you made these changes? If $availableTokens >= $consumedTokens, this means the token can be consumed immediately without waiting. I can't follow the reasoning to add wait time in this case.

@Seldaek
Copy link
Member Author
Seldaek commented Jul 24, 2022

I don't recall exactly sorry, mentally quite far away from this PR at this point.. But I think your change makes sense. I will for sure check in real life conditions whenever it is merged that the original intent is still achieved.

@fabpot
Copy link
Member
fabpot commented Jul 24, 2022

Thank you @Seldaek.

nicolas-grekas added a commit that referenced this pull request Dec 29, 2023
…licies (wouterj)

This PR was merged into the 6.3 branch.

Discussion
----------

[RateLimit] Test and fix peeking behavior on rate limit policies

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Ref #52835
| License       | MIT

Although heavily discouraged for user-land code, we've implemented peeking behavior for rate limiting in 6.2 with #46110. However, I found that our rate limit policies show very inconsistent behavior on this.

As we didn't have great test covering peeking return values, we broke BC with #51676 in 6.4. I propose this PR to verify the behavior of the policies and also make it inconsistent. I target 6.3 because we rely on this in the login throttling since 6.2 and this shows buggy error messages ("try again in 0 minute") when not using the default policy for login throttling.

> [!NOTE]
> When merging this PR, there will be heavy merge conflicts in the SlidingWindowLimiter. You can ignore the changes in this PR for this policy in 6.4. I'll rebase and update #52835 to fix the sliding window limiter in 6.4+

Commits
-------

e4a8c33 [RateLimit] Test and fix peeking behavior on rate limit policies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[RateLimiter][Security] Login rate limiting does not work for APIs/stateless firewalls
5 participants
0