From ee5cc38432031b66e7fe395b14235eeb4b2b0d6e Mon Sep 17 00:00:00 2001 From: Eric Wolf Date: Sat, 24 Jun 2023 19:39:01 +0200 Subject: [PATCH] lemmy: Support secret options This commit implements #101777 by merging the config with an external file at startup. --- nixos/modules/services/web-apps/lemmy.nix | 26 +++++++++++++++++++++-- nixos/tests/lemmy.nix | 18 ++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix index dd335302fa4..afbd7497610 100644 --- a/nixos/modules/services/web-apps/lemmy.nix +++ b/nixos/modules/services/web-apps/lemmy.nix @@ -77,6 +77,11 @@ in }; }; + secretFile = mkOption { + type = with types; nullOr path; + default = null; + description = lib.mdDoc "Path to a secret JSON configuration file which is merged at runtime with the one generated from {option}`services.lemmy.settings`."; + }; }; config = @@ -197,11 +202,14 @@ in } ]; - systemd.services.lemmy = { + systemd.services.lemmy = let + configFile = settingsFormat.generate "config.hjson" cfg.settings; + mergedConfig = "/run/lemmy/config.hjson"; + in { description = "Lemmy server"; environment = { - LEMMY_CONFIG_LOCATION = "${settingsFormat.generate "config.hjson" cfg.settings}"; + LEMMY_CONFIG_LOCATION = if cfg.secretFile == null then configFile else mergedConfig; LEMMY_DATABASE_URL = if cfg.database.uri != null then cfg.database.uri else (mkIf (cfg.database.createLocally) "postgres:///lemmy?host=/run/postgresql&user=lemmy"); }; @@ -216,10 +224,24 @@ in requires = lib.optionals cfg.database.createLocally [ "postgresql.service" ]; + path = mkIf (cfg.secretFile != null) [ pkgs.jq ]; + + # merge the two configs and prevent others from reading the result + # if somehow $CREDENTIALS_DIRECTORY is not set we fail + preStart = mkIf (cfg.secretFile != null) '' + set -u + umask 177 + jq --slurp '.[0] * .[1]' ${lib.escapeShellArg configFile} "$CREDENTIALS_DIRECTORY/secretFile" > ${lib.escapeShellArg mergedConfig} + ''; + serviceConfig = { DynamicUser = true; RuntimeDirectory = "lemmy"; ExecStart = "${cfg.server.package}/bin/lemmy_server"; + LoadCredential = mkIf (cfg.secretFile != null) "secretFile:${toString cfg.secretFile}"; + PrivateTmp = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; }; }; diff --git a/nixos/tests/lemmy.nix b/nixos/tests/lemmy.nix index b1a39827b07..685a1a81738 100644 --- a/nixos/tests/lemmy.nix +++ b/nixos/tests/lemmy.nix @@ -22,16 +22,24 @@ in # Without setup, the /feeds/* and /nodeinfo/* API endpoints won't return 200 setup = { admin_username = "mightyiam"; - admin_password = "ThisIsWhatIUseEverywhereTryIt"; site_name = "Lemmy FTW"; admin_email = "mightyiam@example.com"; }; # https://github.com/LemmyNet/lemmy/blob/50efb1d519c63a7007a07f11cc8a11487703c70d/crates/utils/src/settings/mod.rs#L52 database.uri = "postgres:///lemmy?host=/run/postgresql&user=lemmy"; }; + secretFile = /etc/lemmy-config.hjson; caddy.enable = true; }; + environment.etc."lemmy-config.hjson".text = '' + { + "setup": { + "admin_password": "ThisIsWhatIUseEverywhereTryIt" + } + } + ''; + networking.firewall.allowedTCPPorts = [ 80 ]; # pict-rs seems to need more than 1025114112 bytes @@ -42,8 +50,14 @@ in testScript = '' server = ${lemmyNodeName} - with subtest("the backend starts and responds"): + with subtest("the merged config is secure"): server.wait_for_unit("lemmy.service") + config_permissions = server.succeed("stat --format %A /run/lemmy/config.hjson").rstrip() + assert config_permissions == "-rw-------", f"merged config permissions {config_permissions} are insecure" + directory_permissions = server.succeed("stat --format %A /run/lemmy").rstrip() + assert directory_permissions[5] == directory_permissions[8] == "-", "merged config can be replaced" + + with subtest("the backend starts and responds"): server.wait_for_open_port(${toString backendPort}) server.succeed("curl --fail localhost:${toString backendPort}/api/v3/site")