From 4ad78138c8c28827b318cf114c0a8c9339113838 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Mon, 4 Nov 2019 13:50:09 -0500 Subject: [PATCH] Quick and dirty Let's Encrypt client service using certbot --- morph/grid.config.json | 2 + morph/grid.nix | 1 + morph/issuer.nix | 4 ++ morph/make-grid.nix | 4 +- morph/make-storage.nix | 2 +- morph/testing-grid.config.json | 5 +++ morph/testing-grid.nix | 8 +++- morph/testing000.nix | 2 +- nixos/modules/issuer.nix | 67 +++++++++++++++++++--------------- nixpkgs.rev | 2 +- 10 files changed, 62 insertions(+), 35 deletions(-) create mode 100644 morph/testing-grid.config.json diff --git a/morph/grid.config.json b/morph/grid.config.json index 178f44d3..5b848d31 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 466398b1..3d005f96 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 diff --git a/morph/issuer.nix b/morph/issuer.nix index aac47193..ddf01bdf 100644 --- a/morph/issuer.nix +++ b/morph/issuer.nix @@ -1,5 +1,7 @@ { hardware , ristrettoSigningKeyPath +, issuerDomain +, letsEncryptAdminEmail , stateVersion , ... }: { @@ -27,6 +29,8 @@ 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 32e0b98e..de10df1e 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 768cdb55..84a13be3 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 00000000..018367db --- /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 5591827e..b4b0649d 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 e5f9c3f3..d45086ae 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 2cd63cea..88413333 100644 --- a/nixos/modules/issuer.nix +++ b/nixos/modules/issuer.nix @@ -64,11 +64,18 @@ in { 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 = let - acme = "/var/lib/acme"; + certroot = "/var/lib/letsencrypt/live"; in lib.mkIf cfg.enable { # Add a systemd service to run PaymentServer. systemd.services.zkapissuer = { @@ -80,8 +87,8 @@ in { # interfaces. "network.target" ]; - # Make sure we at least have a self-signed certificate. - requires = lib.optional cfg.tls "acme-selfsigned-${cfg.domain}.service"; + # Make sure we at least have a certificate. + requires = lib.optional cfg.tls "cert-${cfg.domain}"; serviceConfig = { ExecStart = @@ -100,12 +107,9 @@ in { 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" + "--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"; @@ -121,25 +125,30 @@ in { # 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 = [ "account_key.json" "full.pem" "fullchain.pem" "key.pem" ]; - }; - } - else {}; - - services.nginx.virtualHosts = if cfg.tls - then { - "${cfg.domain}" = { - locations."/" = "${acme}/acme-challenges"; - }; - } - else {}; + systemd.services."cert-${cfg.domain}" = { + enable = true; + description = "Issue/Renew certificate for ${cfg.domain}"; + wantedBy = [ "zkapissuer.service" ]; + serviceConfig = { + ExecStart = + 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} --agree-tos -m ${cfg.letsEncryptAdminEmail} || true + # Obtain the certificate. + ${pkgs.certbot}/bin/certbot certonly ${configArgs} -n --standalone --domains ${cfg.domain} + # Restart the server so the new certificate gets used. + systemctl restart zkapissuer.service + ''; + }; + }; + # Open 80 and 443 for the certbot HTTP server and the PaymentServer HTTPS server. + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; }; } diff --git a/nixpkgs.rev b/nixpkgs.rev index 41802ea6..339195d5 100644 --- a/nixpkgs.rev +++ b/nixpkgs.rev @@ -1 +1 @@ -8bf142e001b6876b021c8ee90c2c7cec385fe8e9 \ No newline at end of file +353333ef340952c05332e3c271dff953264cb017 \ No newline at end of file -- GitLab