{ config, lib, pkgs, ... }: with lib; let cfg = config.fudo.secrets; encrypt-on-disk = { secret-name, target-host, target-pubkey, source-file }: pkgs.stdenv.mkDerivation { name = "${target-host}-${secret-name}-secret"; phases = "installPhase"; buildInputs = [ pkgs.age ]; installPhase = '' age -a -r "${target-pubkey}" -o $out ${source-file} ''; }; decrypt-script = { secret-name, source-file, target-host, target-file , host-master-key, user, group, permissions }: pkgs.writeShellScript "decrypt-fudo-secret-${target-host}-${secret-name}.sh" '' rm -rf ${target-file} age -d -i ${host-master-key.key-path} -o ${target-file} ${ encrypt-on-disk { inherit secret-name source-file target-host; target-pubkey = host-master-key.public-key; } } chown ${user}:${group} ${target-file} chmod ${permissions} ${target-file} ''; secret-service = target-host: secret-name: { source-file, target-file, user, group, permissions }: { description = "decrypt secret ${secret-name} for ${target-host}."; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; ExecStartPre = pkgs.writeShellScript "prepare-${target-host}-${secret-name}-secret-dir.sh" '' TARGET_DIR=$(dirname ${target-file}) if [[ ! -d "$TARGET_DIR" ]]; then mkdir -p "$TARGET_DIR" fi ''; ExecStart = let host-master-key = config.fudo.hosts.${target-host}.master-key; in decrypt-script { inherit secret-name source-file target-host target-file host-master-key user group permissions; }; }; path = [ pkgs.age ]; }; secretOpts = { ... }: { options = with types; { source-file = mkOption { type = path; # CAREFUL: this will copy the file to nixstore...keep on deploy host description = "File from which to load the secret."; }; target-file = mkOption { type = str; description = "Target file on the host; the secret will be decrypted to this file."; }; user = mkOption { type = str; description = "User (on target host) to which the file will belong."; }; group = mkOption { type = str; description = "Group (on target host) to which the file will belong."; default = "nogroup"; }; permissions = mkOption { type = str; description = "Permissions to set on the target file."; default = "0400"; }; }; }; nix-build-users = let usernames = attrNames config.users.users; in filter (user: (builtins.match "^nixbld[0-9]{1,2}$" user) != null) usernames; in { options.fudo.secrets = with types; { enable = mkOption { type = bool; description = "Include secrets in the build (disable when secrets are unavailable)"; default = true; }; host-secrets = mkOption { type = attrsOf (attrsOf (submodule secretOpts)); description = "Map of hosts to host secrets"; default = { }; }; secret-users = mkOption { type = listOf str; description = "List of users with read-access to secrets."; default = [ ]; }; secret-group = mkOption { type = str; description = "Group to which secrets will belong."; default = "nixops-secrets"; }; secret-paths = mkOption { type = listOf str; description = "Paths which contain (only) secrets. The contents will be reabable by the secret-group."; default = [ ]; }; }; config = mkIf cfg.enable { users.groups = { ${cfg.secret-group} = { members = cfg.secret-users ++ nix-build-users; }; }; systemd = let hostname = config.instance.hostname; host-secrets = if (hasAttr hostname cfg.host-secrets) then cfg.host-secrets.${hostname} else { }; host-secret-services = mapAttrs' (secret: secretOpts: (nameValuePair "fudo-secret-${hostname}-${secret}" (secret-service hostname secret secretOpts))) host-secrets; in { services = host-secret-services // { fudo-secrets-watcher = { wantedBy = [ "multi-user.target" ]; description = "Ensure access for group ${cfg.secret-group} to fudo secret paths."; serviceConfig = { ExecStart = pkgs.writeShellScript "fudo-secrets-watcher.sh" (concatStringsSep "\n" (map (path: '' chown -R root:${cfg.secret-group} ${path} chmod -R u=rwX,g=rX,o= ${path} '') cfg.secret-paths)); }; }; }; paths.fudo-secrets-watcher = mkIf ((length cfg.secret-paths) > 0) { wantedBy = [ "multi-user.target" ]; description = "Watch fudo secret paths, and correct perms on changes."; pathConfig = { PathChanged = cfg.secret-paths; Unit = "fudo-secrets-watcher.service"; }; }; tmpfiles.rules = map (path: "d '${path}' - root ${cfg.secret-group} - -") cfg.secret-paths; }; }; }