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 && (hasSharedDefinition -> (elem host.name fleetConfiguration.secrets.${name}.expectedOwners));193 message =194 if hasSharedDefinition then195 "secret ${name} has host-specific secret generator, secrets with host-specific generators can not have shared generator in fleet configuration"196 else197 "secret ${name} is declared as shared, for shared secret fleet configuration should include shared secret generator, and expectedOwners should contain this host";198 }199 ) config.secrets;200201 systemd.services.fleet-install-secrets = mkIf useSysusers {202 wantedBy = [ "sysinit.target" ];203 after = [ "systemd-sysusers.service" ];204 restartTriggers = [205 secretsFile206 ];207 aliases = [208 "sops-install-secrets"209 "agenix-install-secrets"210 ];211212 unitConfig.DefaultDependencies = false;213214 serviceConfig = {215 Type = "oneshot";216 RemainAfterExit = true;217 ExecStart = "${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}";218 };219 };220 system.activationScripts.decryptSecrets = mkIf (!useSysusers) (221 stringAfter222 (223 [224 225 "users"226 "groups"227 "specialfs"228 ]229 230 231 232 ++ (optional (config.system.activationScripts ? "persist-files") "persist-files")233 )234 ''235 1>&2 echo "setting up secrets"236 ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}237 ''238 );239 };240}