difftreelog
feat infer secret parts from generator
in: trunk
3 files changed
cmds/install-secrets/src/main.rsdiffbeforeafterboth58 owner: String,58 owner: String,59 root_path: Option<PathBuf>,59 root_path: Option<PathBuf>,606061 #[serde(flatten)]62 parts: BTreeMap<String, Part>,61 parts: BTreeMap<String, Part>,63}62}6463lib/default.nixdiffbeforeafterboth55 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;55 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;565657 secrets = {57 secrets =58 let59 describedGenerator =60 generator: {parts ? {}}:61 {parts = {};}62 // {63 __functor = generator;64 };65 in66 {67 inherit describedGenerator;6858 /**69 /**59 Generate a random secret password, 32 ascii characters by default70 Generate a random secret password, 32 ascii characters by default70 {81 {71 size ? 32,82 size ? 32,72 }:83 }:84 describedGenerator85 (73 {86 {74 coreutils,87 coreutils,75 mkSecretGenerator,88 mkSecretGenerator,76 }:89 }:77 mkSecretGenerator {90 mkSecretGenerator {78 script = ''91 script = ''79 mkdir $out92 mkdir $out80 gh generate password -o $out/secret --size ${toString size}93 gh generate password -o $out/secret --size ${toString size}81 '';94 '';82 };95 }96 )97 {98 parts.secret.encrypted = true;99 };8310084 /**101 /**85 Generate a random ed25519 keypair102 Generate a random ed25519 keypair99 noEmbedPublic ? false,116 noEmbedPublic ? false,100 encoding ? null,117 encoding ? null,101 }:118 }:119 describedGenerator120 (102 { mkSecretGenerator }:121 { mkSecretGenerator }:103 mkSecretGenerator {122 mkSecretGenerator {104 script = ''123 script = ''105 mkdir $out124 mkdir $out106 gh generate ed25519 -p $out/public -s $out/secret \125 gh generate ed25519 -p $out/public -s $out/secret \107 ${optionalString noEmbedPublic "--no-embed-public"} \126 ${optionalString noEmbedPublic "--no-embed-public"} \108 ${optionalString (encoding != null) "--encoding=${encoding}"}127 ${optionalString (encoding != null) "--encoding=${encoding}"}109 '';128 '';110 };129 }130 )131 {132 parts.secret.encrypted = true;133 parts.public.encrypted = false;134 };111135112 /**136 /**113 Generate a random x25519 keypair137 Generate a random x25519 keypair124 {148 {125 encoding ? null,149 encoding ? null,126 }:150 }:151 describedGenerator152 (127 { mkSecretGenerator }:153 { mkSecretGenerator }:128 mkSecretGenerator {154 mkSecretGenerator {129 script = ''155 script = ''130 mkdir $out156 mkdir $out131 gh generate x25519 -p $out/public -s $out/secret \157 gh generate x25519 -p $out/public -s $out/secret \132 ${optionalString (encoding != null) "--encoding=${encoding}"}158 ${optionalString (encoding != null) "--encoding=${encoding}"}133 '';159 '';134 };160 }161 )162 {163 parts.secret.encrypted = true;164 parts.public.encrypted = false;165 };135166136 /**167 /**137 Generate a random RSA keypair168 Generate a random RSA keypair147 {178 {148 size ? 4096,179 size ? 4096,149 }:180 }:181 describedGenerator182 (150 {183 {151 openssl,184 openssl,152 mkSecretGenerator,185 mkSecretGenerator,153 }:186 }:154 mkSecretGenerator {187 mkSecretGenerator {155 script = ''188 script = ''156 mkdir $out189 mkdir $out157190158 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}191 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}159 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key192 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key160193161 cat rsa_private.key | gh private -o $out/secret194 cat rsa_private.key | gh private -o $out/secret162 cat rsa_public.key | gh public -o $out/public195 cat rsa_public.key | gh public -o $out/public163 '';196 '';164 };197 }198 )199 {200 parts.secret.encrypted = true;201 parts.public.encrypted = false;202 };165203166 /**204 /**167 Generate a random byte sequence205 Generate a random byte sequence183 encoding,221 encoding,184 noNuls ? false,222 noNuls ? false,185 }:223 }:224 describedGenerator225 (186 { mkSecretGenerator }:226 { mkSecretGenerator }:187 mkSecretGenerator {227 mkSecretGenerator {188 script = ''228 script = ''189 mkdir $out229 mkdir $out190 gh generate bytes --count=${toString count} --encoding=${encoding} -o $out/secret \230 gh generate bytes --count=${toString count} --encoding=${encoding} -o $out/secret \191 ${optionalString noNuls "--no-nuls"}231 ${optionalString noNuls "--no-nuls"}192 '';232 '';193 };233 }234 )235 {236 parts.secret.encrypted = true;237 };194 /**238 /**195 Shorthand for `mkBytes`, which defaults to "hex" encoding239 Shorthand for `mkBytes`, which defaults to "hex" encoding196 */240 */modules/nixos/secrets.nixdiffbeforeafterboth11 inherit (lib.options) mkOption literalExpression;17 inherit (lib.options) mkOption literalExpression;12 inherit (lib.lists) optional;18 inherit (lib.lists) optional;13 inherit (lib.attrsets) mapAttrs mapAttrsToList;19 inherit (lib.attrsets) mapAttrs mapAttrsToList;14 inherit (lib.modules) mkIf;20 inherit (lib.modules) mkIf mkMerge;15 inherit (lib.types)21 inherit (lib.types)16 submodule22 submodule17 str23 str23 functionTo29 functionTo24 package30 package25 listOf31 listOf32 bool26 ;33 ;27 inherit (fleetLib.strings) decodeRawSecret;34 inherit (fleetLib.strings) decodeRawSecret;283554 in61 in55 {62 {56 options = {63 options = {64 encrypted = mkOption {65 type = bool;66 description = "Is this secret part supposed to be encrypted?";67 };6857 hash = mkOption {69 hash = mkOption {58 type = str;70 type = str;86 secretType = submodule (98 secretType = submodule (87 {99 {88 config,100 config,89 loc,90 options,91 ...101 ...92 }:102 }:93 let103 let94 secretName =104 secretName = config._module.args.name;95 # Due to config definition for freeformType, we can't just use _module.args due to infinite recursion, instead96 # extract the secret name the ugly way...97 let98 saLoc = options._module.specialArgs.loc;99 comp = elemAt saLoc;100 in101 assert102 (length saLoc == 2 ||103 length saLoc == 4 &&104 comp 0 == "secrets" && comp 2 == "_module" && comp 3 == "specialArgs") ||105 throw "Unexpected module structure ${toJSON saLoc}";106 if length saLoc == 2 then "documentation generator stub" else comp 1;107 in105 in108 {106 {107 options = {108 parts = mkOption {109 freeformType = lazyAttrsOf (secretPartType secretName);109 type = lazyAttrsOf (secretPartType secretName);110 options = {110 description = "Definition of secret parts";111 default = {};112 };111 generator = mkOption {113 generator = mkOption {112 type = uniq (nullOr (functionTo package));114 type = uniq (nullOr (functionTo package));113 description = "Derivation to evaluate for secret generation";115 description = "Derivation to evaluate for secret generation";134 description = "Data that gets embedded into secret part";136 description = "Data that gets embedded into secret part";135 default = null;137 default = null;136 };138 };137 expectedPrivateParts = mkOption {138 type = listOf str;139 default = [ ];140 description = "List of parts that are expected to be encrypted";141 };142 expectedPublicParts = mkOption {143 type = listOf str;144 default = [ ];145 description = "List of parts that are expected to be public";146 };147 };139 };148 config = mapAttrs (_: _: { }) (removeAttrs (sysConfig.data.secrets.${secretName} or {}) [ "shared" ]);140 config.parts = mkMerge [141 (mkIf (config.generator != null && config.generator ? parts) config.generator.parts)142 (mapAttrs (_: _: {}) (removeAttrs sysConfig.data.secrets.${secretName} ["shared"]))143 ];149 }144 }150 );145 );151 processPart = secretName: partName: part: {146 processPart = secretName: partName: part: {154 };149 };155 processSecret =150 processSecret =156 secretName: secret:151 secretName: secret:157 {152 {158 inherit (secret) group mode owner;153 inherit (secret.definition) group mode owner;159 }160 // (mapAttrs (processPart secretName) (154 parts = (mapAttrs (processPart secretName) (161 removeAttrs secret [155 secret.definition.parts162 "shared"163 "generator"164 "mode"165 "group"166 "owner"167 "expectedGenerationData"168 "expectedPrivateParts"169 "expectedPublicParts"170 ]171 ));156 ));157 };172 secretsData = (mapAttrs (processSecret) config.secrets);158 secretsData = (mapAttrs (processSecret) config.secrets);173 secretsFile = pkgs.writeTextFile {159 secretsFile = pkgs.writeTextFile {174 name = "secrets.json";160 name = "secrets.json";188 secrets = mkOption {174 secrets = mkOption {189 type = attrsOf secretType;175 type = attrsOf secretType;190 default = { };176 default = { };177 apply = v: (mapAttrs (_: secret: secret.parts // {definition = secret;}) v);191 description = "Host-local secrets";178 description = "Host-local secrets";192 };179 };193 system.secretsData = mkOption {180 system.secretsData = mkOption {200 system = {inherit secretsData;};187 system = { inherit secretsData; };201 environment.systemPackages = [ pkgs.fleet-install-secrets ];188 environment.systemPackages = [ pkgs.fleet-install-secrets ];202203 warnings = filter (v: v!=null) (mapAttrsToList (204 name: secret:205 if206 secret.expectedPrivateParts == [ ]207 && secret.expectedPublicParts == [ ]208 && !(config.data.secrets.${name} or { shared = false; }).shared209 then210 "Secret ${name} has no expected parts defined, this is deprecated for better visibility"211 else212 null213 ) config.secrets);214189215 systemd.services.fleet-install-secrets = mkIf useSysusers {190 systemd.services.fleet-install-secrets = mkIf useSysusers {216 wantedBy = [ "sysinit.target" ];191 wantedBy = [ "sysinit.target" ];