git.delta.rocks / jrsonnet / refs/heads / trunk

difftreelog

source

modules/nixos/secrets.nix6.9 KiBsourcehistory
1{2  lib,3  fleetLib,4  config,5  pkgs,6  host,7  fleetConfiguration,8  ...9}:10let11  inherit (builtins)12    hashString13    toJSON14    ;15  inherit (lib.stringsWithDeps) stringAfter;16  inherit (lib.options) mkOption literalExpression;17  inherit (lib.lists) optional elem;18  inherit (lib.attrsets)19    mapAttrs20    mapAttrsToList21    filterAttrs22    attrNames23    ;24  inherit (lib.modules) mkIf;25  inherit (lib.types)26    submodule27    str28    attrsOf29    unspecified30    uniq31    functionTo32    package33    enum34    either35    listOf36    ;37  inherit (fleetLib.strings) decodeRawSecret;3839  sysConfig = config;40  secretPartType =41    secretName:42    submodule (43      { config, ... }:44      let45        partName = config._module.args.name;46      in47      {48        options = {49          hash = mkOption {50            type = str;51            description = "Hash of secret in encoded format";52          };53          path = mkOption {54            type = str;55            description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";56          };57          stablePath = mkOption {58            type = str;59            description = "Path to secret part, stable path (users are expected to watch for file changes/re-read secret on demand)";60          };61          data = mkOption {62            type = str;63            description = "Secret public data (only available for plaintext)";64          };65          raw = mkOption {66            type = str;67            description = "Raw (encoded/encrypted secret part data)";68          };69        };70        config = {71          hash = hashString "sha1" config.raw;72          data = decodeRawSecret config.raw;73          path = "/run/secrets/${secretName}/${config.hash}-${partName}";74          stablePath = "/run/secrets/${secretName}/${partName}";75        };76      }77    );78  secretType = submodule (79    {80      config,81      ...82    }:83    let84      secretName = config._module.args.name;85      literal = l: enum [ l ];86    in87    {88      options = {89        parts = mkOption {90          type = uniq (attrsOf (secretPartType secretName));91          description = "Definition of secret parts";92        };93        generator = mkOption {94          type = either (functionTo package) (literal "shared");95          description = "Derivation to evaluate for secret generation";96        };97        mode = mkOption {98          type = str;99          description = "Secret mode";100          default = "0440";101        };102        owner = mkOption {103          type = str;104          description = "Owner of the secret";105          default = "root";106        };107        group = mkOption {108          type = str;109          description = "Group of the secret";110          default = sysConfig.users.users.${config.owner}.group;111          defaultText = literalExpression "config.users.users.$${owner}.group";112        };113      };114      config = {115        # C api is broken in regard to thunks116        # https://github.com/NixOS/nix/issues/12800117        parts =118          let119            hostName = host._module.args.name;120            generator = config.generator;121          in122          builtins.deepSeq [123            hostName124            secretName125            generator126          ] (builtins.fleetEnsureHostSecret hostName secretName generator);127      };128    }129  );130  secretsFile = pkgs.writeTextFile {131    name = "secrets.json";132    text = toJSON config.system.secretsData;133  };134  useSysusers =135    (config.systemd ? sysusers && config.systemd.sysusers.enable)136    || (config ? userborn && config.userborn.enable);137in138{139  options = {140    _providedSharedSecrets = mkOption {141      description = ''142        List of shared secrets, for which the current host was specified as `expectedOwners`143      '';144      type = listOf str;145      default = [ ];146      internal = true;147    };148    secrets = mkOption {149      type = attrsOf secretType;150      default = { };151      apply =152        secrets:153        mapAttrs (_: secret: secret.parts // { definition = secret; })154155          (156            let157              hostName = host.name;158              expectedNonshared = attrNames (filterAttrs (_: def: def.generator != "shared") secrets);159              expectedShared = config._providedSharedSecrets;160            in161            builtins.deepSeq [162              hostName163              expectedNonshared164              expectedShared165            ] (builtins.fleetEnsureHostSecrets hostName expectedNonshared expectedShared secrets)166          );167      description = "Host-local secrets";168    };169    system.secretsData = mkOption {170      type = unspecified;171      default = mapAttrs (172        _: s:173        (removeAttrs s.definition [ "generator" ])174        // {175          parts = mapAttrs (_: part: removeAttrs part [ "data" ]) s.definition.parts;176        }177      ) config.secrets;178      description = "secrets.json contents";179    };180  };181  config = {182    environment.systemPackages = [ pkgs.fleet-install-secrets ];183184    assertions = mapAttrsToList (185      name: secret:186      let187        hasSharedDefinition = fleetConfiguration.secrets ? ${name};188      in189      {190        assertion =191          (secret.definition.generator == "shared") == hasSharedDefinition192          && (hasSharedDefinition -> (elem host.name fleetConfiguration.secrets.${name}.expectedOwners));193        message =194          if hasSharedDefinition then195            "secret ${name} has host-specific secret generator, secrets with host-specific generators can not have shared generator in fleet configuration"196          else197            "secret ${name} is declared as shared, for shared secret fleet configuration should include shared secret generator, and expectedOwners should contain this host";198      }199    ) config.secrets;200201    systemd.services.fleet-install-secrets = mkIf useSysusers {202      wantedBy = [ "sysinit.target" ];203      after = [ "systemd-sysusers.service" ];204      restartTriggers = [205        secretsFile206      ];207      aliases = [208        "sops-install-secrets"209        "agenix-install-secrets"210      ];211212      unitConfig.DefaultDependencies = false;213214      serviceConfig = {215        Type = "oneshot";216        RemainAfterExit = true;217        ExecStart = "${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}";218      };219    };220    system.activationScripts.decryptSecrets = mkIf (!useSysusers) (221      stringAfter222        (223          [224            # secrets are owned by user/group, thus we need to refer to those225            "users"226            "groups"227            "specialfs"228          ]229          # nixos-impermanence compatibility: secrets are encrypted by host-key,230          # but with impermanence we expect that the host-key is installed by231          # persist-file activation script.232          ++ (optional (config.system.activationScripts ? "persist-files") "persist-files")233        )234        ''235          1>&2 echo "setting up secrets"236          ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}237        ''238    );239  };240}