git.delta.rocks / jrsonnet / refs/commits / 5b343db89280

difftreelog

feat infer secret parts from generator

uqnzwwslYaroslav Bolyukin2025-10-27parent: #3bff084.patch.diff
in: trunk

3 files changed

modifiedcmds/install-secrets/src/main.rsdiffbeforeafterboth
58 owner: String,58 owner: String,
59 root_path: Option<PathBuf>,59 root_path: Option<PathBuf>,
6060
61 #[serde(flatten)]
62 parts: BTreeMap<String, Part>,61 parts: BTreeMap<String, Part>,
63}62}
6463
modifiedlib/default.nixdiffbeforeafterboth
55 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;55 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;
5656
57 secrets = {57 secrets =
58 let
59 describedGenerator =
60 generator: {parts ? {}}:
61 {parts = {};}
62 // {
63 __functor = generator;
64 };
65 in
66 {
67 inherit describedGenerator;
68
58 /**69 /**
59 Generate a random secret password, 32 ascii characters by default70 Generate a random secret password, 32 ascii characters by default
70 {81 {
71 size ? 32,82 size ? 32,
72 }:83 }:
84 describedGenerator
85 (
73 {86 {
74 coreutils,87 coreutils,
75 mkSecretGenerator,88 mkSecretGenerator,
76 }:89 }:
77 mkSecretGenerator {90 mkSecretGenerator {
78 script = ''91 script = ''
79 mkdir $out92 mkdir $out
80 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 };
83100
84 /**101 /**
85 Generate a random ed25519 keypair102 Generate a random ed25519 keypair
99 noEmbedPublic ? false,116 noEmbedPublic ? false,
100 encoding ? null,117 encoding ? null,
101 }:118 }:
119 describedGenerator
120 (
102 { mkSecretGenerator }:121 { mkSecretGenerator }:
103 mkSecretGenerator {122 mkSecretGenerator {
104 script = ''123 script = ''
105 mkdir $out124 mkdir $out
106 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 };
111135
112 /**136 /**
113 Generate a random x25519 keypair137 Generate a random x25519 keypair
124 {148 {
125 encoding ? null,149 encoding ? null,
126 }:150 }:
151 describedGenerator
152 (
127 { mkSecretGenerator }:153 { mkSecretGenerator }:
128 mkSecretGenerator {154 mkSecretGenerator {
129 script = ''155 script = ''
130 mkdir $out156 mkdir $out
131 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 };
135166
136 /**167 /**
137 Generate a random RSA keypair168 Generate a random RSA keypair
147 {178 {
148 size ? 4096,179 size ? 4096,
149 }:180 }:
181 describedGenerator
182 (
150 {183 {
151 openssl,184 openssl,
152 mkSecretGenerator,185 mkSecretGenerator,
153 }:186 }:
154 mkSecretGenerator {187 mkSecretGenerator {
155 script = ''188 script = ''
156 mkdir $out189 mkdir $out
157190
158 ${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.key
160193
161 cat rsa_private.key | gh private -o $out/secret194 cat rsa_private.key | gh private -o $out/secret
162 cat rsa_public.key | gh public -o $out/public195 cat rsa_public.key | gh public -o $out/public
163 '';196 '';
164 };197 }
198 )
199 {
200 parts.secret.encrypted = true;
201 parts.public.encrypted = false;
202 };
165203
166 /**204 /**
167 Generate a random byte sequence205 Generate a random byte sequence
183 encoding,221 encoding,
184 noNuls ? false,222 noNuls ? false,
185 }:223 }:
224 describedGenerator
225 (
186 { mkSecretGenerator }:226 { mkSecretGenerator }:
187 mkSecretGenerator {227 mkSecretGenerator {
188 script = ''228 script = ''
189 mkdir $out229 mkdir $out
190 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" encoding
196 */240 */
modifiedmodules/nixos/secrets.nixdiffbeforeafterboth
11 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 submodule
17 str23 str
23 functionTo29 functionTo
24 package30 package
25 listOf31 listOf
32 bool
26 ;33 ;
27 inherit (fleetLib.strings) decodeRawSecret;34 inherit (fleetLib.strings) decodeRawSecret;
2835
54 in61 in
55 {62 {
56 options = {63 options = {
64 encrypted = mkOption {
65 type = bool;
66 description = "Is this secret part supposed to be encrypted?";
67 };
68
57 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 let
94 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, instead
96 # extract the secret name the ugly way...
97 let
98 saLoc = options._module.specialArgs.loc;
99 comp = elemAt saLoc;
100 in
101 assert
102 (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 in
108 {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.parts
162 "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 ];
202
203 warnings = filter (v: v!=null) (mapAttrsToList (
204 name: secret:
205 if
206 secret.expectedPrivateParts == [ ]
207 && secret.expectedPublicParts == [ ]
208 && !(config.data.secrets.${name} or { shared = false; }).shared
209 then
210 "Secret ${name} has no expected parts defined, this is deprecated for better visibility"
211 else
212 null
213 ) config.secrets);
214189
215 systemd.services.fleet-install-secrets = mkIf useSysusers {190 systemd.services.fleet-install-secrets = mkIf useSysusers {
216 wantedBy = [ "sysinit.target" ];191 wantedBy = [ "sysinit.target" ];