diff --git a/morph/grid.config.json b/morph/grid.config.json index 178f44d39e5dd88ef709f92713d579c2cd32caff..5b848d31264fb84017752a76376467466c717f35 100644 --- a/morph/grid.config.json +++ b/morph/grid.config.json @@ -1,3 +1,5 @@ { "publicStoragePort": 8898 , "ristrettoSigningKeyPath": "../../PrivateStorageSecrets/ristretto.signing-key" +, "issuerDomain": "payments.privatestorage.io" +, "letsEncryptAdminEmail": "jean-paul@privatestorage.io" } diff --git a/morph/grid.nix b/morph/grid.nix index c29b8866ce5ac82bb06d26c468dcd12bd7fb6d6f..0de7007d3cc98ff90f8a1bc01461014c0149444e 100644 --- a/morph/grid.nix +++ b/morph/grid.nix @@ -3,6 +3,7 @@ # with the testing grid and have one fewer possible point of divergence. import ./make-grid.nix { name = "Production"; + config = ./grid.config.json; nodes = cfg: { # Here are the hosts that are in this morph network. This is sort of like # a server manifest. We try to keep as many of the specific details as @@ -16,6 +17,11 @@ import ./make-grid.nix { # doesn't specify one. # # The names must be unique! + "payments.privatestorage.io" = import ./issuer.nix ({ + hardware = ./issuer-aws.nix; + stateVersion = "19.03"; + } // cfg); + "storage001" = import ./make-storage.nix ({ cfg = import ./storage001-config.nix; hardware = ./storage001-hardware.nix; diff --git a/morph/issuer-aws.nix b/morph/issuer-aws.nix new file mode 100644 index 0000000000000000000000000000000000000000..b4d4757ad5597b69363ef12e4297aec80913f00e --- /dev/null +++ b/morph/issuer-aws.nix @@ -0,0 +1,4 @@ +{ + imports = [ <nixpkgs/nixos/modules/virtualisation/amazon-image.nix> ]; + ec2.hvm = true; +} diff --git a/morph/issuer.nix b/morph/issuer.nix new file mode 100644 index 0000000000000000000000000000000000000000..ddf01bdfc832a13aee357475ef639c539ea5bda3 --- /dev/null +++ b/morph/issuer.nix @@ -0,0 +1,37 @@ +{ hardware +, ristrettoSigningKeyPath +, issuerDomain +, letsEncryptAdminEmail +, stateVersion +, ... +}: { + deployment = { + secrets = { + "ristretto-signing-key" = { + source = ristrettoSigningKeyPath; + destination = "/var/secrets/ristretto.signing-key"; + owner.user = "root"; + owner.group = "root"; + permissions = "0400"; + action = ["sudo" "systemctl" "restart" "zkapissuer.service"]; + }; + }; + }; + + imports = [ + hardware + ../nixos/modules/issuer.nix + ]; + + services.private-storage-issuer = { + enable = true; + # XXX This should be passed as a path. + ristrettoSigningKey = builtins.readFile (./.. + ristrettoSigningKeyPath); + database = "SQLite3"; + databasePath = "/var/db/vouchers.sqlite3"; + inherit letsEncryptAdminEmail; + domain = issuerDomain; + }; + + system.stateVersion = stateVersion; +} diff --git a/morph/make-grid.nix b/morph/make-grid.nix index 32e0b98ed3ffd5741522f9b0e3ef67037fa6a180..de10df1e9a62ee0ac7fde98070743ee4a9cf484b 100644 --- a/morph/make-grid.nix +++ b/morph/make-grid.nix @@ -3,11 +3,11 @@ # and a function that takes the grid configuration as an argument and returns # a set of nodes specifying the addresses and NixOS configurations for each # server in the morph network. -{ name, nodes }: +{ name, config, nodes }: let pkgs = import <nixpkgs> { }; # Load our JSON configuration for later use. - cfg = pkgs.lib.trivial.importJSON ./grid.config.json; + cfg = pkgs.lib.trivial.importJSON config; in { network = { diff --git a/morph/make-storage.nix b/morph/make-storage.nix index 768cdb55f304ef0708a2af434df772861572502d..84a13be3c4089194b0d1a1ff6218b1161c1537f0 100644 --- a/morph/make-storage.nix +++ b/morph/make-storage.nix @@ -9,7 +9,7 @@ # to avoid breaking some software such as # database servers. You should change this only # after NixOS release notes say you should. - +, ... }: rec { deployment = { secrets = { diff --git a/morph/testing-grid.config.json b/morph/testing-grid.config.json new file mode 100644 index 0000000000000000000000000000000000000000..018367db9da09364c718a521dd28ef06a2642288 --- /dev/null +++ b/morph/testing-grid.config.json @@ -0,0 +1,5 @@ +{ "publicStoragePort": 8898 +, "ristrettoSigningKeyPath": "../../PrivateStorageSecrets/ristretto.signing-key" +, "issuerDomain": "payments.privatestorage-staging.com" +, "letsEncryptAdminEmail": "jean-paul@privatestorage.io" +} diff --git a/morph/testing-grid.nix b/morph/testing-grid.nix index 5591827e3e02423abeeb31123c383bd42dfdf1c5..b4b0649d8af349cd08e2b147ffdd207f32e8d1c6 100644 --- a/morph/testing-grid.nix +++ b/morph/testing-grid.nix @@ -3,8 +3,14 @@ # with the production grid and have one fewer possible point of divergence. import ./make-grid.nix { name = "Testing"; + config = ./testing-grid.config.json; nodes = cfg: { - "testing000" = import ./testing000.nix (cfg // { + "payments.privatestorage-staging.com" = import ./issuer.nix ({ + hardware = ./issuer-aws.nix; + stateVersion = "19.03"; + } // cfg); + + "35.157.216.200" = import ./testing000.nix (cfg // { publicIPv4 = "35.157.216.200"; }); }; diff --git a/morph/testing000.nix b/morph/testing000.nix index e5f9c3f32bf4c75fea438a309a92c372f44f8ff8..d45086ae90fb5dfd64b5181d1723c757d219e6bb 100644 --- a/morph/testing000.nix +++ b/morph/testing000.nix @@ -1,4 +1,4 @@ -{ publicIPv4, publicStoragePort, ristrettoSigningKeyPath }: rec { +{ publicIPv4, publicStoragePort, ristrettoSigningKeyPath, ... }: rec { deployment = { secrets = { diff --git a/nixos/modules/issuer.nix b/nixos/modules/issuer.nix index 7cb7cde17b180dceb9c0f80d81bbf9325fbe56f8..fc0d2355f204c3192133e3d63c210aefce7a467b 100644 --- a/nixos/modules/issuer.nix +++ b/nixos/modules/issuer.nix @@ -15,9 +15,26 @@ in { The package to use for the ZKAP issuer. ''; }; + services.private-storage-issuer.domain = lib.mkOption { + default = "payments.privatestorage.io"; + type = lib.types.str; + example = lib.literalExample "payments.example.com"; + description = '' + The domain name at which the issuer is reachable. + ''; + }; + services.private-storage-issuer.tls = lib.mkOption { + default = true; + type = lib.types.bool; + description = '' + Whether or not to listen on TLS. For real-world use you should always + listen on TLS. This is provided as an aid to automated testing where + it might be difficult to obtain a real certificate. + ''; + }; services.private-storage-issuer.issuer = lib.mkOption { default = "Ristretto"; - type = lib.types.str; + type = lib.types.enum [ "Trivial" "Ristretto" ]; example = lib.literalExample "Trivial"; description = '' The issuer algorithm to use. Either Trivial for a fake no-crypto @@ -32,33 +49,108 @@ in { ``Ristretto``. ''; }; + services.private-storage-issuer.database = lib.mkOption { + default = "Memory"; + type = lib.types.enum [ "Memory" "SQLite3" ]; + description = '' + The kind of voucher database to use. + ''; + }; + services.private-storage-issuer.databasePath = lib.mkOption { + default = null; + type = lib.types.str; + description = '' + The path to a database file in the filesystem, if the SQLite3 database + type is being used. + ''; + }; + services.private-storage-issuer.letsEncryptAdminEmail = lib.mkOption { + type = lib.types.str; + description = '' + An email address to give to Let's Encrypt as an operational contact + for the service's TLS certificate. + ''; + }; }; - config = lib.mkIf cfg.enable { + config = + let + certroot = "/var/lib/letsencrypt/live"; + in lib.mkIf cfg.enable { # Add a systemd service to run PaymentServer. systemd.services.zkapissuer = { enable = true; description = "ZKAP Issuer"; wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; + # Make sure we have a certificate the first time, if we are running over + # TLS and require a certificate. + requires = lib.optional cfg.tls "cert-${cfg.domain}.service"; + + after = [ + # Make sure there is a network so we can bind to all of the + # interfaces. + "network.target" + ] ++ + # Make sure we run after the certificate is issued, if we are running + # over TLS and require a certificate. + lib.optional cfg.tls "cert-${cfg.domain}.service"; + + # 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 + # Compute the right command line arguments to pass to it. The + # signing key is only supplied when using the Ristretto issuer. + issuerArgs = + if cfg.issuer == "Trivial" + then "--issuer Trivial" + else "--issuer Ristretto --signing-key ${cfg.ristrettoSigningKey}"; + databaseArgs = + if cfg.database == "Memory" + then "--database Memory" + else "--database SQLite3 --database-path ${cfg.databasePath}"; + httpsArgs = + if cfg.tls + then + "--https-port 443 " + + "--https-certificate-path ${certroot}/${cfg.domain}/cert.pem " + + "--https-certificate-chain-path ${certroot}/${cfg.domain}/chain.pem " + + "--https-key-path ${certroot}/${cfg.domain}/privkey.pem" + else + # Only for automated testing. + "--http-port 80"; + in + "${cfg.package}/bin/PaymentServer-exe ${issuerArgs} ${databaseArgs} ${httpsArgs}"; + }; + + # Certificate renewal. We must declare that we *require* it in our + # service above. + systemd.services."cert-${cfg.domain}" = { + enable = true; + description = "Issue/Renew certificate for ${cfg.domain}"; serviceConfig = { ExecStart = - let - # Compute the right command line arguments to pass to it. The - # signing key is only supplied when using the Ristretto issuer. - args = - if cfg.issuer == "Trivial" - then "--issuer Trivial" - else "--issuer Ristretto --signing-key ${cfg.ristrettoSigningKey}"; - in - "${cfg.package}/bin/PaymentServer-exe ${args}"; - Type = "simple"; - # 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. - Restart = "always"; + let + configArgs = "--config-dir /var/lib/letsencrypt --work-dir /var/run/letsencrypt --logs-dir /var/run/log/letsencrypt"; + in + pkgs.writeScript "cert-${cfg.domain}-start.sh" '' + #!${pkgs.runtimeShell} -e + # Register if necessary. + ${pkgs.certbot}/bin/certbot register ${configArgs} --non-interactive --agree-tos -m ${cfg.letsEncryptAdminEmail} || true + # Obtain the certificate. + ${pkgs.certbot}/bin/certbot certonly ${configArgs} --non-interactive --standalone --domains ${cfg.domain} + ''; }; }; + # Open 80 and 443 for the certbot HTTP server and the PaymentServer HTTPS server. + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; }; } diff --git a/nixos/modules/tests/get-passes.py b/nixos/modules/tests/get-passes.py index ac5cf790fd95d8ce5295879a04d4c19026bf2dba..d76ce3ccbf703bfabd08e120f84ed39063339297 100755 --- a/nixos/modules/tests/get-passes.py +++ b/nixos/modules/tests/get-passes.py @@ -13,15 +13,19 @@ from time import sleep def main(): clientAPIRoot, issuerAPIRoot = argv[1:] + if not clientAPIRoot.endswith("/"): + clientAPIRoot += "/" + if not issuerAPIRoot.endswith("/"): + issuerAPIRoot += "/" # Construct a voucher that's acceptable to various parts of the system. voucher = "a" * 44 - zkapauthz = clientAPIRoot + "/storage-plugins/privatestorageio-zkapauthz-v1" + zkapauthz = clientAPIRoot + "storage-plugins/privatestorageio-zkapauthz-v1" # Simulate a payment for a voucher. post( - issuerAPIRoot + "/v1/stripe/webhook", + issuerAPIRoot + "v1/stripe/webhook", dumps(charge_succeeded_json(voucher)), headers={"content-type": "application/json"}, ) diff --git a/nixos/modules/tests/private-storage.nix b/nixos/modules/tests/private-storage.nix index cc2fa798f3654a0d249c8c21a9c9499a0740fc43..b731404dcad8c6b0729778f9778e9fdbec311b26 100644 --- a/nixos/modules/tests/private-storage.nix +++ b/nixos/modules/tests/private-storage.nix @@ -10,7 +10,7 @@ let exercise-storage = ./exercise-storage.py; # The root URL of the Ristretto-flavored PrivacyPass issuer API. - issuerURL = "http://issuer:8081/"; + issuerURL = "http://issuer/"; # The issuer's signing key. Notionally, this is a secret key. This is only # the value for this system test though so I don't care if it leaks to the @@ -81,6 +81,8 @@ import <nixpkgs/nixos/tests/make-test.nix> { ]; services.private-storage-issuer = { enable = true; + domain = "issuer"; + tls = false; issuer = "Ristretto"; inherit ristrettoSigningKey; }; @@ -140,7 +142,7 @@ import <nixpkgs/nixos/tests/make-test.nix> { # Get some ZKAPs from the issuer. eval { - $client->succeed('set -eo pipefail; ${get-passes} http://127.0.0.1:3456 http://issuer:8081 | systemd-cat'); + $client->succeed('set -eo pipefail; ${get-passes} http://127.0.0.1:3456 ${issuerURL} | systemd-cat'); # succeed() is not success but 1 is. 1; } or do { diff --git a/nixos/pkgs/zkapissuer-repo.nix b/nixos/pkgs/zkapissuer-repo.nix index 44182649362476fdc2b1b203d9f92a5e91f9af42..433ecfba94c162fd1ec4159cb34eedcf37b16b41 100644 --- a/nixos/pkgs/zkapissuer-repo.nix +++ b/nixos/pkgs/zkapissuer-repo.nix @@ -4,6 +4,6 @@ in pkgs.fetchFromGitHub { owner = "PrivateStorageio"; repo = "PaymentServer"; - rev = "028d26152eba4f034aba405caa17627a764c2bbe"; - sha256 = "06hdln97r2ign7phf661wlzh3z06bk9906lvc0gm3lh1pa23d3gb"; + rev = "6dfc02e395fbbec2c70a109874227ab21bddbb25"; + sha256 = "1zc8cxc37zixsh8zcqasvg07rfsravlx0bhnx6zv9c5srm37iqap"; } \ No newline at end of file diff --git a/nixpkgs.rev b/nixpkgs.rev index 41802ea669e9d5bb48fff0fe62e5da506b741bf8..339195d572f2a1784ed2ed911834e0e571d639e2 100644 --- a/nixpkgs.rev +++ b/nixpkgs.rev @@ -1 +1 @@ -8bf142e001b6876b021c8ee90c2c7cec385fe8e9 \ No newline at end of file +353333ef340952c05332e3c271dff953264cb017 \ No newline at end of file