Page MenuHomePhabricator

Exempt some routes from rate limiting and JWT validity checks in the API gateway
Open, HighPublic

Description

There should be a way to exempt some routes from checks so that requests to them always pass through the gateway, even when the sender is otherwise rate-limited, and even when the request has an invalid JWT. We have three use cases for this:

  • The /oauth2/access_token endpoint, which is used to request a new access token (using a refresh token or client credentials) when the old one expires. We want to ignore invalid JWTs, because signing this request with the expired access token is an easy mistake to make (right now this probably wouldn't work at the MediaWiki level either, but we should fix that); and we want to ignore rate limits, because since the access token expired, the user will be seen as unauthenticated when doing this request. If their lower, IP-based unauthenticated rates are already used up (they are on a cgNAT or run another, anonymous client from the same IP or whatever), they won't be able to reauthenticate.
    • Maybe we should do the same for the /oauth2/authorize endpoint which is used to get an access token interactively (by sending the user to the authorization dialog). Less likely to be an actual problem, but there isn't really any disadvantage to doing it.
  • The login-related action API endpoints (action=login, action=clientlogin, action=query&meta=tokens, maybe action=query&meta=authmanagerinfo), to allow users to become authenticated even when they are using a busy IP or their user-agent is being abused.
  • The event intake endpoint (https://intake-logging.wikimedia.org/v1/events), if that goes through the API gateway at all. We probably don't want to break event logging just because the user has hit their API quota; we especially don't want to break JS error logging, since we want to use that to log rate limit errors. (JWT errors on this endpoint aren't possible as far as I can see, so this is about rate limiting only.)

The gateway only needs to deter abusive use of our infrastracture, not protect against DDoS (that happens at the edge), and neither of these endpoints can be abused, or are interesting to scrapers at all, and neither of them is particularly sensitive to high load, so exempting them should be fine AFAICS.

Related Objects

StatusSubtypeAssignedTask
OpenNone
OpenArielGlenn
OpenNone
OpenNone
OpenNone
OpenNone
OpenNone
Resolveddaniel
Resolveddaniel
Resolveddaniel
ResolvedVgutierrez
ResolvedJAllemandou
ResolvedDAlangi_WMF
Resolved Tgr
ResolvedJAllemandou
DuplicateNone
Resolveddaniel
Resolveddaniel
Resolveddaniel
OpenNone
Resolved Tgr
Open Tgr
Resolvedmatmarex
Resolvedmatmarex
Resolveddaniel
Opendaniel
Resolveddaniel
Opendaniel
OpenBUG REPORTNone
Opendaniel

Event Timeline

Change #1248477 had a related patch set uploaded (by Daniel Kinzler; author: Daniel Kinzler):

[operations/deployment-charts@master] rest-gateway: exempt certain paths from jwt checks

https://gerrit.wikimedia.org/r/1248477

How about we excempt everything under /oauth2/? Makes the config easier.

Could the Action API login endpoints (action=clientlogin and action=login) also be exempted? They suffer from the same issues as OAuth login – one cannot be logged in while logging in.

How about we excempt everything under /oauth2/? Makes the config easier.

We talked about this in person, but just for the record, the routes in question are:

  • /oauth2/access_token - needs to be exempt per task description
  • /oauth2/authorize - probably should be exempt per task description (at the very least, won't hurt)
  • /oauth2/resource/profile - OIDC endpoint. Should only get authenticated requests so doesn't need to be exempt, but I don't see any harm in it either.
  • /oauth2/resource/scopes - TBH not sure what this is (it provides a subset of the OIDC data), but it doesn't seem important so does not matter whether it's exempt.
  • /oauth2/client - used by API Portal for proxying OAuth client management actions, in theory this is just a normal API that's not part of the OAuth protocol and should be rate limited, but it's not interesting for scrapers and there is no abuse potential, so no harm in ignoring it

So yes, should be fine to exempt the whole oauth2 component.

Exempting /oauth2/authorize will probably fix https://github.com/wikimedia-gadgets/gadget-deploy/issues/7, so I'm glad that's being included.

Could the Action API login endpoints (action=clientlogin and action=login) also be exempted? They suffer from the same issues as OAuth login – one cannot be logged in while logging in.

Yeah, we were planning to do that. Might not have said so in any public place though, so let's add it to the task description.

Thanks!

action=query&meta=tokens, maybe action=query&meta=authmanagerinfo

I didn’t realize these ones are also necessary. They are tricky, since action=query allows bundling multiple requests (e.g. https://meta.wikimedia.org/w/api.php?action=query&prop=revisions&meta=siteinfo|tokens&titles=Main%20Page&rvprop=user|comment); it even allows including the innocent meta=tokens part in the URL and the expensive prop=revisions in a POST body. So probably to be on the safe side, action=query should be exempted from rate limits only if

  • the request is GET and has no body (this is not absolutely necessary, but I guess it makes much easier to ensure no other parameters are present),
  • and the query parameters are (only) action=query plus meta=tokens, meta=authmanagerinfo, meta=tokens|authmanagerinfo or meta=authmanagerinfo|tokens plus the generic parameters documented on https://meta.wikimedia.org/w/api.php?action=help&modules=main.
  • the request is GET and has no body (this is not absolutely necessary, but I guess it makes much easier to ensure no other parameters are present),
  • and the query parameters are (only) action=query plus meta=tokens, meta=authmanagerinfo, meta=tokens|authmanagerinfo or meta=authmanagerinfo|tokens plus the generic parameters documented on https://meta.wikimedia.org/w/api.php?action=help&modules=main.

I'm not sure that's possible with Envoy's matching rules. @Clement_Goubert what do you think?

Yeah dealing with action API URLs will be a huge pain. You'd need to disallow all other meta/prop/list/generator parameter values, plus the export parameter at least.

Alternatively, we could just generate the common URL combinations for using tokens - I think a typical request will include action=query, meta=tokens, type=login, format=json, maybe formatversion=1 or formatversion=2, maybe errorformat=html or errorformat=plain. Given the order is unspecified, that's something like 200 possible combinations, maybe we should just generate those and use as an explicit match list. (Sad trombone music for CORS users since they have to include their origin as a parameter.)

Or maybe it could be worked into cost-based rate limiting somehow? Envoy could let requests with meta=tokens in them through, and then MediaWiki would somehow indicate whether it was a "pure" token request, and Envoy could block it on the way back if not.

Honestly, making tokens a query modules seems like a mistake. Queries should be stateless data access. If we went back to using action=token the problem would be solved.

Or maybe it could be worked into cost-based rate limiting somehow? Envoy could let requests with meta=tokens in them through, and then MediaWiki would somehow indicate whether it was a "pure" token request, and Envoy could block it on the way back if not.

Envoy can't do that afaik, it can only add that cost to the tally and block subsequent requests.

  • the request is GET and has no body (this is not absolutely necessary, but I guess it makes much easier to ensure no other parameters are present),

GET-only matching can be done with a match on the :method headers. Body would require custom lua logic to check for body presence and either set a temporary header or dynamic metadata.

That may be possible to express using pure QueryParameterMatcher logic but would probably turn into a very complex envoy configuration.

I'm not sure that's possible with Envoy's matching rules. @Clement_Goubert what do you think?

I think the only real solution is to implement this logic as part of the envoy_on_request function of the lua filter, since matching on body presence is required anyways. It's still going to be fairly complex logic if we want to be comprehensive.

I think the only real solution is to implement this logic as part of the envoy_on_request function of the lua filter, since matching on body presence is required anyways. It's still going to be fairly complex logic if we want to be comprehensive.

Hm... I was just pivoting my in exactly the opposite way... all this just because tokens are implemented as a query module?...

I suppose action=query&meta=tokens only needs to be exempt with type=login, right? not for type=csrf?

Change #1248477 merged by jenkins-bot:

[operations/deployment-charts@master] rest-gateway: per-route jwt overrides

https://gerrit.wikimedia.org/r/1248477

That and maybe type=createaccount.

Or maybe it could be worked into cost-based rate limiting somehow? Envoy could let requests with meta=tokens in them through, and then MediaWiki would somehow indicate whether it was a "pure" token request, and Envoy could block it on the way back if not.

I don’t think it’s a good idea. This would mean that if someone sends a thousand expensive action=query requests per second, MediaWiki performs all the thousand expensive operations, it just doesn’t return the result. It means basically the same load as if there was no rate-limiting at all.

Honestly, making tokens a query modules seems like a mistake. Queries should be stateless data access. If we went back to using action=token the problem would be solved.

It does have upsides as well, though: unless one is trying to log in, the ability to batch requests means that one has to issue less requests, so it’s less likely to hit the rate limit (whether the logged-in or the logged-out one).

Also, what do you call “stateless”? Several query modules take the user session into account in one way or the other (meta=userinfo mentioned below obviously, since it returns data on the current user; others use the user’s UI language preference or apihighlimits right). Not to mention data being hidden based on user rights.

GET-only matching can be done with a match on the :method headers. Body would require custom lua logic to check for body presence and either set a temporary header or dynamic metadata.

If we have luck, MediaWiki (or PHP) ignores the body on GET requests anyway, someone should check that. I wrote both conditions because from the API consumer’s perspective, that should happen, but if MediaWiki solves a part of the filtering, it’s enough to do the other part in Envoy.

I suppose action=query&meta=tokens only needs to be exempt with type=login, right? not for type=csrf?

I tested a slightly outdated (early November 2025) version of Pywikibot, and it sends a request to action=query&meta=tokens&type=createaccount|csrf|login|patrol|rollback|userrights|watch (a POST request with parameters in the body, so it needs to be updated if we conclude to exempt only GET). Would it be risky or more complicated to allow any type(s)? I’d assume it’s very low-risk and actually simpler to not check this parameter than to check it.


Pywikibot also sends a request to action=query&meta=userinfo&uiprop=blockinfo|groups|hasmsg|ratelimits|rights. I was thinking about adding an assert=user or assertuser=<username> parameter (it’s a bit tricky, as Pywikibot can do read-only operations without logging in, and we shouldn’t make that impossible on third-party wikis or if the user accepts the risk of being rate-limited, but I hope it can be solved somehow), would it be possible to exempt requests with assert=user, assert=bot or assertuser=<user> where <user> is a named user? Those requests fail fast for IP/temp users anyway, and it would help not eating up the rate limit if the session expires.

Change #1255731 had a related patch set uploaded (by Daniel Kinzler; author: Daniel Kinzler):

[operations/deployment-charts@master] rest gateway: prevent abuse of exempt api modules

https://gerrit.wikimedia.org/r/1255731