# A NixOS module which can run a Ristretto-based issuer for PrivateStorage # ZKAPs. { lib, pkgs, config, ourpkgs, ... }@args: let cfg = config.services.private-storage-spending; in { options = { services.private-storage-spending = { enable = lib.mkEnableOption "PrivateStorage Spending Service"; package = lib.mkOption { default = ourpkgs.zkap-spending-service; type = lib.types.package; example = "ourpkgs.zkap-spending-service"; description = '' The package to use for the spending service. ''; }; unixSocket = lib.mkOption { default = "/run/zkap-spending-service/api.socket"; type = lib.types.path; description = '' The unix socket that the spending service API listens on. ''; }; }; services.private-storage-spending.domain = lib.mkOption { default = config.networking.fqdn; type = lib.types.str; example = [ "spending.example.com" ]; description = '' The domain name at which the spending service is reachable. ''; }; }; config = lib.mkIf cfg.enable { systemd.sockets.zkap-spending-service = { enable = true; wantedBy = [ "sockets.target" ]; listenStreams = [ cfg.unixSocket ]; }; # Add a systemd service to run zkap-spending-service. systemd.services.zkap-spending-service = { enable = true; description = "ZKAP Spending Service"; wantedBy = [ "multi-user.target" ]; serviceConfig.NonBlocking = true; # It really shouldn't ever exit on its own! If it does, it's a bug # we'll have to fix. Restart it and hope it doesn't happen too much # before we can fix whatever the issue is. serviceConfig.Restart = "always"; serviceConfig.Type = "simple"; # Use a unnamed user. serviceConfig.DynamicUser = true; serviceConfig = { # Work around https://twistedmatrix.com/trac/ticket/10261 # Create a runtime directory so that the service has permission # to change the mode on the socket. RuntimeDirectory = "zkap-spending-service"; # This set of restrictions is mostly dervied from # - running `systemd-analyze security zkap-spending-service.service # - Looking at the restrictions from the nixos nginx config. AmbientCapabilities = ""; CapabilityBoundingSet = ""; LockPersonality = true; MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateDevices = true; PrivateMounts = true; PrivateNetwork = 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"; RemoveIPC = true; RestrictAddressFamilies = "AF_UNIX"; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; # Lines starting with "~" are deny-list the others are allow-list # Since the first line is allow, that bounds the set of allowed syscalls # and the further lines restrict it. SystemCallFilter = [ # From systemd.exec(5), @system-service is "A reasonable set of # system calls used by common system [...]" "@system-service" # This is from the nginx config, except that `@ipc` is not removed, # since twisted uses a self-pipe. "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" ]; Umask = "0077"; }; script = let httpArgs = "--http-endpoint systemd:domain=UNIX:index=0"; in "exec ${cfg.package}/bin/${cfg.package.meta.mainProgram} run ${httpArgs}"; }; services.nginx = { enable = true; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedProxySettings = true; recommendedTlsSettings = true; virtualHosts."${cfg.domain}" = { locations."/v1/" = { # Only forward requests beginning with /v1/ so # we pass less scanning spam on to our backend # Want a regex instead? try locations."~ /v\d+/" proxyPass = "http://unix:${cfg.unixSocket}"; }; locations."/metrics" = { proxyPass = "http://unix:${cfg.unixSocket}"; # Only allow our monitoringvpn subnet extraConfig = '' allow 172.23.23.0/24; allow 127.0.0.1; allow ::1; deny all; ''; }; locations."/" = { # Return a 404 error for any paths not specified above. extraConfig = '' return 404; ''; }; }; }; # Open 80 and 443 for nginx networking.firewall.allowedTCPPorts = [ 80 443 ]; }; }