]> git.immae.eu Git - perso/Immae/Config/Nix.git/blobdiff - flakes/secrets/flake.nix
Squash changes containing private information
[perso/Immae/Config/Nix.git] / flakes / secrets / flake.nix
index 0ee6a40929f4f27e28ab327c93b7700e5dd2c7e2..7bf04a4517fb2ed4f839ff56d14526294d9fbfcf 100644 (file)
@@ -3,11 +3,73 @@
 
   outputs = { self }: {
     nixosModule = { config, lib, pkgs, ... }: {
+      # Necessary for situations where flake gets included multiple times
+      key = builtins.hashString "sha256" (builtins.path { path = self.sourceInfo.outPath; name = "source"; });
       options.secrets = with lib; {
         keys = mkOption {
-          type = types.listOf types.unspecified;
-          default = [];
-          description = "Keys to upload to server";
+          type = types.attrsOf (types.submodule {
+            options = {
+              isTemplated = mkOption {
+                type = types.bool;
+                default = true;
+                description = "If the file is a gucci template that needs to be resolved";
+              };
+              isDir = mkOption {
+                type = types.bool;
+                default = false;
+                description = "If the entry is a directory";
+              };
+              group = mkOption {
+                type = types.str;
+                default = "root";
+                description = "Group to associate to the entry";
+              };
+              user = mkOption {
+                type = types.str;
+                default = "root";
+                description = "User to associate to the entry";
+              };
+              permissions = mkOption {
+                type = types.str;
+                default = "0600";
+                description = "Permissions to associate to the entry";
+              };
+              text = mkOption {
+                type = types.str;
+                description = "Content of the entry";
+              };
+              keyDependencies = mkOption {
+                default = [];
+                type = types.listOf (types.either types.path types.package);
+                description = ''
+                  (public) system dependencies that needs to be
+                  uploaded with the key.
+
+                  keyDependencies + ignoredKeyDependencies should
+                  contain the exhaustive list of the text context.
+
+                  A warning will be thrown if there are remaning
+                  dependencies from the text.
+                '';
+              };
+              ignoredKeyDependencies = mkOption {
+                default = [];
+                type = types.listOf (types.either types.path types.package);
+                description = ''
+                  dependencies that must not be sent along with the key.
+
+                  keyDependencies + ignoredKeyDependencies should
+                  contain the exhaustive list of the text context.
+
+                  A warning will be thrown if there are remaning
+                  dependencies from the text.
+                '';
+              };
+            };
+          });
+          default = {};
+          description = "Keys attrs to upload to the server";
+          apply = builtins.mapAttrs (dest: v: v // { inherit dest; });
         };
         gpgKeys = mkOption {
           type = types.listOf types.path;
         # Read-only variables
         fullPaths = mkOption {
           type = types.attrsOf types.path;
-          default = builtins.listToAttrs
-            (map (v: { name = v.dest; value = "${config.secrets.location}/${v.dest}"; }) config.secrets.keys);
+          default = builtins.mapAttrs
+            (n: v: "${config.secrets.location}/${n}") config.secrets.keys;
           readOnly = true;
           description = "set of full paths to secrets";
         };
 
       config = let
         location = config.secrets.location;
-        keys = config.secrets.keys;
+        keys = builtins.attrValues config.secrets.keys;
         empty = pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out && touch $out/done";
-        fpath = v: "secrets/${v.dest}${lib.optionalString (v.isTemplated or true) ".gucci.tpl"}";
+        fpath = v: "secrets/${v.dest}${lib.optionalString v.isTemplated ".gucci.tpl"}";
         dumpKey = v:
-          if v.isDir or false then
+          if v.isDir then
             ''
               mkdir -p secrets/${v.dest}
               cat >> mods <<EOF
-              ${v.user or "root"} ${v.group or "root"} ${v.permissions or "0600"} secrets/${v.dest}
+              ${v.user} ${v.group} ${v.permissions} secrets/${v.dest}
               EOF
             ''
           else ''
             mkdir -p secrets/$(dirname ${v.dest})
             echo -n ${lib.strings.escapeShellArg v.text} > ${fpath v}
             cat >> mods <<EOF
-            ${v.user or "root"} ${v.group or "root"} ${v.permissions or "0600"} ${fpath v}
+            ${v.user} ${v.group} ${v.permissions} ${fpath v}
             EOF
             '';
         secrets = pkgs.runCommand "secrets.tar.enc" {
           '';
         pathChmodExcl =
           let
-            dirs = builtins.filter (v: v.isDir or false) keys;
+            dirs = builtins.filter (v: v.isDir) keys;
             exclPath = builtins.concatStringsSep " -o " (map (d: " -path $TMP/${d.dest}") dirs);
           in
             lib.optionalString (builtins.length dirs > 0) " -not \\( ${exclPath} \\) ";
+
+        checkKeyDependencies = key:
+          let
+            allDeps = builtins.map (n: if builtins.isPath n then "${n}" else n.drvPath) (key.keyDependencies ++ key.ignoredKeyDependencies);
+            context = builtins.attrNames (builtins.getContext key.text);
+            missing = builtins.foldl' (o: n: lib.remove n o) context allDeps;
+          in
+            lib.optional (!key.isDir && builtins.length missing > 0)
+              ''
+                Key ${key.dest} has non declared dependencies in its context: ${builtins.concatStringsSep " " missing}
+                Add them to ignoredKeyDependencies to ignore
+              '';
       in lib.mkIf (builtins.length keys > 0) {
+        warnings = lib.concatMap checkKeyDependencies keys;
+        # FIXME: Use lib.concatMap (k: k.keyDependencies) keys in latest nixpkgs
+        system.extraDependencies = lib.concatMap (k: builtins.map (dep:
+          if builtins.isPath dep then pkgs.writeText "extra-dep" "${dep}" else dep
+        ) k.keyDependencies) keys;
         system.activationScripts.secrets = {
           deps = [ "users" "wrappers" ];
           text = ''
             TMP=$(${pkgs.coreutils}/bin/mktemp -d)
             TMPWORK=$(${pkgs.coreutils}/bin/mktemp -d)
             chmod go-rwx $TMPWORK
-            if [ -n "$TMP" -a -n "$TMPWORK" ]; then
+            if [ -n "$TMP" -a -n "$TMPWORK" -a -f ${config.secrets.secretsVars} ]; then
               install -m0750 -o root -g keys -d $TMP
               ${pkgs.ssh-to-age}/bin/ssh-to-age -private-key -i ${config.secrets.decryptKey} -o $TMPWORK/keys.txt
               SOPS_AGE_KEY_FILE=$TMPWORK/keys.txt ${pkgs.sops}/bin/sops -d ${secrets} | ${pkgs.gnutar}/bin/tar --strip-components 1 -C $TMP -x
-              if [ -f ${config.secrets.secretsVars} ]; then
-                SOPS_AGE_KEY_FILE=$TMPWORK/keys.txt ${pkgs.sops}/bin/sops -d ${config.secrets.secretsVars} > $TMPWORK/vars.yml
-              fi
+              SOPS_AGE_KEY_FILE=$TMPWORK/keys.txt ${pkgs.sops}/bin/sops -d ${config.secrets.secretsVars} > $TMPWORK/vars.yml
               if [ -f $TMPWORK/vars.yml ]; then
                 find $TMP -name "*.gucci.tpl" -exec \
                   /bin/sh -c 'f="{}"; ${pkgs.gucci}/bin/gucci -f '$TMPWORK'/vars.yml "$f" > "''${f%.gucci.tpl}"; touch --reference "$f" ''${f%.gucci.tpl} ; chmod --reference="$f" ''${f%.gucci.tpl} ; chown --reference="$f" ''${f%.gucci.tpl}' \;