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) mapAttrs mapAttrsToList;19 inherit (lib.modules) mkIf;20 inherit (lib.types)21 submodule22 str23 attrsOf24 unspecified25 uniq26 functionTo27 package28 bool29 enum30 either31 ;32 inherit (fleetLib.strings) decodeRawSecret;3334 sysConfig = config;35 secretPartType =36 secretName:37 submodule (38 { config, ... }:39 let40 partName = config._module.args.name;41 in42 {43 options = {44 hash = mkOption {45 type = str;46 description = "Hash of secret in encoded format";47 };48 path = mkOption {49 type = str;50 description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";51 };52 stablePath = mkOption {53 type = str;54 description = "Path to secret part, stable path (users are expected to watch for file changes/re-read secret on demand)";55 };56 data = mkOption {57 type = str;58 description = "Secret public data (only available for plaintext)";59 };60 raw = mkOption {61 type = str;62 description = "Raw (encoded/encrypted secret part data)";63 };64 };65 config = {66 hash = hashString "sha1" config.raw;67 data = decodeRawSecret config.raw;68 path = "/run/secrets/${secretName}/${config.hash}-${partName}";69 stablePath = "/run/secrets/${secretName}/${partName}";70 };71 }72 );73 secretType = submodule (74 {75 config,76 ...77 }:78 let79 secretName = config._module.args.name;80 literal = l: enum [l];81 in82 {83 options = {84 parts = mkOption {85 type = uniq (attrsOf (secretPartType secretName));86 description = "Definition of secret parts";87 };88 generator = mkOption {89 type = either (functionTo package) (literal "shared");90 description = "Derivation to evaluate for secret generation";91 };92 mode = mkOption {93 type = str;94 description = "Secret mode";95 default = "0440";96 };97 owner = mkOption {98 type = str;99 description = "Owner of the secret";100 default = "root";101 };102 group = mkOption {103 type = str;104 description = "Group of the secret";105 default = sysConfig.users.users.${config.owner}.group;106 defaultText = literalExpression "config.users.users.$${owner}.group";107 };108 };109 config = {110 111 112 parts = let 113 hostName = host._module.args.name;114 generator = config.generator;115 in builtins.deepSeq [116 hostName117 secretName118 generator119 ] (builtins.fleetEnsureHostSecret120 hostName121 secretName122 generator);123 };124 }125 );126 secretsFile = pkgs.writeTextFile {127 name = "secrets.json";128 text = toJSON config.system.secretsData;129 };130 useSysusers =131 (config.systemd ? sysusers && config.systemd.sysusers.enable)132 || (config ? userborn && config.userborn.enable);133in134{135 options = {136 secrets = mkOption {137 type = attrsOf secretType;138 default = { };139 apply = mapAttrs (_: secret: secret.parts // {definition = secret;});140 description = "Host-local secrets";141 };142 system.secretsData = mkOption {143 type = unspecified;144 default = mapAttrs (_: s:145 (removeAttrs s.definition ["generator"]) // {146 parts = mapAttrs (_: part: removeAttrs part ["data"]) s.definition.parts;147 }148 ) config.secrets;149 description = "secrets.json contents";150 };151 };152 config = {153 environment.systemPackages = [ pkgs.fleet-install-secrets ];154155 assertions = mapAttrsToList (name: secret: let156 hasSharedDefinition = fleetConfiguration.secrets ? name;157 in {158 assertion = (secret.definition.generator == "shared") == hasSharedDefinition && hasSharedDefinition -> (elem host._module.args.name fleetConfiguration.secrets.${name}.expectedOwners);159 message = if hasSharedDefinition then"secret ${name} has host-specific secret generator, secrets with host-specific generators can not have shared generator in fleet configuration"160 else "secret ${name} is declared as shared, for shared secret fleet configuration should include shared secret generator, and expectedOwners should contain this host";161 }) config.secrets;162163 systemd.services.fleet-install-secrets = mkIf useSysusers {164 wantedBy = [ "sysinit.target" ];165 after = [ "systemd-sysusers.service" ];166 restartTriggers = [167 secretsFile168 ];169 aliases = [170 "sops-install-secrets"171 "agenix-install-secrets"172 ];173174 unitConfig.DefaultDependencies = false;175176 serviceConfig = {177 Type = "oneshot";178 RemainAfterExit = true;179 ExecStart = "${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}";180 };181 };182 system.activationScripts.decryptSecrets = mkIf (!useSysusers) (183 stringAfter184 (185 [186 187 "users"188 "groups"189 "specialfs"190 ]191 192 193 194 ++ (optional (config.system.activationScripts ? "persist-files") "persist-files")195 )196 ''197 1>&2 echo "setting up secrets"198 ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}199 ''200 );201 };202}