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
--- a/cmds/install-secrets/src/main.rs
+++ b/cmds/install-secrets/src/main.rs
@@ -58,7 +58,6 @@
 	owner: String,
 	root_path: Option<PathBuf>,
 
-	#[serde(flatten)]
 	parts: BTreeMap<String, Part>,
 }
 
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
--- a/modules/nixos/secrets.nix
+++ b/modules/nixos/secrets.nix
@@ -6,12 +6,18 @@
   ...
 }:
 let
-  inherit (builtins) hashString elemAt length toJSON filter;
+  inherit (builtins)
+    hashString
+    elemAt
+    length
+    toJSON
+    filter
+    ;
   inherit (lib.stringsWithDeps) stringAfter;
   inherit (lib.options) mkOption literalExpression;
   inherit (lib.lists) optional;
   inherit (lib.attrsets) mapAttrs mapAttrsToList;
-  inherit (lib.modules) mkIf;
+  inherit (lib.modules) mkIf mkMerge;
   inherit (lib.types)
     submodule
     str
@@ -23,6 +29,7 @@
     functionTo
     package
     listOf
+    bool
     ;
   inherit (fleetLib.strings) decodeRawSecret;
 
@@ -54,6 +61,11 @@
       in
       {
         options = {
+          encrypted = mkOption {
+            type = bool;
+            description = "Is this secret part supposed to be encrypted?";
+          };
+
           hash = mkOption {
             type = str;
             description = "Hash of secret in encoded format";
@@ -86,28 +98,18 @@
   secretType = submodule (
     {
       config,
-      loc,
-      options,
       ...
     }:
     let
-      secretName =
-        # Due to config definition for freeformType, we can't just use _module.args due to infinite recursion, instead
-        # extract the secret name the ugly way...
-        let
-          saLoc = options._module.specialArgs.loc;
-          comp = elemAt saLoc;
-        in
-        assert
-          (length saLoc == 2 ||
-          length saLoc == 4 &&
-          comp 0 == "secrets" && comp 2 == "_module" && comp 3 == "specialArgs") ||
-          throw "Unexpected module structure ${toJSON saLoc}";
-        if length saLoc == 2 then "documentation generator stub" else comp 1;
+      secretName = config._module.args.name;
     in
     {
-      freeformType = lazyAttrsOf (secretPartType secretName);
       options = {
+        parts = mkOption {
+          type = lazyAttrsOf (secretPartType secretName);
+          description = "Definition of secret parts";
+          default = {};
+        };
         generator = mkOption {
           type = uniq (nullOr (functionTo package));
           description = "Derivation to evaluate for secret generation";
@@ -134,18 +136,11 @@
           description = "Data that gets embedded into secret part";
           default = null;
         };
-        expectedPrivateParts = mkOption {
-          type = listOf str;
-          default = [ ];
-          description = "List of parts that are expected to be encrypted";
-        };
-        expectedPublicParts = mkOption {
-          type = listOf str;
-          default = [ ];
-          description = "List of parts that are expected to be public";
-        };
       };
-      config = mapAttrs (_: _: { }) (removeAttrs (sysConfig.data.secrets.${secretName} or {}) [ "shared" ]);
+      config.parts = mkMerge [
+        (mkIf (config.generator != null && config.generator ? parts) config.generator.parts)
+        (mapAttrs (_: _: {}) (removeAttrs sysConfig.data.secrets.${secretName} ["shared"]))
+      ];
     }
   );
   processPart = secretName: partName: part: {
@@ -155,20 +150,11 @@
   processSecret =
     secretName: secret:
     {
-      inherit (secret) group mode owner;
-    }
-    // (mapAttrs (processPart secretName) (
-      removeAttrs secret [
-        "shared"
-        "generator"
-        "mode"
-        "group"
-        "owner"
-        "expectedGenerationData"
-        "expectedPrivateParts"
-        "expectedPublicParts"
-      ]
-    ));
+      inherit (secret.definition) group mode owner;
+      parts = (mapAttrs (processPart secretName) (
+        secret.definition.parts
+      ));
+    };
   secretsData = (mapAttrs (processSecret) config.secrets);
   secretsFile = pkgs.writeTextFile {
     name = "secrets.json";
@@ -188,29 +174,18 @@
     secrets = mkOption {
       type = attrsOf secretType;
       default = { };
+      apply = v: (mapAttrs (_: secret: secret.parts // {definition = secret;}) v);
       description = "Host-local secrets";
     };
     system.secretsData = mkOption {
       type = unspecified;
-      default = {};
+      default = { };
       description = "secrets.json contents";
     };
   };
   config = {
-    system = {inherit secretsData;};
+    system = { inherit secretsData; };
     environment.systemPackages = [ pkgs.fleet-install-secrets ];
-
-    warnings = filter (v: v!=null) (mapAttrsToList (
-      name: secret:
-      if
-        secret.expectedPrivateParts == [ ]
-        && secret.expectedPublicParts == [ ]
-        && !(config.data.secrets.${name} or { shared = false; }).shared
-      then
-        "Secret ${name} has no expected parts defined, this is deprecated for better visibility"
-      else
-        null
-    ) config.secrets);
 
     systemd.services.fleet-install-secrets = mkIf useSysusers {
       wantedBy = [ "sysinit.target" ];