From aa11fa4864bf6ebdd4acfdba837675abbdcffc83 Mon Sep 17 00:00:00 2001 From: Alex S Date: Thu, 16 May 2019 12:49:40 +0700 Subject: [PATCH 1/3] add report uri and report to --- CHANGELOG.md | 3 +- config/test.exs | 2 + docs/config.md | 3 +- lib/pleroma/plugs/http_security_plug.ex | 31 ++++-- test/plugs/http_security_plug_test.exs | 133 ++++++++++++++---------- 5 files changed, 107 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0e849285..ea1f29304 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Configuration: `fetch_initial_posts` option - Configuration: `notify_email` option - Configuration: Media proxy `whitelist` option +- Configuration: `report_uri` option - Pleroma API: User subscriptions - Pleroma API: Healthcheck endpoint - Admin API: Endpoints for listing/revoking invite tokens @@ -98,7 +99,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`] ## Removed -- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations` +- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations` ## [0.9.9999] - 2019-04-05 ### Security diff --git a/config/test.exs b/config/test.exs index a0c90c371..40db66170 100644 --- a/config/test.exs +++ b/config/test.exs @@ -61,6 +61,8 @@ config :pleroma, :app_account_creation, max_requests: 5 +config :pleroma, :http_security, report_uri: "https://endpoint.com" + try do import_config "test.secret.exs" rescue diff --git a/docs/config.md b/docs/config.md index 470f71b7c..c2af5c012 100644 --- a/docs/config.md +++ b/docs/config.md @@ -286,7 +286,8 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start * ``sts``: Whether to additionally send a `Strict-Transport-Security` header * ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent * ``ct_max_age``: The maximum age for the `Expect-CT` header if sent -* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`. +* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"` +* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header. ## :mrf_user_allowlist diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex index a476f1d49..485ddfbc7 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/plugs/http_security_plug.ex @@ -20,8 +20,9 @@ def call(conn, _options) do defp headers do referrer_policy = Config.get([:http_security, :referrer_policy]) + report_uri = Config.get([:http_security, :report_uri]) - [ + headers = [ {"x-xss-protection", "1; mode=block"}, {"x-permitted-cross-domain-policies", "none"}, {"x-frame-options", "DENY"}, @@ -30,12 +31,27 @@ defp headers do {"x-download-options", "noopen"}, {"content-security-policy", csp_string() <> ";"} ] + + if report_uri do + report_group = %{ + "group" => "csp-endpoint", + "max-age" => 10_886_400, + "endpoints" => [ + %{"url" => report_uri} + ] + } + + headers ++ [{"reply-to", Jason.encode!(report_group)}] + else + headers + end end defp csp_string do scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme] static_url = Pleroma.Web.Endpoint.static_url() websocket_url = Pleroma.Web.Endpoint.websocket_url() + report_uri = Config.get([:http_security, :report_uri]) connect_src = "connect-src 'self' #{static_url} #{websocket_url}" @@ -53,7 +69,7 @@ defp csp_string do "script-src 'self'" end - [ + main_part = [ "default-src 'none'", "base-uri 'self'", "frame-ancestors 'none'", @@ -63,11 +79,14 @@ defp csp_string do "font-src 'self'", "manifest-src 'self'", connect_src, - script_src, - if scheme == "https" do - "upgrade-insecure-requests" - end + script_src ] + + report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: [] + + insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: [] + + (main_part ++ report ++ insecure) |> Enum.join("; ") end diff --git a/test/plugs/http_security_plug_test.exs b/test/plugs/http_security_plug_test.exs index 0cbb7e4b1..7dfd50c1f 100644 --- a/test/plugs/http_security_plug_test.exs +++ b/test/plugs/http_security_plug_test.exs @@ -7,28 +7,89 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do alias Pleroma.Config alias Plug.Conn - test "it sends CSP headers when enabled", %{conn: conn} do - Config.put([:http_security, :enabled], true) + describe "http security enabled" do + setup do + enabled = Config.get([:http_securiy, :enabled]) - conn = - conn - |> get("/api/v1/instance") + Config.put([:http_security, :enabled], true) - refute Conn.get_resp_header(conn, "x-xss-protection") == [] - refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == [] - refute Conn.get_resp_header(conn, "x-frame-options") == [] - refute Conn.get_resp_header(conn, "x-content-type-options") == [] - refute Conn.get_resp_header(conn, "x-download-options") == [] - refute Conn.get_resp_header(conn, "referrer-policy") == [] - refute Conn.get_resp_header(conn, "content-security-policy") == [] + on_exit(fn -> + Config.put([:http_security, :enabled], enabled) + end) + + :ok + end + + test "it sends CSP headers when enabled", %{conn: conn} do + conn = get(conn, "/api/v1/instance") + + refute Conn.get_resp_header(conn, "x-xss-protection") == [] + refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == [] + refute Conn.get_resp_header(conn, "x-frame-options") == [] + refute Conn.get_resp_header(conn, "x-content-type-options") == [] + refute Conn.get_resp_header(conn, "x-download-options") == [] + refute Conn.get_resp_header(conn, "referrer-policy") == [] + refute Conn.get_resp_header(conn, "content-security-policy") == [] + end + + test "it sends STS headers when enabled", %{conn: conn} do + Config.put([:http_security, :sts], true) + + conn = get(conn, "/api/v1/instance") + + refute Conn.get_resp_header(conn, "strict-transport-security") == [] + refute Conn.get_resp_header(conn, "expect-ct") == [] + end + + test "it does not send STS headers when disabled", %{conn: conn} do + Config.put([:http_security, :sts], false) + + conn = get(conn, "/api/v1/instance") + + assert Conn.get_resp_header(conn, "strict-transport-security") == [] + assert Conn.get_resp_header(conn, "expect-ct") == [] + end + + test "referrer-policy header reflects configured value", %{conn: conn} do + conn = get(conn, "/api/v1/instance") + + assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"] + + Config.put([:http_security, :referrer_policy], "no-referrer") + + conn = + build_conn() + |> get("/api/v1/instance") + + assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"] + end + + test "it sends `report-to` & `report-uri` CSP response headers" do + conn = + build_conn() + |> get("/api/v1/instance") + + [csp] = Conn.get_resp_header(conn, "content-security-policy") + + assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;| + + [reply_to] = Conn.get_resp_header(conn, "reply-to") + + assert reply_to == + "{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}" + end end test "it does not send CSP headers when disabled", %{conn: conn} do + enabled = Config.get([:http_securiy, :enabled]) + Config.put([:http_security, :enabled], false) - conn = - conn - |> get("/api/v1/instance") + on_exit(fn -> + Config.put([:http_security, :enabled], enabled) + end) + + conn = get(conn, "/api/v1/instance") assert Conn.get_resp_header(conn, "x-xss-protection") == [] assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == [] @@ -38,46 +99,4 @@ test "it does not send CSP headers when disabled", %{conn: conn} do assert Conn.get_resp_header(conn, "referrer-policy") == [] assert Conn.get_resp_header(conn, "content-security-policy") == [] end - - test "it sends STS headers when enabled", %{conn: conn} do - Config.put([:http_security, :enabled], true) - Config.put([:http_security, :sts], true) - - conn = - conn - |> get("/api/v1/instance") - - refute Conn.get_resp_header(conn, "strict-transport-security") == [] - refute Conn.get_resp_header(conn, "expect-ct") == [] - end - - test "it does not send STS headers when disabled", %{conn: conn} do - Config.put([:http_security, :enabled], true) - Config.put([:http_security, :sts], false) - - conn = - conn - |> get("/api/v1/instance") - - assert Conn.get_resp_header(conn, "strict-transport-security") == [] - assert Conn.get_resp_header(conn, "expect-ct") == [] - end - - test "referrer-policy header reflects configured value", %{conn: conn} do - Config.put([:http_security, :enabled], true) - - conn = - conn - |> get("/api/v1/instance") - - assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"] - - Config.put([:http_security, :referrer_policy], "no-referrer") - - conn = - build_conn() - |> get("/api/v1/instance") - - assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"] - end end From 8355d0607e986f5a2cc5addfb2bcb35f799f10cb Mon Sep 17 00:00:00 2001 From: Alex S Date: Thu, 16 May 2019 17:03:12 +0700 Subject: [PATCH 2/3] trailing spaces --- docs/introduction.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/introduction.md b/docs/introduction.md index 4af0747fe..045dc7c05 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,30 +1,30 @@ # Introduction to Pleroma ## What is Pleroma? -Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3. -It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing. -It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other. +Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3. +It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing. +It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other. One account on a instance is enough to talk to the entire fediverse! - + ## How can I use it? -Pleroma instances are already widely deployed, a list can be found here: +Pleroma instances are already widely deployed, a list can be found here: http://distsn.org/pleroma-instances.html -If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too! -Installation instructions can be found here: +If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too! +Installation instructions can be found here: [main Pleroma wiki](/) - + ## I got an account, now what? -Great! Now you can explore the fediverse! -- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password. -(If you don't have one yet, click on Register) :slightly_smiling_face: +Great! Now you can explore the fediverse! +- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password. +(If you don't have one yet, click on Register) :slightly_smiling_face: At this point you will have two columns in front of you. ### Left column -- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers). -Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000). -If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile: +- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers). +Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000). +If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile: To post your status, simply press Submit. - second block: Here you can switch between the different timelines: @@ -38,7 +38,7 @@ To post your status, simply press Submit. - fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses. ### Right column -This is where the interesting stuff happens! :slight_smile: +This is where the interesting stuff happens! :slight_smile: Depending on the timeline you will see different statuses, but each status has a standard structure: - Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status). - A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime! @@ -47,9 +47,9 @@ Depending on the timeline you will see different statuses, but each status has a - Four buttons (left to right): Reply, Repeat, Favorite, Delete. ## Mastodon interface -If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile: -Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks: -For more information on the Mastodon interface, please look here: +If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile: +Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks: +For more information on the Mastodon interface, please look here: https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma. From 4711d8151c8cb7ce2024f378e17b3a113a3aac1c Mon Sep 17 00:00:00 2001 From: Alex S Date: Thu, 16 May 2019 17:35:07 +0700 Subject: [PATCH 3/3] try this --- test/web/common_api/common_api_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 8d4f401ee..b0c441a37 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -279,14 +279,14 @@ test "creates a report" do test "add a reblog mute", %{muter: muter, muted: muted} do {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) - assert Pleroma.User.showing_reblogs?(muter, muted) == false + assert User.showing_reblogs?(muter, muted) == false end test "remove a reblog mute", %{muter: muter, muted: muted} do {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) {:ok, muter} = CommonAPI.show_reblogs(muter, muted) - assert Pleroma.User.showing_reblogs?(muter, muted) == true + assert User.showing_reblogs?(muter, muted) == true end end end