SNARE.CONF(5)

NAME
snare.conf - snare configuration file

DESCRIPTION
snare.conf is the configuration file for snare(1). It consists of one or more top-level options and one GitHub block.

The top-level options are:

listen = "address";
    is a mandatory address and port number to listen on. The format of address is either:

    x.x.x.x:port
        an IPv4 address and port. For example, ‘0.0.0.0:8765’ will listen on port 8765 on for all IPv4 addresses.

    [x:x:x]:port
        an IPv6 address and port. For example, ‘[::]:8765’ will listen on port 8765 for all IPv4 and IPv6 addresses.

maxjobs = int;
    is an optional non-zero positive integer specifying the maximum number of jobs to run in parallel. Defaults to the number of CPUs in the machine.

user = "user-name";
    is an optional username that snare.conf will try and change into after it has bound to a network port. Note that snare.conf will refuse to run as root unless user is specified. As part of changing user, snare.conf:

    •   changes its uid, euid, suid to the UID of user-name.

    •   changes its gid, egid, sgid to the primary GID of user-name.

    •   sets the $HOME environment variable to the home directory of user-name.

    •   sets the $USER environment variable to user-name.

    All other environment variables are passed through to commands unchanged.

github { ... }
    specifies GitHub specific options.

A ‘github’ block supports the following options:

match "regex" { match-options }
    where regex is a regular expression in Rust regex format: https://docs.rs/regex/ that must match against a "owner/repo" full repository name. If it matches, then match-options are applied. "regex" must match against the full repository name: in other words, it is equivalent to ^regex$. Thus the regex "a/b" does not match against the full repository name "a/bc", but the regex "a/b.*" does match against "a/bc".

A ‘match’ block supports the following options:

cmd = "shell-cmd";
    optionally specifies a command to be run. shell-cmd will be executed via ‘$SHELL -c’. The following escape sequences are recognised and replaced before execution:

    %e  the GitHub event type (e.g. ‘pull_request’).

    %j  the path to the GitHub JSON.

    %o  the repository owner.

    %r  the repository.

    %%  a literal ‘%’.

    Note that ‘%’ may not be followed by any character other than those above.

    The escape sequences are guaranteed to satisfy the regular expression "[a-zA-Z0-9._-]+" and not to be the strings "". or "..". This means that they are safe to pass as shell arguments and/or to be included in file system paths.

errorcmd = "shell-cmd";
    optionally specifies a command to be run when a job exits unsuccessfully. shell-cmd will be executed via ‘$SHELL -c’. The following escape sequences are recognised and replaced before execution:

    %e  the GitHub event type (e.g. ‘pull_request’).

    %j  the path to the GitHub JSON.

    %o  the repository owner.

    %r  the repository.

    %s  the path to the file containing the job's combined stderr / stdout.

    %x  the exit type: "status" (i.e. normal exit); "signal"; or "unknown".

    %?  the exit status / signal number (either an integer or the literal string "unknown") that cmd failed with.

    %%  a literal ‘%’.

    Note that ‘%’ may not be followed by any character other than those above.

    The escape sequences are guaranteed to satisfy the regular expression "[a-zA-Z0-9._-]+" and not to be the strings "". or "..". This means that they are safe to pass as shell arguments and/or to be included in file system paths.

queue = (evict | parallel | sequential);
    specifies what to do when multiple requests for the same repository are queued at once:

    evict
        only run one job for this repository at a time. Additional jobs will stay on the queue: if a new job comes in for that repository, it evicts any previously queued jobs for that repository. In other words, for this repository there can be at most one running job and one queued job at any point.

    parallel
        run as many jobs for this repository in parallel as possible.

    sequential
        only run one job for this repository at a time. Additional jobs will stay on the queue and be executed in FIFO order.

    The default match block sets this to sequential, which is always safe, though at the possible expense of lower job throughput for any given repository.

secret = "secret";
    is the optional GitHub secret used to sign the webhook request. This allows snare.conf to tell the difference between genuine webhook requests and those from malfeasants. Although this is optional, we highly recommend setting it in all cases. Note also that if a GitHub request is signed, but you have not specified a secret, then snare will return the request as “unauthorised” to remind you to use the secret at both ends.

