difftreelog
refactor move parts to secret generator derivation
in: trunk
5 files changed
cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth158 expectations: &Expectations,158 expectations: &Expectations,159) -> Result<FleetSharedSecret> {159) -> Result<FleetSharedSecret> {160 let reason = secret_needs_regeneration(&secret.secret, &secret.owners, expectations);160 let reason = secret_needs_regeneration(&secret.secret, &secret.owners, expectations);161 let value = definition.inner();161 let value = definition.definition_value();162162163 let (should_reencrypt, reason) = match reason {163 let (should_reencrypt, reason) = match reason {164 Some(RegenerationReason::OwnersAdded(_)) => {164 Some(RegenerationReason::OwnersAdded(_)) => {401 // let owners: Vec<String> = nix_go_json!(secret.expectedOwners);401 // let owners: Vec<String> = nix_go_json!(secret.expectedOwners);402 Ok(FleetSharedSecret {402 Ok(FleetSharedSecret {403 managed: Some(true),403 managed: Some(true),404 secret: generate(config, display_name, secret.inner(), expectations).await?,404 secret: generate(405 config,406 display_name,407 secret.definition_value(),408 expectations,409 )410 .await?,713 let definition = config.shared_secret_definition(&name)?;719 let definition = config.shared_secret_definition(&name)?;714 let expectations = definition.expectations()?;720 let expectations = definition721 .expectations()722 .with_context(|| format!("expectations for shared {name:?}"))?;715723716 let updated = maybe_regenerate_shared_secret(724 let updated = maybe_regenerate_shared_secret(717 &name,725 &name,746 }754 }747 let expectations = definition.expectations()?;755 let expectations = definition756 .expectations()757 .with_context(|| format!("expectations for shared {missing:?}"))?;748 info!("generating secret: {missing}");758 info!("generating secret: {missing}");749 let shared = generate_shared(config, missing, definition, &expectations)759 let shared = generate_shared(config, missing, definition, &expectations)750 .in_current_span()760 .in_current_span()768 .into_iter()778 .into_iter()769 .collect::<HashSet<_>>();779 .collect::<HashSet<_>>();770 for missing_secret in expected_set.difference(&stored_set) {780 for missing_secret in expected_set.difference(&stored_set) {771 info!("generating missing secret: {missing_secret}");772 let definition = host.secret_definition(missing_secret)?;781 let secret = host.secret_definition(missing_secret)?;782 if secret.is_shared()? {783 continue;784 }785 info!("generating missing secret: {missing_secret}");773 let expectations = definition.expectations()?;786 let expectations = secret.expectations().with_context(|| {787 format!("expectations for {missing_secret:?} of {:?}", host.name)788 })?;774 let generated = match generate(789 let generated = match generate(775 config,790 config,776 missing_secret,791 missing_secret,777 definition.inner(),792 secret.definition_value()?,778 &expectations,793 &expectations,779 )794 )780 .in_current_span()795 .in_current_span()796 )811 )797 }812 }798 for known_secret in stored_set.intersection(&expected_set) {813 for known_secret in stored_set.intersection(&expected_set) {814 let secret = host.secret_definition(known_secret)?;815 if secret.is_shared()? {816 continue;817 }799 info!("updating secret: {known_secret}");818 info!("updating secret: {known_secret}");800 let data = config.host_secret(&host.name, known_secret)?;819 let data = config.host_secret(&host.name, known_secret)?;801 let definition = host.secret_definition(known_secret)?;802 let expectations = definition.expectations()?;820 let expectations = secret.expectations()?;803 if let Some(regen_reason) = data.needs_regeneration(&expectations) {821 if let Some(regen_reason) = data.needs_regeneration(&expectations) {804 info!("needs regeneration: {regen_reason}");822 info!("needs regeneration: {regen_reason}");805 let generated = match generate(823 let generated = match generate(806 config,824 config,807 known_secret,825 known_secret,808 definition.inner(),826 secret.definition_value()?,809 &expectations,827 &expectations,810 )828 )811 .in_current_span()829 .in_current_span()828 }846 }829 }847 }830 for removed_secret in stored_set.difference(&expected_set) {848 for removed_secret in stored_set.difference(&expected_set) {849 let definition = host.secret_definition(removed_secret)?;850 if definition.is_shared()? {851 continue;852 }831 info!("removing secret: {removed_secret}");853 info!("removing secret: {removed_secret}");832 config.remove_secret(&host.name, removed_secret);854 config.remove_secret(&host.name, removed_secret);833 }855 }crates/fleet-base/src/secret.rsdiffbeforeafterboth17pub struct HostSecretDefinition(pub(crate) String, pub(crate) Value);17pub struct HostSecretDefinition(pub(crate) String, pub(crate) Value);18impl HostSecretDefinition {18impl HostSecretDefinition {19 pub fn is_managed(&self) -> Result<bool> {19 pub fn is_managed(&self) -> Result<bool> {20 let value = &self.1;20 let def = self.definition_value()?;21 Ok(!nix_go!(value.generator).is_null())21 Ok(!nix_go!(def.generator).is_null())22 }22 }23 pub fn is_shared(&self) -> Result<bool> {24 let def = self.definition_value()?;25 Ok(nix_go_json!(def.shared))26 }23 pub fn expectations(&self) -> Result<Expectations> {27 pub fn expectations(&self) -> Result<Expectations> {24 let value = &self.1;28 let def = self.definition_value()?;29 let parts = nix_go!(def.parts);3031 let mut public_parts = BTreeSet::new();32 let mut private_parts = BTreeSet::new();33 for part in parts.list_fields()? {34 if nix_go_json!(parts[&part].encrypted) {35 private_parts.insert(part.clone());36 } else {37 public_parts.insert(part.clone());38 }39 }4025 Ok(Expectations {41 Ok(Expectations {26 owners: BTreeSet::from([self.0.clone()]),42 owners: BTreeSet::from([self.0.clone()]),27 generation_data: nix_go_json!(value.expectedGenerationData),43 generation_data: nix_go_json!(def.expectedGenerationData),28 public_parts: nix_go_json!(value.expectedPublicParts),44 public_parts,29 private_parts: nix_go_json!(value.expectedPrivateParts),45 private_parts,30 })46 })31 }47 }32 pub fn inner(&self) -> Value {48 pub fn definition_value(&self) -> Result<Value> {33 self.1.clone()49 let value = &self.1;50 Ok(nix_go!(value.definition))34 }51 }35}52}365349 private_parts: nix_go_json!(value.expectedPrivateParts),66 private_parts: nix_go_json!(value.expectedPrivateParts),50 })67 })51 }68 }52 pub fn inner(&self) -> Value {69 pub fn definition_value(&self) -> Value {53 self.0.clone()70 self.0.clone()54 }71 }55}72}lib/default.nixdiffbeforeafterboth58 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;58 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;595960 secrets =60 secrets = {61 let62 describedGenerator =63 generator: {parts ? {}}:64 {parts = {};}65 // {66 __functionArgs = functionArgs generator;67 __functor = _: generator;68 };69 in70 {71 inherit describedGenerator;726173 /**62 /**74 Generate a random secret password, 32 ascii characters by default63 Generate a random secret password, 32 ascii characters by default85 {74 {86 size ? 32,75 size ? 32,87 }:76 }:88 describedGenerator89 (77 (90 {78 {91 coreutils,79 coreutils,92 mkSecretGenerator,80 mkSecretGenerator,93 }:81 }:94 mkSecretGenerator {82 mkSecretGenerator {95 script = ''83 script = ''96 mkdir $out84 mkdir $out97 gh generate password -o $out/secret --size ${toString size}85 gh generate password -o $out/secret --size ${toString size}98 '';86 '';87 parts.secret.encrypted = true;99 }88 }100 )89 );101 {102 parts.secret.encrypted = true;103 };10490105 /**91 /**106 Generate a random ed25519 keypair92 Generate a random ed25519 keypair120 noEmbedPublic ? false,106 noEmbedPublic ? false,121 encoding ? null,107 encoding ? null,122 }:108 }:123 describedGenerator124 (109 (125 { mkSecretGenerator }:110 { mkSecretGenerator }:126 mkSecretGenerator {127 script = ''128 mkdir $out129 gh generate ed25519 -p $out/public -s $out/secret \130 ${optionalString noEmbedPublic "--no-embed-public"} \131 ${optionalString (encoding != null) "--encoding=${encoding}"}132 '';133 }134 )135 {111 mkSecretGenerator {112 script = ''113 mkdir $out114 gh generate ed25519 -p $out/public -s $out/secret \115 ${optionalString noEmbedPublic "--no-embed-public"} \116 ${optionalString (encoding != null) "--encoding=${encoding}"}117 '';136 parts.secret.encrypted = true;118 parts.secret.encrypted = true;137 parts.public.encrypted = false;119 parts.public.encrypted = false;138 };120 }121 );139122140 /**123 /**141 Generate a random x25519 keypair124 Generate a random x25519 keypair152 {135 {153 encoding ? null,136 encoding ? null,154 }:137 }:155 describedGenerator156 (138 (157 { mkSecretGenerator }:139 { mkSecretGenerator }:158 mkSecretGenerator {159 script = ''160 mkdir $out161 gh generate x25519 -p $out/public -s $out/secret \162 ${optionalString (encoding != null) "--encoding=${encoding}"}163 '';164 }165 )166 {140 mkSecretGenerator {141 script = ''142 mkdir $out143 gh generate x25519 -p $out/public -s $out/secret \144 ${optionalString (encoding != null) "--encoding=${encoding}"}145 '';146167 parts.secret.encrypted = true;147 parts.secret.encrypted = true;168 parts.public.encrypted = false;148 parts.public.encrypted = false;169 };149 }150 );170151171 /**152 /**172 Generate a random RSA keypair153 Generate a random RSA keypair182 {163 {183 size ? 4096,164 size ? 4096,184 }:165 }:185 describedGenerator186 (166 (187 {167 {188 openssl,168 openssl,189 mkSecretGenerator,169 mkSecretGenerator,190 }:170 }:191 mkSecretGenerator {192 script = ''193 mkdir $out194195 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}196 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key197198 cat rsa_private.key | gh private -o $out/secret199 cat rsa_public.key | gh public -o $out/public200 '';201 }202 )203 {171 mkSecretGenerator {172 script = ''173 mkdir $out174175 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}176 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key177178 cat rsa_private.key | gh private -o $out/secret179 cat rsa_public.key | gh public -o $out/public180 '';181204 parts.secret.encrypted = true;182 parts.secret.encrypted = true;205 parts.public.encrypted = false;183 parts.public.encrypted = false;206 };184 }185 );207186208 /**187 /**209 Generate a random byte sequence188 Generate a random byte sequence225 encoding,204 encoding,226 noNuls ? false,205 noNuls ? false,227 }:206 }:228 describedGenerator229 (207 (230 { mkSecretGenerator }:208 { mkSecretGenerator }:231 mkSecretGenerator {209 mkSecretGenerator {232 script = ''210 script = ''233 mkdir $out211 mkdir $out234 gh generate bytes --count=${toString count} --encoding=${encoding} -o $out/secret \212 gh generate bytes --count=${toString count} --encoding=${encoding} -o $out/secret \235 ${optionalString noNuls "--no-nuls"}213 ${optionalString noNuls "--no-nuls"}236 '';214 '';215 parts.secret.encrypted = true;237 }216 }238 )217 );239 {240 parts.secret.encrypted = true;241 };242 /**218 /**243 Shorthand for `mkBytes`, which defaults to "hex" encoding219 Shorthand for `mkBytes`, which defaults to "hex" encoding244 */220 */modules/nixos/secrets.nixdiffbeforeafterboth105 in105 in106 {106 {107 options = {107 options = {108 shared = mkOption {109 type = bool;110 description = "Was this secret propagated from a shared secret?";111 };108 parts = mkOption {112 parts = mkOption {109 type = lazyAttrsOf (secretPartType secretName);113 type = lazyAttrsOf (secretPartType secretName);110 description = "Definition of secret parts";114 description = "Definition of secret parts";137 default = null;141 default = null;138 };142 };139 };143 };140 config.parts = mkMerge [144 config = {145 shared = (sysConfig.data.secrets.${secretName} or { shared = false; }).shared;146 parts = mkMerge [141 (mkIf (config.generator != null && config.generator ? parts) config.generator.parts)147 (mkIf (config.generator != null)148 (149 # Get fake derivation body, in future it should be implemented the same way as in Rust.150 lib.callPackageWith (151 pkgs152 // {153 mkSecretGenerator = pkgs.stdenv.mkDerivation;154 mkImpureSecretGenerator = pkgs.stdenv.mkDerivation;155 }156 ) config.generator { }157 ).parts158 )142 (mapAttrs (_: _: {}) (removeAttrs (sysConfig.data.secrets.${secretName} or {}) ["shared" "managed"]))159 (mapAttrs (_: _: { }) (160 removeAttrs (sysConfig.data.secrets.${secretName} or { }) [161 "shared"162 "managed"163 ]164 ))143 ];165 ];166 };144 }167 }145 );168 );146 processPart = secretName: partName: part: {169 processPart = secretName: partName: part: {modules/secrets.nixdiffbeforeafterboth124 # If set - script will be run on remote machine, otherwise it will be run with fleet project in CWD124 # If set - script will be run on remote machine, otherwise it will be run with fleet project in CWD125 # (Some secrets-encryption-in-git/managed PKI solution is expected)125 # (Some secrets-encryption-in-git/managed PKI solution is expected)126 impureOn ? null,126 impureOn ? null,127 parts,127 }:128 }:128 (prev.writeShellScript "impureGenerator.sh" ''129 (prev.writeShellScript "impureGenerator.sh" ''129 #!/bin/sh130 #!/bin/sh151 '').overrideAttrs152 '').overrideAttrs152 (old: {153 (old: {153 passthru = {154 passthru = {154 inherit impureOn;155 inherit impureOn parts;155 generatorKind = "impure";156 generatorKind = "impure";156 };157 };157 });158 });158 # Pure generators are disabled for now159 # Pure generators are disabled for now159 mkSecretGenerator = { script }: mkImpureSecretGenerator { inherit script; };160 mkSecretGenerator = { script, parts }: mkImpureSecretGenerator { inherit script parts; };160161161 # TODO: Implement consistent naming162 # TODO: Implement consistent naming162 # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...163 # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...