Unverified Commit 46b87a8f authored by Ivan Mincik's avatar Ivan Mincik Committed by GitHub
Browse files

reaction: 2.2.1 -> 2.3.0; reaction-plugins: init (#490062)

parents a7b70888 4d4b662a
Loading
Loading
Loading
Loading
+112 −13
Original line number Diff line number Diff line
@@ -7,10 +7,21 @@
let
  settingsFormat = pkgs.formats.yaml { };

  cfg = config.services.reaction;

  inherit (lib)
    mkOption
    concatMapStringsSep
    filterAttrs
    getExe
    mkDefault
    mkEnableOption
    mkIf
    mkOption
    mkPackageOption
    mapAttrs
    optional
    optionals
    optionalString
    types
    ;
in
@@ -30,7 +41,65 @@ in
      default = { };
      type = types.submodule {
        freeformType = settingsFormat.type;
        options = { };
        options = {
          plugins = mkOption {
            description = ''
              Nixpkgs provides a `reaction-plugins` package set which includes both offical and community plugins for reaction.

              To use the plugins in your module configuration, in `settings.plugins` you can use for e.g. `''${lib.getExe reaction-plugins.reaction-plugin-ipset}`
              See https://reaction.ppom.me/plugins/ to configure plugins.
            '';
            default = { };
            type = types.attrsOf (
              types.submodule (
                { name, ... }:
                {
                  options = {
                    enable = mkOption {
                      description = "enable reaction-plugin-${name}";
                      type = types.bool;
                      default = true;
                    };
                    path = mkOption {
                      description = "path to the plugin binary";
                      type = types.str;
                      default = "${cfg.package.plugins."reaction-plugin-${name}"}/bin/reaction-plugin-${name}";
                      defaultText = lib.literalExpression ''''${cfg.package.plugins."reaction-plugin-${name}"}/bin/reaction-plugin-${name}'';
                    };
                    check_root = mkOption {
                      description = "Whether reaction must check that the executable is owned by root";
                      type = types.bool;
                      default = true;
                    };
                    systemd = mkOption {
                      description = "Whether reaction must isolate the plugin using systemd's run0";
                      type = types.bool;
                      default = cfg.runAsRoot;
                      defaultText = "config.services.reaction.runAsRoot";
                    };
                    systemd_options = mkOption {
                      description = ''
                        A key-value map of systemd options.
                        Keys must be strings and values must be string arrays.

                        See `man systemd.directives` for all supported options, and particularly options in `man systemd.exec`
                      '';
                      type = types.attrsOf (types.listOf types.str);
                      default = { };
                    };
                  };
                }
              )
            );
            # Filter plugins which are disabled
            apply =
              self:
              lib.pipe self [
                (filterAttrs (name: p: p.enable))
                (mapAttrs (name: p: removeAttrs p [ "enable" ]))
              ];
          };
        };
      };
    };

@@ -113,25 +182,39 @@ in
          services.openssh.settings.LogLevel = lib.mkDefault "VERBOSE";
        }
        ```

        ```nix
        # core ipset plugin requires these if running as non-root
        systemd.services.reaction.serviceConfig = {
          CapabilityBoundingSet = [
            "CAP_NET_ADMIN"
            "CAP_NET_RAW"
            "CAP_DAC_READ_SEARCH" # for journalctl
          ];
          AmbientCapabilities = [
            "CAP_NET_ADMIN"
            "CAP_NET_RAW"
            "CAP_DAC_READ_SEARCH"
          ];
        };
        ```
      '';
    };
  };

  config =
    let
      cfg = config.services.reaction;

      generatedSettings = settingsFormat.generate "reaction.yml" cfg.settings;
      settingsDir = pkgs.runCommand "reaction-settings-dir" { } ''
        mkdir -p $out
        ${lib.concatMapStringsSep "\n" (file: ''
        ${concatMapStringsSep "\n" (file: ''
          filename=$(basename "${file}")
          ln -s "${file}" "$out/$filename"
        '') cfg.settingsFiles}
        ln -s ${generatedSettings} $out/reaction.yml
      '';
    in
    lib.mkIf cfg.enable {
    mkIf cfg.enable {
      assertions = [
        {
          assertion = cfg.settings != { } || (builtins.length cfg.settingsFiles) != 0;
@@ -139,7 +222,7 @@ in
        }
      ];

      users = lib.mkIf (!cfg.runAsRoot) {
      users = mkIf (!cfg.runAsRoot) {
        users.reaction = {
          isSystemUser = true;
          group = "reaction";
@@ -148,27 +231,29 @@ in
      };

      system.checks =
        lib.optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform)
        optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform)
          (
            pkgs.runCommand "reaction-config-validation" { } ''
              ${lib.getExe cfg.package} test-config -c ${settingsDir} >/dev/null
              ${getExe cfg.package} test-config -c ${settingsDir} >/dev/null
              echo "reaction config ${settingsDir} is valid"
              touch $out
            ''
          );

      systemd.services.reaction = {
        description = "Scan logs and take action";
        description = "A daemon that scans program outputs for repeated patterns, and takes action.";
        documentation = [ "https://reaction.ppom.me" ];
        after = [ "network.target" ];
        wantedBy = [ "multi-user.target" ];
        partOf = lib.optionals cfg.stopForFirewall [ "firewall.service" ];
        partOf = optionals cfg.stopForFirewall [ "firewall.service" ];
        path = [ pkgs.iptables ];
        serviceConfig = {
          Type = "simple";
          KillMode = "mixed"; # for plugins
          User = if (!cfg.runAsRoot) then "reaction" else "root";
          ExecStart = ''
            ${lib.getExe cfg.package} start -c ${settingsDir}${
              lib.optionalString (cfg.loglevel != null) " -l ${cfg.loglevel}"
            ${getExe cfg.package} start -c ${settingsDir}${
              optionalString (cfg.loglevel != null) " -l ${cfg.loglevel}"
            }
          '';

@@ -197,6 +282,20 @@ in
        };
      };

      # pre-configure official plugins
      services.reaction.settings.plugins = {
        ipset = {
          enable = mkDefault true;
          systemd_options = {
            CapabilityBoundingSet = [
              "~CAP_NET_ADMIN"
              "~CAP_PERFMON"
            ];
          };
        };
        virtual.enable = mkDefault true;
      };

      environment.systemPackages = [ cfg.package ];
    };

+4 −2
Original line number Diff line number Diff line
@@ -1386,8 +1386,10 @@ in
  rasdaemon = runTest ./rasdaemon.nix;
  rathole = runTest ./rathole.nix;
  rauc = runTest ./rauc.nix;
  reaction = runTest ./reaction.nix;
  reaction-firewall = runTest ./reaction-firewall.nix;
  reaction = import ./reaction {
    inherit (pkgs) lib;
    inherit runTest;
  };
  readarr = runTest ./readarr.nix;
  readeck = runTest ./readeck.nix;
  realm = runTest ./realm.nix;
+2 −1
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@
    services.reaction = {
      enable = true;
      stopForFirewall = false;
      # example.jsonnet/example.yml can be copied and modified from ${pkgs.reaction}/share/examples
      # example.jsonnet or example.yml can be copied and modified from ${pkgs.reaction}/share/examples
      settingsFiles = [ "${pkgs.reaction}/share/examples/example.jsonnet" ];
      runAsRoot = false;
    };
@@ -92,6 +92,7 @@
    {
      # not needed, only for manual interactive debugging
      virtualisation.memorySize = 4096;
      virtualisation.graphics = false;
      environment.systemPackages = with pkgs; [
        btop
        sysz
+6 −0
Original line number Diff line number Diff line
{ lib, runTest }:
lib.recurseIntoAttrs {
  basic = runTest ./basic.nix;
  firewall = runTest ./firewall.nix;
  plugins = runTest ./plugins.nix;
}
+9 −0
Original line number Diff line number Diff line
@@ -17,6 +17,12 @@
          # "${pkgs.reaction}/share/examples/example.yml" # can't specify both because conflicts
        ];
        runAsRoot = true;
        settings = {
          # In the qemu vm `run0 ls` as root prints nothing, so we can't use it
          # see https://reaction.ppom.me/reference.html#systemd
          plugins.ipset.systemd = false;
          plugins.virtual.systemd = false;
        };
      };
      networking.firewall.enable = true;
    };
@@ -68,6 +74,9 @@
  # - nix run .#nixosTests.reaction-firewall.driverInteractive -L
  # - run_tests()
  interactive.sshBackdoor.enable = true; # ssh -o User=root vsock%3
  interactive.nodes.machine = _: {
    virtualisation.graphics = false;
  };

  meta.maintainers =
    with lib.maintainers;
Loading