add report uri and report to
authorAlex S <alex.strizhakov@gmail.com>
Thu, 16 May 2019 05:49:40 +0000 (12:49 +0700)
committerAlex S <alex.strizhakov@gmail.com>
Thu, 16 May 2019 05:49:40 +0000 (12:49 +0700)
CHANGELOG.md
config/test.exs
docs/config.md
lib/pleroma/plugs/http_security_plug.ex
test/plugs/http_security_plug_test.exs

index b0e8492857fae3d1d5e92eed816db9cd557bb8a7..ea1f293045066f04c30548014f71511efe8672a1 100644 (file)
@@ -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
index a0c90c3717c5fbcfcd0a8fba11699a8f98a9b54a..40db66170f2af4ed98f5f92d051fb043bf13f92e 100644 (file)
@@ -61,6 +61,8 @@ config :pleroma, Pleroma.ScheduledActivity,
 
 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
index 470f71b7cf71c9c289aedec2d9573be878031966..c2af5c0125ca869245f6e0841c0ad4bfb63f01ec 100644 (file)
@@ -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
 
index a476f1d49ab2ed02e98b0a1aba677b37f845a541..485ddfbc72ef03263199aaa4d4abd6c16829daa1 100644 (file)
@@ -20,8 +20,9 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug 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 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug 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 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
         "script-src 'self'"
       end
 
-    [
+    main_part = [
       "default-src 'none'",
       "base-uri 'self'",
       "frame-ancestors 'none'",
@@ -63,11 +79,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug 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
 
index 0cbb7e4b11fcfd6f57bb752ad583c840ad7c91a0..7dfd50c1febd9a6a6f5ace616c853725bb16cf39 100644 (file)
@@ -7,77 +7,96 @@ 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)
-
-    conn =
-      conn
-      |> get("/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
+  describe "http security enabled" do
+    setup do
+      enabled = Config.get([:http_securiy, :enabled])
 
-  test "it does not send CSP headers when disabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], false)
+      Config.put([:http_security, :enabled], true)
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      on_exit(fn ->
+        Config.put([:http_security, :enabled], enabled)
+      end)
 
-    assert Conn.get_resp_header(conn, "x-xss-protection") == []
-    assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
-    assert Conn.get_resp_header(conn, "x-frame-options") == []
-    assert Conn.get_resp_header(conn, "x-content-type-options") == []
-    assert Conn.get_resp_header(conn, "x-download-options") == []
-    assert Conn.get_resp_header(conn, "referrer-policy") == []
-    assert Conn.get_resp_header(conn, "content-security-policy") == []
-  end
+      :ok
+    end
 
-  test "it sends STS headers when enabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
-    Config.put([:http_security, :sts], true)
+    test "it sends CSP headers when enabled", %{conn: conn} do
+      conn = get(conn, "/api/v1/instance")
 
-    conn =
-      conn
-      |> get("/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
 
-    refute Conn.get_resp_header(conn, "strict-transport-security") == []
-    refute Conn.get_resp_header(conn, "expect-ct") == []
-  end
+    test "it sends STS headers when enabled", %{conn: conn} do
+      Config.put([:http_security, :sts], true)
 
-  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 = get(conn, "/api/v1/instance")
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      refute Conn.get_resp_header(conn, "strict-transport-security") == []
+      refute Conn.get_resp_header(conn, "expect-ct") == []
+    end
 
-    assert Conn.get_resp_header(conn, "strict-transport-security") == []
-    assert 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"]
 
-  test "referrer-policy header reflects configured value", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
+      Config.put([:http_security, :referrer_policy], "no-referrer")
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      conn =
+        build_conn()
+        |> get("/api/v1/instance")
 
-    assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
+      assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
+    end
 
-    Config.put([:http_security, :referrer_policy], "no-referrer")
+    test "it sends `report-to` & `report-uri` CSP response headers" do
+      conn =
+        build_conn()
+        |> get("/api/v1/instance")
 
-    conn =
-      build_conn()
-      |> get("/api/v1/instance")
+      [csp] = Conn.get_resp_header(conn, "content-security-policy")
 
-    assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
+      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)
+
+    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") == []
+    assert Conn.get_resp_header(conn, "x-frame-options") == []
+    assert Conn.get_resp_header(conn, "x-content-type-options") == []
+    assert Conn.get_resp_header(conn, "x-download-options") == []
+    assert Conn.get_resp_header(conn, "referrer-policy") == []
+    assert Conn.get_resp_header(conn, "content-security-policy") == []
   end
 end