flagger(8)
NAME
flagger - ban hosts that abuse services, by watching the systemd journal
SYNOPSIS
flagger [--config PATH] [--rules-dir DIR]... [--dry-run]
flagger --check [--config PATH] [--rules-dir DIR]...
flagger --test LINE [--rules-dir DIR]...
DESCRIPTION
Put sshd on a public address and within minutes something is guessing passwords at it. It won't get in if your passwords are any good, but it fills your journal, it burns CPU on key exchanges, and it never stops. flagger makes it stop, for a while.
It reads the systemd journal, picks out the messages that say somebody is up to no good, and once an address has done enough of that, it drops every packet from it in the firewall. The journal does the filtering, regular expressions do the matching, and nftables does the blocking. That's the whole design, and it's the same one tallow(1) had. flagger is a rewrite of it.
How does it decide?
Each rule file names a service and a list of patterns. A message from that service that matches a pattern gives up an IP address, and that address gets the rule's score added to its running total. When the total reaches 1.0, the address is banned: it goes into an nftables set, and packets from it are dropped until the set entry expires.
More than one rule can match a message, and then all of their scores count. An sshd root password failure matches both the general failure rule and the root-specific one, and that's on purpose.
A rule can also carry a ban. That drops the address for a short fixed time on every match, without touching the score. tallow(1) called this a throttle. It's for messages that are suspicious on their own but not conclusive.
The rule format is in flagger.rules(5).
Doesn't it just come back an hour later?
Usually, yes. So repeat offenders get longer bans each time. The first ban lasts expires, and each one after that is escalate.factor times longer, up to escalate.max. An address is forgotten escalate.forget after its last ban ended, and then its ladder starts from the bottom again.
Offenders are also grouped by network: an IPv4 /24 and an IPv6 /64 by default. Bans already earned in a network make every other address in it score higher, and push that address further up its own ladder. A first offense next to three banned neighbours is banned as if it were a fourth offense.
That never bans an address that has done nothing itself. The penalty is a multiplier, and any multiple of no activity is still none. See [subnet] in flagger.toml(5).
Both the ladder and the neighbourhood history are kept in a state file, so a restart or a reboot doesn't hand every offender a clean slate.
What does it do to my firewall?
It creates one nftables table and manages it for as long as the daemon runs:
table inet flagger chain input -- type filter hook input priority filter; policy accept set v4 -- ipv4_addr, with timeouts set v6 -- ipv6_addr, with timeouts
Two rules in the chain drop traffic whose source address is in the matching set. A banned address is a set element with its own timeout, so the kernel expires it, not flagger.
The table is created at startup and deleted on a clean shutdown. If you'd rather own the ruleset yourself, set manage_rules = false in flagger.toml(5). That page lists the exact commands to build it by hand.
flagger needs CAP_NET_ADMIN and an nft binary, and nothing else that's privileged. The shipped unit drops everything else. If nft doesn't work at startup, the daemon refuses to start rather than run without teeth.
OPTIONS
With no mode flag, flagger runs as the daemon. The two modes exist so you can work on rules and configuration without root, without the journal, and without touching the firewall.
--check- Parse flagger.toml(5) and every rule file, print the resolved configuration and the rule inventory, and exit. It exits non-zero if anything fails to parse, or if no rules were loaded at all. Run this after every edit, before you restart the service.
--testLINE- Run LINE past every loaded rule, whatever service the rule is for, and report each one that matches: its score, its ban, the address it pulled out, and the pattern as written in its file. See EXIT STATUS.
--configPATH- Read the configuration from PATH instead of
/etc/flagger/flagger.toml. A missing default config isn't an error, you just get the built-in defaults. A missing PATH that you named here is an error, because you asked for it by name. --rules-dirDIR- Read rules from DIR. Can be repeated. Directories are read in order, and a file name in a later directory replaces the same file name from an earlier one. Passing this at all replaces the default search path entirely, and a directory you name here has to exist.
--dry-run- Score messages and log every decision, but never create firewall rules, ban anything, or tear anything down. The state file is still read and written. Use this to find out what
flaggerwould have done to your users before you let it do it. -V,--version- Print the version and exit.
-h,--help- Print usage and exit.
SIGNALS
SIGTERM,SIGINT- Write the state file, remove the firewall table created at startup, and exit. The bans leave the firewall with it, but they come back from the state file when
flaggerstarts again. SIGUSR1- Write the tracking table to the journal: every address being watched, its score, its offense count, how long any ban has left, and the neighbourhood it's in, followed by the ban count of each tracked network. Unlike tallow(1), this works in ordinary builds.
EXIT STATUS
In daemon and --check mode, 0 on success and 1 on failure.
--test has three outcomes, so you can script a rule test suite around it:
0- A rule matched and pulled out a valid address.
1- No rule matched the line. That's an answer, not necessarily a fault.
2- A rule matched, but what it captured isn't an IP address. That's a bug in the rule: its pattern is matching text it shouldn't.
FILES
/etc/flagger/flagger.toml- Configuration. Optional. See flagger.toml(5).
/etc/flagger/rules.d/*.toml- Your own rules, and overrides of shipped ones by file name.
/usr/share/flagger/rules.d/*.toml- Rules shipped with the package. Don't edit these. A file with the same name under
/etcreplaces one here entirely. /var/lib/flagger/state.toml- Scores, offense counts and per-network history. Written on exit and periodically while running. Losing it costs accuracy, not correctness.
EXAMPLES
Check the configuration after editing it:
flagger --check
Find out whether a log line would be caught, and by which rule:
$ flagger --test 'Invalid user admin from 192.0.2.10 port 55232' sshd.toml:3 score 0.2 ban 10s ip 192.0.2.10 Invalid user .* from %ip% port \d+ 1 of 12 rules matched
The :3 is the rule's position in its file, counting [[rule]] blocks from 1.
Watch what it would do for a day, without doing any of it:
flagger --dry-run
Ask a running daemon what it's tracking:
systemctl kill -s USR1 flagger journalctl -u flagger -n 50
BUGS
flagger is NOT A SECURITY SOLUTION. It cuts down log clutter and the resources wasted on hostile traffic, and it pays for that by sometimes denying access to valid users. It does nothing about a password guess that succeeds.
Think about what it is: a program whose entire job is to create a denial of service. Put it on a host whose real users connect from lots of changing addresses, and sooner or later it will lock one of them out. A mistyped username is enough. Put the addresses that must never be banned in allow in flagger.toml(5), and run with --dry-run before you trust it with a service that matters.
SEE ALSO
flagger.toml(5), flagger.rules(5), nft(8), systemd.journal-fields(7), sd-journal(3), systemd-journald.service(8)