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

Consider not forcing www canonicalization for API requests, due to endless stream of errant API requests now happening #1139

Open
ccaputo opened this issue Mar 30, 2022 · 22 comments
Assignees

Comments

@ccaputo
Copy link
Contributor

ccaputo commented Mar 30, 2022

#916 implemented www.peeringdb.com canonicalization.

While I understand there are benefits for browser-originated non-API requests, I am not sure there is a benefit to forcing this canonicalization on API requests, and yet a major downside has come up. We now have a continuous stream of API requests in which the client is ignoring the 301 redirect. These are burning CPU and log space, in addition to obscuring other issues when observing logs, since they outnumber everything else. A sample from one server instance, with first three octets of IPv4 addresses obscured:

119 - - [30/Mar/2022:16:39:55 +0000] "GET /api/netixlan?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ixfac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/poc?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ix?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/net?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
192 - - [30/Mar/2022:16:39:55 +0000] "GET /api/org?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/netixlan?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
243 - - [30/Mar/2022:16:39:55 +0000] "GET /api/netixlan?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.002
192 - - [30/Mar/2022:16:39:55 +0000] "GET /api/netfac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
243 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ixfac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.002
192 - - [30/Mar/2022:16:39:55 +0000] "GET /api/netixlan?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
119 - - [30/Mar/2022:16:39:55 +0000] "GET /api/org?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
243 - - [30/Mar/2022:16:39:55 +0000] "GET /api/net?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
243 - - [30/Mar/2022:16:39:55 +0000] "GET /api/poc?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
243 - - [30/Mar/2022:16:39:55 +0000] "GET /api/fac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
243 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ixpfx?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
243 - - [30/Mar/2022:16:39:55 +0000] "GET /api/org?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
119 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ixlan?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
119 - - [30/Mar/2022:16:39:55 +0000] "GET /api/net?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
119 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ix?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
119 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ixfac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ixfac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
243 - - [30/Mar/2022:16:39:55 +0000] "GET /api/netixlan?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ixpfx?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ix?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/poc?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/netfac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/fac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
147 - - [30/Mar/2022:16:39:55 +0000] "GET /api/ixlan?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
192 - - [30/Mar/2022:16:39:55 +0000] "GET /api/netfac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.002
192 - - [30/Mar/2022:16:39:55 +0000] "GET /api/fac?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001
192 - - [30/Mar/2022:16:39:55 +0000] "GET /api/net?depth=1 HTTP/1.1" 301 0 "-" "python-httpx/0.20.0" 0.001

Short of null routing or ACLing the requests, I don't know how stop them or contact them (without substantial work), and so I fear we may have years of this happening.

We may want to consider not forcing this canonicalization of API requests, if it doesn't break anything to do so, and helps restore clients to functionality.

Current and upcoming API throttling tools (ex #1126) may not help this issue, depending on whether the canonicalization happens before or after API throttling. If nothing else, we may need to have canonicalization after API throttling.

@arnoldnipper
Copy link
Contributor

@peeringdb/pc should be hotfixed IMO

@arnoldnipper arnoldnipper added this to the 1 Decide milestone Mar 31, 2022
@ynbrthr
Copy link

ynbrthr commented Apr 1, 2022

+1 in principle, I got bitten by that issue (curl not following the redirect) but I don't know if that'd break anything wrt auth indeed

@ccaputo
Copy link
Contributor Author

ccaputo commented Apr 4, 2022

Current and upcoming API throttling tools (ex #1126) may not help this issue, depending on whether the canonicalization happens before or after API throttling. If nothing else, we may need to have canonicalization after API throttling.

@vegu has indicated off GitHub that changing the ordering of API throttling to be ahead of canonicalization is likely a non-trivial change. He also indicated that changing the canonicalization to only apply to non-API requests does not appear problematic from a coding standpoint.

I recommend this change - don't canonicalize API requests - ASAP move forward unless there is an objection to be considered.

@leovegoda
Copy link
Contributor

@peeringdb/pc @ccaputo is this something that needs a point release or should we schedule it for the next schedule release?

@grizz
Copy link
Member

grizz commented Apr 4, 2022

To play devil's advocate, since those aren't working now and not having www. will break caching, wouldn't it make more sense to just drop those requests after the first the redirect?

Eventually someone will notice and correct their script. :)

@martinhannigan
Copy link

martinhannigan commented Apr 4, 2022 via email

@ccaputo
Copy link
Contributor Author

ccaputo commented Apr 4, 2022

To play devil's advocate, since those aren't working now and not having www. will break caching, wouldn't it make more sense to just drop those requests after the first the redirect?

Eventually someone will notice and correct their script. :)

@grizz: I'm intrigued. By drop do you mean no response whatsoever, just close TCP, or a 4xx response or ???

Would you maintain state in some manner, like for a given IP having already been told to redirect in the last 5 mins, if they ignore just drop?

@peeringdb/pc @ccaputo is this something that needs a point release or should we schedule it for the next schedule release?

@leovegoda: Not sure. Let's see where the above goes before figuring that out.

@grizz
Copy link
Member

grizz commented Apr 4, 2022

@grizz: I'm intrigued. By drop do you mean no response whatsoever, just close TCP, or a 4xx response or ???

I was originally thinking just close TCP, but we could probably return a 4XX as well, as long as it's caught early in the pipeline (which it would be on the redirect handler).

Would you maintain state in some manner, like for a given IP having already been told to redirect in the last 5 mins, if they ignore just drop?

