Reject requests from specified instances if authorized_fetch_mode
is enabled
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
51edd70319
commit
3a23924ee8
4 changed files with 148 additions and 12 deletions
|
@ -44,8 +44,7 @@ defp remove_suffix(uri, [test | rest]) do
|
|||
defp remove_suffix(uri, []), do: uri
|
||||
|
||||
def fetch_public_key(conn) do
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||
with {:ok, actor_id} <- get_actor_id(conn),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
|
@ -55,8 +54,7 @@ def fetch_public_key(conn) do
|
|||
end
|
||||
|
||||
def refetch_public_key(conn) do
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||
with {:ok, actor_id} <- get_actor_id(conn),
|
||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
|
@ -66,6 +64,16 @@ def refetch_public_key(conn) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_actor_id(conn) do
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
{:ok, actor_id} <- key_id_to_actor_id(kid) do
|
||||
{:ok, actor_id}
|
||||
else
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def sign(%User{keys: keys} = user, headers) do
|
||||
with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
|
||||
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller, only: [get_format: 1, text: 2]
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
require Logger
|
||||
|
||||
def init(options) do
|
||||
|
@ -19,7 +23,9 @@ def call(conn, _opts) do
|
|||
if get_format(conn) in ["json", "activity+json"] do
|
||||
conn
|
||||
|> maybe_assign_valid_signature()
|
||||
|> maybe_assign_actor_id()
|
||||
|> maybe_require_signature()
|
||||
|> maybe_filter_requests()
|
||||
else
|
||||
conn
|
||||
end
|
||||
|
@ -83,6 +89,16 @@ defp maybe_assign_valid_signature(conn) do
|
|||
end
|
||||
end
|
||||
|
||||
defp maybe_assign_actor_id(%{assigns: %{valid_signature: true}} = conn) do
|
||||
adapter = Application.get_env(:http_signatures, :adapter)
|
||||
|
||||
{:ok, actor_id} = adapter.get_actor_id(conn)
|
||||
|
||||
assign(conn, :actor_id, actor_id)
|
||||
end
|
||||
|
||||
defp maybe_assign_actor_id(conn), do: conn
|
||||
|
||||
defp has_signature_header?(conn) do
|
||||
conn |> get_req_header("signature") |> Enum.at(0, false)
|
||||
end
|
||||
|
@ -90,13 +106,62 @@ defp has_signature_header?(conn) do
|
|||
defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
|
||||
|
||||
defp maybe_require_signature(conn) do
|
||||
cond do
|
||||
get_ip(conn) in Config.get([:instance, :trusted_unsigned], []) ->
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> assign(:actor_id, Pleroma.Web.ActivityPub.Relay.ap_id)
|
||||
|
||||
Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) ->
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> text("Request not signed")
|
||||
|> halt()
|
||||
|
||||
true ->
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_filter_requests(%{halted: true} = conn), do: conn
|
||||
|
||||
defp maybe_filter_requests(conn) do
|
||||
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> text("Request not signed")
|
||||
|> halt()
|
||||
%{host: host} = URI.parse(conn.assigns.actor_id)
|
||||
|
||||
if MRF.subdomain_match?(rejected_domains(), host) do
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> halt()
|
||||
else
|
||||
conn
|
||||
end
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
defp rejected_domains do
|
||||
Config.get([:instance, :rejected_instances])
|
||||
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|
||||
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
|
||||
end
|
||||
|
||||
defp get_ip(conn) do
|
||||
forwarded_for =
|
||||
conn
|
||||
|> Plug.Conn.get_req_header("x-forwarded-for")
|
||||
|> List.first()
|
||||
|
||||
if forwarded_for do
|
||||
forwarded_for
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.trim/1)
|
||||
|> List.first()
|
||||
else
|
||||
conn.remote_ip
|
||||
|> :inet_parse.ntoa()
|
||||
|> to_string()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,6 +67,14 @@ test "it returns error when not found user" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "get_actor_id/1" do
|
||||
test "it returns actor id" do
|
||||
ap_id = "https://mastodon.social/users/lambadalambda"
|
||||
|
||||
assert Signature.get_actor_id(make_fake_conn(ap_id)) == {:ok, ap_id}
|
||||
end
|
||||
end
|
||||
|
||||
describe "sign/2" do
|
||||
test "it returns signature headers" do
|
||||
user =
|
||||
|
|
|
@ -10,11 +10,15 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
import Phoenix.Controller, only: [put_format: 2]
|
||||
import Mock
|
||||
|
||||
test "it call HTTPSignatures to check validity if the actor sighed it" do
|
||||
test "it call HTTPSignatures to check validity if the actor signed it" do
|
||||
params = %{"actor" => "http://mastodon.example.org/users/admin"}
|
||||
conn = build_conn(:get, "/doesntmattter", params)
|
||||
|
||||
with_mock HTTPSignatures, validate_conn: fn _ -> true end do
|
||||
with_mock HTTPSignatures,
|
||||
validate_conn: fn _ -> true end,
|
||||
signature_for_conn: fn _ ->
|
||||
%{"keyId" => "http://mastodon.example.org/users/admin#main-key"}
|
||||
end do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
|
@ -41,7 +45,11 @@ test "it call HTTPSignatures to check validity if the actor sighed it" do
|
|||
end
|
||||
|
||||
test "when signature header is present", %{conn: conn} do
|
||||
with_mock HTTPSignatures, validate_conn: fn _ -> false end do
|
||||
with_mock HTTPSignatures,
|
||||
validate_conn: fn _ -> false end,
|
||||
signature_for_conn: fn _ ->
|
||||
%{"keyId" => "http://mastodon.example.org/users/admin#main-key"}
|
||||
end do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
|
@ -58,7 +66,11 @@ test "when signature header is present", %{conn: conn} do
|
|||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
end
|
||||
|
||||
with_mock HTTPSignatures, validate_conn: fn _ -> true end do
|
||||
with_mock HTTPSignatures,
|
||||
validate_conn: fn _ -> true end,
|
||||
signature_for_conn: fn _ ->
|
||||
%{"keyId" => "http://mastodon.example.org/users/admin#main-key"}
|
||||
end do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
|
@ -82,4 +94,47 @@ test "halts the connection when `signature` header is not present", %{conn: conn
|
|||
assert conn.resp_body == "Request not signed"
|
||||
end
|
||||
end
|
||||
|
||||
test "rejects requests from `rejected_instances` when `authorized_fetch_mode` is enabled" do
|
||||
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||
clear_config([:instance, :rejected_instances], [{"mastodon.example.org", "no reason"}])
|
||||
|
||||
with_mock HTTPSignatures,
|
||||
validate_conn: fn _ -> true end,
|
||||
signature_for_conn: fn _ ->
|
||||
%{"keyId" => "http://mastodon.example.org/users/admin#main-key"}
|
||||
end do
|
||||
conn =
|
||||
build_conn(:get, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
assert conn.halted == true
|
||||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
end
|
||||
|
||||
with_mock HTTPSignatures,
|
||||
validate_conn: fn _ -> true end,
|
||||
signature_for_conn: fn _ ->
|
||||
%{"keyId" => "http://allowed.example.org/users/admin#main-key"}
|
||||
end do
|
||||
conn =
|
||||
build_conn(:get, "/doesntmattter", %{"actor" => "http://allowed.example.org/users/admin"})
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://allowed.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
assert conn.halted == false
|
||||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue