nixfiles/modules/nixos/secrets.nix
2022-11-15 14:52:49 +00:00

254 lines
6.8 KiB
Nix

{ config, lib, pkgs, ... }:
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 ({ config, 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 (name: secret: secret.manual) cfg.secrets;
nonManualSecrets = filterAttrs (name: 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 "%U" "$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}"
];
})
];
}