Yes, that's what I was thinking. Give a redirect, any other requests from the same IP for X time period will just get ignored.

@ccaputo
Copy link
Contributor Author

ccaputo commented Apr 4, 2022

@grizz: I'm intrigued. By drop do you mean no response whatsoever, just close TCP, or a 4xx response or ???

I was originally thinking just close TCP, but we could probably return a 4XX as well, as long as it's caught early in the pipeline (which it would be on the redirect handler).

TBD how nginx would handle the closed TCP request, but something that slows the requests like that could, would be nice. I wonder if there is a hack possible (or already in existence) to signal nginx to be similarly harsh with the client.

Would you maintain state in some manner, like for a given IP having already been told to redirect in the last 5 mins, if they ignore just drop?

Yes, that's what I was thinking. Give a redirect, any other requests from the same IP for X time period will just get ignored.

I love the idea. Let's see if anyone else has thoughts on this.

@netravnen
Copy link
Contributor

Would you maintain state in some manner, like for a given IP having already been told to redirect in the last 5 mins, if they ignore just drop?

Yes, that's what I was thinking. Give a redirect, any other requests from the same IP for X time period will just get ignored.

I love the idea. Let's see if anyone else has thoughts on this.

I'm in favour of this approach!

If we do not enforce the change to www for all requests and "just" implement workarounds. We are no closer to the goal than
when we set out to start the change, in my honest opinion.

@ccaputo
Copy link
Contributor Author

ccaputo commented Apr 16, 2022

@peeringdb/pc can we make sure @grizz's solution goes into the next release? The endless 301's continue unabated.

@mcmanuss8
Copy link
Contributor

mcmanuss8 commented Apr 18, 2022

Can we reach out to the users of these requests (if they're using user or org api keys) and ask them to switch?

@ccaputo
Copy link
Contributor Author

ccaputo commented Apr 18, 2022

Can we reach out to the users of these requests (if they're using user or org api keys) and ask them to switch?

This isn't really practical.

We don't have auth-id logging enabled on production to know, but I highly doubt these are using api keys.

In the last log rotation day (ie. a full day, now gzipped) there were over 1,900 unique source IP addresses making API queries and being redirected. Of these, 195 different source IP addresses made the same query 100 times or more. Of that, 116 different source IP addresses made the same query 1,000 times or more.

@ccaputo
Copy link
Contributor Author

ccaputo commented May 13, 2022

A quick check indicates about 30% of the HTTP requests to the API servers result in 301 redirects. It would be good to make progress on this.

@ccaputo
Copy link
Contributor Author

ccaputo commented May 17, 2022

I learned today that this problem triggered a bug in one user's code that resulted in PeeringDB getting far more requests than previous, from the user. It would be good to make progress on this.

@grizz
Copy link
Member

grizz commented May 18, 2022

Actually, I think it would make more sense to do this in AWS or a thin responder that's not tied or touching PeeringDB at all. With the added advantage that we don't need to wait for a release.

Since they're spamming and not following the redirects, instead of closing the connection, it would be nice to send a redirect and then tarpit the connection.
I think doing the redirect in AWS would just fix it, but it would still be spamming up to the Load Balancers. If we care about that, we can do a quick App that responds as I said above for all non www requests.

Thoughts?

@ccaputo
Copy link
Contributor Author

ccaputo commented May 18, 2022

Potentially interesting re AWS. Following up with you via email.

@ccaputo
Copy link
Contributor Author

ccaputo commented Aug 2, 2022

I think we need to revisit this. The forcing of canonical for /api/ requests continues to waste developer time (#1206). There is an apparent bug in the python requests module in which the followup query after a 301 redirect, fails to include (or mangles?) the request headers necessary for authentication. Thus the PeeringDB server responds as if it is an unauthenticated request, while the developer thinks they are authenticating. Chaos ensues. Totally opaque.

@mcmanuss8
Copy link
Contributor

+1 @grizz what are our options here?

@ccaputo
Copy link
Contributor Author

ccaputo commented Aug 9, 2022

I think we need to revisit this. The forcing of canonical for /api/ requests continues to waste developer time (#1206). There is an apparent bug in the python requests module in which the followup query after a 301 redirect, fails to include (or mangles?) the request headers necessary for authentication. Thus the PeeringDB server responds as if it is an unauthenticated request, while the developer thinks they are authenticating. Chaos ensues. Totally opaque.

Turns out this isn't a bug in the python requests module (code here: https://github.com/psf/requests/blob/177dd90f/requests/sessions.py#L284-L296), but rather "by-design" to prevent leakage of credentials in the face of a malicious 301 redirect. Other software packages may perform this same safety credential dropping upon redirect.

BTW, another PeeringDB client developer hit this problem today. It is opaque because they get an HTTP 200 and some useful data after the redirect, without realizing it is unauthenticated and thus possibly missing User-only fields.

@dwcarder
Copy link

dwcarder commented Aug 9, 2022

So, I hit this issue today (see #1220 for a bit more color)

The symptom was that the API worked via the redirect, but the auth header was stripped off for my protection via python-requests as @ccaputo describes. This had the effect of the api appearing to work, but note this would now be an anonymous query subjecting my client to the lower rate-limit now in effect. I would have preferred either that there was no redirect for API access, or a hard 4xx error forcing me to fix my client code.

@martinhannigan
Copy link

martinhannigan commented Oct 11, 2022 via email

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

No branches or pull requests

9 participants