diff --git a/book/src/admin-guide/deployment/nixos.md b/book/src/admin-guide/deployment/nixos.md index b30f7de..53ad032 100644 --- a/book/src/admin-guide/deployment/nixos.md +++ b/book/src/admin-guide/deployment/nixos.md @@ -41,8 +41,8 @@ You can import the module in one of two ways: services.atticd = { enable = true; - # Replace with absolute path to your credentials file - credentialsFile = "/etc/atticd.env"; + # Replace with absolute path to your environment file + environmentFile = "/etc/atticd.env"; settings = { listen = "[::]:8080"; diff --git a/crane.nix b/crane.nix index f7abf6e..fba29a5 100644 --- a/crane.nix +++ b/crane.nix @@ -108,6 +108,7 @@ let license = licenses.asl20; maintainers = with maintainers; [ zhaofengli ]; platforms = platforms.linux ++ platforms.darwin; + mainProgram = "attic"; }; passthru = { @@ -147,6 +148,10 @@ let CARGO_PROFILE_RELEASE_LTO = "fat"; CARGO_PROFILE_RELEASE_CODEGEN_UNITS = "1"; + + meta = { + mainProgram = "atticd"; + }; } // extraArgs); # Attic interacts with Nix directly and its tests require trusted-user access diff --git a/integration-tests/basic/default.nix b/integration-tests/basic/default.nix index f54b467..9b63cdc 100644 --- a/integration-tests/basic/default.nix +++ b/integration-tests/basic/default.nix @@ -152,7 +152,7 @@ in { services.atticd = { enable = true; - credentialsFile = "/etc/atticd.env"; + environmentFile = "/etc/atticd.env"; settings = { listen = "[::]:8080"; diff --git a/nixos/atticd.nix b/nixos/atticd.nix index cfd3c42..dd57b56 100644 --- a/nixos/atticd.nix +++ b/nixos/atticd.nix @@ -1,4 +1,9 @@ -{ lib, pkgs, config, ... }: +{ + lib, + pkgs, + config, + ... +}: let inherit (lib) types; @@ -11,16 +16,19 @@ let format = pkgs.formats.toml { }; - checkedConfigFile = pkgs.runCommand "checked-attic-server.toml" { - configFile = cfg.configFile; - } '' - cat $configFile + checkedConfigFile = + pkgs.runCommand "checked-attic-server.toml" + { + configFile = cfg.configFile; + } + '' + cat $configFile - export ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="dGVzdCBzZWNyZXQ=" - export ATTIC_SERVER_DATABASE_URL="sqlite://:memory:" - ${cfg.package}/bin/atticd --mode check-config -f $configFile - cat <$configFile >$out - ''; + export ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="dGVzdCBzZWNyZXQ=" + export ATTIC_SERVER_DATABASE_URL="sqlite://:memory:" + ${lib.getExe cfg.package} --mode check-config -f $configFile + cat <$configFile >$out + ''; atticadmShim = pkgs.writeShellScript "atticadm" '' if [ -n "$ATTICADM_PWD" ]; then @@ -42,7 +50,7 @@ let --wait \ --collect \ --service-type=exec \ - --property=EnvironmentFile=${cfg.credentialsFile} \ + --property=EnvironmentFile=${cfg.environmentFile} \ --property=DynamicUser=yes \ --property=User=${cfg.user} \ --property=Environment=ATTICADM_PWD=$(pwd) \ @@ -51,30 +59,30 @@ let ${atticadmShim} "$@" ''; - hasLocalPostgresDB = let - url = cfg.settings.database.url or ""; - localStrings = [ "localhost" "127.0.0.1" "/run/postgresql" ]; - hasLocalStrings = lib.any (lib.flip lib.hasInfix url) localStrings; - in config.services.postgresql.enable && lib.hasPrefix "postgresql://" url && hasLocalStrings; + hasLocalPostgresDB = + let + url = cfg.settings.database.url or ""; + localStrings = [ + "localhost" + "127.0.0.1" + "/run/postgresql" + ]; + hasLocalStrings = lib.any (lib.flip lib.hasInfix url) localStrings; + in + config.services.postgresql.enable && lib.hasPrefix "postgresql://" url && hasLocalStrings; in { + imports = [ + (lib.mkRenamedOptionModule [ "services" "atticd" "credentialsFile" ] [ "services" "atticd" "environmentFile" ]) + ]; + options = { services.atticd = { - enable = lib.mkOption { - description = '' - Whether to enable the atticd, the Nix Binary Cache server. - ''; - type = types.bool; - default = false; - }; - package = lib.mkOption { - description = '' - The package to use. - ''; - type = types.package; - default = pkgs.attic-server; - }; - credentialsFile = lib.mkOption { + enable = lib.mkEnableOption "the atticd, the Nix Binary Cache server"; + + package = lib.mkPackageOption pkgs "attic-server" { }; + + environmentFile = lib.mkOption { description = '' Path to an EnvironmentFile containing required environment variables: @@ -85,6 +93,7 @@ in type = types.nullOr types.path; default = null; }; + user = lib.mkOption { description = '' The group under which attic runs. @@ -92,6 +101,7 @@ in type = types.str; default = "atticd"; }; + group = lib.mkOption { description = '' The user under which attic runs. @@ -99,13 +109,15 @@ in type = types.str; default = "atticd"; }; + settings = lib.mkOption { description = '' Structured configurations of atticd. ''; type = format.type; - default = {}; # setting defaults here does not compose well + default = { }; # setting defaults here does not compose well }; + configFile = lib.mkOption { description = '' Path to an existing atticd configuration file. @@ -131,7 +143,11 @@ in There are several other supported modes that perform one-off operations, but these are the only ones that make sense to run via the NixOS module. ''; - type = lib.types.enum ["monolithic" "api-server" "garbage-collector"]; + type = lib.types.enum [ + "monolithic" + "api-server" + "garbage-collector" + ]; default = "monolithic"; }; @@ -146,77 +162,108 @@ in }; }; }; - config = lib.mkIf (cfg.enable) (lib.mkMerge [ - { - assertions = [ - { - assertion = cfg.credentialsFile != null; - message = '' - is not set. - - Run `openssl genrsa -traditional -out private_key.pem 4096 | base64 -w0` and create a file with the following contents: - - ATTIC_SERVER_TOKEN_RS256_SECRET="output from command" - - Then, set `services.atticd.credentialsFile` to the quoted absolute path of the file. - ''; - } - { - assertion = !lib.isStorePath cfg.credentialsFile; - message = '' - points to a path in the Nix store. The Nix store is globally readable. - - You should use a quoted absolute path to prevent this. - ''; - } - ]; - services.atticd.settings = { - database.url = lib.mkDefault "sqlite:///var/lib/atticd/server.db?mode=rwc"; + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.environmentFile != null; + message = '' + is not set. + + Run `openssl genrsa -traditional -out private_key.pem 4096 | base64 -w0` and create a file with the following contents: + + ATTIC_SERVER_TOKEN_RS256_SECRET="output from command" + + Then, set `services.atticd.environmentFile` to the quoted absolute path of the file. + ''; + } + { + assertion = !lib.isStorePath cfg.environmentFile; + message = '' + points to a path in the Nix store. The Nix store is globally readable. + + You should use a quoted absolute path to prevent leaking secrets in the Nix store. + ''; + } + ]; - # "storage" is internally tagged - # if the user sets something the whole thing must be replaced - storage = lib.mkDefault { - type = "local"; - path = "/var/lib/atticd/storage"; - }; + services.atticd.settings = { + database.url = lib.mkDefault "sqlite:///var/lib/atticd/server.db?mode=rwc"; + + # "storage" is internally tagged + # if the user sets something the whole thing must be replaced + storage = lib.mkDefault { + type = "local"; + path = "/var/lib/atticd/storage"; }; + }; + + systemd.services.atticd = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ] ++ lib.optionals hasLocalPostgresDB [ "postgresql.service" ]; + requires = lib.optionals hasLocalPostgresDB [ "postgresql.service" ]; + wants = [ "network-online.target" ]; + + serviceConfig = { + ExecStart = "${lib.getExe cfg.package} -f ${checkedConfigFile} --mode ${cfg.mode}"; + EnvironmentFile = cfg.environmentFile; + StateDirectory = "atticd"; # for usage with local storage and sqlite + DynamicUser = true; + User = cfg.user; + Group = cfg.group; + Restart = "on-failure"; + RestartSec = 10; - systemd.services.atticd = { - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ] - ++ lib.optionals hasLocalPostgresDB [ "postgresql.service" "nss-lookup.target" ]; - serviceConfig = { - ExecStart = "${cfg.package}/bin/atticd -f ${checkedConfigFile} --mode ${cfg.mode}"; - EnvironmentFile = cfg.credentialsFile; - StateDirectory = "atticd"; # for usage with local storage and sqlite - DynamicUser = true; - User = cfg.user; - Group = cfg.group; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - ProtectSystem = "strict"; - Restart = "on-failure"; - RestartSec = 10; - RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - ReadWritePaths = let + CapabilityBoundingSet = [ "" ]; + DeviceAllow = ""; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + ReadWritePaths = + let path = cfg.settings.storage.path; isDefaultStateDirectory = path == "/var/lib/atticd" || lib.hasPrefix "/var/lib/atticd/" path; - in lib.optionals (cfg.settings.storage.type or "" == "local" && !isDefaultStateDirectory) [ path ]; - }; + in + lib.optionals (cfg.settings.storage.type or "" == "local" && !isDefaultStateDirectory) [ path ]; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@resources" + "~@privileged" + ]; + UMask = "0077"; }; + }; + + environment.systemPackages = [ + atticadmWrapper + ]; - environment.systemPackages = [ atticadmWrapper ]; - } - (lib.mkIf cfg.useFlakeCompatOverlay { - nixpkgs.overlays = [ overlay ]; - }) - ]); + nixpkgs.overlays = lib.mkIf cfg.useFlakeCompatOverlay [ + overlay + ]; + }; } diff --git a/package.nix b/package.nix index e40c765..1614316 100644 --- a/package.nix +++ b/package.nix @@ -70,5 +70,6 @@ in rustPlatform.buildRustPackage rec { license = licenses.asl20; maintainers = with maintainers; [ zhaofengli ]; platforms = platforms.linux ++ platforms.darwin; + mainProgram = "attic"; }; }