diff --git a/modules/features/mtls.nix b/modules/features/mtls.nix index 3fa7b26..a0eb5cd 100644 --- a/modules/features/mtls.nix +++ b/modules/features/mtls.nix @@ -117,23 +117,29 @@ let mkMtlsRenewScript = { pkgs, - cfg, + certFile, + keyFile, + bundleFile, + user ? null, + group ? null, + reloadUnits ? [ ], + postCommands ? [ ], systemctlArgs ? [ ], }: let systemctlCmd = "systemctl ${lib.escapeShellArgs systemctlArgs}"; - hasReloadUnits = cfg.renew.reloadUnits != [ ]; + hasReloadUnits = reloadUnits != [ ]; renewReloadScript = lib.concatMapStringsSep "\n" (unit: '' if ${systemctlCmd} --quiet is-active "${unit}"; then ${systemctlCmd} try-reload-or-restart "${unit}" fi - '') cfg.renew.reloadUnits; + '') reloadUnits; - hasPostCommands = cfg.renew.postCommands != [ ]; - renewPostCommands = lib.concatStringsSep "\n" cfg.renew.postCommands; + hasPostCommands = postCommands != [ ]; + renewPostCommands = lib.concatStringsSep "\n" postCommands; - fileOwner = "${cfg.renew.user}:${cfg.renew.group}"; + hasOwnership = user != null && group != null; in pkgs.writeShellApplication { name = "mtls-renew"; @@ -157,101 +163,54 @@ let esac done - if [[ $force -eq 0 ]] && ! step certificate needs-renewal "${cfg.certFile}"; then + if [[ $force -eq 0 ]] && ! step certificate needs-renewal "${certFile}"; then echo "Skipping renew" exit 0 fi echo "Renewing mTLS certificate" - step ca renew --force "${cfg.certFile}" "${cfg.keyFile}" - (umask 077; cat "${cfg.certFile}" "${cfg.keyFile}" > "${cfg.bundleFile}") - chown ${fileOwner} ${cfg.certFile} ${cfg.keyFile} ${cfg.bundleFile} - chmod 640 ${cfg.certFile} ${cfg.keyFile} ${cfg.bundleFile} + step ca renew --force "${certFile}" "${keyFile}" + (umask 077; cat "${certFile}" "${keyFile}" > "${bundleFile}") + + ${lib.optionalString hasOwnership '' + chown ${user}:${group} ${certFile} ${keyFile} ${bundleFile} + chmod 640 ${certFile} ${keyFile} ${bundleFile} + ''} ${lib.optionalString hasReloadUnits '' - echo "Reloading units: ${lib.concatStringsSep ", " cfg.renew.reloadUnits}" + echo "Reloading units: ${lib.concatStringsSep ", " reloadUnits}" ${renewReloadScript} ''} - ${lib.optionalString hasPostCommands ''echo "Post commands:" ${renewPostCommands}''} + ${lib.optionalString hasPostCommands '' + echo "Running post commands" + ${renewPostCommands} + ''} ''; }; - mkNixosMtlsRenewService = { pkgs, cfg, ... }: - { - description = "Renew the mTLS certificate when Smallstep marks it ready"; - wantedBy = [ ]; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - serviceConfig = { - Type = "oneshot"; - User = cfg.renew.user; - Group = cfg.renew.group; - ExecStart = lib.getExe (mkMtlsRenewScript { inherit pkgs cfg; }); - }; - }; - - mkNixosMtlsRenewTimer = { - onCalendar, - randomizedDelaySec, - unit ? "mtls-renew.service", - }: { - description = "Periodic Smallstep renewal for the mTLS certificate"; - wantedBy = [ "timers.target" ]; - timerConfig = { - Persistent = true; - OnCalendar = onCalendar; - AccuracySec = "1us"; - RandomizedDelaySec = randomizedDelaySec; - Unit = unit; - }; - }; - - mkHomeManagerMtlsRenewService = { pkgs, cfg, ... }: - let - renewScript = mkMtlsRenewScript { - inherit pkgs cfg; - systemctlArgs = [ "--user" ]; - }; - in - { - Unit = { - Description = "Renew the mTLS certificate when Smallstep marks it ready"; - After = [ "network-online.target" ]; - Wants = [ "network-online.target" ]; - }; - Service = { - Type = "oneshot"; - ExecStart = lib.getExe renewScript; - }; - }; - - mkHomeManagerMtlsRenewTimer = { - onCalendar, - randomizedDelaySec, - unit ? "mtls-renew.service", - }: { - Unit = { - Description = "Periodic Smallstep renewal for the mTLS certificate"; - }; - Timer = { - Persistent = true; - OnCalendar = onCalendar; - AccuracySec = "1us"; - RandomizedDelaySec = randomizedDelaySec; - Unit = unit; - }; - Install = { - WantedBy = [ "timers.target" ]; - }; - }; - in { flake.modules.nixos.mtls = { config, lib, pkgs, ... }: let cfg = config.mtls; - sanArgs = lib.concatMapStringsSep " " (san: "--san \"${san}\"") cfg.san; + mtlsRenewWrapper = inputs.self.wrappers.mtlsRenew.apply { + inherit pkgs; + inherit (cfg) certDir keyFile certFile bundleFile; + inherit (cfg.renew) user group reloadUnits postCommands; + systemd = { + description = "Renew the mTLS certificate when Smallstep marks it ready"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + Type = "oneshot"; + User = cfg.renew.user; + Group = cfg.renew.group; + }; + } // lib.optionalAttrs cfg.renew.enable { + startAt = cfg.renew.onCalendar; + }; + }; in { options.mtls = (mkOpts config) // { @@ -284,29 +243,45 @@ in inherit pkgs; inherit (cfg) bundleFile; }).wrapper - (mkMtlsRenewScript { inherit pkgs cfg; }) + mtlsRenewWrapper.wrapper ]; systemd.tmpfiles.rules = [ "d ${cfg.certDir} 0750 ${cfg.renew.user} ${cfg.renew.group} -" ]; - systemd.services.mtls-renew = lib.mkIf cfg.renew.enable - (mkNixosMtlsRenewService { inherit pkgs cfg; }); + systemd.packages = lib.mkIf cfg.renew.enable [ + mtlsRenewWrapper.outputs.systemd-system + ]; - systemd.timers.mtls-renew = lib.mkIf cfg.renew.enable (mkNixosMtlsRenewTimer { - inherit (cfg.renew) onCalendar randomizedDelaySec; - }); + systemd.timers.mtls-renew = lib.mkIf cfg.renew.enable { + wantedBy = [ "timers.target" ]; + timerConfig = { + Persistent = true; + AccuracySec = "1us"; + RandomizedDelaySec = cfg.renew.randomizedDelaySec; + }; + }; }; }; flake.modules.homeManager.mtls = { config, lib, pkgs, ... }: let cfg = config.mtls; - keyFile = cfg.keyFile; - certFile = cfg.certFile; - bundleFile = cfg.bundleFile; - sanArgs = lib.concatMapStringsSep " " (san: "--san \"${san}\"") cfg.san; + mtlsRenewWrapper = inputs.self.wrappers.mtlsRenew.apply { + inherit pkgs; + inherit (cfg) certDir keyFile certFile bundleFile; + inherit (cfg.renew) reloadUnits postCommands; + systemctlArgs = [ "--user" ]; + systemd = { + description = "Renew the mTLS certificate when Smallstep marks it ready"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig.Type = "oneshot"; + } // lib.optionalAttrs cfg.renew.enable { + startAt = cfg.renew.onCalendar; + }; + }; in { options.mtls = (mkOpts config) // { @@ -330,23 +305,37 @@ in inherit pkgs; inherit (cfg) bundleFile; }).wrapper - # (mkMtlsRenewScript { inherit pkgs cfg; systemctlArgs = [ "--user" ]; }) - (inputs.self.wrappers.mtlsRenew.apply { - inherit pkgs; - inherit (cfg) certDir keyFile certFile bundleFile; - }).wrapper + mtlsRenewWrapper.wrapper ]; systemd.user.tmpfiles.rules = lib.mkIf cfg.enable [ "d ${cfg.certDir} 0700 - - -" ]; - systemd.user.services.mtls-renew = lib.mkIf cfg.renew.enable - (mkHomeManagerMtlsRenewService { inherit pkgs cfg; }); + xdg.dataFile = lib.mkIf (cfg.enable && cfg.renew.enable) { + "systemd/user/mtls-renew.service".source = + "${mtlsRenewWrapper.outputs.systemd-user}/systemd/user/mtls-renew.service"; + "systemd/user/mtls-renew.timer".source = + "${mtlsRenewWrapper.outputs.systemd-user}/systemd/user/mtls-renew.timer"; + "systemd/user/mtls-renew.timer.d/override.conf".text = '' + [Timer] + Persistent=true + AccuracySec=1us + RandomizedDelaySec=${cfg.renew.randomizedDelaySec} + ''; + }; - systemd.user.timers.mtls-renew = lib.mkIf cfg.renew.enable (mkHomeManagerMtlsRenewTimer { - inherit (cfg.renew) onCalendar randomizedDelaySec; - }); + home.activation.mtlsRenewTimer = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + if [ -n "$XDG_RUNTIME_DIR" ] && [ -S "$XDG_RUNTIME_DIR/systemd/private" ]; then + if [ "${lib.boolToString (cfg.enable && cfg.renew.enable)}" = "true" ]; then + run ${pkgs.systemd}/bin/systemctl --user daemon-reload + run ${pkgs.systemd}/bin/systemctl --user enable --now mtls-renew.timer + else + run ${pkgs.systemd}/bin/systemctl --user disable --now mtls-renew.timer || true + run ${pkgs.systemd}/bin/systemctl --user daemon-reload || true + fi + fi + ''; }; }; @@ -398,6 +387,8 @@ in }); mtlsRenew = inputs.wrappers.lib.wrapModule ({ config, lib, wlib, ... }: { + imports = [ wlib.modules.systemd ]; + options = { certDir = lib.mkOption { description = "String path to the directory where the certs will be stored"; @@ -418,41 +409,41 @@ in type = lib.types.str; default = "${config.certDir}/mtls.pem"; }; + user = lib.mkOption { + description = "User that should own the renewed certificate files."; + type = lib.types.nullOr lib.types.str; + default = null; + }; + group = lib.mkOption { + description = "Group that should own the renewed certificate files."; + type = lib.types.nullOr lib.types.str; + default = null; + }; + reloadUnits = lib.mkOption { + description = "systemd units to try-reload-or-restart after a successful renewal."; + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + postCommands = lib.mkOption { + description = "Shell commands to run after a successful renewal."; + type = lib.types.listOf lib.types.lines; + default = [ ]; + }; + systemctlArgs = lib.mkOption { + description = "Additional arguments to pass to systemctl when reloading units."; + type = lib.types.listOf lib.types.str; + default = [ ]; + }; }; config = { binName = "mtls-renew"; - extraPackages = [ - (inputs.self.wrappers.mtlsNeedsRenewal.apply { - inherit (config) pkgs certFile; - }).wrapper - ]; - preHook = '' - YELLOW_BANG="\e[33m!\e[0m" - - force=0 - while [[ $# -gt 0 ]]; do - case $1 in - --force) - force=1 - shift - ;; - *) - echo -e "$YELLOW_BANG Warning: ignoring unrecognized argument '$1'" - exit 1 - ;; - esac - done - - if [[ $force -eq 0 ]] && ! mtls-needs-renewal; then - echo "Skipping renew" - exit 0 - else - echo "Renewing mTLS certificate" - fi - ''; - package = config.pkgs.step-cli; - args = [ "ca" "renew" "--force" config.certFile config.keyFile ]; + package = mkMtlsRenewScript { + inherit (config) pkgs certFile keyFile bundleFile user group reloadUnits postCommands systemctlArgs; + }; + systemd = { + serviceConfig.Type = lib.mkDefault "oneshot"; + }; }; }); };