Initial commit on Forgejo
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s

Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
Maxfield Luke 2026-07-29 04:43:40 -04:00
commit af6077b9c3
455 changed files with 78366 additions and 0 deletions

View file

@ -0,0 +1,114 @@
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