1{2 lib,3 fleetLib,4 config,5 pkgs,6 ...7}:8let9 inherit (builtins)10 hashString11 elemAt12 length13 toJSON14 filter15 ;16 inherit (lib.stringsWithDeps) stringAfter;17 inherit (lib.options) mkOption literalExpression;18 inherit (lib.lists) optional;19 inherit (lib.attrsets) mapAttrs mapAttrsToList;20 inherit (lib.modules) mkIf mkMerge;21 inherit (lib.types)22 submodule23 str24 attrsOf25 nullOr26 unspecified27 lazyAttrsOf28 uniq29 functionTo30 package31 listOf32 bool33 ;34 inherit (fleetLib.strings) decodeRawSecret;3536 sysConfig = config;37 secretPartDataType = submodule {38 options = {39 raw = mkOption {40 type = str;41 internal = true;42 description = "Encoded & Encrypted secret part data, passed from fleet.nix";43 };44 };45 };46 secretDataType = submodule {47 freeformType = lazyAttrsOf secretPartDataType;48 options = {49 shared = mkOption {50 description = "Is this secret owned by this machine, or propagated from shared secrets";51 default = false;52 };53 };54 };55 secretPartType =56 secretName:57 submodule (58 { config, ... }:59 let60 partName = config._module.args.name;61 in62 {63 options = {64 encrypted = mkOption {65 type = bool;66 description = "Is this secret part supposed to be encrypted?";67 };6869 hash = mkOption {70 type = str;71 description = "Hash of secret in encoded format";72 };73 path = mkOption {74 type = str;75 description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";76 };77 stablePath = mkOption {78 type = str;79 description = "Path to secret part, stable path (users are expected to watch for file changes/re-read secret on demand)";80 };81 data = mkOption {82 type = str;83 description = "Secret public data (only available for plaintext)";84 };85 };86 config =87 let88 raw = sysConfig.data.secrets.${secretName}.${partName}.raw;89 in90 {91 hash = hashString "sha1" raw;92 data = decodeRawSecret raw;93 path = "/run/secrets/${secretName}/${config.hash}-${partName}";94 stablePath = "/run/secrets/${secretName}/${partName}";95 };96 }97 );98 secretType = submodule (99 {100 config,101 ...102 }:103 let104 secretName = config._module.args.name;105 in106 {107 options = {108 parts = mkOption {109 type = lazyAttrsOf (secretPartType secretName);110 description = "Definition of secret parts";111 default = {};112 };113 generator = mkOption {114 type = uniq (nullOr (functionTo package));115 description = "Derivation to evaluate for secret generation";116 default = null;117 };118 mode = mkOption {119 type = str;120 description = "Secret mode";121 default = "0440";122 };123 owner = mkOption {124 type = str;125 description = "Owner of the secret";126 default = "root";127 };128 group = mkOption {129 type = str;130 description = "Group of the secret";131 default = sysConfig.users.users.${config.owner}.group;132 defaultText = literalExpression "config.users.users.$${owner}.group";133 };134 expectedGenerationData = mkOption {135 type = unspecified;136 description = "Data that gets embedded into secret part";137 default = null;138 };139 };140 config.parts = mkMerge [141 (mkIf (config.generator != null && config.generator ? parts) config.generator.parts)142 (mapAttrs (_: _: {}) (removeAttrs sysConfig.data.secrets.${secretName} ["shared"]))143 ];144 }145 );146 processPart = secretName: partName: part: {147 inherit (part) path stablePath;148 raw = config.data.secrets.${secretName}.${partName}.raw;149 };150 processSecret =151 secretName: secret:152 {153 inherit (secret.definition) group mode owner;154 parts = (mapAttrs (processPart secretName) (155 secret.definition.parts156 ));157 };158 secretsData = (mapAttrs (processSecret) config.secrets);159 secretsFile = pkgs.writeTextFile {160 name = "secrets.json";161 text = toJSON secretsData;162 };163 useSysusers =164 (config.systemd ? sysusers && config.systemd.sysusers.enable)165 || (config ? userborn && config.userborn.enable);166in167{168 options = {169 data.secrets = mkOption {170 type = attrsOf secretDataType;171 default = { };172 description = "Host-local secret data";173 };174 secrets = mkOption {175 type = attrsOf secretType;176 default = { };177 apply = v: (mapAttrs (_: secret: secret.parts // {definition = secret;}) v);178 description = "Host-local secrets";179 };180 system.secretsData = mkOption {181 type = unspecified;182 default = { };183 description = "secrets.json contents";184 };185 };186 config = {187 system = { inherit secretsData; };188 environment.systemPackages = [ pkgs.fleet-install-secrets ];189190 systemd.services.fleet-install-secrets = mkIf useSysusers {191 wantedBy = [ "sysinit.target" ];192 after = [ "systemd-sysusers.service" ];193 restartTriggers = [194 secretsFile195 ];196 aliases = [197 "sops-install-secrets"198 "agenix-install-secrets"199 ];200201 unitConfig.DefaultDependencies = false;202203 serviceConfig = {204 Type = "oneshot";205 RemainAfterExit = true;206 ExecStart = "${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}";207 };208 };209 system.activationScripts.decryptSecrets = mkIf (!useSysusers) (210 stringAfter211 (212 [213 214 "users"215 "groups"216 "specialfs"217 ]218 219 220 221 ++ (optional (config.system.activationScripts ? "persist-files") "persist-files")222 )223 ''224 1>&2 echo "setting up secrets"225 ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}226 ''227 );228 };229}