141 lines
4.5 KiB
Elixir
141 lines
4.5 KiB
Elixir
defmodule MicrowavepropWeb.Plugs.RemoteIpPropertyTest do
|
|
@moduledoc """
|
|
Property tests for the CIDR-matching logic in
|
|
`MicrowavepropWeb.Plugs.RemoteIp`. The `peer_trusted?` helper is
|
|
private, so we exercise it via the plug's `init/1` + `call/2`
|
|
pipeline: set up a trusted-proxy list, hand the plug a conn whose
|
|
`remote_ip` we control, and assert whether the forwarded-IP header
|
|
was honoured.
|
|
"""
|
|
|
|
use ExUnit.Case, async: true
|
|
use ExUnitProperties
|
|
|
|
import ExUnit.CaptureLog
|
|
|
|
alias MicrowavepropWeb.Plugs.RemoteIp
|
|
|
|
# The X-Forwarded-For value we supply whenever we want to check
|
|
# whether the peer was trusted.
|
|
@forwarded_ipv4 "198.51.100.7"
|
|
@forwarded_tuple {198, 51, 100, 7}
|
|
|
|
# ── Generators ──────────────────────────────────────────────────
|
|
|
|
defp ipv4_octet, do: integer(0..255)
|
|
|
|
defp ipv4_tuple_gen do
|
|
gen all(
|
|
a <- ipv4_octet(),
|
|
b <- ipv4_octet(),
|
|
c <- ipv4_octet(),
|
|
d <- ipv4_octet()
|
|
) do
|
|
{a, b, c, d}
|
|
end
|
|
end
|
|
|
|
defp ipv4_string(tuple) do
|
|
tuple |> Tuple.to_list() |> Enum.join(".")
|
|
end
|
|
|
|
# ── Helpers ─────────────────────────────────────────────────────
|
|
|
|
defp call_plug(remote_ip, trusted_cidrs) do
|
|
opts = RemoteIp.init(trusted_proxies: trusted_cidrs)
|
|
|
|
conn =
|
|
Phoenix.ConnTest.build_conn()
|
|
|> Map.put(:remote_ip, remote_ip)
|
|
|> Plug.Conn.put_req_header("x-forwarded-for", @forwarded_ipv4)
|
|
|
|
RemoteIp.call(conn, opts)
|
|
end
|
|
|
|
defp trusted?(remote_ip, cidrs) do
|
|
call_plug(remote_ip, cidrs).remote_ip == @forwarded_tuple
|
|
end
|
|
|
|
defp in_cidr?({a, b, c, d}, {{na, nb, nc, nd}, prefix}) do
|
|
host = <<a, b, c, d>>
|
|
network = <<na, nb, nc, nd>>
|
|
<<host_prefix::bitstring-size(prefix), _::bitstring>> = host
|
|
<<net_prefix::bitstring-size(prefix), _::bitstring>> = network
|
|
host_prefix == net_prefix
|
|
end
|
|
|
|
# ── Properties ──────────────────────────────────────────────────
|
|
|
|
property "/0 trusts every IPv4 peer" do
|
|
check all(peer <- ipv4_tuple_gen()) do
|
|
assert trusted?(peer, ["0.0.0.0/0"])
|
|
end
|
|
end
|
|
|
|
property "/32 trusts only the exact IP" do
|
|
check all(
|
|
anchor <- ipv4_tuple_gen(),
|
|
other <- ipv4_tuple_gen(),
|
|
anchor != other
|
|
) do
|
|
cidrs = [ipv4_string(anchor) <> "/32"]
|
|
assert trusted?(anchor, cidrs)
|
|
refute trusted?(other, cidrs)
|
|
end
|
|
end
|
|
|
|
property "every X.Y.Z.W in 10.0.0.0/8 is trusted; nothing outside is" do
|
|
cidrs = ["10.0.0.0/8"]
|
|
|
|
check all(ip <- ipv4_tuple_gen()) do
|
|
inside = in_cidr?(ip, {{10, 0, 0, 0}, 8})
|
|
assert trusted?(ip, cidrs) == inside
|
|
end
|
|
end
|
|
|
|
property "arbitrary /prefix CIDRs match iff the peer's high-order prefix bits agree" do
|
|
check all(
|
|
network <- ipv4_tuple_gen(),
|
|
prefix <- integer(1..32),
|
|
peer <- ipv4_tuple_gen()
|
|
) do
|
|
cidr = ipv4_string(network) <> "/" <> Integer.to_string(prefix)
|
|
assert trusted?(peer, [cidr]) == in_cidr?(peer, {network, prefix})
|
|
end
|
|
end
|
|
|
|
property "union of multiple CIDRs matches the logical OR of each" do
|
|
check all(
|
|
a <- ipv4_tuple_gen(),
|
|
prefix_a <- integer(4..32),
|
|
b <- ipv4_tuple_gen(),
|
|
prefix_b <- integer(4..32),
|
|
peer <- ipv4_tuple_gen()
|
|
) do
|
|
cidrs = [
|
|
ipv4_string(a) <> "/" <> Integer.to_string(prefix_a),
|
|
ipv4_string(b) <> "/" <> Integer.to_string(prefix_b)
|
|
]
|
|
|
|
expected = in_cidr?(peer, {a, prefix_a}) or in_cidr?(peer, {b, prefix_b})
|
|
assert trusted?(peer, cidrs) == expected
|
|
end
|
|
end
|
|
|
|
property "/0 as the only CIDR still trusts everything even with bogus entries mixed in" do
|
|
# Invalid CIDR strings are dropped at init time with a warning;
|
|
# the surviving /0 should still match every peer.
|
|
capture_log(fn ->
|
|
check all(peer <- ipv4_tuple_gen()) do
|
|
assert trusted?(peer, ["0.0.0.0/0", "not-a-cidr", "999.999.999.999/24", "10.0.0.0/33"])
|
|
end
|
|
end)
|
|
end
|
|
|
|
property "an empty trusted-proxy list rejects every peer" do
|
|
check all(peer <- ipv4_tuple_gen()) do
|
|
# No CIDRs → the forwarded header must not flow through.
|
|
refute trusted?(peer, [])
|
|
end
|
|
end
|
|
end
|