FAQ

Here's a FAQ for reaction:

What is JSONnet? Why should I use it over YAML?

Answer

JSONnet already has a good tutorial to start with.

It permits to define functions, variables, etc, and thus to avoid duplication.

Other configuration languages exist, like Nix, Dhall, etc. You should feel at home if you already used one of those.

JSONnet semantics look close to Javascript too.

How do I write defaults at one place and reuse them elsewhere?

Answer

You first define your defaults set.

Here's an example for default options for a filter:

local filter_default = {
  retry: 3,
  retryperiod: '3h',
  actions: banFor('24h'),
};

Then you can use it in your filters:

{
  streams: {
    ssh: {
      filters: {
        failedlogin: filter_default + {
          regex: ['...'],
        },
      },
    },
  },
}

You can override those defaults:

{
  streams: {
    ssh: {
      filters: {
        failedlogin: filter_default + {
          regex: ['...'],
          // retry is overriden here
          retry: 1,
        },
      },
    },
  },
}

And the + is optional.

{
  streams: {
    ssh: {
      filters: {
        failedlogin: filter_default {
          regex: ['...'],
        },
      },
    },
  },
}

How do I add multiple actions defined by JSONnet functions on the same filter?

Answer

Let's take this example: we have two functions defining actions:

The first is made to ban the IP using linux's iptables firewall:

local banFor(time) = {
  ban: {
    cmd: ['ip46tables', '-w', '-A', 'reaction', '-s', '<ip>', '-j', 'DROP'],
  },
  unban: {
    after: time,
    cmd: ['ip46tables', '-w', '-D', 'reaction', '-s', '<ip>', '-j', 'DROP'],
  },
};

The second sends a mail:

local sendmail(text) = {
  mail: {
    cmd: ['sh', '-c', '/root/scripts/mailreaction.sh', text],
  },
};

Both create a set of actions. We want to merge the two sets.

To merge two sets with JSONnet, it's as easy as set1 + set2.

Let's see what it looks like with a real example.

local banFor(time) = {
  ban: {
    cmd: ['ip46tables', '-w', '-A', 'reaction', '-s', '<ip>', '-j', 'DROP'],
  },
  unban: {
    after: time,
    cmd: ['ip46tables', '-w', '-D', 'reaction', '-s', '<ip>', '-j', 'DROP'],
  },
};

local sendmail(text) = {
  mail: {
    cmd: ['sh', '-c', '/root/scripts/mailreaction.sh', text],
  },
};

{
  streams: {
    ssh: {
      filters: {
        failedlogin: {
          regex: [
            // skipping
          ],
          retry: 3,
          retryperiod: '3h',
          actions: banFor('720h') + sendmail('banned <ip> from service ssh'),
        },
      },
    },
  },
}

This will generate this configuration:

{
  "streams": {
    "ssh": {
      "filters": {
        "failedlogin": {
          "regex": [ ],
          "retry": 3,
          "retryperiod": "3h",
          "actions": {
            "ban": {
              "cmd": [ "ip46tables", "-w", "-A", "reaction", "-s", "<ip>", "-j", "DROP" ]
            },
            "unban": {
              "after": "720h",
              "cmd": [ "ip46tables", "-w", "-D", "reaction", "-s", "<ip>", "-j", "DROP" ]
            },
            "mail": {
              "cmd": [ "sh", "-c", "/root/scripts/mailreaction.sh", "banned <ip> from service ssh" ]
            }
          }
        }
      }
    }
  }
}

How do I separate my configuration in multiple files?

Answer

Starting with reaction v2.0.0, you can specify a folder containing multiple configuration files. reaction will read and merge all files that:

  • Do not start with . or _.
  • And that ends with .json, .jsonnet, .yml or .yaml.

JSONnet files starting with _ or . will not be directly read. You can manually import them in other files, for example to store definitions used accross files:

You can use the import JSONnet keyword to import files, relative to the file calling them. (Remember, the JSONnet tutorial is a good place to understand its basics).

Here's an example of how you could do this:

/etc/reaction/main.jsonnet

{
  streams: {
    ssh: {
      cmd: [ "tail", "-F", /* skip */ ],
    },
    // ...
  },
}

/etc/reaction/ssh.jsonnet

local lib = import '_lib.jsonnet';
{
  streams: {
    ssh: {
        filters: {
          failedlogin: lib.filter_default + {
          regex: ['...'],
          retry: 3,
          retryperiod: '2h',
          actions: lib.banFor('30d'),
        },
      },
    },
  },
}

/etc/reaction/_lib.jsonnet (definitions you can reuse in all other files)

local banFor(time) = {
  ban: {
    cmd: ['ip46tables', '-w', '-A', 'reaction', '-s', '<ip>', '-j', 'DROP'],
  },
  unban: {
    after: time,
    cmd: ['ip46tables', '-w', '-D', 'reaction', '-s', '<ip>', '-j', 'DROP'],
  },
};

local filter_default = {
  retry: 3,
  retryperiod: '3h',
  actions: banFor('24h'),
};

{
  banFor: banFor,
  filter_default: filter_default,
}

You can split different patterns, streams and filters accross files.

However, you can't spread the definition of one pattern or filter accross multiple files.

How do I use environment variables in actions?

Answer

Environment variables available to the reaction daemon are also available to stream's and actions's commands.

Specifying environment variables

You may specify Environment or EnvironmentFile attributes to the systemd file:

/etc/systemd/system/reaction.service or /etc/systemd/system/reaction.service.d/env.conf:

[Service]
Environment=MY_ENV_VAR=...
EnvironmentFile=/path/to/secrets/file

See man systemd.exec for more details. These options can be specified multiple times.

Using environment variables in scripts

Let's say you run a command with a custom shell script:

cmd: ['/usr/local/bin/my_script.sh', '<ip>']

You may use environment variables inside, for example: $MY_ENV_VAR.

echo "$MY_ENV_VAR"

For a Python script, variables are accessible with os.environ.

import os
print(os.environ['MY_ENV_VAR'])

Directly substituting environment variables in reaction actions

Note that this is not substituted, because this is a shell feature and reaction doesn't currently have any shell-like features:

# Doesn't work!
cmd: ['curl', '$HOOK_URL', '--json', '{ ... }']

See issue #127 for discussion.