Merge pull request 'Use upstream remote_ip package' (#7900) from issue-3314-remote-ip-hex into develop

Reviewed-on: https://git.pleroma.social/pleroma/pleroma/pulls/7900
This commit is contained in:
lain 2026-05-14 05:47:32 +00:00
commit 5b63307f85
7 changed files with 85 additions and 10 deletions

View file

@ -0,0 +1 @@
Use upstream Hex releases for the `remote_ip` dependency and expose client IP ranges for remote IP resolution.

View file

@ -738,6 +738,7 @@ config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifeti
config :pleroma, Pleroma.Web.Plugs.RemoteIp, config :pleroma, Pleroma.Web.Plugs.RemoteIp,
enabled: true, enabled: true,
headers: ["x-forwarded-for"], headers: ["x-forwarded-for"],
clients: [],
proxies: [], proxies: [],
reserved: [ reserved: [
"127.0.0.0/8", "127.0.0.0/8",

View file

@ -2910,7 +2910,7 @@ config :pleroma, :config_description, [
key: Pleroma.Web.Plugs.RemoteIp, key: Pleroma.Web.Plugs.RemoteIp,
type: :group, type: :group,
description: """ description: """
`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. `Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://hex.pm/packages/remote_ip) but with runtime configuration.
**If your instance is not behind at least one reverse proxy, you should not enable this plug.** **If your instance is not behind at least one reverse proxy, you should not enable this plug.**
""", """,
children: [ children: [
@ -2926,6 +2926,12 @@ config :pleroma, :config_description, [
A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `["x-forwarded-for"]`. A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `["x-forwarded-for"]`.
""" """
}, },
%{
key: :clients,
type: {:list, :string},
description:
"A list of client IPs or subnets in CIDR notation. These will not be treated as proxies or reserved ranges. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128."
},
%{ %{
key: :proxies, key: :proxies,
type: {:list, :string}, type: {:list, :string},

View file

@ -4,7 +4,7 @@
defmodule Pleroma.Web.Plugs.RemoteIp do defmodule Pleroma.Web.Plugs.RemoteIp do
@moduledoc """ @moduledoc """
This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. This is a shim to call [`RemoteIp`](https://hex.pm/packages/remote_ip) but with runtime configuration.
""" """
alias Pleroma.Config alias Pleroma.Config
@ -17,15 +17,29 @@ defmodule Pleroma.Web.Plugs.RemoteIp do
def call(%{remote_ip: original_remote_ip} = conn, _) do def call(%{remote_ip: original_remote_ip} = conn, _) do
if Config.get([__MODULE__, :enabled]) do if Config.get([__MODULE__, :enabled]) do
%{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts()) new_remote_ip = remote_ip(conn) || original_remote_ip
conn = %{conn | remote_ip: new_remote_ip}
assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
else else
conn conn
end end
end end
defp remote_ip(conn) do
opts = remote_ip_opts()
# Do not use RemoteIp.from/2 here: upstream remote_ip always applies its
# built-in reserved ranges. Pleroma keeps :reserved configurable, so reuse
# only the header parsing and apply Pleroma's own block classification.
conn.req_headers
|> RemoteIp.Headers.take(opts[:headers])
|> RemoteIp.Headers.parse()
|> Enum.reverse()
|> Enum.find(&client?(&1, opts))
end
defp remote_ip_opts do defp remote_ip_opts do
headers = Config.get([__MODULE__, :headers], []) |> MapSet.new()
reserved = Config.get([__MODULE__, :reserved], []) reserved = Config.get([__MODULE__, :reserved], [])
proxies = proxies =
@ -33,6 +47,26 @@ defmodule Pleroma.Web.Plugs.RemoteIp do
|> Enum.concat(reserved) |> Enum.concat(reserved)
|> Enum.map(&InetHelper.parse_cidr/1) |> Enum.map(&InetHelper.parse_cidr/1)
{headers, proxies} clients =
Config.get([__MODULE__, :clients], [])
|> Enum.map(&InetHelper.parse_cidr/1)
[
headers: Config.get([__MODULE__, :headers], []),
clients: clients,
proxies: proxies
]
end
defp client?(ip, opts) do
client_ip?(ip, opts[:clients]) || !proxy_ip?(ip, opts[:proxies])
end
defp client_ip?(ip, clients) do
Enum.any?(clients, &InetCidr.contains?(&1, ip))
end
defp proxy_ip?(ip, proxies) do
Enum.any?(proxies, &InetCidr.contains?(&1, ip))
end end
end end

View file

@ -182,9 +182,8 @@ defmodule Pleroma.Mixfile do
{:plug_static_index_html, "~> 1.0.0"}, {:plug_static_index_html, "~> 1.0.0"},
{:flake_id, "~> 0.1.0"}, {:flake_id, "~> 0.1.0"},
{:concurrent_limiter, "~> 0.1.1"}, {:concurrent_limiter, "~> 0.1.1"},
{:remote_ip, {:remote_ip, "~> 1.2.0"},
git: "https://git.pleroma.social/pleroma/remote_ip.git", {:inet_cidr, "~> 1.0"},
ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},
{:captcha, "~> 1.0.3", hex: :pleroma_captcha}, {:captcha, "~> 1.0.3", hex: :pleroma_captcha},
{:restarter, path: "./restarter"}, {:restarter, path: "./restarter"},
{:majic, "~> 1.2"}, {:majic, "~> 1.2"},

View file

@ -65,7 +65,7 @@
"http_signatures": {:hex, :http_signatures, "0.1.3", "19f26aee35b4684e37efdce3ac4638605e6e41a04368186bd39d2b6138a60ea9", [:mix], [{:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20313a65516db88006f85b090f6f76cc5b04e9609b45943657e6781eb91174f4"}, "http_signatures": {:hex, :http_signatures, "0.1.3", "19f26aee35b4684e37efdce3ac4638605e6e41a04368186bd39d2b6138a60ea9", [:mix], [{:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20313a65516db88006f85b090f6f76cc5b04e9609b45943657e6781eb91174f4"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"}, "inet_cidr": {:hex, :inet_cidr, "1.0.9", "e0ef72a2942529da78c8e4147d53f2ef5f6f5293335c3637b0fdf83c012cc816", [:mix], [], "hexpm", "172da15ff7cf635b1feaf14f5818be28c811b37cc5fb7c5f7c01058c1c1066cc"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"}, "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
@ -132,7 +132,7 @@
"quic": {:hex, :quic, "0.10.2", "4b390507a85f65ce47808f3df1a864e0baf9adb7a1b4ea9f4dcd66fe9d0cb166", [:rebar3], [], "hexpm", "7c196a66973c877a59768a5687f0a0610ff11817254d0a4e45cc4e3a16b1d00b"}, "quic": {:hex, :quic, "0.10.2", "4b390507a85f65ce47808f3df1a864e0baf9adb7a1b4ea9f4dcd66fe9d0cb166", [:rebar3], [], "hexpm", "7c196a66973c877a59768a5687f0a0610ff11817254d0a4e45cc4e3a16b1d00b"},
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"},
"rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"}, "rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"},
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"}, "sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},

View file

@ -106,4 +106,38 @@ defmodule Pleroma.Web.Plugs.RemoteIpTest do
assert conn.remote_ip == {1, 1, 1, 1} assert conn.remote_ip == {1, 1, 1, 1}
end end
test "reserved ranges are configurable" do
clear_config([RemoteIp, :reserved], [])
conn =
conn(:get, "/")
|> put_req_header("x-forwarded-for", "1.1.1.1, 10.0.0.3")
|> RemoteIp.call(nil)
assert conn.remote_ip == {10, 0, 0, 3}
end
test "clients override reserved ranges" do
clear_config([RemoteIp, :clients], ["10.0.0.0/8"])
conn =
conn(:get, "/")
|> put_req_header("x-forwarded-for", "1.1.1.1, 10.0.0.3")
|> RemoteIp.call(nil)
assert conn.remote_ip == {10, 0, 0, 3}
end
test "clients override proxies" do
clear_config([RemoteIp, :clients], ["10.0.0.3"])
clear_config([RemoteIp, :proxies], ["10.0.0.0/8"])
conn =
conn(:get, "/")
|> put_req_header("x-forwarded-for", "1.1.1.1, 10.0.0.3")
|> RemoteIp.call(nil)
assert conn.remote_ip == {10, 0, 0, 3}
end
end end