diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix index 576b54a7edf2..300a0f892ef7 100644 --- a/nixos/modules/services/web-apps/plausible.nix +++ b/nixos/modules/services/web-apps/plausible.nix @@ -11,13 +11,6 @@ in { package = mkPackageOptionMD pkgs "plausible" { }; - releaseCookiePath = mkOption { - type = with types; either str path; - description = lib.mdDoc '' - The path to the file with release cookie. (used for remote connection to the running node). - ''; - }; - adminUser = { name = mkOption { default = "admin"; @@ -92,6 +85,13 @@ in { framework docs](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content). ''; }; + listenAddress = mkOption { + default = "127.0.0.1"; + type = types.str; + description = lib.mdDoc '' + The IP address on which the server is listening. + ''; + }; port = mkOption { default = 8000; type = types.port; @@ -162,6 +162,10 @@ in { }; }; + imports = [ + (mkRemovedOptionModule [ "services" "plausible" "releaseCookiePath" ] "Plausible uses no distributed Erlang features, so this option is no longer necessary and was removed") + ]; + config = mkIf cfg.enable { assertions = [ { assertion = cfg.adminUser.activate -> cfg.database.postgres.setup; @@ -180,8 +184,6 @@ in { enable = true; }; - services.epmd.enable = true; - environment.systemPackages = [ cfg.package ]; systemd.services = mkMerge [ @@ -209,6 +211,32 @@ in { # Configuration options from # https://plausible.io/docs/self-hosting-configuration PORT = toString cfg.server.port; + LISTEN_IP = cfg.server.listenAddress; + + # Note [plausible-needs-no-erlang-distributed-features]: + # Plausible does not use, and does not plan to use, any of + # Erlang's distributed features, see: + # https://github.com/plausible/analytics/pull/1190#issuecomment-1018820934 + # Thus, disable distribution for improved simplicity and security: + # + # When distribution is enabled, + # Elixir spwans the Erlang VM, which will listen by default on all + # interfaces for messages between Erlang nodes (capable of + # remote code execution); it can be protected by a cookie; see + # https://erlang.org/doc/reference_manual/distributed.html#security). + # + # It would be possible to restrict the interface to one of our choice + # (e.g. localhost or a VPN IP) similar to how we do it with `listenAddress` + # for the Plausible web server; if distribution is ever needed in the future, + # https://github.com/NixOS/nixpkgs/pull/130297 shows how to do it. + # + # But since Plausible does not use this feature in any way, + # we just disable it. + RELEASE_DISTRIBUTION = "none"; + # Additional safeguard, in case `RELEASE_DISTRIBUTION=none` ever + # stops disabling the start of EPMD. + ERL_EPMD_ADDRESS = "127.0.0.1"; + DISABLE_REGISTRATION = if isBool cfg.server.disableRegistration then boolToString cfg.server.disableRegistration else cfg.server.disableRegistration; RELEASE_TMP = "/var/lib/plausible/tmp"; @@ -238,7 +266,10 @@ in { path = [ cfg.package ] ++ optional cfg.database.postgres.setup config.services.postgresql.package; script = '' - export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )" + # Elixir does not start up if `RELEASE_COOKIE` is not set, + # even though we set `RELEASE_DISTRIBUTION=none` so the cookie should be unused. + # Thus, make a random one, which should then be ignored. + export RELEASE_COOKIE=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 20) export ADMIN_USER_PWD="$(< $CREDENTIALS_DIRECTORY/ADMIN_USER_PWD )" export SECRET_KEY_BASE="$(< $CREDENTIALS_DIRECTORY/SECRET_KEY_BASE )" @@ -265,7 +296,6 @@ in { LoadCredential = [ "ADMIN_USER_PWD:${cfg.adminUser.passwordFile}" "SECRET_KEY_BASE:${cfg.server.secretKeybaseFile}" - "RELEASE_COOKIE:${cfg.releaseCookiePath}" ] ++ lib.optionals (cfg.mail.smtp.passwordFile != null) [ "SMTP_USER_PWD:${cfg.mail.smtp.passwordFile}"]; }; }; diff --git a/nixos/tests/plausible.nix b/nixos/tests/plausible.nix index 9afd3db75de8..9c26c509a5ab 100644 --- a/nixos/tests/plausible.nix +++ b/nixos/tests/plausible.nix @@ -8,9 +8,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { virtualisation.memorySize = 4096; services.plausible = { enable = true; - releaseCookiePath = "${pkgs.runCommand "cookie" { } '' - ${pkgs.openssl}/bin/openssl rand -base64 64 >"$out" - ''}"; adminUser = { email = "admin@example.org"; passwordFile = "${pkgs.writeText "pwd" "foobar"}"; @@ -28,6 +25,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { machine.wait_for_unit("plausible.service") machine.wait_for_open_port(8000) + # Ensure that the software does not make not make the machine + # listen on any public interfaces by default. + machine.fail("ss -tlpn 'src = 0.0.0.0 or src = [::]' | grep LISTEN") + machine.succeed("curl -f localhost:8000 >&2") machine.succeed("curl -f localhost:8000/js/script.js >&2")