Newer
Older
# A NixOS module which can run a Ristretto-based issuer for PrivacyStorage
# ZKAPs.
{ lib, pkgs, config, ... }: let
pspkgs = pkgs.callPackage ./pspkgs.nix { };
zkapissuer = pspkgs.callPackage ../pkgs/zkapissuer.nix { };
cfg = config.services.private-storage-issuer;
in {
options = {
services.private-storage-issuer.enable = lib.mkEnableOption "PrivateStorage ZKAP Issuer Service";
services.private-storage-issuer.package = lib.mkOption {
default = 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.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" ];
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.ristrettoSigningKey = lib.mkOption {
default = null;
type = lib.types.str;
description = ''
The Ristretto signing key to use. Required if the issuer is
``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.
'';
};
};
config =
let
acme = "/var/lib/acme";
in lib.mkIf cfg.enable {
systemd.services.zkapissuer = {
enable = true;
description = "ZKAP Issuer";
wantedBy = [ "multi-user.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 =
let
# Compute the right command line arguments to pass to it. The
# signing key is only supplied when using the Ristretto issuer.
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 " +
# 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";
"${cfg.package}/bin/PaymentServer-exe ${issuerArgs} ${databaseArgs} ${httpsArgs}";
# 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.
};
};
# 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 {};