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

rpc: add option to whitelist ips in rate limiting #3701

Merged
merged 18 commits into from
May 9, 2024

Conversation

niklasad1
Copy link
Member

@niklasad1 niklasad1 commented Mar 14, 2024

This PR adds two new CLI options to disable rate limiting for certain ip addresses and whether to trust "proxy header".
After going back in forth I decided to use ip addr instead host because we don't want rely on the host header which can be spoofed but another solution is to resolve the ip addr from the socket to host name.

Example:

$ polkadot --rpc-rate-limit 10 --rpc-rate-limit-whitelisted-ips 127.0.0.1/8 --rpc-rate-limit-trust-proxy-headers

The ip addr is read from the HTTP proxy headers Forwarded, X-Forwarded-For X-Real-IP if --rpc-rate-limit-trust-proxy-headers is enabled if that is not enabled or the headers are not found then the ip address is read from the socket.

//cc @BulatSaif can you test this and give some feedback on it?

@niklasad1 niklasad1 changed the title rpc: add option to whitelist hosts in rate limit rpc: add option to whitelist hosts in rate limiting Mar 14, 2024
@niklasad1 niklasad1 changed the title rpc: add option to whitelist hosts in rate limiting rpc: add option to whitelist hosts in rate limiting Mar 15, 2024
// X-Forwarded-For: 203.0.113.195,198.51.100.178
if let Some(ips) = req.headers().get(&X_FORWARDED_FOR).and_then(|v| v.to_str().ok()) {
if let Some(proxy_ip) = ips.split_once(',').and_then(|(v, _)| IpAddr::from_str(v).ok()) {
// NOTE: we assume that ip addr is global
Copy link
Member Author

@niklasad1 niklasad1 Mar 15, 2024

Choose a reason for hiding this comment

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

checking if the ip address is global is a PITA (would be nice with https://doc.rust-lang.org/std/net/struct.Ipv4Addr.html#method.is_global stabilized) but shouldn't be an issue in practice.

Enabling rate-limiting for a local rpc server doesn't make sense to me :)

@niklasad1 niklasad1 marked this pull request as ready for review March 15, 2024 18:11
@BulatSaif
Copy link
Contributor

Thank you, @niklasad1.

It seems we had different interpretations of "whitelisted hosts". I was expecting to see a filter to check "origins", similar to --rpc-cors, or a filter to check "Host" or X-Forwarded-Host headers. Since the RPC node is behind a proxy, I can manipulate the Host headers or origins on the proxy side, for example, adding authentication for one domain. However, based on the code implementation, it seems you implemented the IPs filter, which will also work for us. I will test it on Monody.

Given that we have an IP-based filter, can we rename the flag from:

polkadot --rpc-rate-limit-whitelisted-hosts localhost polkadot.js.org

to:

polkadot --rpc-rate-limit-excluded-ips 10.0.0.0/8 1.2.3.4/32

hosts_to_ip_addrs function can be removed.

@niklasad1
Copy link
Member Author

niklasad1 commented Mar 17, 2024

Ah right, checking the X-Forwarded-Host header is nicer and easier to implement (missed that)
I'll take of it on Monday

@niklasad1
Copy link
Member Author

niklasad1 commented Mar 18, 2024

I'm a bit unsure about this now because it's easy for someone to just set the X-Forwarded-For or X-Forwarded-Host and we can't really check if whether it's spoofed or not which is quite easy to do to "bypass rate-limiting" if it's configured on server.

Thus, I think we need another CLI configuration that the node operation can set whether to trust certain proxies.
For instance "my node is behind proxy a and no other proxies are allowed."

If the node is not not behind a load balancer/reverse proxy then it doesn't make sense to trust X-Forwarded-headers.
Using the X-Forwarded-Host and Origin header makes me less sure about it and perhaps it's better to use ip anyway.

@BulatSaif
Copy link
Contributor

I'm a bit unsure about this now because it's easy for someone to just set the X-Forwarded-For or X-Forwarded-Host and we can't really check if whether it's spoofed or not which is quite easy to do to "bypass rate-limiting" if it's configured on server.

Most proxies are set to drop X-Forwarded-For or X-Forwarded-Host by default. If a client tries to send X-Forwarded-*, it will be ignored.

My use case was a little bit different. I was planning to have two domains: public-rpc.polkadot.io with rate limiting and a proxy, and internal-rpc.local without rate limiting. Since the second domain is in the local zone, you cannot route outside traffic there.

@niklasad1 niklasad1 changed the title rpc: add option to whitelist hosts in rate limiting WIP rpc: add option to whitelist hosts in rate limiting Mar 18, 2024
@niklasad1
Copy link
Member Author

Got it but my only concern with the current code is that if a public node enables --rpc-rate-limit-allow-host trusted.com and is not behind a reverse proxy or anything.

It would trivial for a client to just set X-Forwarded-Host: trusted.com and fool the server to disable the rate-limiting for that connection.

So, I still think I want another flag for that to make it explicit but probably most likely that the server will be behind reverse proxy when rate-limiting is enabled. Lemme think about it a little more and see whether I can come up with something reasonable

@BulatSaif
Copy link
Contributor

@niklasad1 do you have any update?

It would trivial for a client to just set X-Forwarded-Host: trusted.com and fool the server to disable the rate-limiting for that connection.

You can fool the server but not the proxy. If node is behind proxy (and it is always behind proxy since https certs are not supported) the X-Forwarded-Host will be set to correct host.

@niklasad1
Copy link
Member Author

@niklasad1 do you have any update?

oops, forgot about it, I'll take another iteration tomorrow

@niklasad1 niklasad1 changed the title WIP rpc: add option to whitelist hosts in rate limiting rpc: add option to whitelist ips in rate limiting Apr 26, 2024
@niklasad1
Copy link
Member Author

niklasad1 commented Apr 26, 2024

You can fool the server but not the proxy. If node is behind proxy (and it is always behind proxy since https certs are not supported) the X-Forwarded-Host will be set to correct host.

Yes, my point was that if one doesn't run the rpc node behind a reverse proxy that could be dangerous to trust the proxy headers because they could be spoofed.

I have now added another flag --rpc-rate-limit-trust-proxy-headers which users that know the node is behind a reverse proxy can set and without this flag only the remote addr of the socket is used.

Are you okay with these changes @BulatSaif?

@niklasad1 niklasad1 requested review from lexnv and jsdw April 26, 2024 16:28
Copy link
Contributor

@jsdw jsdw left a comment

Choose a reason for hiding this comment

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

Codewise, this looks good to me so long as whitelisting based on IP is acceptable!

I do agree that the rpc_rate_limit_trust_proxy_headers flag is necessary; this prevents users who spin up nodes that are not in front of a load balancer from accidentally falling foul of malicious actors who realise they can pass those flags to bypass rate limiting :)

Copy link
Contributor

@lexnv lexnv left a comment

Choose a reason for hiding this comment

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

LGTM! Nice one!

@BulatSaif
Copy link
Contributor

You can fool the server but not the proxy. If node is behind proxy (and it is always behind proxy since https certs are not supported) the X-Forwarded-Host will be set to correct host.

Yes, my point was that if one doesn't run the rpc node behind a reverse proxy that could be dangerous to trust the proxy headers because they could be spoofed.

I have now added another flag --rpc-rate-limit-trust-proxy-headers which users that know the node is behind a reverse proxy can set and without this flag only the remote addr of the socket is used.

Are you okay with these changes @BulatSaif?

Sorry, I was ooo last week, just saw this.

Yes, it is a good idea to add a flag to disable/enable trust to headers. I will test the PR on Monday and provide feedback.

@bkchr
Copy link
Member

bkchr commented May 5, 2024

Why are we adding all this stuff deeply into the node? What again was the excuse for this? Why isn't this all just being done by some proxy in front of the RPC port?

@niklasad1
Copy link
Member Author

Why are we adding all this stuff deeply into the node? What again was the excuse for this? Why isn't this all just being done by some proxy in front of the RPC port?

I agree that ideally this should be done by a proxy but we decided to add a simple rpc-rate-limit middleware into the node because it's a tricky because the most proxies works on the HTTP layer. Thus, when the connection is upgraded to websocket connection the rate-limit etc from HTTP proxy doesn't restrict each RPC call/WebSocket message.

Thus, this PR just adds another CLI to disable such middleware for certain peers.

I'm aware that https://github.com/AcalaNetwork/subway would work but I don't know how stable it's but would definitely be great to remove this from the node if possible.

@xlc
Copy link
Contributor

xlc commented May 6, 2024

we are using Subway for almost a year for Acala production RPC service and it is pretty stable and it will be more stable if more people are using it. it is very unlikely for it to be less stable compare to related features implemented here

@bkchr
Copy link
Member

bkchr commented May 6, 2024

I'm aware that https://github.com/AcalaNetwork/subway would work but I don't know how stable it's but would definitely be great to remove this from the node if possible.

Then we should start trying it.

@BulatSaif
Copy link
Contributor

@niklasad1, I tested it locally. Is it possible to accept IP addresses with a mask (10.0.0.0/8) or how to set a range of IPs like 10.0.0.0-10.255.255.255?

docker run -it --rm paritypr/polkadot-debug:3701-b3787db0 --rpc-rate-limit-whitelisted-ips  10.0.0.0/8
error: invalid value '10.0.0.0/8' for '--rpc-rate-limit-whitelisted-ips <RPC_RATE_LIMIT_WHITELISTED_IPS>...': invalid IP address syntax

The remaining functions are working as expected. Thank you

@niklasad1
Copy link
Member Author

I tested it locally. Is it possible to accept IP addresses with a mask (10.0.0.0/8) or how to set a range of IPs like 10.0.0.0-10.255.255.255?

I have changed it now and it requires the IP address to be in correct CIDR notation to support ranges good catch

@niklasad1 niklasad1 added the T0-node This PR/Issue is related to the topic “node”. label May 7, 2024
@niklasad1 niklasad1 added this pull request to the merge queue May 9, 2024
Merged via the queue into master with commit d37719d May 9, 2024
149 of 150 checks passed
@niklasad1 niklasad1 deleted the na-rpc-rate-limiting-whitelist-hosts branch May 9, 2024 07:49
hitchhooker pushed a commit to ibp-network/polkadot-sdk that referenced this pull request Jun 5, 2024
This PR adds two new CLI options to disable rate limiting for certain ip
addresses and whether to trust "proxy header".
After going back in forth I decided to use ip addr instead host because
we don't want rely on the host header which can be spoofed but another
solution is to resolve the ip addr from the socket to host name.

Example:

```bash
$ polkadot --rpc-rate-limit 10 --rpc-rate-limit-whitelisted-ips 127.0.0.1/8 --rpc-rate-limit-trust-proxy-headers
```

The ip addr is read from the HTTP proxy headers `Forwarded`,
`X-Forwarded-For` `X-Real-IP` if `--rpc-rate-limit-trust-proxy-headers`
is enabled if that is not enabled or the headers are not found then the
ip address is read from the socket.

//cc @BulatSaif can you test this and give some feedback on it?
TarekkMA pushed a commit to moonbeam-foundation/polkadot-sdk that referenced this pull request Aug 2, 2024
This PR adds two new CLI options to disable rate limiting for certain ip
addresses and whether to trust "proxy header".
After going back in forth I decided to use ip addr instead host because
we don't want rely on the host header which can be spoofed but another
solution is to resolve the ip addr from the socket to host name.

Example:

```bash
$ polkadot --rpc-rate-limit 10 --rpc-rate-limit-whitelisted-ips 127.0.0.1/8 --rpc-rate-limit-trust-proxy-headers
```

The ip addr is read from the HTTP proxy headers `Forwarded`,
`X-Forwarded-For` `X-Real-IP` if `--rpc-rate-limit-trust-proxy-headers`
is enabled if that is not enabled or the headers are not found then the
ip address is read from the socket.

//cc @BulatSaif can you test this and give some feedback on it?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T0-node This PR/Issue is related to the topic “node”.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants