WIP mtls wrapper

This commit is contained in:
John Lancaster
2026-04-30 22:28:35 -05:00
parent 68483c0231
commit bae2b3027e
+125 -134
View File
@@ -117,23 +117,29 @@ let
mkMtlsRenewScript = { mkMtlsRenewScript = {
pkgs, pkgs,
cfg, certFile,
keyFile,
bundleFile,
user ? null,
group ? null,
reloadUnits ? [ ],
postCommands ? [ ],
systemctlArgs ? [ ], systemctlArgs ? [ ],
}: }:
let let
systemctlCmd = "systemctl ${lib.escapeShellArgs systemctlArgs}"; systemctlCmd = "systemctl ${lib.escapeShellArgs systemctlArgs}";
hasReloadUnits = cfg.renew.reloadUnits != [ ]; hasReloadUnits = reloadUnits != [ ];
renewReloadScript = lib.concatMapStringsSep "\n" (unit: '' renewReloadScript = lib.concatMapStringsSep "\n" (unit: ''
if ${systemctlCmd} --quiet is-active "${unit}"; then if ${systemctlCmd} --quiet is-active "${unit}"; then
${systemctlCmd} try-reload-or-restart "${unit}" ${systemctlCmd} try-reload-or-restart "${unit}"
fi fi
'') cfg.renew.reloadUnits; '') reloadUnits;
hasPostCommands = cfg.renew.postCommands != [ ]; hasPostCommands = postCommands != [ ];
renewPostCommands = lib.concatStringsSep "\n" cfg.renew.postCommands; renewPostCommands = lib.concatStringsSep "\n" postCommands;
fileOwner = "${cfg.renew.user}:${cfg.renew.group}"; hasOwnership = user != null && group != null;
in in
pkgs.writeShellApplication { pkgs.writeShellApplication {
name = "mtls-renew"; name = "mtls-renew";
@@ -157,101 +163,54 @@ let
esac esac
done 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" echo "Skipping renew"
exit 0 exit 0
fi fi
echo "Renewing mTLS certificate" echo "Renewing mTLS certificate"
step ca renew --force "${cfg.certFile}" "${cfg.keyFile}" step ca renew --force "${certFile}" "${keyFile}"
(umask 077; cat "${cfg.certFile}" "${cfg.keyFile}" > "${cfg.bundleFile}") (umask 077; cat "${certFile}" "${keyFile}" > "${bundleFile}")
chown ${fileOwner} ${cfg.certFile} ${cfg.keyFile} ${cfg.bundleFile}
chmod 640 ${cfg.certFile} ${cfg.keyFile} ${cfg.bundleFile} ${lib.optionalString hasOwnership ''
chown ${user}:${group} ${certFile} ${keyFile} ${bundleFile}
chmod 640 ${certFile} ${keyFile} ${bundleFile}
''}
${lib.optionalString hasReloadUnits '' ${lib.optionalString hasReloadUnits ''
echo "Reloading units: ${lib.concatStringsSep ", " cfg.renew.reloadUnits}" echo "Reloading units: ${lib.concatStringsSep ", " reloadUnits}"
${renewReloadScript} ${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 in
{ {
flake.modules.nixos.mtls = { config, lib, pkgs, ... }: flake.modules.nixos.mtls = { config, lib, pkgs, ... }:
let let
cfg = config.mtls; 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 in
{ {
options.mtls = (mkOpts config) // { options.mtls = (mkOpts config) // {
@@ -284,29 +243,45 @@ in
inherit pkgs; inherit pkgs;
inherit (cfg) bundleFile; inherit (cfg) bundleFile;
}).wrapper }).wrapper
(mkMtlsRenewScript { inherit pkgs cfg; }) mtlsRenewWrapper.wrapper
]; ];
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d ${cfg.certDir} 0750 ${cfg.renew.user} ${cfg.renew.group} -" "d ${cfg.certDir} 0750 ${cfg.renew.user} ${cfg.renew.group} -"
]; ];
systemd.services.mtls-renew = lib.mkIf cfg.renew.enable systemd.packages = lib.mkIf cfg.renew.enable [
(mkNixosMtlsRenewService { inherit pkgs cfg; }); mtlsRenewWrapper.outputs.systemd-system
];
systemd.timers.mtls-renew = lib.mkIf cfg.renew.enable (mkNixosMtlsRenewTimer { systemd.timers.mtls-renew = lib.mkIf cfg.renew.enable {
inherit (cfg.renew) onCalendar randomizedDelaySec; wantedBy = [ "timers.target" ];
}); timerConfig = {
Persistent = true;
AccuracySec = "1us";
RandomizedDelaySec = cfg.renew.randomizedDelaySec;
};
};
}; };
}; };
flake.modules.homeManager.mtls = { config, lib, pkgs, ... }: flake.modules.homeManager.mtls = { config, lib, pkgs, ... }:
let let
cfg = config.mtls; cfg = config.mtls;
keyFile = cfg.keyFile; mtlsRenewWrapper = inputs.self.wrappers.mtlsRenew.apply {
certFile = cfg.certFile; inherit pkgs;
bundleFile = cfg.bundleFile; inherit (cfg) certDir keyFile certFile bundleFile;
sanArgs = lib.concatMapStringsSep " " (san: "--san \"${san}\"") cfg.san; 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 in
{ {
options.mtls = (mkOpts config) // { options.mtls = (mkOpts config) // {
@@ -330,23 +305,37 @@ in
inherit pkgs; inherit pkgs;
inherit (cfg) bundleFile; inherit (cfg) bundleFile;
}).wrapper }).wrapper
# (mkMtlsRenewScript { inherit pkgs cfg; systemctlArgs = [ "--user" ]; }) mtlsRenewWrapper.wrapper
(inputs.self.wrappers.mtlsRenew.apply {
inherit pkgs;
inherit (cfg) certDir keyFile certFile bundleFile;
}).wrapper
]; ];
systemd.user.tmpfiles.rules = lib.mkIf cfg.enable [ systemd.user.tmpfiles.rules = lib.mkIf cfg.enable [
"d ${cfg.certDir} 0700 - - -" "d ${cfg.certDir} 0700 - - -"
]; ];
systemd.user.services.mtls-renew = lib.mkIf cfg.renew.enable xdg.dataFile = lib.mkIf (cfg.enable && cfg.renew.enable) {
(mkHomeManagerMtlsRenewService { inherit pkgs cfg; }); "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 { home.activation.mtlsRenewTimer = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
inherit (cfg.renew) onCalendar randomizedDelaySec; 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, ... }: { mtlsRenew = inputs.wrappers.lib.wrapModule ({ config, lib, wlib, ... }: {
imports = [ wlib.modules.systemd ];
options = { options = {
certDir = lib.mkOption { certDir = lib.mkOption {
description = "String path to the directory where the certs will be stored"; description = "String path to the directory where the certs will be stored";
@@ -418,41 +409,41 @@ in
type = lib.types.str; type = lib.types.str;
default = "${config.certDir}/mtls.pem"; 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 = { config = {
binName = "mtls-renew"; binName = "mtls-renew";
extraPackages = [ package = mkMtlsRenewScript {
(inputs.self.wrappers.mtlsNeedsRenewal.apply { inherit (config) pkgs certFile keyFile bundleFile user group reloadUnits postCommands systemctlArgs;
inherit (config) pkgs certFile; };
}).wrapper systemd = {
]; serviceConfig.Type = lib.mkDefault "oneshot";
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 ];
}; };
}); });
}; };