354 lines
10 KiB
Nix
354 lines
10 KiB
Nix
{
|
|
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 jq];
|
|
in rec {
|
|
mkVaultLoginScript = cfg:
|
|
writeShellApplication {
|
|
name = "vault-login";
|
|
runtimeInputs = with pkgs; [
|
|
vault
|
|
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}
|
|
'';
|
|
}
|