Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
114 lines
3.7 KiB
Elixir
114 lines
3.7 KiB
Elixir
defmodule TarakanWeb.Plugs.ClientIpTest do
|
|
use ExUnit.Case, async: false
|
|
|
|
import Plug.Conn
|
|
import Plug.Test
|
|
|
|
alias Tarakan.TrustedProxies
|
|
alias TarakanWeb.Plugs.ClientIp
|
|
|
|
setup do
|
|
previous = Application.get_env(:tarakan, :trusted_proxies, [])
|
|
|
|
on_exit(fn ->
|
|
Application.put_env(:tarakan, :trusted_proxies, previous)
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
|
|
test "does not trust X-Forwarded-For without configured proxies" do
|
|
Application.put_env(:tarakan, :trusted_proxies, [])
|
|
|
|
conn =
|
|
conn(:get, "/")
|
|
|> Map.put(:remote_ip, {10, 0, 0, 5})
|
|
|> put_req_header("x-forwarded-for", "203.0.113.9")
|
|
|> ClientIp.call([])
|
|
|
|
assert conn.remote_ip == {10, 0, 0, 5}
|
|
end
|
|
|
|
test "rewrites remote_ip when the peer is a trusted proxy" do
|
|
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
|
|
|
|
conn =
|
|
conn(:get, "/")
|
|
|> Map.put(:remote_ip, {10, 0, 0, 5})
|
|
|> put_req_header("x-forwarded-for", "203.0.113.9, 10.0.0.5")
|
|
|> ClientIp.call([])
|
|
|
|
assert conn.remote_ip == {203, 0, 113, 9}
|
|
end
|
|
|
|
test "ignores forwarded headers from untrusted peers" do
|
|
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
|
|
|
|
conn =
|
|
conn(:get, "/")
|
|
|> Map.put(:remote_ip, {198, 51, 100, 1})
|
|
|> put_req_header("x-forwarded-for", "203.0.113.9")
|
|
|> ClientIp.call([])
|
|
|
|
assert conn.remote_ip == {198, 51, 100, 1}
|
|
end
|
|
|
|
test "picks the rightmost untrusted hop, not a client-spoofed leftmost entry" do
|
|
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
|
|
|
|
# Attacker sends a forged leftmost value; the trusted proxy appends the real
|
|
# peer it observed. remote_ip must be the real hop, never the forged one.
|
|
conn =
|
|
conn(:get, "/")
|
|
|> Map.put(:remote_ip, {10, 0, 0, 5})
|
|
|> put_req_header("x-forwarded-for", "203.0.113.9, 198.51.100.7")
|
|
|> ClientIp.call([])
|
|
|
|
assert conn.remote_ip == {198, 51, 100, 7}
|
|
end
|
|
|
|
test "strips multiple trailing trusted proxies down to the client" do
|
|
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
|
|
|
|
conn =
|
|
conn(:get, "/")
|
|
|> Map.put(:remote_ip, {10, 0, 0, 9})
|
|
|> put_req_header("x-forwarded-for", "203.0.113.9, 10.0.0.5, 10.0.0.9")
|
|
|> ClientIp.call([])
|
|
|
|
assert conn.remote_ip == {203, 0, 113, 9}
|
|
end
|
|
|
|
test "matches an IPv4 trusted proxy against an IPv6-mapped IPv4 peer" do
|
|
# On a dual-stack (`::`) listener, an IPv4 proxy arrives as ::ffff:10.0.0.5.
|
|
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
|
|
|
|
conn =
|
|
conn(:get, "/")
|
|
|> Map.put(:remote_ip, {0, 0, 0, 0, 0, 0xFFFF, 0x0A00, 0x0005})
|
|
|> put_req_header("x-forwarded-for", "203.0.113.9")
|
|
|> ClientIp.call([])
|
|
|
|
assert conn.remote_ip == {203, 0, 113, 9}
|
|
end
|
|
|
|
test "normalizes an IPv6-mapped remote_ip to plain IPv4 for rate-limit keys" do
|
|
conn = %Plug.Conn{remote_ip: {0, 0, 0, 0, 0, 0xFFFF, 0xCB00, 0x7109}}
|
|
assert ClientIp.remote_ip_string(conn) == "203.0.113.9"
|
|
end
|
|
|
|
test "remote_ip_bucket keeps the full IPv4 address" do
|
|
conn = %Plug.Conn{remote_ip: {203, 0, 113, 9}}
|
|
assert ClientIp.remote_ip_bucket(conn) == {:v4, 203, 0, 113, 9}
|
|
end
|
|
|
|
test "remote_ip_bucket truncates IPv6 to the /64 prefix" do
|
|
conn = %Plug.Conn{remote_ip: {0x2001, 0x0DB8, 0x0001, 0x0002, 1, 2, 3, 4}}
|
|
assert ClientIp.remote_ip_bucket(conn) == {:v6, 0x2001, 0x0DB8, 0x0001, 0x0002}
|
|
end
|
|
|
|
test "remote_ip_bucket folds IPv4-mapped IPv6 into the IPv4 bucket" do
|
|
conn = %Plug.Conn{remote_ip: {0, 0, 0, 0, 0, 0xFFFF, 0xCB00, 0x7109}}
|
|
assert ClientIp.remote_ip_bucket(conn) == {:v4, 203, 0, 113, 9}
|
|
end
|
|
end
|