Skip to content
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 up 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

server: rate limiter v1 #1708

Merged
merged 1 commit into from Feb 21, 2024
Merged

server: rate limiter v1 #1708

merged 1 commit into from Feb 21, 2024

Conversation

TBonnin
Copy link
Collaborator

@TBonnin TBonnin commented Feb 21, 2024

Describe your changes

Add rate-limiting capabilities to server

Issue ticket number and link

https://linear.app/nango/issue/NAN-359/implement-rate-limiting

Checklist before requesting a review (skip if just adding/editing APIs & templates)

  • I added tests, otherwise the reason is:
  • I added observability, otherwise the reason is:
  • I added analytics, otherwise the reason is:

Copy link

linear bot commented Feb 21, 2024

@TBonnin TBonnin force-pushed the tbonnin/NAN-359/rate-limiting branch 2 times, most recently from 0086f2c to 0eed9cb Compare February 21, 2024 08:55
packages/server/lib/server.ts Dismissed Show dismissed Hide dismissed
packages/server/lib/server.ts Dismissed Show dismissed Hide dismissed
@TBonnin TBonnin force-pushed the tbonnin/NAN-359/rate-limiting branch from 0eed9cb to f6decbe Compare February 21, 2024 09:41
@TBonnin TBonnin requested review from khaliqgant and bastienbeurier and removed request for khaliqgant February 21, 2024 09:41
@TBonnin TBonnin marked this pull request as ready for review February 21, 2024 09:42
const opts = {
keyPrefix: 'middleware',
points: 1200,
duration: 60,
Copy link
Collaborator Author

@TBonnin TBonnin Feb 21, 2024

Choose a reason for hiding this comment

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

I picked a pretty high global limit (20rps). The goal is for now to prevent an "infinite" number of requests to overload the server when users misbehave like it happened with gretel last month.
We can tweak the logic as much as we want later. Let me know what you think

Copy link
Contributor

Choose a reason for hiding this comment

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

no need to change anything, just thinking out loud.
when I set a ratelimiter I always try to put a large window because it allows a few burst but not a constant one, so you don't end up locking the system constantly.

  • in a 60s window you can burst 1200 http call every 60s
  • in a 600s window you can burst once every 600s, but if you reach the limit sooner you are blocked for a long period of time

In other word, an attacker/bad script would only be block for a few seconds now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Setting the right limit is tricky indeed. A larger window with the same limit also means the avg rate per seconds is lower.
I am sure we will be tweaking the logic to handle more scenarii in the future but for now I would sleep better at night knowing that beyond a certain threshold, request will be discarded quickly and not hit the rest of the system, especially the db

logger.info(`Rate limit exceeded for ${key}. Request: ${req.method} ${req.path})`);
next();
// TODO:
// res.status(429).send('Too Many Requests');
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As a first step, we are just logging and not sending the 429. Just to make sure there is no obvious issue. I will let it run a few hours like this before to start sending 429

const apiPublicAuth = authMiddleware.publicKeyAuth.bind(authMiddleware);

const apiAuth = [authMiddleware.secretKeyAuth, rateLimiterMiddleware];
const apiPublicAuth = [authMiddleware.publicKeyAuth, rateLimiterMiddleware];
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@khaliqgant why were we using bind before?

Copy link
Member

Choose a reason for hiding this comment

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

Legacy, I'm actually not sure why and not sure if it is needed.

@TBonnin TBonnin requested review from bodinsamuel and khaliqgant and removed request for bastienbeurier February 21, 2024 09:46
@TBonnin TBonnin force-pushed the tbonnin/NAN-359/rate-limiting branch 2 times, most recently from d01fb4c to 0dbcd4d Compare February 21, 2024 11:12
};

function getKey(req: Request, res: Response): string {
try {
Copy link
Member

Choose a reason for hiding this comment

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

Clarification question: for api usage the key will be the account and for web it’ll be the user?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes with a fallback to IP for non-authenticated endpoints

Copy link
Member

@khaliqgant khaliqgant left a comment

Choose a reason for hiding this comment

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

Clarification question inline. Looks good overall. Think some tweaks will need to be made once shipped but this is a solid initial version

@TBonnin TBonnin force-pushed the tbonnin/NAN-359/rate-limiting branch from 0dbcd4d to 733f8fc Compare February 21, 2024 15:34
@TBonnin TBonnin enabled auto-merge (squash) February 21, 2024 15:37
Copy link
Contributor

@bodinsamuel bodinsamuel left a comment

Choose a reason for hiding this comment

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

Seems to work 👍🏻

autocannon -c 10 -m 'GET' -H 'Content-Type: application/json' http://127.0.0.1:3003/api/v1/meta  --amount 1000 --renderStatusCodes --debug

NB: There is no rate limit on 404

Comment on lines +10 to +11
points: 1200,
duration: 60,
Copy link
Contributor

Choose a reason for hiding this comment

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

more actionable comment: can you make them env var so we don't need to deploy in case of urgent need?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sure. I will do that when I actually enable 429s. Now it is just logging

})();

export const rateLimiterMiddleware = (req: Request, res: Response, next: NextFunction) => {
const setXRateLimitHeaders = (rateLimiterRes: RateLimiterRes) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have a env var to entirely enable/disable ratelimit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will also do that when actually returning 429s

const pointsToConsume = getPointsToConsume(req);
rateLimiter
.consume(key, pointsToConsume)
.then((rateLimiterRes) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

any reason for not using try/catch await?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

no other reason than using the same style as the rate-limit-flexible documentation. Also found it easier to read

if (req.user) {
return `user-${req.user.id}`;
}
return `ip-${req.ip}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if proxy are handled with this. I remember that old version of express would not display the right IP if you had proxy (always happen in cloud env)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am not 100% sure. That's the main reason why I prefer to first just log and not return 429 so I can check with real requests

@TBonnin TBonnin merged commit 590072a into master Feb 21, 2024
15 of 16 checks passed
@TBonnin TBonnin deleted the tbonnin/NAN-359/rate-limiting branch February 21, 2024 15:37
@TBonnin TBonnin mentioned this pull request Mar 1, 2024
TBonnin added a commit that referenced this pull request Mar 25, 2024
## Describe your changes

Enabling rate-limiting (was only logging so far). 
Since I pushed the memoization of getConnection yesterday, no rate
limiting has been logged so it should not impact anybody.

followup of #1708
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants