depot/cluster/services/locksmith/provider.nix

116 lines
4.1 KiB
Nix

{ config, depot, lib, pkgs, ... }:
let
kvRoot = "secrets/locksmith";
activeProvders = lib.filterAttrs (_: cfg: lib.any (secret: secret.nodes != []) (lib.attrValues cfg.secrets)) config.services.locksmith.providers;
in
{
options.services.locksmith = with lib; {
providers = mkOption {
type = types.attrsOf (types.submodule ({ ... }: {
options = {
wantedBy = mkOption {
type = types.listOf types.str;
default = [];
};
after = mkOption {
type = types.listOf types.str;
default = [];
};
secrets = mkOption {
type = types.attrsOf (types.submodule ({ ... }: {
options = {
nodes = mkOption {
type = types.listOf types.str;
default = [];
};
command = mkOption {
type = types.coercedTo types.package (package: "${package}") types.str;
};
checkUpdate = mkOption {
type = types.coercedTo types.package (package: "${package}") types.str;
default = "true";
};
owner = mkOption {
type = types.str;
default = "root";
};
group = mkOption {
type = types.str;
default = "root";
};
mode = mkOption {
type = types.str;
default = "0400";
};
};
}));
default = {};
};
};
}));
default = {};
};
};
config.systemd.services = lib.mapAttrs' (providerName: providerConfig: {
name = "locksmith-provider-${providerName}";
value = let
providerRoot = "${kvRoot}/${providerName}";
in {
description = "Locksmith Provider | ${providerName}";
distributed.enable = true;
inherit (providerConfig) wantedBy after;
serviceConfig = {
Type = "oneshot";
PrivateTmp = true;
LoadCredential = lib.mkForce [];
};
path = [
config.services.consul.package
pkgs.age
];
script = let
activeSecrets = lib.filterAttrs (_: secret: secret.nodes != []) providerConfig.secrets;
activeNodes = lib.unique (lib.flatten (lib.mapAttrsToList (_: secret: secret.nodes) activeSecrets));
secretNames = map (name: "${providerRoot}-${name}/") (lib.attrNames activeSecrets);
createSecret = { path, nodes, owner, mode, group, command, checkUpdate }: ''
if (${checkUpdate}); then
consul kv put ${lib.escapeShellArg path}/mode ${lib.escapeShellArg mode}
consul kv put ${lib.escapeShellArg path}/owner ${lib.escapeShellArg owner}
consul kv put ${lib.escapeShellArg path}/group ${lib.escapeShellArg group}
secret="$(mktemp -ut)"
(${command}) > "$secret"
${lib.concatStringsSep "\n" (map (node: ''
consul kv put ${lib.escapeShellArg path}/recipient/${node} "$(age < "$secret" --encrypt --armor -r ${lib.escapeShellArg depot.hours.${node}.ssh.id.publicKey})"
'') nodes)}
else
echo Skipping update for ${lib.escapeShellArg path}
fi
'';
in ''
# create/update secrets
umask 77
${lib.pipe activeSecrets [
(lib.mapAttrsToList (secretName: secretConfig: createSecret {
path = "${providerRoot}-${secretName}";
inherit (secretConfig) nodes mode owner group command checkUpdate;
}))
(lib.concatStringsSep "\n")
]}
# delete leftover secrets of this provider
consul kv get --keys '${providerRoot}-' | grep -v ${lib.concatStringsSep " \\\n " (map (secret: "-e ${lib.escapeShellArg secret}") secretNames)} | xargs --no-run-if-empty -n1 consul kv delete --recurse
# notify
${lib.pipe activeNodes [
(map (node: "consul event --name=chant:locksmith --node=${node}"))
(lib.concatStringsSep "\n")
]}
'';
};
}) activeProvders;
}