Git Product home page Git Product logo

Comments (3)

ajvondrak avatar ajvondrak commented on May 28, 2024

Summarizing what I understand from your descriptions:

iex> RemoteIp.from([{"x-forwarded-for", "97.126.47.5, 172.33.29.126,::ffff:172.33.253.26, 34.228.243.32"}])
{34, 228, 243, 32}

IPs are processed right-to-left to prevent spoofing. 34.228.243.32 is not a known proxy, so it's presumed to be the client immediately. None of the other IPs are considered.

iex> RemoteIp.from([{"x-forwarded-for", "97.126.47.5, 34.97.78.79,::ffff:10.116.4.32, 35.245.31.68"}], proxies: ~w[34.97.78.79])
{35, 245, 31, 68}

Again, the rightmost IP in the header is not a known proxy, so it is returned immediately.

iex> RemoteIp.from([{"x-forwarded-for", "97.126.47.5, 34.97.78.79,::ffff:10.116.4.32, 35.245.31.68"}], proxies: ~w[34.97.78.79 35.245.31.68])
{0, 0, 0, 0, 0, 65535, 2676, 1056}

Here, the rightmost IP is a known proxy, so it's skipped. The next IP is ::ffff:10.116.4.32, which is not recognized as a reserved IP or as a proxy, so it's presumed to be the client.

So:

Does the algorithm account for private ranges in this notation format?

It does not. The current list of IPs is given by

remote_ip/lib/remote_ip.ex

Lines 262 to 269 in 4826fc4

@reserved ~w[
127.0.0.0/8
::1/128
fc00::/7
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
] |> Enum.map(&RemoteIp.Block.parse!/1)

This permalink is circa v1.0.0, but the list has been the same for the life of the remote_ip library. The base IPs are parsed via :inet.parse_strict_address/1, which will take the IPv4 notation and produce an IPv4-style tuple of 4 integers, which won't compare the same to an IPv6 address in mapped-IPv4 format:

> :inet.parse_strict_address('10.0.0.0')
{:ok, {10, 0, 0, 0}}
> :inet.parse_strict_address('::ffff:10.116.4.32')
{:ok, {0, 0, 0, 0, 0, 65535, 2676, 1056}}

You can add a CIDR for the mapped-IPv4 range to your known proxies to prevent the IPv6 block from being considered.

Is it just a fluke that the first configuration works?

The first configuration works because you aren't saying that the rightmost IP is a known proxy, so it's presumed to be the client.

thanks much

Thanks for the thorough write-up! Hope I've cleared up any confusion. :)

from remote_ip.

bgroupe avatar bgroupe commented on May 28, 2024

@ajvondrak thank you for your response. I can confirm that whitelisting ::ffff:10.112.0.0/12 in my second case does in fact solve the problem. However I wish to clarify something about my issue.

...
iex> RemoteIp.from([{"x-forwarded-for", "97.126.47.5, 172.33.29.126,::ffff:172.33.253.26, 34.228.243.32"}])
{34, 228, 243, 32}
...
IPs are processed right-to-left to prevent spoofing. 34.228.243.32 is not a known proxy, so it's presumed to be the client immediately. None of the other IPs are considered.

In this particular case, 34.228.243.32 is not returned, the desired IP 97.126.47.5 is returned. This has worked consistently in a production environment for some time, and was the impetus for my filing this issue because in attempting to move to a new network topology (described in my second example) I realized I didn't understand why it works.

What's more, I have observed inconsistent behavior in parsing with the new topology as well. I have upgraded to v1.0.0 in the interim in order to make use of the new runtime configuration feature. The following header returns the leftmost IP:

"67.168.104.21, 172.33.148.54,::ffff:172.33.13.200, 3.84.201.74, 167.82.224.22"
# ^remote --^internal host - ^internal proxy host - ^host (unknown) -- ^known-proxy

I would expect 3.84.201.74 to be returned because it is not whitelisted. However 67.168.104.21 is returned.

But this example:

"67.168.104.21, 172.33.207.160,::ffff:172.33.129.250, 3.84.201.210, 157.52.99.83"
# ^remote --^internal host - ^internal proxy host - ^host (unknown) -- ^known-proxy

correctly returns

RemoteIp.from([{"x-forwarded-for", "67.168.104.21, 172.33.207.160,::ffff:172.33.129.250, 3.89.201.210, 157.52.99.83"}], proxies:  [<very-long-proxy-list>])
{3, 89, 201, 210}

These scenarios are leaving me genuinely confused about what to expect.

from remote_ip.

ajvondrak avatar ajvondrak commented on May 28, 2024

I would expect 3.84.201.74 to be returned because it is not whitelisted. However 67.168.104.21 is returned.

That sounds strange, indeed. Would it be helpful to use the debugger? I imagine it'd be noisy in production, but maybe on a canary deploy or something?

These scenarios are leaving me genuinely confused about what to expect.

It really just depends on what's in the list of :clients and :proxies. The algorithm doesn't involve much: scan IPs right-to-left, return the first client. The distinction between "client" vs "non-client" has a couple moving pieces. In order:

  1. Is the IP in one of the CIDRs in :clients? Then it's a client.
  2. Is the IP in one of the CIDRs in :proxies? Then it's not a client.
  3. Is the IP in one of the CIDRs hard-coded for reserved addresses? Then it's not a client.
  4. Otherwise, the IP is assumed to be a client.

In code:

remote_ip/lib/remote_ip.ex

Lines 255 to 257 in 4826fc4

defp client?(ip, opts) do
type(ip, opts) in [:client, :unknown]
end
and

remote_ip/lib/remote_ip.ex

Lines 271 to 282 in 4826fc4

defp type(ip, opts) do
debug :type, [ip] do
ip = RemoteIp.Block.encode(ip)
cond do
opts[:clients] |> contains?(ip) -> :client
opts[:proxies] |> contains?(ip) -> :proxy
@reserved |> contains?(ip) -> :reserved
true -> :unknown
end
end
end

Beyond that, it might be that remote_ip isn't seeing the headers you think it's seeing. For example, maybe it's pulling in some non-XFF header when you're using the default :headers option (which includes a bunch of different values). The debugger can give more insight into the parsing steps.

from remote_ip.

Related Issues (16)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.