{ config, pkgs, lib, ... }: with lib; let cfg = config.security.runtimeSecrets; # A recursive attrset of submodule storeType = types.attrsOf (types.submodule { freeformType = storeType; options = secretOptions; }); # Secret file definition secretOptions = { user = mkOption { type = types.str; default = "root"; description = "Owner of the secret"; }; group = mkOption { type = types.str; default = "root"; description = "Group with access to the secret."; }; mode = mkOption { type = types.str; default = "0440"; description = "File permission (octal format)"; }; path = mkOption { type = types.nullOr types.path; default = null; apply = toString; description = "File to include in the secret store"; }; }; # Turns a nested attrset into a list # of (path, value) pairs. It recurs # until `cond val` is false. attrsToIndex = cond: set: let recurse = path: set: let index = name: value: if isAttrs value && cond value then recurse (path ++ [name]) value else singleton { path = path ++ [name]; value = value; }; in concatLists (mapAttrsToList index set); in recurse [] set; isFile = v: isAttrs v && v.path != ""; # Secrets flattened to an index. This is needed # to iterate over the set. flatSecrets = attrsToIndex (v: !isFile v) cfg; # Secrets with paths rewritten to the store location storedSecrets = mapAttrsRecursiveCond (v: !isFile v) (names: secret: if isFile secret then "/run/secrets/${concatStringsSep "-" names}" else secret) cfg; in { options.security.runtimeSecrets = mkOption { type = storeType; default = { }; description = '' Definitions of runtime secrets. This is a freeform attributes set: it can contain arbitrarily nested sets of secrets. Secrets are paths to be copied into the secrets store (/run/secrets) with proper permission and owenership. ''; }; options.security.buildSecrets = mkOption { type = types.attrs; default = { }; description = '' Definitions of build secrets. This is a freeform attrset to be merged with the secrets-store and intended to store unsafe secrets. This will be copied into the world-readable Nix store, only use at a last resort. ''; }; options.secrets = mkOption { type = types.attrs; readOnly = true; default = recursiveUpdate storedSecrets config.security.buildSecrets; description = '' The attrset used to access stored secrets from NixOS configuration and modules. ''; }; config.system.activationScripts.secretsStore = { deps = [ ]; text = '' # Initialise clean directory rm -rf /run/secrets '' + concatMapStrings (pair: let name = "${concatStringsSep "-" pair.path}"; secret = pair.value; in optionalString (isFile secret) '' # Install secret ${name} install -m ${secret.mode} \ -o ${secret.user} -g ${secret.group} \ -D ${secret.path} /run/secrets/${name} '') flatSecrets; }; }