diff --git a/nixos/modules/spending.nix b/nixos/modules/spending.nix new file mode 100644 index 0000000000000000000000000000000000000000..32470b4689725f64d1e341ebdaa1d5a59b34c74b --- /dev/null +++ b/nixos/modules/spending.nix @@ -0,0 +1,87 @@ +# 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 = lib.literalExample "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 = lib.literalExample [ "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"; + + 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."/" = { + # Return a 404 error for any paths not specified above. + extraConfig = '' + return 404; + ''; + }; + }; + }; + }; +} diff --git a/nixos/system-tests.nix b/nixos/system-tests.nix index 73b6665ab91e4d9a8a2200fb0eec7ff596f79b39..7b6d382ada53c1121a1bc3d0edbf82964d644ad2 100644 --- a/nixos/system-tests.nix +++ b/nixos/system-tests.nix @@ -3,5 +3,6 @@ let pkgs = import ../nixpkgs-2105.nix { }; in { private-storage = pkgs.nixosTest ./tests/private-storage.nix; + spending = pkgs.nixosTest ./tests/spending.nix; tahoe = pkgs.nixosTest ./tests/tahoe.nix; } diff --git a/nixos/tests/spending.nix b/nixos/tests/spending.nix new file mode 100644 index 0000000000000000000000000000000000000000..c970157b9375e0d99e2be8d4f782992163a6c948 --- /dev/null +++ b/nixos/tests/spending.nix @@ -0,0 +1,32 @@ +{ pkgs, lib, ... }: +{ + name = "zkap-spending-service"; + nodes = { + spending = { config, pkgs, ourpkgs, modulesPath, ... }: { + imports = [ + ../modules/packages.nix + ../modules/spending.nix + ]; + + services.private-storage-spending.enable = true; + services.private-storage-spending.domain = "localhost"; + }; + }; + testScript = { nodes }: let + revision = nodes.spending.config.passthru.ourpkgs.zkap-spending-service.meta.rev; + curl = "${pkgs.curl}/bin/curl -sSf"; + in + '' + import json + + start_all() + + spending.wait_for_open_port(80) + with subtest("Ensure we can ping the spending service"): + output = spending.succeed("${curl} http://localhost/v1/_ping") + assert json.loads(output)["status"] == "ok", "Could not ping spending service." + with subtest("Ensure that the spending service version matches the expected version"): + output = spending.succeed("${curl} http://localhost/v1/_version") + assert json.loads(output)["revision"] == "${revision}", "Spending service revision does not match." + ''; +}