Skip to content
Snippets Groups Projects
spending.nix 5.23 KiB
Newer Older
# 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";

Tom Prince's avatar
Tom Prince committed
        # 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
      ];