Newer
Older
# A NixOS module which can run a Ristretto-based issuer for PrivateStorage
# ZKAPs.
{ lib, pkgs, config, ... }: let
cfg = config.services.private-storage-issuer;
ourpkgs = import ../../nixpkgs-ps.nix {};
imports = [
# Give it a good SSH configuration.
../../nixos/modules/ssh.nix
];
options = {
services.private-storage-issuer.enable = lib.mkEnableOption "PrivateStorage ZKAP Issuer Service";
services.private-storage-issuer.package = lib.mkOption {
default = ourpkgs.zkapissuer.components.exes."PaymentServer-exe";
type = lib.types.package;
example = lib.literalExample "pkgs.zkapissuer.components.exes.\"PaymentServer-exe\"";
description = ''
The package to use for the ZKAP issuer.
'';
};
services.private-storage-issuer.domains = lib.mkOption {
type = lib.types.listOf lib.types.str;
example = lib.literalExample [ "payments.example.com" ];
The domain names 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";
example = lib.literalExample "Trivial";
description = ''
The issuer algorithm to use. Either Trivial for a fake no-crypto
algorithm or Ristretto for Ristretto-flavored PrivacyPass.
'';
};
services.private-storage-issuer.ristrettoSigningKeyPath = lib.mkOption {
type = lib.types.path;
The path to a file containing the Ristretto signing key to use.
Required if the issuer is ``Ristretto``.
services.private-storage-issuer.stripeSecretKeyPath = lib.mkOption {
type = lib.types.path;
description = ''
The path to a file containing a Stripe secret key to use for charge
and payment management.
'';
};
services.private-storage-issuer.stripeEndpointDomain = lib.mkOption {
type = lib.types.str;
description = ''
The domain name for the Stripe API HTTP endpoint.
'';
default = "api.stripe.com";
};
services.private-storage-issuer.stripeEndpointScheme = lib.mkOption {
type = lib.types.enum [ "HTTP" "HTTPS" ];
description = ''
Whether to use HTTP or HTTPS for the Stripe API.
'';
default = "HTTPS";
};
services.private-storage-issuer.stripeEndpointPort = lib.mkOption {
type = lib.types.int;
description = ''
The port number for the Stripe API HTTP endpoint.
'';
default = 443;
};
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.
'';
};
Jean-Paul Calderone
committed
services.private-storage-issuer.allowedChargeOrigins = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
The CORS "Origin" values which are allowed to submit charges to the
payment server. Note this is not currently enforced by the
PaymentServer. It just controls the CORS headers served.
'';
};
};
certroot = "/var/lib/letsencrypt/live";
# We'll refer to this collection of domains by the first domain in the
# list.
domain = builtins.head cfg.domains;
certServiceName = "cert-${domain}";
systemd.services.zkapissuer = {
enable = true;
description = "ZKAP Issuer";
wantedBy = [ "multi-user.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 "${certServiceName}.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 "${certServiceName}.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-path ${cfg.ristrettoSigningKeyPath}";
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}/${domain}/cert.pem " +
"--https-certificate-chain-path ${certroot}/${domain}/chain.pem " +
"--https-key-path ${certroot}/${domain}/privkey.pem"
else
# Only for automated testing.
"--http-port 80";
Jean-Paul Calderone
committed
prefixOption = s: "--cors-origin=" + s;
Jean-Paul Calderone
committed
originStrings = map prefixOption cfg.allowedChargeOrigins;
originArgs = builtins.concatStringsSep " " originStrings;
stripeArgs =
"--stripe-key-path ${cfg.stripeSecretKeyPath} " +
"--stripe-endpoint-domain ${cfg.stripeEndpointDomain} " +
"--stripe-endpoint-scheme ${cfg.stripeEndpointScheme} " +
"--stripe-endpoint-port ${toString cfg.stripeEndpointPort}";
Jean-Paul Calderone
committed
"${cfg.package}/bin/PaymentServer-exe ${originArgs} ${issuerArgs} ${databaseArgs} ${httpsArgs} ${stripeArgs}";
# Certificate renewal. A short-lived service meant to be repeatedly
# activated to request a new certificate be issued, if the current one is
# close to expiring.
systemd.services.${certServiceName} = {
enable = cfg.tls;
description = "Certificate ${domain}";
serviceConfig = {
ExecStart =
let
configArgs = "--config-dir /var/lib/letsencrypt --work-dir /var/run/letsencrypt --logs-dir /var/run/log/letsencrypt";
in
pkgs.writeScript "cert-${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 --expand --domains ${builtins.concatStringsSep "," cfg.domains}
'';
};
};
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# Periodically trigger the certificate renewal service.
systemd.timers.${certServiceName} = {
enable = cfg.tls;
timerConfig = {
# "Defines a timer relative to when the unit the timer unit is
# activating was last deactivated."
#
# Trigger the renewal service periodically. Since it will activate
# and then deactivate each time this timer triggers, this timer will
# trigger it repeatedly. The delay specified here is relative to the
# last time the target unit is deactivated and that advances to the
# current time after each time the trigger fires.
OnUnitInactiveSec = "3d";
# "Defines a timer relative to the moment the timer unit itself is
# activated."
#
# Since at the time this timer is activated we're not sure whether the
# renewal service has ever been activated or deactivated we don't know
# when if or when the other trigger will fire. This ensures that
# shortly after this timer is activated it will trigger. Thereafter,
# the other trigger will take over for periodic re-triggering.
OnActiveSec = "5m";
};
};
# Open 80 and 443 for the certbot HTTP server and the PaymentServer HTTPS server.
networking.firewall.allowedTCPPorts = [
80
443
];