timeout = period;
    specifies the elapsed time, as a positive integer, in seconds that a process can run before being sent SIGTERM. The default match block sets this to one hour (3600 seconds).

match blocks are evaluated in order from top to bottom with each successful match overriding previous settings. A default match block is inserted before any user match blocks:

    match ".*" {
      queue = sequential;
      timeout = 3600;
    }

EXAMPLES
The minimal recommended snare.conf file is as follows:

    listen = "<address>:<port>";
    github {
      match ".*" {
        cmd = "/path/to/prps/%o/%r %e %j";
        errorcmd = "cat %s | mailx -s \"snare error: github.com/%o/%r\" someone@example.com";
        secret = "<secret>";
      }
    }

where "/path/to/prps" is a path to a directory where per-repo programs are stored. Each repository then has a unique program "%o/%r" which will be executed with two arguments: the GitHub event; and the path to the GitHub JSON. If a job exits unsuccessfully then an email will be sent to someone@example.com containing the job's comined stderr and stdout output (assuming that a suitable sendmail clone has been installed and activated).

The top-to-bottom evaluation of match blocks allow users to specify defaults which are only overridden for specific repositories. For example, for the following configuration file:

    listen = "<address>:<port>";
    github {
      match ".*" {
        cmd = "/path/to/prps/%o/%r %e %j";
        errorcmd = "cat %s | mailx -s \"snare error: github.com/%o/%r\" abc@def.com";
        secret = "sec";
      }
      match "a/b" {
        errorcmd = "lpr %s";
      }
    }

the following repositories will have these settings:

    a/b:
      queue = sequential
      timeout = 3600
      cmd = "/path/to/prps/%o/%r %e %j";
      errorcmd = "lpr %s";
      secret = "sec"
    c/d:
      queue = sequential
      timeout = 3600
      cmd = "/path/to/prps/%o/%r %e %j";
      errorcmd = "cat %s | mailx -s \"snare error: github.com/%o/%r\" abc@def.com";
      secret = "sec"

The following program expects to be called with an event and a JSON path (i.e. "%e %j") and uses shell script to send a list of commits and diffs to the address specified in $EMAIL on each “push” to master. It works for any public GitHub repository:

    #! /bin/sh
    set -euf
    # A list of email addresses separated by spaces.
    EMAILS="someone@example.com someone.else@example.com"
    # A GitHub URL either https or git.
    REPO_URL="git@github.com:owner/repo.git"
    if [ "$1" != "push" ]; then
        exit 0
    fi
    ref=`jq .ref "$2" | tr -d '
    if [ "$ref" != "refs/heads/master" ]; then
        exit 0
    fi
    repo_fullname=`jq .repository.full_name "$2" | tr -d '
    repo_url=`jq .repository.html_url "$2" | tr -d '
    before_hash=`jq .before "$2" | tr -d '
    after_hash=`jq .after "$2" | tr -d '
    echo "$before_hash" | grep -E "^[a-fA-F0-9]+$" 2>&1 > /dev/null
    echo "$after_hash" | grep -E "^[a-fA-F0-9]+$" 2>&1 > /dev/null
    git clone "$REPO_URL" repo
    cd repo
    for email in `echo "$EMAILS"`; do
        git log --reverse -p "$before_hash..$after_hash" \
          | mail -s "Push to $repo_fullname" "$email"
    done

where jq: https://stedolan.github.io/jq/ is a command-line JSON processor. Depending on your needs, you can make this type of script arbitrarily more complex and powerful (for example, not cloning afresh on each pull).

Note that this program is deliberately untrusting of external input: it is careful to quote all arguments obtained from JSON; and it uses a fixed directory name (“repo”) rather than a file name from JSON that might include characters (such as “../..”) that would cause the script to leak data about other parts of the file system.

SEE ALSO
snare(1)

GitHub's webhooks documentation: https://developer.github.com/webhooks/.

AUTHORS
-nosplit snare(1) was written by Laurence Tratt https://tratt.net/laurie/