{ pkgs, lib, ... }: let inherit (builtins) attrNames hasAttr isString toFile; inherit (lib.lists) forEach unique flatten filter; inherit (lib.strings) concatStringsSep optionalString; inherit (lib.attrsets) mapAttrsToList filterAttrs; inherit (pkgs) writeShellApplication; genScripts = cfg: let scriptBase = '' set -e -o pipefail ${optionalString cfg.debug "set -x"} set +u # If sysroot is set then make sure it has trailing / if [ -n "$SYSROOT" ]; then if ! (echo "$SYSROOT" | grep -q "/$"); then SYSROOT="$SYSROOT/" fi fi # If sysroot is empty then make sure it is empty so it doesn't error [ -z "$SYSROOT" ] && SYSROOT= set -u if [ -n "$SYSROOT" ]; then echo "Using sysroot: $SYSROOT" fi ${optionalString cfg.createSecretsDir '' if [ ! -d "$SYSROOT${cfg.secretsDir}" ]; then mkdir -p "$SYSROOT${cfg.secretsDir}" chown "${userOrMappedID cfg.secretsDirUser}:${groupOrMappedID cfg.secretsDirGroup}" "$SYSROOT${cfg.secretsDir}" fi userLookupFailed=false ${concatStringsSep "\n" (forEach allUsersNotMappedToUID (user: '' if ! getent passwd ${user} >/dev/null; then echo "User ${user} could not be found on the host system" userLookupFailed=true fi ''))} groupLookupFailed=false ${concatStringsSep "\n" (forEach allGroupsNotMappedToGID (group: '' if ! getent group ${group} >/dev/null; then echo "Group ${group} could not be found on the host system" groupLookupFailed=true fi ''))} if $userLookupFailed; then echo "Please add mappings in uidMap in order for this script to work" fi if $groupLookupFailed; then echo "Please add mappings in gidMap in order for this script to work" fi if $userLookupFailed ̣ || $groupLookupFailed; then exit 1 fi ''} ''; allUsers = unique ([cfg.secretsDirUser] ++ flatten ( forEach (attrNames cfg.secrets) (name: cfg.secrets.${name}.user) )); allGroups = unique ([cfg.secretsDirGroup] ++ flatten ( forEach (attrNames cfg.secrets) (name: cfg.secrets.${name}.group) )); allUsersByName = filter isString allUsers; allGroupsByName = filter isString allGroups; allUsersNotMappedToUID = filter (name: !(hasAttr name cfg.uidMap)) allUsersByName; allGroupsNotMappedToGID = filter (name: !(hasAttr name cfg.gidMap)) allGroupsByName; isUserMapped = name: (hasAttr name cfg.uidMap); isGroupMapped = name: (hasAttr name cfg.gidMap); userOrMappedID = user: if (isString user && (hasAttr user cfg.uidMap)) then (toString cfg.uidMap.${user}) else toString user; groupOrMappedID = group: if (isString group && (hasAttr group cfg.gidMap)) then (toString cfg.gidMap.${group}) else toString group; manualSecrets = filterAttrs (_: secret: secret.manual) cfg.secrets; nonManualSecrets = filterAttrs (_: secret: !secret.manual) cfg.secrets; in { 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} '' + (concatStringsSep "\n" (mapAttrsToList (_name: secret: let secretPath = secret.path; secretUser = userOrMappedID secret.user; secretGroup = groupOrMappedID secret.group; secretPermissions = secret.permissions; in '' if [[ ! -f "$SYSROOT${secretPath}" ]]; then echo "Initializing Secret ${secretPath}" else echo "Updating Secret ${secretPath}" fi secretFile="$SYSROOT${secretPath}" ${secret.fetchScript} chown ${secretUser}:${secretGroup} "$SYSROOT${secretPath}" chmod ${secretPermissions} "$SYSROOT${secretPath}" '') nonManualSecrets)) + (concatStringsSep "\n" (mapAttrsToList (_name: secret: let secretPath = secret.path; secretUser = userOrMappedID secret.user; secretGroup = groupOrMappedID secret.group; secretPermissions = secret.permissions; in '' if [[ ! -f "$SYSROOT${secretPath}" ]]; then echo "Manual Secret ${secretPath} Doesn't Exist; Please add before continuing" exit 1 fi echo "Updating Permissions on Manual Secret ${secretPath}" chown ${secretUser}:${secretGroup} "$SYSROOT${secretPath}" chmod ${secretPermissions} "$SYSROOT${secretPath}" '') manualSecrets)) + '' echo "Secrets Deployed" ''; checkScript = '' ${scriptBase} getUser() { stat --format "%U" "$1" 2>/dev/null } getUserID() { stat --format "%u" "$1" 2>/dev/null } getGroup() { stat --format "%G" "$1" 2>/dev/null } getGroupID() { stat --format "%g" "$1" 2>/dev/null } userNameMatches() { [[ "$(getUser "$1")" == "$2" ]] } userIDMatches() { [[ "$(getUserID "$1")" == "$2" ]] } groupNameMatches() { [[ "$(getGroup "$1")" == "$2" ]] } groupIDMatches() { [[ "$(getGroupID "$1")" == "$2" ]] } getPermissions() { stat --format "%a" "$1" 2>/dev/null } emojiTick="✅" emojiCross="❌" ${cfg.extraCheckFunctions} GLOBAL_FAIL=false '' + (concatStringsSep "\n" (mapAttrsToList (_name: secret: let secretPath = secret.path; secretUser = secret.user; secretUserMaybeMapped = userOrMappedID secretUser; secretGroup = secret.group; secretGroupMaybeMapped = groupOrMappedID secretGroup; secretPermissions = secret.permissions; userCheck = if (isString secretUser && !isUserMapped secretUser) then "userNameMatches \"${secretPath}\" ${secretUser}" else "userIDMatches \"${secretPath}\" ${secretUserMaybeMapped}"; groupCheck = if (isString secretGroup && !isGroupMapped secretGroup) then "groupNameMatches \"${secretPath}\" ${secretGroup}" else "groupIDMatches \"${secretPath}\" ${secretGroupMaybeMapped}"; in '' LOCAL_FAIL=false echo "Checking ${secretPath}" # some variables which can be used by checkScript # shellcheck disable=SC2034 secretFile="$SYSROOT${secretPath}" if [[ -f "$SYSROOT${secretPath}" ]]; then echo "$emojiTick File Exists" else echo "$emojiCross File Does Not Exist" LOCAL_FAIL=true fi if getUserID "$SYSROOT${secretPath}" >/dev/null && ${userCheck}; then echo "$emojiTick File Is Owned By Correct User" else echo "$emojiCross File Is Not Owned By Correct User (${toString secretUser})" LOCAL_FAIL=true fi if getGroupID "$SYSROOT${secretPath}" >/dev/null && ${groupCheck}; then echo "$emojiTick File Is Owned By Correct Group" else echo "$emojiCross File Is Not Owned By Correct Group (${toString secretGroup})" LOCAL_FAIL=true fi if getPermissions "$SYSROOT${secretPath}" >/dev/null && [[ "$(getPermissions "$SYSROOT${secretPath}")" -eq "${secretPermissions}" ]]; then echo "$emojiTick File Has Correct Permissions" else echo "$emojiCross File Does Not Have Correct Permissions (${secretPermissions})" LOCAL_FAIL=true fi ${optionalString (secret.checkScript != null) secret.checkScript} if [[ "$LOCAL_FAIL" == "true" ]]; then echo "$emojiCross File Did Not Pass The Vibe Check" GLOBAL_FAIL=true else echo "$emojiTick File Passed The Vibe Check" fi echo '') cfg.secrets)) + '' if [[ "$GLOBAL_FAIL" == "true" ]]; then echo "$emojiCross One Or More Secrets Did Not Pass The Vibe Check" exit 1 else echo "$emojiTick All Secrets Passed The Vibe Check" fi ''; }; defaultPackages = with pkgs; [vault-bin jq]; in rec { mkVaultLoginScript = cfg: writeShellApplication { name = "vault-login"; runtimeInputs = with pkgs; [ vault-bin getent ]; text = let vaultLoginConfig = cfg.vaultLogin; in '' VAULT_ADDR="${vaultLoginConfig.vaultURL}" \ vault login -no-print -method=userpass \ username=${vaultLoginConfig.loginUsername} \ password="$(cat ${vaultLoginConfig.loginPasswordFile})" ''; }; mkSecretsInitScript = cfg: mkSecretsInitScriptWithName cfg null; mkSecretsInitScriptWithName = ( cfg: name: let scriptName = if name == null then "secrets-init" else "secrets-init-${name}"; scripts = genScripts cfg; in (writeShellApplication { name = scriptName; runtimeInputs = defaultPackages ++ cfg.packages; text = scripts.initScript; }) ); mkSecretsCheckScript = cfg: mkSecretsCheckScriptWithName cfg null; mkSecretsCheckScriptWithName = ( cfg: name: let scriptName = if name == null then "secrets-check" else "secrets-check-${name}"; scripts = genScripts cfg; in (writeShellApplication { name = scriptName; runtimeInputs = defaultPackages ++ cfg.checkPackages; text = scripts.checkScript; }) ); genVaultPolicy = ( cfg: name: let inherit (cfg) requiredVaultPaths; policies = forEach requiredVaultPaths (policyConfig: let path = if isString policyConfig then policyConfig else policyConfig.path; capabilities = if isString policyConfig then ["read" "list"] else policyConfig.capabilities; escapeString = str: "\"" + str + "\""; in '' path "${path}" { capabilities = [${concatStringsSep "," (forEach capabilities escapeString)}] } ''); in (toFile "vault-policy-${name}.hcl" '' ${concatStringsSep "\n" policies} '') ); }