273 lines
7.3 KiB
Nix
273 lines
7.3 KiB
Nix
{
|
|
lib,
|
|
pkgs,
|
|
config,
|
|
...
|
|
}:
|
|
with lib; let
|
|
cfg = config.services.secrets;
|
|
defaultPackages = with pkgs; [pkgs.vault pkgs.jq];
|
|
in {
|
|
options = {
|
|
services.secrets = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
};
|
|
|
|
debug = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
};
|
|
|
|
createSecretsDir = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
|
|
secretsDirUser = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
};
|
|
|
|
secretsDirGroup = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
};
|
|
|
|
secretsDir = mkOption {
|
|
type = types.str;
|
|
default = "/secrets";
|
|
};
|
|
|
|
vaultURL = mkOption {
|
|
type = types.str;
|
|
default = "https://vault.owo.monster";
|
|
description = "default Vault URL, can be overrided with env variables";
|
|
};
|
|
|
|
extraFunctions = mkOption {
|
|
type = types.lines;
|
|
default = "";
|
|
description = "extra bash functions to add to top of script";
|
|
};
|
|
|
|
extraPackages = mkOption {
|
|
type = types.listOf types.package;
|
|
default = [];
|
|
description = "extra packages for script";
|
|
};
|
|
|
|
secrets = mkOption {
|
|
type = types.attrsOf (types.submodule ({name, ...}: {
|
|
options = {
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
};
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
};
|
|
permissions = mkOption {
|
|
type = types.str;
|
|
default = "660";
|
|
};
|
|
path = mkOption {
|
|
type = types.str;
|
|
default = "${cfg.secretsDir}/${name}";
|
|
};
|
|
|
|
fetchScript = mkOption {
|
|
type = types.lines;
|
|
description = ''
|
|
script used to fetch secrets, $file is secret.path
|
|
'';
|
|
};
|
|
|
|
checkScript = mkOption {
|
|
type = types.nullOr types.lines;
|
|
default = null;
|
|
description = ''
|
|
script used to check contents of secret file, set LOCAL_FAIL to true on failure
|
|
'';
|
|
};
|
|
|
|
manual = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "should the secret be manually deployed";
|
|
};
|
|
};
|
|
}));
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkMerge [
|
|
(mkIf (cfg.enable) (let
|
|
scriptBase = ''
|
|
set -e -o pipefail
|
|
${
|
|
if cfg.debug
|
|
then "set -x"
|
|
else ""
|
|
}
|
|
'';
|
|
|
|
manualSecrets = filterAttrs (_: secret: secret.manual) cfg.secrets;
|
|
nonManualSecrets = filterAttrs (_: secret: !secret.manual) cfg.secrets;
|
|
|
|
initScript =
|
|
''
|
|
${scriptBase}
|
|
|
|
VAULT_ADDR_DEFAULT="${cfg.vaultURL}"
|
|
set +u
|
|
[ -z "$VAULT_ADDR" ] && export VAULT_ADDR="$VAULT_ADDR_DEFAULT"
|
|
set -u
|
|
|
|
kv_get() {
|
|
vault kv get -format json "$1"
|
|
}
|
|
|
|
simple_get() {
|
|
kv_get "$1" | jq ".data.data$2" -r
|
|
}
|
|
|
|
${cfg.extraFunctions}
|
|
''
|
|
+ (lib.concatStringsSep "\n" (lib.mapAttrsToList (_name: secret: ''
|
|
if [[ ! -f "${secret.path}" ]]; then
|
|
echo "Initializing Secret ${secret.path}"
|
|
else
|
|
echo "Updating Secret ${secret.path}"
|
|
fi
|
|
|
|
secretFile="${secret.path}"
|
|
${secret.fetchScript}
|
|
|
|
chown ${secret.user}:${secret.group} "${secret.path}"
|
|
chmod ${secret.permissions} "${secret.path}"
|
|
'')
|
|
nonManualSecrets))
|
|
+ (lib.concatStringsSep "\n" (lib.mapAttrsToList
|
|
(_name: secret: ''
|
|
if [[ ! -f "${secret.path}" ]]; then
|
|
echo "Manual Secret ${secret.path} Doesn't Exist"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Updating Permissions on Manual Secret ${secret.path}"
|
|
|
|
chown ${secret.user}:${secret.group} "${secret.path}"
|
|
chmod ${secret.permissions} "${secret.path}"
|
|
'')
|
|
manualSecrets))
|
|
+ ''
|
|
echo "Secrets Deployed"
|
|
'';
|
|
|
|
checkScript =
|
|
''
|
|
${scriptBase}
|
|
|
|
getUser() {
|
|
stat --format "%U" "$1" 2>/dev/null
|
|
}
|
|
|
|
getGroup() {
|
|
stat --format "%G" "$1" 2>/dev/null
|
|
}
|
|
|
|
getPermissions() {
|
|
stat --format "%a" "$1" 2>/dev/null
|
|
}
|
|
|
|
GLOBAL_FAIL=false
|
|
''
|
|
+ (lib.concatStringsSep "\n" (lib.mapAttrsToList (name: secret: ''
|
|
LOCAL_FAIL=false
|
|
|
|
echo "Secret: ${name}"
|
|
echo "Checking ${secret.path}"
|
|
|
|
# some variables which can be used by checkScript
|
|
# shellcheck disable=SC2034
|
|
secretFile="${secret.path}"
|
|
|
|
if [[ -f "${secret.path}" ]]; then
|
|
echo "✅ File Exists"
|
|
else
|
|
echo "❌ File Does Not Exist"
|
|
LOCAL_FAIL=true
|
|
fi
|
|
|
|
if getUser "${secret.path}" >/dev/null && [[ "$(getUser "${secret.path}")" == "${secret.user}" ]]; then
|
|
echo "✅ File Is Owned By Correct User"
|
|
else
|
|
echo "❌ File Is Not Owned By Correct User (${secret.user})"
|
|
LOCAL_FAIL=true
|
|
fi
|
|
|
|
if getGroup "${secret.path}" >/dev/null && [[ "$(getGroup "${secret.path}")" == "${secret.group}" ]]; then
|
|
echo "✅ File Is Owned By Correct Group"
|
|
else
|
|
echo "❌ File Is Not Owned By Correct Group (${secret.user})"
|
|
LOCAL_FAIL=true
|
|
fi
|
|
|
|
if getPermissions "${secret.path}" >/dev/null && [[ "$(getPermissions "${secret.path}")" -eq "${secret.permissions}" ]]; then
|
|
echo "✅ File Has Correct Permissions"
|
|
else
|
|
echo "❌ File Does Not Have Correct Permissions (${secret.permissions})"
|
|
LOCAL_FAIL=true
|
|
fi
|
|
|
|
${
|
|
if secret.checkScript != null
|
|
then secret.checkScript
|
|
else ""
|
|
}
|
|
|
|
if [[ "$LOCAL_FAIL" == "true" ]]; then
|
|
echo "❌ File Did Not Pass The Vibe Check"
|
|
GLOBAL_FAIL=true
|
|
else
|
|
echo "✅ File Passed The Vibe Check"
|
|
fi
|
|
|
|
echo
|
|
'')
|
|
cfg.secrets))
|
|
+ ''
|
|
if [[ "$GLOBAL_FAIL" == "true" ]]; then
|
|
echo "❌ One Or More Secrets Did Not Pass The Vibe Check"
|
|
exit 1
|
|
else
|
|
echo "✅ All Secrets Passed The Vibe Check"
|
|
fi
|
|
'';
|
|
in {
|
|
environment.systemPackages = [
|
|
(pkgs.writeShellApplication {
|
|
name = "secrets-check";
|
|
runtimeInputs = defaultPackages ++ cfg.extraPackages;
|
|
text = checkScript;
|
|
})
|
|
(pkgs.writeShellApplication {
|
|
name = "secrets-init";
|
|
runtimeInputs = defaultPackages ++ cfg.extraPackages;
|
|
text = initScript;
|
|
})
|
|
];
|
|
}))
|
|
|
|
(mkIf (cfg.enable && cfg.createSecretsDir) {
|
|
systemd.tmpfiles.rules = [
|
|
"d ${cfg.secretsDir} - ${cfg.secretsDirUser} ${cfg.secretsDirGroup}"
|
|
];
|
|
})
|
|
];
|
|
}
|