nixos/utils: Add support for LoadCredential= with genJqSecretsReplacementSnippet
This commit is contained in:
@@ -229,7 +229,7 @@ let
|
|||||||
listToAttrs (flatten (recurse "." item));
|
listToAttrs (flatten (recurse "." item));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Takes an attrset and a file path and generates a bash snippet that
|
Takes some options, an attrset and a file path and generates a bash snippet that
|
||||||
outputs a JSON file at the file path with all instances of
|
outputs a JSON file at the file path with all instances of
|
||||||
|
|
||||||
{ _secret = "/path/to/secret" }
|
{ _secret = "/path/to/secret" }
|
||||||
@@ -237,6 +237,28 @@ let
|
|||||||
in the attrset replaced with the contents of the file
|
in the attrset replaced with the contents of the file
|
||||||
"/path/to/secret" in the output JSON.
|
"/path/to/secret" in the output JSON.
|
||||||
|
|
||||||
|
The first argument exposes the following options:
|
||||||
|
|
||||||
|
- attr: The name of the secret attribute that will be processed, defaults to "_secret"
|
||||||
|
- loadCredential: A boolean determining whether the script should load secrets directly (false)
|
||||||
|
or load them from $CREDENTIALS_DIRECTORY (true). In the latter case the output attribute set
|
||||||
|
will contain a .credentials attribute with the necessary credential list that can be passed
|
||||||
|
to systemd's `LoadCredential=` option.
|
||||||
|
|
||||||
|
The output of this utility is an attribute set containing the main script and optionally
|
||||||
|
a list of credentials:
|
||||||
|
|
||||||
|
{
|
||||||
|
# The main script
|
||||||
|
script = "...";
|
||||||
|
|
||||||
|
# If the loadCredential option was set:
|
||||||
|
credentials = [
|
||||||
|
"secret1:/path/to/secret1"
|
||||||
|
#...
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
When a configuration option accepts an attrset that is finally
|
When a configuration option accepts an attrset that is finally
|
||||||
converted to JSON, this makes it possible to let the user define
|
converted to JSON, this makes it possible to let the user define
|
||||||
arbitrary secret values.
|
arbitrary secret values.
|
||||||
@@ -245,7 +267,7 @@ let
|
|||||||
If the file "/path/to/secret" contains the string
|
If the file "/path/to/secret" contains the string
|
||||||
"topsecretpassword1234",
|
"topsecretpassword1234",
|
||||||
|
|
||||||
genJqSecretsReplacementSnippet {
|
genJqSecretsReplacement { } {
|
||||||
example = [
|
example = [
|
||||||
{
|
{
|
||||||
irrelevant = "not interesting";
|
irrelevant = "not interesting";
|
||||||
@@ -293,7 +315,7 @@ let
|
|||||||
{ "b": "topsecretpassword5678" }
|
{ "b": "topsecretpassword5678" }
|
||||||
]
|
]
|
||||||
|
|
||||||
genJqSecretsReplacementSnippet {
|
genJqSecretsReplacement { } {
|
||||||
example = [
|
example = [
|
||||||
{
|
{
|
||||||
irrelevant = "not interesting";
|
irrelevant = "not interesting";
|
||||||
@@ -330,12 +352,12 @@ let
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' "_secret";
|
genJqSecretsReplacement =
|
||||||
|
{
|
||||||
# Like genJqSecretsReplacementSnippet, but allows the name of the
|
attr ? "_secret",
|
||||||
# attr which identifies the secret to be changed.
|
loadCredential ? false,
|
||||||
genJqSecretsReplacementSnippet' =
|
}:
|
||||||
attr: set: output:
|
set: output:
|
||||||
let
|
let
|
||||||
secretsRaw = recursiveGetAttrsetWithJqPrefix set attr;
|
secretsRaw = recursiveGetAttrsetWithJqPrefix set attr;
|
||||||
# Set default option values
|
# Set default option values
|
||||||
@@ -347,38 +369,115 @@ let
|
|||||||
// set
|
// set
|
||||||
) secretsRaw;
|
) secretsRaw;
|
||||||
stringOrDefault = str: def: if str == "" then def else str;
|
stringOrDefault = str: def: if str == "" then def else str;
|
||||||
in
|
|
||||||
''
|
|
||||||
if [[ -h '${output}' ]]; then
|
|
||||||
rm '${output}'
|
|
||||||
fi
|
|
||||||
|
|
||||||
inherit_errexit_enabled=0
|
# Sanitize path to create a valid credential tag (same as in genLoadCredentialForJqSecretsReplacementSnippet)
|
||||||
shopt -pq inherit_errexit && inherit_errexit_enabled=1
|
sanitizePath =
|
||||||
shopt -s inherit_errexit
|
path: lib.stringAsChars (c: if builtins.match "[a-zA-Z0-9_.#=!-]" c != null then c else "_") path;
|
||||||
''
|
|
||||||
+ concatStringsSep "\n" (
|
# Generate credential tag for a given index and path
|
||||||
imap1 (index: name: ''
|
credentialTag = index: path: "${toString index}_${sanitizePath (secrets.${path}.${attr})}";
|
||||||
secret${toString index}=$(<'${secrets.${name}.${attr}}')
|
|
||||||
export secret${toString index}
|
credentialPath =
|
||||||
'') (attrNames secrets)
|
index: name:
|
||||||
)
|
if loadCredential then
|
||||||
+ "\n"
|
''"$CREDENTIALS_DIRECTORY/${credentialTag index name}"''
|
||||||
+ "${pkgs.jq}/bin/jq >'${output}' "
|
else
|
||||||
+ escapeShellArg (
|
"'${secrets.${name}.${attr}}'";
|
||||||
stringOrDefault (concatStringsSep " | " (
|
in
|
||||||
|
{
|
||||||
|
script = ''
|
||||||
|
if [[ -h '${output}' ]]; then
|
||||||
|
rm '${output}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
inherit_errexit_enabled=0
|
||||||
|
shopt -pq inherit_errexit && inherit_errexit_enabled=1
|
||||||
|
shopt -s inherit_errexit
|
||||||
|
''
|
||||||
|
+ concatStringsSep "\n" (
|
||||||
imap1 (
|
imap1 (
|
||||||
index: name:
|
index: name:
|
||||||
''${name} = ($ENV.secret${toString index}${optionalString (!secrets.${name}.quote) " | fromjson"})''
|
# We keep variable assignment and export separated to avoid masking the return code of the file access.
|
||||||
) (attrNames secrets)
|
# With `set -e` this will now fail if a file doesn't exist.
|
||||||
)) "."
|
''
|
||||||
)
|
secret${toString index}=$(<${credentialPath index name})
|
||||||
+ ''
|
export secret${toString index}
|
||||||
<<'EOF'
|
'') (attrNames secrets)
|
||||||
${toJSON set}
|
)
|
||||||
EOF
|
+ "\n"
|
||||||
(( ! inherit_errexit_enabled )) && shopt -u inherit_errexit
|
+ "${pkgs.jq}/bin/jq >'${output}' "
|
||||||
'';
|
+ escapeShellArg (
|
||||||
|
stringOrDefault (concatStringsSep " | " (
|
||||||
|
imap1 (
|
||||||
|
index: name:
|
||||||
|
''${name} = ($ENV.secret${toString index}${optionalString (!secrets.${name}.quote) " | fromjson"})''
|
||||||
|
) (attrNames secrets)
|
||||||
|
)) "."
|
||||||
|
)
|
||||||
|
+ ''
|
||||||
|
<<'EOF'
|
||||||
|
${toJSON set}
|
||||||
|
EOF
|
||||||
|
(( ! inherit_errexit_enabled )) && shopt -u inherit_errexit
|
||||||
|
'';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generates a list of systemd LoadCredential entries if loadCredential was set,
|
||||||
|
otherwise returns null.
|
||||||
|
|
||||||
|
The tag is sanitized to only contain characters a-zA-Z0-9_-.#=! and prefixed
|
||||||
|
with an index to ensure uniqueness.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
genLoadCredentialForJqSecretsReplacementSnippet { } {
|
||||||
|
example = {
|
||||||
|
secret1 = { _secret = "/path/to/secret"; };
|
||||||
|
secret2 = { _secret = "/another/secret"; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
-> [ "0_path_to_secret:/path/to/secret" "1_another_secret:/another/secret" ]
|
||||||
|
*/
|
||||||
|
credentials =
|
||||||
|
if loadCredential then
|
||||||
|
imap1 (
|
||||||
|
index: path:
|
||||||
|
"${toString index}_${sanitizePath (secretsRaw.${path}.${attr})}:${secretsRaw.${path}.${attr}}"
|
||||||
|
) (attrNames secretsRaw)
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
A convenience function around `genJqSecretsReplacement` without any additional
|
||||||
|
settings that returns just the script that does the secret replacing. Make sure
|
||||||
|
to have a look at `genJqSecretsReplacement` first to decide whether you need
|
||||||
|
the additional functionality.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
If the file "/path/to/secret" contains the string
|
||||||
|
"topsecretpassword1234",
|
||||||
|
|
||||||
|
genJqSecretsReplacementSnippet {
|
||||||
|
example = [
|
||||||
|
{
|
||||||
|
irrelevant = "not interesting";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ignored = "ignored attr";
|
||||||
|
relevant = {
|
||||||
|
secret = {
|
||||||
|
_secret = "/path/to/secret";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} "/path/to/output.json"
|
||||||
|
|
||||||
|
will return a set of bash commands that replaces the secret values
|
||||||
|
in the given attrset with values from the respective files and saves the result
|
||||||
|
as a JSON file.
|
||||||
|
*/
|
||||||
|
genJqSecretsReplacementSnippet = set: output: (genJqSecretsReplacement { } set output).script;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Remove packages of packagesToRemove from packages, based on their names.
|
Remove packages of packagesToRemove from packages, based on their names.
|
||||||
|
|||||||
Reference in New Issue
Block a user