git.delta.rocks / jrsonnet / refs/commits / d8f3e18873ec

difftreelog

refactor move parts to secret generator derivation

rquyvuskYaroslav Bolyukin2025-11-05parent: #488d19a.patch.diff
in: trunk

5 files changed

modifiedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
158 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();
162162
163 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 = definition
721 .expectations()
722 .with_context(|| format!("expectations for shared {name:?}"))?;
715723
716 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 = definition
756 .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 }
modifiedcrates/fleet-base/src/secret.rsdiffbeforeafterboth
17pub 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);
30
31 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 }
40
25 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}
3653
49 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}
modifiedlib/default.nixdiffbeforeafterboth
58 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;58 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;
5959
60 secrets =60 secrets = {
61 let
62 describedGenerator =
63 generator: {parts ? {}}:
64 {parts = {};}
65 // {
66 __functionArgs = functionArgs generator;
67 __functor = _: generator;
68 };
69 in
70 {
71 inherit describedGenerator;
7261
73 /**62 /**
74 Generate a random secret password, 32 ascii characters by default63 Generate a random secret password, 32 ascii characters by default
85 {74 {
86 size ? 32,75 size ? 32,
87 }:76 }:
88 describedGenerator
89 (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 $out
97 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 };
10490
105 /**91 /**
106 Generate a random ed25519 keypair92 Generate a random ed25519 keypair
120 noEmbedPublic ? false,106 noEmbedPublic ? false,
121 encoding ? null,107 encoding ? null,
122 }:108 }:
123 describedGenerator
124 (109 (
125 { mkSecretGenerator }:110 { mkSecretGenerator }:
126 mkSecretGenerator {
127 script = ''
128 mkdir $out
129 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 $out
114 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 );
139122
140 /**123 /**
141 Generate a random x25519 keypair124 Generate a random x25519 keypair
152 {135 {
153 encoding ? null,136 encoding ? null,
154 }:137 }:
155 describedGenerator
156 (138 (
157 { mkSecretGenerator }:139 { mkSecretGenerator }:
158 mkSecretGenerator {
159 script = ''
160 mkdir $out
161 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 $out
143 gh generate x25519 -p $out/public -s $out/secret \
144 ${optionalString (encoding != null) "--encoding=${encoding}"}
145 '';
146
167 parts.secret.encrypted = true;147 parts.secret.encrypted = true;
168 parts.public.encrypted = false;148 parts.public.encrypted = false;
169 };149 }
150 );
170151
171 /**152 /**
172 Generate a random RSA keypair153 Generate a random RSA keypair
182 {163 {
183 size ? 4096,164 size ? 4096,
184 }:165 }:
185 describedGenerator
186 (166 (
187 {167 {
188 openssl,168 openssl,
189 mkSecretGenerator,169 mkSecretGenerator,
190 }:170 }:
191 mkSecretGenerator {
192 script = ''
193 mkdir $out
194
195 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}
196 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key
197
198 cat rsa_private.key | gh private -o $out/secret
199 cat rsa_public.key | gh public -o $out/public
200 '';
201 }
202 )
203 {171 mkSecretGenerator {
172 script = ''
173 mkdir $out
174
175 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}
176 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key
177
178 cat rsa_private.key | gh private -o $out/secret
179 cat rsa_public.key | gh public -o $out/public
180 '';
181
204 parts.secret.encrypted = true;182 parts.secret.encrypted = true;
205 parts.public.encrypted = false;183 parts.public.encrypted = false;
206 };184 }
185 );
207186
208 /**187 /**
209 Generate a random byte sequence188 Generate a random byte sequence
225 encoding,204 encoding,
226 noNuls ? false,205 noNuls ? false,
227 }:206 }:
228 describedGenerator
229 (207 (
230 { mkSecretGenerator }:208 { mkSecretGenerator }:
231 mkSecretGenerator {209 mkSecretGenerator {
232 script = ''210 script = ''
233 mkdir $out211 mkdir $out
234 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" encoding
244 */220 */
modifiedmodules/nixos/secrets.nixdiffbeforeafterboth
105 in105 in
106 {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 pkgs
152 // {
153 mkSecretGenerator = pkgs.stdenv.mkDerivation;
154 mkImpureSecretGenerator = pkgs.stdenv.mkDerivation;
155 }
156 ) config.generator { }
157 ).parts
158 )
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: {
modifiedmodules/secrets.nixdiffbeforeafterboth
124 # 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 CWD
125 # (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/sh
151 '').overrideAttrs152 '').overrideAttrs
152 (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 now
159 mkSecretGenerator = { script }: mkImpureSecretGenerator { inherit script; };160 mkSecretGenerator = { script, parts }: mkImpureSecretGenerator { inherit script parts; };
160161
161 # TODO: Implement consistent naming162 # TODO: Implement consistent naming
162 # 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...