1{2 lib,3 fleetLib,4 config,5 pkgs,6 host,7 fleetConfiguration,8 ...9}:10let11 inherit (builtins)12 hashString13 toJSON14 ;15 inherit (lib.stringsWithDeps) stringAfter;16 inherit (lib.options) mkOption literalExpression;17 inherit (lib.lists) optional elem;18 inherit (lib.attrsets)19 mapAttrs20 mapAttrsToList21 filterAttrs22 attrNames23 ;24 inherit (lib.modules) mkIf;25 inherit (lib.types)26 submodule27 str28 attrsOf29 unspecified30 uniq31 functionTo32 package33 enum34 either35 listOf36 ;37 inherit (fleetLib.strings) decodeRawSecret;3839 sysConfig = config;40 secretPartType =41 secretName:42 submodule (43 { config, ... }:44 let45 partName = config._module.args.name;46 in47 {48 options = {49 hash = mkOption {50 type = str;51 description = "Hash of secret in encoded format";52 };53 path = mkOption {54 type = str;55 description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";56 };57 stablePath = mkOption {58 type = str;59 description = "Path to secret part, stable path (users are expected to watch for file changes/re-read secret on demand)";60 };61 data = mkOption {62 type = str;63 description = "Secret public data (only available for plaintext)";64 };65 raw = mkOption {66 type = str;67 description = "Raw (encoded/encrypted secret part data)";68 };69 };70 config = {71 hash = hashString "sha1" config.raw;72 data = decodeRawSecret config.raw;73 path = "/run/secrets/${secretName}/${config.hash}-${partName}";74 stablePath = "/run/secrets/${secretName}/${partName}";75 };76 }77 );78 secretType = submodule (79 {80 config,81 ...82 }:83 let84 secretName = config._module.args.name;85 literal = l: enum [ l ];86 in87 {88 options = {89 parts = mkOption {90 type = uniq (attrsOf (secretPartType secretName));91 description = "Definition of secret parts";92 };93 generator = mkOption {94 type = either (functionTo package) (literal "shared");95 description = "Derivation to evaluate for secret generation";96 };97 mode = mkOption {98 type = str;99 description = "Secret mode";100 default = "0440";101 };102 owner = mkOption {103 type = str;104 description = "Owner of the secret";105 default = "root";106 };107 group = mkOption {108 type = str;109 description = "Group of the secret";110 default = sysConfig.users.users.${config.owner}.group;111 defaultText = literalExpression "config.users.users.$${owner}.group";112 };113 };114 config = {115 116 117 parts =118 let119 hostName = host._module.args.name;120 generator = config.generator;121 in122 builtins.deepSeq [123 hostName124 secretName125 generator126 ] (builtins.fleetEnsureHostSecret hostName secretName generator);127 };128 }129 );130 secretsFile = pkgs.writeTextFile {131 name = "secrets.json";132 text = toJSON config.system.secretsData;133 };134 useSysusers =135 (config.systemd ? sysusers && config.systemd.sysusers.enable)136 || (config ? userborn && config.userborn.enable);137in138{139 options = {140 _providedSharedSecrets = mkOption {141 description = ''142 List of shared secrets, for which the current host was specified as `expectedOwners`143 '';144 type = listOf str;145 default = [];146 internal = true;147 };148 secrets = mkOption {149 type = attrsOf secretType;150 default = { };151 apply =152 secrets:153 mapAttrs (_: secret: secret.parts // { definition = secret; })154155 (156 let157 hostName = host.name;158 expectedNonshared = attrNames (filterAttrs (_: def: def.generator != "shared") secrets);159 expectedShared = config._providedSharedSecrets;160 in161 builtins.deepSeq [162 hostName163 expectedNonshared164 expectedShared165 ] (builtins.fleetEnsureHostSecrets hostName expectedNonshared expectedShared secrets)166 );167 description = "Host-local secrets";168 };169 system.secretsData = mkOption {170 type = unspecified;171 default = mapAttrs (172 _: s:173 (removeAttrs s.definition [ "generator" ])174 // {175 parts = mapAttrs (_: part: removeAttrs part [ "data" ]) s.definition.parts;176 }177 ) config.secrets;178 description = "secrets.json contents";179 };180 };181 config = {182 environment.systemPackages = [ pkgs.fleet-install-secrets ];183184 assertions = mapAttrsToList (185 name: secret:186 let187 hasSharedDefinition = fleetConfiguration.secrets ? ${name};188 in189 {190 assertion =191 (secret.definition.generator == "shared") == hasSharedDefinition192 && (193 hasSharedDefinition194 -> (elem host.name fleetConfiguration.secrets.${name}.expectedOwners)195 );196 message =197 if hasSharedDefinition then198 "secret ${name} has host-specific secret generator, secrets with host-specific generators can not have shared generator in fleet configuration"199 else200 "secret ${name} is declared as shared, for shared secret fleet configuration should include shared secret generator, and expectedOwners should contain this host";201 }202 ) config.secrets;203204 systemd.services.fleet-install-secrets = mkIf useSysusers {205 wantedBy = [ "sysinit.target" ];206 after = [ "systemd-sysusers.service" ];207 restartTriggers = [208 secretsFile209 ];210 aliases = [211 "sops-install-secrets"212 "agenix-install-secrets"213 ];214215 unitConfig.DefaultDependencies = false;216217 serviceConfig = {218 Type = "oneshot";219 RemainAfterExit = true;220 ExecStart = "${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}";221 };222 };223 system.activationScripts.decryptSecrets = mkIf (!useSysusers) (224 stringAfter225 (226 [227 228 "users"229 "groups"230 "specialfs"231 ]232 233 234 235 ++ (optional (config.system.activationScripts ? "persist-files") "persist-files")236 )237 ''238 1>&2 echo "setting up secrets"239 ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}240 ''241 );242 };243}