pi/intermediate/secrets.nix
Katharina Heidenreich ecf10628c3 feat: try rework
2026-04-04 16:34:02 +02:00

163 lines
5.3 KiB
Nix

let
lib = import <nixpkgs/lib>;
secretsConfig = (import ../validation/secrets.nix).getSecretsConfig (import ../config/secrets.nix);
isSopsEncrypted = filePath:
let
content = builtins.readFile filePath;
in
lib.hasInfix "\nsops:" content
|| lib.hasInfix "\n\"sops\":" content
|| lib.hasInfix "\"sops\":" content
|| builtins.substring 0 5 content == "sops:";
extractJsonKeys = filePath:
let
content = builtins.readFile (builtins.toString filePath);
parsed = builtins.fromJSON content;
recurseKeys = prefix: obj:
if builtins.isAttrs obj then
lib.concatMap (name:
if name == "sops" then
[]
else
let
newPrefix = if prefix == "" then name else "${prefix}.${name}";
in
recurseKeys newPrefix obj.${name}
) (builtins.attrNames obj)
else
[ prefix ];
in
recurseKeys "" parsed;
getRuntimePath = path:
"/run/secrets/${builtins.concatStringsSep "_" path}";
defaultMetadata = {
path = null;
owner = null;
group = null;
mode = null;
};
# Step 1: Convert path/string leaves to a normalized attrset shape.
normalizeLeaf = path: node:
if builtins.isString node || builtins.isPath node then
{
file = node;
keys = null;
metadata = defaultMetadata;
}
else if builtins.isAttrs node && node ? file then
{
file = node.file;
keys = node.keys or null;
metadata = {
path = node.path or null;
owner = node.owner or null;
group = node.group or null;
mode = node.mode or null;
};
}
else
throw "Invalid secret leaf at ${builtins.concatStringsSep "." path}: must be string, path, or attrset with 'file'";
# Step 2: Flatten normalized leaf structure into entries.
flattenTree = path: node:
if builtins.isAttrs node && !(node ? file) then
lib.concatMap (name:
flattenTree (path ++ [ name ]) node.${name}
) (builtins.attrNames node)
else
let
normalized = normalizeLeaf path node;
in
[ {
inherit path;
file = normalized.file;
keys = normalized.keys;
metadata = normalized.metadata;
} ];
entries = flattenTree [] secretsConfig;
# Step 3a: Look for keys when type is JSON and keys are not explicitly provided.
enrichedEntries = map (entry:
let
isJson = builtins.match ".*\\.json$" (builtins.toString entry.file) != null;
extractedKeys = if isJson && entry.keys == null then extractJsonKeys entry.file else null;
in
entry // { keys = if extractedKeys != null then extractedKeys else entry.keys; }
) entries;
isReady = entry:
if !builtins.pathExists entry.file then
false
else if builtins.match ".*\\.(ya?ml|json)$" (builtins.toString entry.file) != null then
true
else
isSopsEncrypted entry.file;
readyEntries = builtins.filter isReady enrichedEntries;
missingEntries = builtins.filter (entry: !(isReady entry)) enrichedEntries;
# Step 3b: Expand key lists to per-secret entries and build sops.secrets attrset.
mkSopsSecrets = sourceEntries:
let
expanded = lib.concatMap (entry:
if entry.keys != null && entry.keys != [] then
map (key: entry // { singleKey = key; }) entry.keys
else
[ (entry // { singleKey = null; }) ]
) sourceEntries;
mkEntry = entry:
let
normalizedKeyName = if entry.singleKey != null then
builtins.replaceStrings [ "." ] [ "_" ] entry.singleKey
else
null;
secretName = if entry.singleKey != null then
builtins.concatStringsSep "_" (entry.path ++ [ normalizedKeyName ])
else
builtins.concatStringsSep "_" entry.path;
isJson = builtins.match ".*\\.json$" (builtins.toString entry.file) != null;
isYaml = builtins.match ".*\\.ya?ml$" (builtins.toString entry.file) != null;
sopsKey =
if entry.singleKey == null then
null
else
builtins.replaceStrings [ "." ] [ "/" ] entry.singleKey;
in
{
name = secretName;
value = {
sopsFile = entry.file;
# Compatibility: current sops-install-secrets key traversal expects
# YAML map shape for nested key extraction. JSON documents are valid
# YAML, so parse .json via "yaml" to support nested keys.
format = if isJson || isYaml then "yaml" else "binary";
path = if entry.metadata.path != null then
entry.metadata.path
else
getRuntimePath (entry.path ++ lib.optional (normalizedKeyName != null) normalizedKeyName);
owner = if entry.metadata.owner != null then entry.metadata.owner else "root";
group = if entry.metadata.group != null then entry.metadata.group else "root";
mode = if entry.metadata.mode != null then entry.metadata.mode else "0400";
} // lib.optionalAttrs (sopsKey != null) {
key = sopsKey;
};
};
in
builtins.listToAttrs (map mkEntry expanded);
in
{
source = secretsConfig;
byName = mkSopsSecrets readyEntries;
missing = missingEntries;
}