git.delta.rocks / jrsonnet / refs/commits / 20a41a30344f

difftreelog

source

modules/nixos/secrets.nix6.9 KiBsourcehistory
1{2  lib,3  fleetLib,4  config,5  pkgs,6  ...7}:8let9  inherit (builtins)10    hashString11    elemAt12    length13    toJSON14    filter15    ;16  inherit (lib.stringsWithDeps) stringAfter;17  inherit (lib.options) mkOption literalExpression;18  inherit (lib.lists) optional;19  inherit (lib.attrsets) mapAttrs mapAttrsToList;20  inherit (lib.modules) mkIf mkMerge;21  inherit (lib.types)22    submodule23    str24    attrsOf25    nullOr26    unspecified27    lazyAttrsOf28    uniq29    functionTo30    package31    listOf32    bool33    ;34  inherit (fleetLib.strings) decodeRawSecret;3536  sysConfig = config;37  secretPartDataType = submodule {38    options = {39      raw = mkOption {40        type = str;41        internal = true;42        description = "Encoded & Encrypted secret part data, passed from fleet.nix";43      };44    };45  };46  secretDataType = submodule {47    freeformType = lazyAttrsOf secretPartDataType;48    options = {49      shared = mkOption {50        description = "Is this secret owned by this machine, or propagated from shared secrets";51        default = false;52      };53    };54  };55  secretPartType =56    secretName:57    submodule (58      { config, ... }:59      let60        partName = config._module.args.name;61      in62      {63        options = {64          encrypted = mkOption {65            type = bool;66            description = "Is this secret part supposed to be encrypted?";67          };6869          hash = mkOption {70            type = str;71            description = "Hash of secret in encoded format";72          };73          path = mkOption {74            type = str;75            description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";76          };77          stablePath = mkOption {78            type = str;79            description = "Path to secret part, stable path (users are expected to watch for file changes/re-read secret on demand)";80          };81          data = mkOption {82            type = str;83            description = "Secret public data (only available for plaintext)";84          };85        };86        config =87          let88            raw = sysConfig.data.secrets.${secretName}.${partName}.raw;89          in90          {91            hash = hashString "sha1" raw;92            data = decodeRawSecret raw;93            path = "/run/secrets/${secretName}/${config.hash}-${partName}";94            stablePath = "/run/secrets/${secretName}/${partName}";95          };96      }97    );98  secretType = submodule (99    {100      config,101      ...102    }:103    let104      secretName = config._module.args.name;105    in106    {107      options = {108        shared = mkOption {109          type = bool;110          description = "Was this secret propagated from a shared secret?";111        };112        parts = mkOption {113          type = lazyAttrsOf (secretPartType secretName);114          description = "Definition of secret parts";115          default = { };116        };117        generator = mkOption {118          type = uniq (nullOr (functionTo package));119          description = "Derivation to evaluate for secret generation";120          default = null;121        };122        mode = mkOption {123          type = str;124          description = "Secret mode";125          default = "0440";126        };127        owner = mkOption {128          type = str;129          description = "Owner of the secret";130          default = "root";131        };132        group = mkOption {133          type = str;134          description = "Group of the secret";135          default = sysConfig.users.users.${config.owner}.group;136          defaultText = literalExpression "config.users.users.$${owner}.group";137        };138        expectedGenerationData = mkOption {139          type = unspecified;140          description = "Data that gets embedded into secret part";141          default = null;142        };143      };144      config = {145        shared = (sysConfig.data.secrets.${secretName} or { shared = false; }).shared;146        parts = mkMerge [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          )159          (mapAttrs (_: _: { }) (160            removeAttrs (sysConfig.data.secrets.${secretName} or { }) [161              "shared"162              "managed"163            ]164          ))165        ];166      };167    }168  );169  processPart = secretName: partName: part: {170    inherit (part) path stablePath;171    raw = config.data.secrets.${secretName}.${partName}.raw;172  };173  processSecret = secretName: secret: {174    inherit (secret.definition) group mode owner;175    parts = (mapAttrs (processPart secretName) (secret.definition.parts));176  };177  secretsData = (mapAttrs (processSecret) config.secrets);178  secretsFile = pkgs.writeTextFile {179    name = "secrets.json";180    text = toJSON secretsData;181  };182  useSysusers =183    (config.systemd ? sysusers && config.systemd.sysusers.enable)184    || (config ? userborn && config.userborn.enable);185in186{187  options = {188    data.secrets = mkOption {189      type = attrsOf secretDataType;190      default = { };191      description = "Host-local secret data";192    };193    secrets = mkOption {194      type = attrsOf secretType;195      default = { };196      apply = v: (mapAttrs (_: secret: secret.parts // { definition = secret; }) v);197      description = "Host-local secrets";198    };199    system.secretsData = mkOption {200      type = unspecified;201      default = { };202      description = "secrets.json contents";203    };204  };205  config = {206    system = { inherit secretsData; };207    environment.systemPackages = [ pkgs.fleet-install-secrets ];208209    systemd.services.fleet-install-secrets = mkIf useSysusers {210      wantedBy = [ "sysinit.target" ];211      after = [ "systemd-sysusers.service" ];212      restartTriggers = [213        secretsFile214      ];215      aliases = [216        "sops-install-secrets"217        "agenix-install-secrets"218      ];219220      unitConfig.DefaultDependencies = false;221222      serviceConfig = {223        Type = "oneshot";224        RemainAfterExit = true;225        ExecStart = "${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}";226      };227    };228    system.activationScripts.decryptSecrets = mkIf (!useSysusers) (229      stringAfter230        (231          [232            # secrets are owned by user/group, thus we need to refer to those233            "users"234            "groups"235            "specialfs"236          ]237          # nixos-impermanence compatibility: secrets are encrypted by host-key,238          # but with impermanence we expect that the host-key is installed by239          # persist-file activation script.240          ++ (optional (config.system.activationScripts ? "persist-files") "persist-files")241        )242        ''243          1>&2 echo "setting up secrets"244          ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}245        ''246    );247  };248}