summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--config/config.exs7
-rw-r--r--config/config.md7
-rw-r--r--installation/caddyfile-pleroma.example22
-rw-r--r--installation/pleroma-apache.conf9
-rw-r--r--installation/pleroma.nginx11
-rw-r--r--installation/pleroma.vcl10
-rw-r--r--lib/mix/tasks/sample_config.eex4
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex59
-rw-r--r--lib/pleroma/web/endpoint.ex1
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex8
-rw-r--r--test/plugs/http_security_plug_test.exs77
-rw-r--r--test/web/twitter_api/views/user_view_test.exs7
13 files changed, 173 insertions, 56 deletions
diff --git a/README.md b/README.md
index 642d3e954..c48930960 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,13 @@
## About Pleroma
-Pleroma is an OStatus-compatible social networking server written in Elixir, compatible with GNU Social and Mastodon. It is high-performance and can run on small devices like a Raspberry Pi.
+Pleroma is a microblogging server software that can federate (= exchange messages with) other servers that support the same federation standards (OStatus and ActivityPub). What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Pleroma will federate with all servers that implement either OStatus or ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
+
+Pleroma is written in Elixir, high-performance and can run on small devices like a Raspberry Pi.
For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md).
-Mobile clients that are known to work well:
+Client applications that are known to work well:
* Twidere
* Tusky
@@ -15,6 +17,7 @@ Mobile clients that are known to work well:
* Amaroq (iOS)
* Tootdon (Android + iOS)
* Tootle (iOS)
+* Whalebird (Windows + Mac + Linux)
No release has been made yet, but several servers have been online for months already. If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org.
diff --git a/config/config.exs b/config/config.exs
index e82c490e3..9cc558564 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -176,6 +176,13 @@ config :pleroma, :suggestions,
limit: 23,
web: "https://vinayaka.distsn.org/?{{host}}+{{user}}"
+config :pleroma, :http_security,
+ enabled: true,
+ sts: false,
+ sts_max_age: 31_536_000,
+ ct_max_age: 2_592_000,
+ referrer_policy: "same-origin"
+
config :cors_plug,
max_age: 86_400,
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"],
diff --git a/config/config.md b/config/config.md
index 51172fc4d..5b4110646 100644
--- a/config/config.md
+++ b/config/config.md
@@ -80,3 +80,10 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
* ``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
+
+## :http_security
+* ``enabled``: Whether the managed content security policy is enabled
+* ``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"`.
diff --git a/installation/caddyfile-pleroma.example b/installation/caddyfile-pleroma.example
index 305f2aa79..03ff000b6 100644
--- a/installation/caddyfile-pleroma.example
+++ b/installation/caddyfile-pleroma.example
@@ -21,28 +21,6 @@ example.tld {
ciphers ECDHE-ECDSA-WITH-CHACHA20-POLY1305 ECDHE-RSA-WITH-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256
}
- header / {
- X-XSS-Protection "1; mode=block"
- X-Frame-Options "DENY"
- X-Content-Type-Options "nosniff"
- Referrer-Policy "same-origin"
- Strict-Transport-Security "max-age=31536000; includeSubDomains;"
- Expect-CT "enforce, max-age=2592000"
- Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://{host}; upgrade-insecure-requests;"
- }
-
- # If you do not want remote frontends to be able to access your Pleroma backend server, remove these lines.
- # If you want to allow all origins access, remove the origin lines.
- # To use this directive, you need the http.cors plugin for Caddy.
- cors / {
- origin https://halcyon.example.tld
- origin https://pinafore.example.tld
- methods POST,PUT,DELETE,GET,PATCH,OPTIONS
- allowed_headers Authorization,Content-Type,Idempotency-Key
- exposed_headers Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id
- }
- # Stop removing lines here.
-
# If you do not want to use the mediaproxy function, remove these lines.
# To use this directive, you need the http.cache plugin for Caddy.
cache {
diff --git a/installation/pleroma-apache.conf b/installation/pleroma-apache.conf
index fb777983e..d5e75044f 100644
--- a/installation/pleroma-apache.conf
+++ b/installation/pleroma-apache.conf
@@ -34,15 +34,6 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLCompression off
SSLSessionTickets off
- Header always set X-Xss-Protection "1; mode=block"
- Header always set X-Frame-Options "DENY"
- Header always set X-Content-Type-Options "nosniff"
- Header always set Referrer-Policy same-origin
- Header always set Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://${servername}; upgrade-insecure-requests;"
-
- # Uncomment this only after you get HTTPS working.
- # Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
-
RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx
index 9b7419497..f0e684f2c 100644
--- a/installation/pleroma.nginx
+++ b/installation/pleroma.nginx
@@ -60,17 +60,6 @@ server {
client_max_body_size 16m;
location / {
- add_header X-XSS-Protection "1; mode=block" always;
- add_header X-Permitted-Cross-Domain-Policies "none" always;
- add_header X-Frame-Options "DENY" always;
- add_header X-Content-Type-Options "nosniff" always;
- add_header Referrer-Policy "same-origin" always;
- add_header X-Download-Options "noopen" always;
- add_header Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action *; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://$server_name; upgrade-insecure-requests;" always;
-
- # Uncomment this only after you get HTTPS working.
- # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
-
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
diff --git a/installation/pleroma.vcl b/installation/pleroma.vcl
index 74490be2a..63c1cb74d 100644
--- a/installation/pleroma.vcl
+++ b/installation/pleroma.vcl
@@ -119,13 +119,3 @@ sub vcl_pipe {
set bereq.http.connection = req.http.connection;
}
}
-
-sub vcl_deliver {
- set resp.http.X-Frame-Options = "DENY";
- set resp.http.X-XSS-Protection = "1; mode=block";
- set resp.http.X-Content-Type-Options = "nosniff";
- set resp.http.Referrer-Policy = "same-origin";
- set resp.http.Content-Security-Policy = "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://" + req.http.host + "; upgrade-insecure-requests;";
- # Uncomment this only after you get HTTPS working.
- # set resp.http.Strict-Transport-Security= "max-age=31536000; includeSubDomains";
-}
diff --git a/lib/mix/tasks/sample_config.eex b/lib/mix/tasks/sample_config.eex
index 3881ead26..462c34636 100644
--- a/lib/mix/tasks/sample_config.eex
+++ b/lib/mix/tasks/sample_config.eex
@@ -25,6 +25,10 @@ config :pleroma, Pleroma.Repo,
hostname: "localhost",
pool_size: 10
+# Enable Strict-Transport-Security once SSL is working:
+# config :pleroma, :http_security,
+# sts: true
+
# Configure S3 support if desired.
# The public S3 endpoint is different depending on region and provider,
# consult your S3 provider's documentation for details on what to use.
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
new file mode 100644
index 000000000..960c7f6bf
--- /dev/null
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -0,0 +1,59 @@
+defmodule Pleroma.Plugs.HTTPSecurityPlug do
+ alias Pleroma.Config
+ import Plug.Conn
+
+ def init(opts), do: opts
+
+ def call(conn, options) do
+ if Config.get([:http_security, :enabled]) do
+ conn =
+ merge_resp_headers(conn, headers())
+ |> maybe_send_sts_header(Config.get([:http_security, :sts]))
+ else
+ conn
+ end
+ end
+
+ defp headers do
+ referrer_policy = Config.get([:http_security, :referrer_policy])
+
+ [
+ {"x-xss-protection", "1; mode=block"},
+ {"x-permitted-cross-domain-policies", "none"},
+ {"x-frame-options", "DENY"},
+ {"x-content-type-options", "nosniff"},
+ {"referrer-policy", referrer_policy},
+ {"x-download-options", "noopen"},
+ {"content-security-policy", csp_string() <> ";"}
+ ]
+ end
+
+ defp csp_string do
+ [
+ "default-src 'none'",
+ "base-uri 'self'",
+ "form-action *",
+ "frame-ancestors 'none'",
+ "img-src 'self' data: https:",
+ "media-src 'self' https:",
+ "style-src 'self' 'unsafe-inline'",
+ "font-src 'self'",
+ "script-src 'self'",
+ "connect-src 'self' " <> String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
+ "upgrade-insecure-requests"
+ ]
+ |> Enum.join("; ")
+ end
+
+ defp maybe_send_sts_header(conn, true) do
+ max_age_sts = Config.get([:http_security, :sts_max_age])
+ max_age_ct = Config.get([:http_security, :ct_max_age])
+
+ merge_resp_headers(conn, [
+ {"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
+ {"expect-ct", "enforce, max-age=#{max_age_ct}"}
+ ])
+ end
+
+ defp maybe_send_sts_header(conn, _), do: conn
+end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index cb5de087b..7783b8e5c 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.Endpoint do
# You should set gzip to true if you are running phoenix.digest
# when deploying your static files in production.
plug(CORSPlug)
+ plug(Pleroma.Plugs.HTTPSecurityPlug)
plug(Plug.Static, at: "/media", from: Pleroma.Uploaders.Local.upload_path(), gzip: false)
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index a662f83b6..a100a1127 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -55,8 +55,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"statusnet_blocking" => statusnet_blocking,
"friends_count" => user_info[:following_count],
"id" => user.id,
- "name" => user.name,
- "name_html" => HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
+ "name" => user.name || user.nickname,
+ "name_html" =>
+ if(user.name,
+ do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
+ else: user.nickname
+ ),
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
diff --git a/test/plugs/http_security_plug_test.exs b/test/plugs/http_security_plug_test.exs
new file mode 100644
index 000000000..55040a108
--- /dev/null
+++ b/test/plugs/http_security_plug_test.exs
@@ -0,0 +1,77 @@
+defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
+ use Pleroma.Web.ConnCase
+ 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
+
+ test "it does not send CSP headers when disabled", %{conn: conn} do
+ Config.put([:http_security, :enabled], false)
+
+ conn =
+ conn
+ |> get("/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
+
+ 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
+ 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
diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs
index 2deb22fb1..2c583c0d3 100644
--- a/test/web/twitter_api/views/user_view_test.exs
+++ b/test/web/twitter_api/views/user_view_test.exs
@@ -13,6 +13,13 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
[user: user]
end
+ test "A user with only a nickname", %{user: user} do
+ user = %{user | name: nil, nickname: "scarlett@catgirl.science"}
+ represented = UserView.render("show.json", %{user: user})
+ assert represented["name"] == user.nickname
+ assert represented["name_html"] == user.nickname
+ end
+
test "A user with an avatar object", %{user: user} do
image = "image"
user = %{user | avatar: %{"url" => [%{"href" => image}]}}