Browse Source

Add an option to require fetches to be signed

chore/tag-settings-with-reboot
Egor Kislitsyn 4 years ago
parent
commit
a12b6454bb
No known key found for this signature in database GPG Key ID: 1B49CB15B71E7805
5 changed files with 95 additions and 19 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +2
    -1
      config/config.exs
  3. +5
    -4
      docs/configuration/cheatsheet.md
  4. +29
    -14
      lib/pleroma/plugs/http_signature.ex
  5. +58
    -0
      test/plugs/http_signature_plug_test.exs

+ 1
- 0
CHANGELOG.md View File

@@ -53,6 +53,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- User notification settings: Add `privacy_option` option.
- User settings: Add _This account is a_ option.
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
- Add an option `authorized_fetch_mode` to requrie HTTP Signature for AP fetches.
<details>
<summary>API Changes</summary>



+ 2
- 1
config/config.exs View File

@@ -343,7 +343,8 @@ config :pleroma, :activitypub,
unfollow_blocked: true,
outgoing_blocks: true,
follow_handshake_timeout: 500,
sign_object_fetches: true
sign_object_fetches: true,
authorized_fetch_mode: false

config :pleroma, :streamer,
workers: 3,


+ 5
- 4
docs/configuration/cheatsheet.md View File

@@ -147,10 +147,11 @@ config :pleroma, :mrf_user_allowlist,
* `:reject` rejects the message entirely

### :activitypub
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
* ``outgoing_blocks``: Whether to federate blocks to other instances
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
* ``sign_object_fetches``: Sign object fetches with HTTP signatures
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
* `outgoing_blocks`: Whether to federate blocks to other instances
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
* `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP Signature for AP fetches

### :fetch_initial_posts
* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts


+ 29
- 14
lib/pleroma/plugs/http_signature.ex View File

@@ -15,25 +15,23 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
end

def call(conn, _opts) do
headers = get_req_header(conn, "signature")
signature = Enum.at(headers, 0)
conn
|> maybe_assign_valid_signature()
|> maybe_require_signature()
end

if signature do
defp maybe_assign_valid_signature(conn) do
if has_signature_header?(conn) do
# set (request-target) header to the appropriate value
# we also replace the digest header with the one we computed
conn =
conn
|> put_req_header(
"(request-target)",
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"

conn =
if conn.assigns[:digest] do
conn
|> put_req_header("digest", conn.assigns[:digest])
else
conn
conn
|> put_req_header("(request-target)", request_target)
|> case do
%{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
conn -> conn
end

assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
@@ -42,4 +40,21 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
conn
end
end

defp has_signature_header?(conn) do
conn |> get_req_header("signature") |> Enum.at(0, false)
end

defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn

defp maybe_require_signature(conn) do
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
conn
|> put_status(:unauthorized)
|> Phoenix.Controller.text("Request not signed")
|> halt()
else
conn
end
end
end

+ 58
- 0
test/plugs/http_signature_plug_test.exs View File

@@ -23,7 +23,65 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|> HTTPSignaturePlug.call(%{})

assert conn.assigns.valid_signature == true
assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_))
end
end

describe "requries a signature when `authorized_fetch_mode` is enabled" do
setup do
Pleroma.Config.put([:activitypub, :authorized_fetch_mode], true)

on_exit(fn ->
Pleroma.Config.put([:activitypub, :authorized_fetch_mode], false)
end)

params = %{"actor" => "http://mastodon.example.org/users/admin"}
conn = build_conn(:get, "/doesntmattter", params)

[conn: conn]
end

test "when signature header is present", %{conn: conn} do
with_mock HTTPSignatures, validate_conn: fn _ -> false end do
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})

assert conn.assigns.valid_signature == false
assert conn.halted == true
assert conn.status == 401
assert conn.state == :sent
assert conn.resp_body == "Request not signed"
assert called(HTTPSignatures.validate_conn(:_))
end

with_mock HTTPSignatures, validate_conn: fn _ -> true end do
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})

assert conn.assigns.valid_signature == true
assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_))
end
end

test "halts the connection when `signature` header is not present", %{conn: conn} do
conn = HTTPSignaturePlug.call(conn, %{})
assert conn.assigns[:valid_signature] == nil
assert conn.halted == true
assert conn.status == 401
assert conn.state == :sent
assert conn.resp_body == "Request not signed"
end
end
end

Loading…
Cancel
Save