From 19eedc0be2419caf14cbaf1dbe3fc4bffb120431 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Wed, 30 Oct 2019 15:13:39 -0400 Subject: [PATCH] enable tls with acme certificates --- nixos/modules/issuer.nix | 82 ++++++++++++++++++++++++- nixos/modules/tests/get-passes.py | 8 ++- nixos/modules/tests/private-storage.nix | 6 +- nixos/pkgs/zkapissuer-repo.nix | 4 +- 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/nixos/modules/issuer.nix b/nixos/modules/issuer.nix index 41143ce0..2c1fab77 100644 --- a/nixos/modules/issuer.nix +++ b/nixos/modules/issuer.nix @@ -15,6 +15,23 @@ 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.enum [ " Trivial" "Ristretto" ]; @@ -49,13 +66,22 @@ in { }; }; - config = lib.mkIf cfg.enable { + config = + let + acme = "/var/lib/acme"; + 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" ]; + after = [ + # Make sure there is a network so we can bind to all of the + # interfaces. + "network.target" + ]; + # Make sure we at least have a self-signed certificate. + requires = lib.optional cfg.tls "acme-selfsigned-${cfg.domain}.service"; serviceConfig = { ExecStart = @@ -70,8 +96,21 @@ in { if cfg.database == "Memory" then "--database Memory" else "--database SQLite3 --database-path ${cfg.databasePath}"; + httpsArgs = + if cfg.tls + then + "--https-port 443 " + + # acme has plugins to write the files in different ways but the + # self-signed certificate generator doesn't. The files it + # writes are weirdly named and shaped but they work. + "--https-certificate-path ${acme}/${cfg.domain}/full.pem " + + "--https-certificate-chain-path ${acme}/${cfg.domain}/fullchain.pem " + + "--https-key-path ${acme}/${cfg.domain}/key.pem" + else + # Only for automated testing. + "--http-port 80"; in - "${cfg.package}/bin/PaymentServer-exe ${issuerArgs} ${databaseArgs}"; + "${cfg.package}/bin/PaymentServer-exe ${issuerArgs} ${databaseArgs} ${httpsArgs}"; 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 @@ -79,5 +118,42 @@ in { Restart = "always"; }; }; + + # Certificate renewal. Note that preliminarySelfsigned only creates the + # service. We must declare that we *require* it in our service above. + security.acme = if cfg.tls + then { + production = false; + preliminarySelfsigned = true; + certs."${cfg.domain}" = { + email = "jean-paul@privatestorage.io"; + postRun = "systemctl restart zkapissuer.service"; + webroot = "${acme}/acme-challenges"; + plugins = [ "full.pem" "fullchain.pem" "key.pem" ]; + }; + } + else {}; + + systemd.timers = if cfg.tls + then { + "acme-${cfg.domain}-initial" = config.systemd.timers."acme-${cfg.domain}" // { + timerConfig = { + OnUnitActiveSec = "0"; + Unit = "acme-${cfg.domain}.service"; + Persistent = "yes"; + AccuracySec = "1us"; + RandomizedDelaySec = "0s"; + }; + }; + } + else {}; + + services.nginx.virtualHosts = if cfg.tls + then { + "${cfg.domain}" = { + locations."/" = "${acme}/acme-challenges"; + }; + } + else {}; }; } diff --git a/nixos/modules/tests/get-passes.py b/nixos/modules/tests/get-passes.py index ac5cf790..d76ce3cc 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 cc2fa798..b731404d 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 44182649..06faac68 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 = "0acf43493a7525b0eba01b1c69fb69b41f411ecc"; + sha256 = "0zaqm6qmylclxn25vghafwqxpm5h13c727x3zjnqgp7vbmi2aglk"; } \ No newline at end of file -- GitLab