1{2 lib,3 fleetLib,4 config,5 ...6}:7with lib;8with fleetLib; let9 sharedSecret = with types; ({config, ...}: {10 options = {11 expectedOwners = mkOption {12 type = nullOr (listOf str);13 description = ''14 List of hosts to encrypt secret for. null if managed by user (= via owners field from fleet.nix)1516 Secrets would be decrypted and stored to /run/secrets/$\{name} on owners17 '';18 default = null;19 };20 21 regenerateOnOwnerAdded = mkOption {22 type = bool;23 description = ''24 Is this secret owner-dependent, and needs to be regenerated on ownership set change, or it may be just reencrypted.2526 You want to have this option set to true, when this secret contains some reference to its owners, i.e x509 SANs.27 '';28 };29 regenerateOnOwnerRemoved = mkOption {30 default = config.regenerateOnOwnerAdded;31 type = bool;32 description = ''33 Should this secret be removed on owner removal, or it may be just reencrypted3435 Most probably its value should be equal to regenerateOnOwnerAdded, override only if you know what are you doing.36 Contrary to regenerateOnOwnerAdded, you may want to set this option to false, when host permissions are revoked37 in some other way than by this secret ownership, I.e by firewall/etc.38 '';39 };40 generator = mkOption {41 type = nullOr unspecified;42 description = "Derivation to evaluate for secret generation";43 default = null;44 };45 createdAt = mkOption {46 type = nullOr str;47 description = "When this secret was (re)generated";48 default = null;49 };50 expiresAt = mkOption {51 type = nullOr str;52 description = "On which date this secret will expire, someone should regenerate this secret before it expires.";53 default = null;54 };5556 owners = mkOption {57 type = listOf str;58 description = ''59 For which owners this secret is currently encrypted,60 if not matches expectedOwners - then this secret is considered outdated, and61 should be regenerated/reencrypted.6263 Imported from fleet.nix64 '';65 default = [];66 };67 68 69 70 public = mkOption {71 type = nullOr str;72 description = "Secret public data. Imported from fleet.nix";73 default = null;74 };75 secret = mkOption {76 type = nullOr str;77 description = "Encrypted secret data. Imported from fleet.nix";78 default = null;79 internal = true;80 };81 };82 });83 hostSecret = with types; {84 options = {85 createdAt = mkOption {86 type = nullOr str;87 default = null;88 };89 expiresAt = mkOption {90 type = nullOr str;91 default = null;92 };93 public = mkOption {94 type = nullOr str;95 description = "Secret public data. Imported from fleet.nix";96 default = null;97 };98 secret = mkOption {99 type = nullOr str;100 description = "Encrypted secret data. Imported from fleet.nix";101 default = null;102 internal = true;103 };104 };105 };106in {107 options = with types; {108 sharedSecrets = mkOption {109 type = attrsOf (submodule sharedSecret);110 default = {};111 description = "Shared secrets";112 };113 hostSecrets = mkOption {114 type = attrsOf (attrsOf (submodule hostSecret));115 default = {};116 description = "Host secrets. Imported from fleet.nix";117 internal = true;118 };119 };120 config = {121 assertions =122 mapAttrsToList123 (name: secret: {124 assertion = secret.expectedOwners == null || builtins.sort (a: b: a < b) secret.owners == builtins.sort (a: b: a < b) secret.expectedOwners;125 message = "Shared secret ${name} is expected to be encrypted for ${builtins.toJSON secret.expectedOwners}, but it is encrypted for ${builtins.toJSON secret.owners}. Run fleet secrets regenerate to fix";126 })127 config.sharedSecrets;128 hosts = hostsToAttrs (host: {129 modules = let130 cleanupSecret = secretName: v: {131 inherit (v) public secret;132 shared = true;133 };134 in [135 {136 secrets =137 (138 mapAttrs cleanupSecret139 (filterAttrs (_: v: builtins.elem host v.owners) config.sharedSecrets)140 )141 // (mapAttrs cleanupSecret (config.hostSecrets.${host} or {}));142 }143 ];144 });145 146 overlays = [147 (final: prev: let148 lib = final.lib;149 in {150 mkPassword = {size ? 32}:151 final.mkSecretGenerator ''152 ${final.coreutils}/bin/tr -dc 'A-Za-z0-9!?%=' < /dev/random \153 | ${final.coreutils}/bin/head -c ${toString size} \154 | encrypt > $out/secret155 '';156 mkRsa = {size ? 4096}:157 final.mkSecretGenerator ''158 ${final.openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}159 ${final.openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key160161 sudo cat rsa_private.key | encrypt > $out/secret162 sudo cat rsa_public.key > $out/public163 '';164 165 166 167 168 mkImpureSecretGenerator = generatorText: machine:169 (prev.writeShellScript "impureGenerator.sh" ''170 171 set -eu172173 174 function encrypt() {175 eval ${final.rage}/bin/rage $rageArgs176 }177178 created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")179 echo -n $created_at > $out/created_at180181 ${generatorText}182183 echo -n SUCCESS > $out/marker184 '')185 .overrideAttrs (old: {186 passthru = {187 generatorKind = "impure";188 impureOn = machine;189 };190 });191 192 193 194 mkSecretGenerator = generatorText:195 (prev.writeShellScript "generator.sh" ''196 197 set -eu198 199 cd $out200201 202 function encrypt() {203 eval ${final.rage}/bin/rage $rageArgs204 }205206 created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")207 echo -n $created_at > $out/created_at208209 ${generatorText}210211 echo -n SUCCESS > $out/marker212 '')213 .overrideAttrs (old: {214 passthru = {215 generatorKind = "pure";216 };217 218 219 });220 })221 ];222 };223}