# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0

{ pkgs, config, depot, lib, ... }:
let
  inherit (lib) mkOption types mkBefore optionalAttrs mkDefault mapAttrs;

  secrets = lib.mapAttrsToList (name: cOrig: cOrig // { inherit name; }) config.my.vault.secrets;

  secretsGroups = lib.unique (map (c: c.group) secrets);
  secretsRoot = "/var/lib/secrets";

  secretsTemplate = map (c: {
    contents = c.template;
    destination = c.path;
    perms = "0640";
    command = let
    in pkgs.writeShellScript "post-secret-${c.name}" ''
      chgrp "${c.group}" "${c.path}"
      ${lib.concatMapStringsSep "\n" (x: ''
        /run/current-system/sw/bin/systemctl reload-or-restart ${x}
      '') c.reloadOrRestartUnits}
      ${lib.concatMapStringsSep "\n" (x: ''
        /run/current-system/sw/bin/systemctl restart ${x}
      '') c.restartUnits}
      ${lib.optionalString (c.command != "") c.command}
    '';
  }) secrets;

  secretsTmpdirs = [
    "d ${secretsRoot} 0111 vault-agent vault-agent - -"
  ] ++ map (c: "d ${c.parentDir} 0750 vault-agent ${c.group} - -") secrets;

  allRestartableUnits = lib.unique (builtins.concatMap (c: c.reloadOrRestartUnits ++ c.restartUnits) secrets);
in
{
  imports = [
    ./vault-agent.nix
  ];

  options.my.vault.secrets = mkOption {
    type = with types; attrsOf (submodule ({ name, config, ...}: {
      options = {
        name = mkOption {
          type = str;
          default = name;
          readOnly = true;
        };
        parentDir = mkOption {
          type = path;
          default = "${secretsRoot}/${config.name}";
          readOnly = true;
        };
        path = mkOption {
          type = path;
          default = "${config.parentDir}/secret";
          readOnly = true;
        };

        template = mkOption {
          type = lines;
          description = "Template to use for generating secret output.";
        };

        command = mkOption {
          type = lines;
          default = "";
          description = "Command to run after writing the secrets file.";
        };
        reloadOrRestartUnits = mkOption {
          type = listOf str;
          default = [];
          description = "List of systemd units to reload/restart after writing the secrets file.";
        };
        restartUnits = mkOption {
          type = listOf str;
          default = [];
          description = "List of systemd units to restart after writing the secrets file.";
        };

        group = mkOption {
          type = str;
          description = "Owner group to set for the generated file.";
        };
      };
    }));
    default = {};
  };

  config = {
    my.vault.settings = {
      template = mkBefore secretsTemplate;
    };

    systemd = optionalAttrs config.my.vault.enable {
      services.vault-agent = {
        serviceConfig = {
          SupplementaryGroups = mkBefore secretsGroups;
        };
        unitConfig = {
          Before = mkBefore allRestartableUnits;
        };
        wantedBy = mkBefore allRestartableUnits;
      };

      tmpfiles.rules = secretsTmpdirs;
    };

    security.polkit.extraConfig = lib.mkAfter ''
      // NixOS module: depot/lib/vault-agent-secrets.nix
      polkit.addRule(function(action, subject) {
        if (action.id !== "org.freedesktop.systemd1.manage-units" ||
            subject.user !== "vault-agent") {
          return polkit.Result.NOT_HANDLED;
        }

        var verb = action.lookup("verb");
        if (verb !== "restart" && verb !== "reload-or-restart") {
          return polkit.Result.NOT_HANDLED;
        }

        var allowedUnits = ${builtins.toJSON allRestartableUnits};
        var unit = action.lookup("unit");
        for (var i = 0; i < allowedUnits.length; i++) {
          if (allowedUnits[i] === unit) {
            return polkit.Result.YES;
          }
        }
        return polkit.Result.NOT_HANDLED;
      });
    '';
  };
}