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 shared = mkOption {109 type = bool;110 description = "Was this secret propagated from a shared secret?";111 };112 parts = mkOption {113 type = lazyAttrsOf (secretPartType secretName);114 description = "Definition of secret parts";115 default = { };116 };117 generator = mkOption {118 type = uniq (nullOr (functionTo package));119 description = "Derivation to evaluate for secret generation";120 default = null;121 };122 mode = mkOption {123 type = str;124 description = "Secret mode";125 default = "0440";126 };127 owner = mkOption {128 type = str;129 description = "Owner of the secret";130 default = "root";131 };132 group = mkOption {133 type = str;134 description = "Group of the secret";135 default = sysConfig.users.users.${config.owner}.group;136 defaultText = literalExpression "config.users.users.$${owner}.group";137 };138 expectedGenerationData = mkOption {139 type = unspecified;140 description = "Data that gets embedded into secret part";141 default = null;142 };143 };144 config = {145 shared = (sysConfig.data.secrets.${secretName} or { shared = false; }).shared;146 parts = mkMerge [147 (mkIf (config.generator != null)148 (149 150 lib.callPackageWith (151 pkgs152 // {153 mkSecretGenerator = pkgs.stdenv.mkDerivation;154 mkImpureSecretGenerator = pkgs.stdenv.mkDerivation;155 }156 ) config.generator { }157 ).parts158 )159 (mapAttrs (_: _: { }) (160 removeAttrs (sysConfig.data.secrets.${secretName} or { }) [161 "shared"162 "managed"163 ]164 ))165 ];166 };167 }168 );169 processPart = secretName: partName: part: {170 inherit (part) path stablePath;171 raw = config.data.secrets.${secretName}.${partName}.raw;172 };173 processSecret = secretName: secret: {174 inherit (secret.definition) group mode owner;175 parts = (mapAttrs (processPart secretName) (secret.definition.parts));176 };177 secretsData = (mapAttrs (processSecret) config.secrets);178 secretsFile = pkgs.writeTextFile {179 name = "secrets.json";180 text = toJSON secretsData;181 };182 useSysusers =183 (config.systemd ? sysusers && config.systemd.sysusers.enable)184 || (config ? userborn && config.userborn.enable);185in186{187 options = {188 data.secrets = mkOption {189 type = attrsOf secretDataType;190 default = { };191 description = "Host-local secret data";192 };193 secrets = mkOption {194 type = attrsOf secretType;195 default = { };196 apply = v: (mapAttrs (_: secret: secret.parts // { definition = secret; }) v);197 description = "Host-local secrets";198 };199 system.secretsData = mkOption {200 type = unspecified;201 default = { };202 description = "secrets.json contents";203 };204 };205 config = {206 system = { inherit secretsData; };207 environment.systemPackages = [ pkgs.fleet-install-secrets ];208209 systemd.services.fleet-install-secrets = mkIf useSysusers {210 wantedBy = [ "sysinit.target" ];211 after = [ "systemd-sysusers.service" ];212 restartTriggers = [213 secretsFile214 ];215 aliases = [216 "sops-install-secrets"217 "agenix-install-secrets"218 ];219220 unitConfig.DefaultDependencies = false;221222 serviceConfig = {223 Type = "oneshot";224 RemainAfterExit = true;225 ExecStart = "${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}";226 };227 };228 system.activationScripts.decryptSecrets = mkIf (!useSysusers) (229 stringAfter230 (231 [232 233 "users"234 "groups"235 "specialfs"236 ]237 238 239 240 ++ (optional (config.system.activationScripts ? "persist-files") "persist-files")241 )242 ''243 1>&2 echo "setting up secrets"244 ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}245 ''246 );247 };248}