From 84bf6e47121b1c0de1701f97fedeaf888e101649 Mon Sep 17 00:00:00 2001 From: Florian Brandes Date: Fri, 12 Jul 2024 12:53:11 +0200 Subject: [PATCH 1/2] wip: add module.nix Signed-off-by: Florian Brandes --- .gitignore | 1 + config.example | 1 - default.nix | 7 -- flake.nix | 1 + module.nix | 198 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 module.nix diff --git a/.gitignore b/.gitignore index 29ee88c..ec86951 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ config.ini *.pfx *.crt *.pem +*.pw !tests/privkey.pem !tests/signer.pem diff --git a/config.example b/config.example index b39ac16..3663644 100644 --- a/config.example +++ b/config.example @@ -14,7 +14,6 @@ sender = foo@example.com # start_tls = false # smime_cert = /path/to/cert # smime_cert_private = /path/to/cert.key -# smime_to_cert = /path/to/recipient_cert [emails] # If you want to encrypt outgoing email, add the recipient certificate to the email diff --git a/default.nix b/default.nix index 7b4999f..59ce8f2 100644 --- a/default.nix +++ b/default.nix @@ -22,13 +22,6 @@ pkgs.python3Packages.buildPythonPackage rec { nativeCheckInputs = [ pkgs.python3Packages.pytestCheckHook ]; pythonImportsCheck = [ "smtprd_ng" ]; - postInstall = '' - install -D -m 644 systemd/smtprd-ng.service \ - $out/lib/systemd/system/smtprd-ng.service - substituteInPlace $out/lib/systemd/system/smtprd-ng.service \ - --replace-fail @smtprd@ "$out/bin/smtprd-ng" - ''; - meta = { description = "SMTP forwarding relay daemon with signing and encryption"; homepage = "https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z3gWc1qgaeZaoGwL4WTstLNoqjayM"; diff --git a/flake.nix b/flake.nix index 3620b7c..7114680 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,7 @@ ]; in { + nixosModules.smtprd-ng = import ./module.nix; overlays.default = import ./overlay.nix { inherit self; }; } // flake-utils.lib.eachSystem supportedSystems ( diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..8d08c3e --- /dev/null +++ b/module.nix @@ -0,0 +1,198 @@ +{ config +, lib +, pkgs +, ... +}: +with lib; +let + cfg = config.smtprd-ng; + smtprd-ng = pkgs.callPackage ./. { }; + + emails = submodule { + options = { + email = mkOption { + type = types.str; + description = '' + Email to relay locally received emails to. + ''; + }; + certificate = mkOption { + type = types.nullOr types.path; + description = '' + Path to the public S/MIME certificate for this receiver. Emails will be encrypted with this certificate. + ''; + }; + }; + }; + +in +{ + options.smtprd-ng = { + enable = mkEnableOption "smtprd-ng"; + + package = mkOption { + type = types.package; + default = smtprd-ng; + description = mdDoc '' + The smtprd-ng package to use. + ''; + }; + + server = { + hostname = mkOption { + type = types.str; + default = "localhost"; + description = mdDoc '' + The hostname the local SMTP server should listen on. + ''; + }; + port = mkOption { + type = types.port; + default = 8025; + description = mdDoc '' + The port the local SMTP server should listen on. + ''; + }; + }; + client = { + hostname = mkOption { + type = types.str; + default = ""; + description = mdDoc '' + The hostname of the remote SMTP server. + ''; + }; + port = mkOption { + type = types.port; + default = 587; + description = mdDoc '' + The port of the remote SMTP server. + ''; + }; + username = mkOption { + type = types.nullOr types.str; + default = null; + description = mdDoc '' + The username to login to the remote SMTP server. + ''; + }; + password_file = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + The path to the file containing the password to login to the remote SMTP server. + Should not be in /nix/store! + ''; + }; + sender = mkOption { + type = types.nullOr types.str; + default = null; + description = mdDoc '' + The email of the sender. Some SMTP servers require this to be the real FROM email address of the user. + ''; + }; + use_tls = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Use TLS to connect. Mutually exclusive with `start_tls` + ''; + }; + start_tls = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Use STARTLS to connect. Mutually exclusive with `use_tls` + ''; + }; + smime_cert = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + The path to the S/MIME certificate used to sign messages. + If empty, will neither encrypt, nor sign relayed messages. + ''; + }; + smime_cert_private = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + The path to the S/MIME private key for the certificate used to sign messages. + Should not be in /nix/store! + ''; + }; + }; + emails = mkOption { + type = nullOr (listOf emails); + default = null; + description = '' + A required list of recipients. A certificate is optional, but required if + messages should be signed and encrypted. + ''; + example = [ + { + email = "monitoring.foo@example.com"; + certificate = "`/path/to/certificate`"; + } + { + email = "unencrypted@example.com"; + certificate = "`null`"; + } + ]; + }; + }; + + config = mkIf cfg.enable { + + assertions = [ + { + assertion = cfg.client.use_tls == true && cfg.client.start_tls == true; + message = "Use either TLS or STARTTLS, not both."; + } + { + assertion = cfg.client.smime_cert != null && cfg.client.smime_cert_private == null; + message = "If a S/MIME certificate should be used to sign messages, the private key to this certificate must be supplied."; + } + { + assertion = cfg.emails != null; + message = "At least one recipient must be configured."; + } + ]; + + configFile = { + text = generators.toINI { } { + server = { + hostname = cfg.server.hostname; + port = cfg.server.port; + }; + client = { + hostname = cfg.client.hostname; + port = cfg.client.port; + username = cfg.client.username; + password_file = cfg.client.password_file; + sender = cfg.client.sender; + use_tls = cfg.client.use_tls; + start_tls = cfg.client.start_tls; + smime_cert = cfg.client.smime_cert; + smime_cert_private = cfg.client.smime_cert_private; + }; + emails = cfg.emails; + }; + + systemd.services = { + smtprd-ng = { + description = "Run local SMTP relay"; + wantedBy = [ "multi-user.target" ]; + requires = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + User = "smtprd-ng"; + Group = "smtprd-ng"; + Restart = "on-failure"; + ExecStart = "${cfg.package}/bin/smtprd-ng --config ${configFile}"; + }; + }; + }; + }; + }; +} From 0494812be5385a1b965b4bb80483859222c67850 Mon Sep 17 00:00:00 2001 From: Florian Brandes Date: Fri, 12 Jul 2024 16:11:31 +0200 Subject: [PATCH 2/2] add module.nix and checked its functionality Signed-off-by: Florian Brandes --- module.nix | 104 +++++++++++++++++++++-------------------------------- 1 file changed, 41 insertions(+), 63 deletions(-) diff --git a/module.nix b/module.nix index 8d08c3e..b202c78 100644 --- a/module.nix +++ b/module.nix @@ -5,29 +5,31 @@ }: with lib; let - cfg = config.smtprd-ng; + cfg = config.services.smtprd-ng; smtprd-ng = pkgs.callPackage ./. { }; - emails = submodule { - options = { - email = mkOption { - type = types.str; - description = '' - Email to relay locally received emails to. - ''; - }; - certificate = mkOption { - type = types.nullOr types.path; - description = '' - Path to the public S/MIME certificate for this receiver. Emails will be encrypted with this certificate. - ''; - }; + cfgText = generators.toINI { } { + server = { + hostname = cfg.server.hostname; + port = cfg.server.port; }; + client = { + hostname = cfg.client.hostname; + port = cfg.client.port; + username = cfg.client.username; + password_file = cfg.client.password_file; + sender = cfg.client.sender; + use_tls = cfg.client.use_tls; + start_tls = cfg.client.start_tls; + smime_cert = cfg.client.smime_cert; + smime_cert_private = cfg.client.smime_cert_private; + }; + emails = cfg.emails; }; - + confFile = pkgs.writeText "config.ini" cfgText; in { - options.smtprd-ng = { + options.services.smtprd-ng = { enable = mkEnableOption "smtprd-ng"; package = mkOption { @@ -108,6 +110,7 @@ in smime_cert = mkOption { type = types.nullOr types.path; default = null; + example = "''\${./mycert.pem}"; description = mdDoc '' The path to the S/MIME certificate used to sign messages. If empty, will neither encrypt, nor sign relayed messages. @@ -123,22 +126,16 @@ in }; }; emails = mkOption { - type = nullOr (listOf emails); + type = types.nullOr (types.attrsOf (types.nullOr types.str)); default = null; description = '' - A required list of recipients. A certificate is optional, but required if + A required set of recipients. A certificate is optional, but required if messages should be signed and encrypted. ''; - example = [ - { - email = "monitoring.foo@example.com"; - certificate = "`/path/to/certificate`"; - } - { - email = "unencrypted@example.com"; - certificate = "`null`"; - } - ]; + example = { + "monitoring.foo@example.com" = "`/path/to/certificate`"; + "unencrypted@example.com" = "`null`"; + }; }; }; @@ -146,11 +143,13 @@ in assertions = [ { - assertion = cfg.client.use_tls == true && cfg.client.start_tls == true; + assertion = !cfg.client.use_tls || !cfg.client.start_tls; message = "Use either TLS or STARTTLS, not both."; } { - assertion = cfg.client.smime_cert != null && cfg.client.smime_cert_private == null; + assertion = + cfg.client.smime_cert == null + || (cfg.client.smime_cert != null && cfg.client.smime_cert_private != null); message = "If a S/MIME certificate should be used to sign messages, the private key to this certificate must be supplied."; } { @@ -159,38 +158,17 @@ in } ]; - configFile = { - text = generators.toINI { } { - server = { - hostname = cfg.server.hostname; - port = cfg.server.port; - }; - client = { - hostname = cfg.client.hostname; - port = cfg.client.port; - username = cfg.client.username; - password_file = cfg.client.password_file; - sender = cfg.client.sender; - use_tls = cfg.client.use_tls; - start_tls = cfg.client.start_tls; - smime_cert = cfg.client.smime_cert; - smime_cert_private = cfg.client.smime_cert_private; - }; - emails = cfg.emails; - }; - - systemd.services = { - smtprd-ng = { - description = "Run local SMTP relay"; - wantedBy = [ "multi-user.target" ]; - requires = [ "network.target" ]; - serviceConfig = { - DynamicUser = true; - User = "smtprd-ng"; - Group = "smtprd-ng"; - Restart = "on-failure"; - ExecStart = "${cfg.package}/bin/smtprd-ng --config ${configFile}"; - }; + systemd.services = { + smtprd-ng = { + description = "Run local SMTP relay"; + wantedBy = [ "multi-user.target" ]; + requires = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + User = "smtprd-ng"; + Group = "smtprd-ng"; + Restart = "on-failure"; + ExecStart = "${cfg.package}/bin/smtprd-ng --config ${confFile}"; }; }; };