46 Commits
v4 ... v18

Author SHA1 Message Date
Auke Kok 83201e8b32 v18 2019-10-28 14:27:31 -07:00
Auke Kok 32fc0ecdaa Hide unwanted firewalld-cmd error messages. 2019-10-28 14:27:02 -07:00
Auke Kok 79f89d8b79 v17 2019-10-22 13:39:34 -07:00
puneetse ab57cc5dd7 Add firewalld support
Adds support to use firewalld if it is running and updates documentation accordingly.
2019-10-22 13:37:21 -07:00
puneetse 96fa692bf6 Fix command order in tallow.conf man page
An ipset needs to be created before an iptables rule can reference it.
2019-10-22 13:37:21 -07:00
Auke Kok 71e0fc6cca Add json-c to travis. 2019-10-03 14:04:30 -07:00
Boris Manojlovic 31205d7f16 make older compilers a bit happier 2019-08-26 14:08:04 -07:00
Boris Manojlovic 43998632aa add dovecot as postfix auth backend parsing 2019-08-26 14:08:04 -07:00
Auke Kok e4b3977704 Ensure we don't replay old events.
Based on the RTC timestamp in the journal message, discard events
that happened before events we already processed.

This ensures that when the journal rotates, we won't reprocess
events again.
2019-04-25 13:23:03 -07:00
Auke Kok feee1a2556 Add example whitelist defaults. 2019-04-19 14:36:46 -07:00
Auke Kok 35182b8447 Force insert iptables rules as rule #1.
This will better work together with other firewall tools.
2019-02-19 09:47:54 -08:00
Auke Kok d29132144c Debug: print path to skipped file, not the other one. 2019-02-19 09:44:44 -08:00
Auke Kok 0a0a912c70 Move src files to /src/. 2019-01-23 15:56:17 -08:00
Auke Kok 6545cb0d33 Move man pages to /man/ folder. 2019-01-23 15:52:25 -08:00
Auke Kok fdaa8ed34c v16 2019-01-23 15:19:50 -08:00
Auke Kok ea34b5b78c Return memory to the OS on prune. 2019-01-23 15:17:14 -08:00
Auke Kok 33191b04bf Run as nice by default. 2019-01-23 15:14:46 -08:00
Auke Kok 9174590b04 Convert patterns to JSON input files.
Tallow will now read JSON files from /usr/share/tallow/ and /etc/tallow
and parse them to retrieve filters and patterns. The sshd patterns
are converted to JSON and used to test this change.

If a file exists in /etc/tallow with the same name as a file in
/usr/share/tallow, only the file in /etc/tallow will be parsed.

This change allows much more dynamic insertion of rules and people
to create custom patterns and filters and monitor the logs of other
daemons besides sshd that may be subject to brutefoce login attempts.

Potential use cases:
- IMAP/POP services
- SMTP
- HTTP services permitted they log to syslog
- DNS servers logging malformed requests
- etc.
2019-01-23 13:55:06 -08:00
Auke Kok 14152b1dad Re-create docs. 2018-10-04 11:35:59 -07:00
Auke Kok e2f92ff75b Add 10. and 192.168. as default whitelist entries to tallow.
These entries can be removed from the whitelist by adding any
whitelist entry to the config file. If you add any entry, you
must repeat these in order to have them included, otherwise those
entries are not added to the custom list.
2018-10-04 11:34:47 -07:00
Auke Kok 4ff1206974 Tune down badness for attempts a small bit.
We're still blocking really agressively. Tune it down a notch
and make timeouts a bit less for human-error like conditions
(forgotten key, ^C etc).
2018-10-04 11:34:47 -07:00
ahkok 8c836013cd Merge pull request #8 from puneetse/master
Minor typo on tallow.conf man page
2018-10-04 11:30:39 -07:00
puneetse a6fb19ff4f Minor typo on tallow.conf man page 2018-06-25 16:27:25 -07:00
Auke Kok 5503ff0b20 Possibly handle journald restarts better.
I've encountered two runaway tallow daemons now that seem to
coincide with journald restarts that send it spinning tight
on the `continue` statement and hitting the same _get_data()
error (ENOENT).

I'm unsure if the `break` will fix it, but the `continue`
is definitely broken here. Hopefully the `sd_journal_wait()`
will properly reassess the journal state and notify us of
rotations or other issues.
2018-06-25 12:13:30 -07:00
Auke Kok 8655223248 Remove HUP/TERM/INT sighandler, shield USR1 sighandler.
We remove, by default, all signal handlers. The USR1 handler
remains, but is shielded behind `#ifdef DEBUG`.
2018-03-28 09:44:06 -07:00
Auke Kok 76a59df0f1 Fine-tune blocking rules one notch down.
Before, most rules would block on the 3rd rule hit, with this,
it's 4, which means 1 extra failure before a 1hr block is started
and this is a bit more sympathetic towards `typo` failures.
2018-03-23 16:17:34 -07:00
Auke Kok 36946deeee v8 2018-02-05 11:11:17 -08:00
Auke Kok d51577bd4f Add one more preauth disconnect pattern.
This pattern has been recurring a lot recently and does not
get dropped as expected. It is another typical preauth failure.
2018-02-05 11:02:59 -08:00
Auke Kok e15bfe4dc0 Document USR1, update man page output.
Some minor adjustments to Makefile.am to make this passable.
2018-01-19 10:25:20 -08:00
Auke Kok d6e53e6f3a Rename to proper extension. 2018-01-19 09:39:50 -08:00
Auke Kok a2a6d8e9ac v7. 2018-01-10 10:29:53 -08:00
Auke Kok c5569176d2 Allow whitelist patterns.
This allows for some simple form of netmask type patterning
which will work for /8, /16 and /24 subnets to be whitelisted
for ipv4, and for any multiple of /32 subnets for ipv6.
2018-01-08 16:06:26 -08:00
Auke Kok bf81c259b0 Add 'nocreate' option to bypass all rule/set creation.
This may help in situations where external tools are used to maintain
iptables or ipset setups and we should not disturb them by creating
rules.
2018-01-05 12:57:45 -08:00
Auke Kok ff2a47756f v6 2017-12-14 14:52:35 -08:00
Auke Kok b18f636489 Assure blocked IP's expire before they can be detected again.
Otherwise, in rare conditions, an IP address may appear just
before it would expire, which would cause the IP to not be
blocked again.
2017-12-14 14:31:41 -08:00
Auke Kok 6e0251d3dc Update manual pages to indicate the various recent changes. 2017-12-14 10:00:44 -08:00
Auke Kok 60a90adbc5 Instant throttling of confirmed abuse.
Automatically `block` certain clients based on severity of the
produced error messages. These clients are for sure doing something
bad, and we don't want to let them try this more times before dropping
their packets.

The block is issued immediately, but it only lasts a short time.
Most likely, additional messages will come in after that cause a
longer ban anyway.

This also forces overwriting of ipset entries without warning, which
helps to keep the ipset list in sync without further statekeeping.

The pattern list has been expanded with the instant_block integer
value, which indicates that if the pattern matches, the IP should be
dropped for how many seconds.
2017-12-14 09:57:52 -08:00
Auke Kok 1f43bcbf12 Debug code for pruning. 2017-12-13 15:02:13 -08:00
Auke Kok 8da71a2184 Remove multiple block spam.
Some minor cleanups in here, and 2 extra rules. This now prevents
multiple messages coming in and causing 2 block commands to be
issued.
2017-12-12 15:26:30 -08:00
Auke Kok 38b09c3b07 Re-add debug printout of state, move various prints to debug build.
Compiling with -DDEBUG=1 will now create an extra verbose version
that can be used to debug the pattern matching in more detail.

The non-debug build is now less verbose, as a result.

Send a USR1 signal to the process to make it dump the current
state table.
2017-12-11 16:47:59 -08:00
Auke Kok 20f4c970de Add 6 more relevant SSHD patterns.
Some of these come with a higher weight, as they're very obvious
points of abuse/probing, like attempting to use old protocols or not
being able to use modern key types.
2017-12-11 16:47:32 -08:00
Auke Kok 32c20f190f Allow multiple patterns.
We do not want to rely solely on one pattern for detecting login
attempts. This change creates a simple static list with patterns that
have a weight. If the pattern matches, the weight is added to the IP
score total. If the score total exceeds the max, the IP is blocked.

Previously we blocked on count=3, now we block when score=1.0.
The weight from the standard invalid user login is now dropped to
0.4 to have the same effect.

The `threshold` parameter is now therefore obsolete, and if found in
the config file, it will be ignored.
2017-12-11 14:04:14 -08:00
Gwenn Gueguen e3e4388654 Replace path_iptables with ipt_path in sample tallow.conf 2017-05-22 13:46:27 -07:00
Auke Kok 8a05303802 v5 2017-05-17 17:18:01 -07:00
Auke Kok e296f501c7 Use pcre to match logs, and find IP addresses.
This is a much more reliable method to extract the IP address
from the log entries, and allows us to consolidate 2 matches into
a single operation.

Once matched, we extract the IP substring and pass it to `find()`
as usual. We can add more regexes later if that is useful.
2017-05-16 15:55:18 -07:00
Auke Kok 379f74a071 Fix Travis.
Check in the man pages so we don't need to run `ronn` on CI.
2017-05-11 11:24:20 -07:00
21 changed files with 1299 additions and 525 deletions
+2 -5
View File
@@ -10,12 +10,9 @@ depcomp
install-sh
missing
tallow
tallow-*.tar.gz
tallow-*.tar.*
tallow-*/
tallow.conf.5
tallow.1
tallow.1.html
tallow.o
*.o
tallow.service
*~
DEADJOE
+7 -9
View File
@@ -1,24 +1,22 @@
sudo: required
dist: trusty
compiler:
- gcc
os:
- linux
language: c
compiler: gcc
os: linux
before_script:
./autogen.sh
language: c
- ./autogen.sh
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libsystemd-journal-dev
- valgrind
- autoconf
- automake
- libjson-c-dev
script:
- ./configure && make && make distcheck
+28 -12
View File
@@ -1,26 +1,42 @@
AM_CFLAGS = -g $(LIBSYSTEMD_CFLAGS) $(LIBSYSTEMD_JOURNAL_CFLAGS) -Wall -Wno-uninitialized -W -D_FORTIFY_SOURCE=2
AM_CFLAGS = -g \
$(JSON_C_CFLAGS) $(PCRE_CFLAGS) $(LIBSYSTEMD_CFLAGS) \
-Wall -Wno-uninitialized -W -D_FORTIFY_SOURCE=2
AM_CPPFLAGS = \
-DDATADIR='"$(datadir)"' -DSYSCONFDIR='"$(sysconfdir)"'
systemdsystemunitdir = @SYSTEMD_SYSTEMUNITDIR@
systemdsystemunit_DATA = tallow.service
systemdsystemunit_DATA = data/tallow.service
sbin_PROGRAMS = tallow
tallow_SOURCES = tallow.c
tallow_LDADD = $(LIBSYSTEMD_LIBS) $(LIBSYSTEMD_JOURNAL_LIBS)
tallow_SOURCES = \
src/tallow.c \
src/json.c \
src/json.h \
src/data.c \
src/data.h
tallow_LDADD = $(JSON_C_LIBS) $(PCRE_LIBS) $(LIBSYSTEMD_LIBS)
EXTRA_DIST = AUTHORS COPYING INSTALL tallow.service.in tallow.1.md
pkgdata_DATA = data/sshd.json
dist_man_MANS = tallow.1 tallow.conf.5
EXTRA_DIST = \
AUTHORS COPYING INSTALL README.md \
data/tallow.service.in \
data/sshd.json \
man/tallow.conf.5.md \
man/tallow.1.md
dist_man_MANS = man/tallow.1 man/tallow.conf.5
dist_doc_DATA = tallow.conf
DISTCHECK_CONFIGURE_FLAGS = \
--with-systemdsystemunitdir=$(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)
docs: tallow.1 tallow.conf.5
tallow.conf.5:
ronn -r tallow.conf.5.md --pipe > tallow.conf.5
tallow.1:
ronn -r tallow.1.md --pipe > tallow.1
docs: $(dist_man_MANS)
man/%.5: man/%.5.md
ronn -r $< --pipe > $@
man/%.1: man/%.1.md
ronn -r $< --pipe > $@
+4 -2
View File
@@ -16,7 +16,9 @@ Tallow attaches to the journal and subscribes to messages from
/usr/sbin/sshd. The messages are matched against rules and the IP
address is extracted from the message. For each IP address that is
extracted, the last timestamp and count is kept. Once the count exceeds
a threshold, iptables is executed to set a IP-based blocking rule.
a threshold, the offending IP address is added to an ipset and blocked
with a corresponding firewall rule. It will use firewalld or
iptables / ip6tables.
The timestamp is kept for pruning. Records are pruned from the list
if the IP address hasn't been seen by tallow for longer than the
@@ -66,4 +68,4 @@ tallow.
Be very careful if you deploy tallow on systems that expect valid
users to log on from many random source addresses. If your user
mistypes their username, they could find themselves denied access.
mistypes their username, they could find themselves denied access.
+7 -4
View File
@@ -2,15 +2,18 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.64])
AC_INIT([tallow], [4], [auke-jan.h.kok@intel.com])
AM_INIT_AUTOMAKE([])
AC_INIT([tallow], [18], [auke-jan.h.kok@intel.com])
AM_INIT_AUTOMAKE([foreign -Wall -Werror -Wno-portability silent-rules subdir-objects color-tests
no-dist-gzip dist-xz])
AC_CONFIG_FILES([Makefile])
# Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL
PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd,, [PKG_CHECK_MODULES(LIBSYSTEMD_JOURNAL, libsystemd-journal)])
PKG_CHECK_MODULES(PCRE, libpcre)
PKG_CHECK_MODULES(JSON_C, json-c)
PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd,, [PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd-journal)])
AC_SUBST(LIBSYSTEMD_CFLAGS)
AC_SUBST(LIBSYSTEMD_LIBS)
AC_SUBST(LIBSYSTEMD_JOURNAL_CFLAGS)
@@ -27,5 +30,5 @@ fi
AC_CHECK_HEADERS([stdlib.h stdio.h string.h stdarg.h limits.h sys/time.h])
AC_OUTPUT([
tallow.service
data/tallow.service
])
+12
View File
@@ -0,0 +1,12 @@
[
{
"filter": "SYSLOG_IDENTIFIER=auth",
"items": [
{
"ban": 50,
"score": 0.6,
"pattern": "MESSAGE=pam_unix[(]dovecot:auth[)]: authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=.*@.* rhost=([0-9a-z:.]+)"
}
]
}
]
+57
View File
@@ -0,0 +1,57 @@
[
{
"filter": "SYSLOG_IDENTIFIER=sshd",
"items": [
{
"ban": 0,
"score": 0.2,
"pattern": "MESSAGE=Failed .* for .* from ([0-9a-z:.]+) port \\d+ ssh2"
},
{
"ban": 0,
"score": 0.2,
"pattern": "MESSAGE=error: PAM: Authentication failure for .* from ([0-9a-z:.]+)"
},
{
"ban": 10,
"score": 0.2,
"pattern": "MESSAGE=Invalid user .* from ([0-9a-z:.]+) port \\d+"
},
{
"ban": 10,
"score": 0.3,
"pattern": "MESSAGE=Did not receive identification string from ([0-9a-z:.]+) port \\d+"
},
{
"ban": 15,
"score": 0.4,
"pattern": "MESSAGE=Bad protocol version identification .* from ([0-9a-z:.]+)"
},
{
"ban": 15,
"score": 0.4,
"pattern": "MESSAGE=Connection closed by authenticating user .* ([0-9a-z:.]+) port \\d+"
},
{
"ban": 10,
"score": 0.3,
"pattern": "MESSAGE=Received disconnect from ([0-9a-z:.]+) port .*\\[preauth\\]"
},
{
"ban": 10,
"score": 0.3,
"pattern": "MESSAGE=Connection closed by ([0-9a-z:.]+) port .*\\[preauth\\]"
},
{
"ban": 30,
"score": 0.5,
"pattern": "MESSAGE=Failed .* for root from ([0-9a-z:.]+) port \\d+ ssh2"
},
{
"ban": 60,
"score": 0.6,
"pattern": "MESSAGE=Unable to negotiate with ([0-9a-z:.]+) port \\d+: no matching key exchange method found."
}
]
}
]
@@ -6,6 +6,7 @@ Description=Tallow Service
ExecStart=@prefix@/sbin/tallow
Restart=always
RestartSec=3
Nice=10
[Install]
WantedBy=network.target
+40
View File
@@ -0,0 +1,40 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "TALLOW" "1" "October 2018" "" ""
.
.SH "NAME"
\fBtallow\fR
.
.SH "tallow"
Reduce log clutter due to ssh login attempts\.
.
.SH "SYNOPSIS"
\fB/usr/sbin/tallow\fR
.
.SH "DESCRIPTION"
\fBtallow\fR is a daemon that watches the systemd journal for messages from the \fBsshd\fR service\. It parses the messages and looks for attempted random logins such as failed logins to the root account and failed logins to invalid user accounts, and various other obviously malicious login attempts that try things as forcing old protocols, or weak key systems\.
.
.P
If such logins were detected, the offending IP address is stored in a list\. Items from this list are regularly purged, but if the amount of times that a specific IP address is seen exceeds a threshold, an ipset(1) entry is inserted in the \fBtallow\fR or \fBtallow6\fR ipset, and further packets from that ip address will be blocked by an \fBiptables(1)\fR or \fBip6tables(1)\fR rule that tallow creates at startup\. Additionally, certain types of login failure will trigger a short term ban of further packets from the offending IP address immediately\.
.
.P
The system administrator needs to assure that the tallow and tallow6 ipsets are left alone and that the inserted iptables rules are properly matching on packets\.
.
.P
Care should be taken to assure that legitimate users are not blocked inadvertently\. You may wish to list any valid IP address with the whitelist option in tallow\.conf(5)\. Multiple addresses can be whitelisted\.
.
.SH "OPTIONS"
The \fBtallow\fR daemon itself has no runtime configuration\. All configuration is done through the tallow\.conf(5) config file\.
.
.SH "SIGNALS"
The \fBUSR1\fR signal causes \fBtallow\fR to print out it\'s internal tracking table of IP addresses\. This requires that tallow is compiled with the \fB\-DDEBUG=1\fR symbol passed to the compiler\.
.
.SH "SEE ALSO"
systemd\-journald(1), iptables(1), ipset(1), tallow\.conf(5)
.
.SH "BUGS"
\fBtallow\fR is \fBNOT A SECURITY SOLUTION\fR, nor does it protect against random password logins\. A attacker may still be able to logon to your systems if you allow password logins\.
.
.SH "AUTHOR"
Auke Kok \fIauke\-jan\.h\.kok@intel\.com\fR
+61
View File
@@ -0,0 +1,61 @@
## tallow
Reduce log clutter due to ssh login attempts.
## SYNOPSIS
`/usr/sbin/tallow`
## DESCRIPTION
`tallow` is a daemon that watches the systemd journal for messages
from the `sshd` service. It parses the messages and looks for
attempted random logins such as failed logins to the root account and
failed logins to invalid user accounts, and various other obviously
malicious login attempts that try things as forcing old protocols,
or weak key systems.
If such logins were detected, the offending IP address is stored in
a list. Items from this list are regularly purged, but if the amount
of times that a specific IP address is seen exceeds a threshold,
an ipset(1) entry is inserted in the `tallow` or `tallow6`
ipset, and further packets from that ip address will be blocked
by an `iptables(1)` or `ip6tables(1)` rule that tallow creates at
startup. Additionally, certain types of login failure will trigger
a short term ban of further packets from the offending IP address
immediately.
The system administrator needs to assure that the tallow and tallow6
ipsets are left alone and that the inserted iptables rules are properly
matching on packets.
Care should be taken to assure that legitimate users are not
blocked inadvertently. You may wish to list any valid IP address
with the whitelist option in tallow.conf(5). Multiple addresses can
be whitelisted.
## OPTIONS
The `tallow` daemon itself has no runtime configuration. All
configuration is done through the tallow.conf(5) config file.
## SIGNALS
The `USR1` signal causes `tallow` to print out it's internal tracking
table of IP addresses. This requires that tallow is compiled with
the `-DDEBUG=1` symbol passed to the compiler.
## SEE ALSO
systemd-journald(1), iptables(1), ipset(1), tallow.conf(5)
## BUGS
`tallow` is `NOT A SECURITY SOLUTION`, nor does it protect against
random password logins. A attacker may still be able to logon to your
systems if you allow password logins.
## AUTHOR
Auke Kok <auke-jan.h.kok@intel.com>
+77
View File
@@ -0,0 +1,77 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "TALLOW" "5" "October 2019" "" ""
.
.SH "NAME"
\fBtallow\fR
.
.SH "tallow\.conf"
The tallow configuration file
.
.SH "NAME"
tallow\.conf \- Tallow daemon configuration file
.
.SH "SYNOPSIS"
\fB/etc/tallow\.conf\fR
.
.SH "DESCRIPTION"
This file is read on startup by the tallow(1) daemon, and can be used to provide options to the tallow daemon\. If not present, tallow will operate with built\-in defaults\.
.
.SH "OPTIONS"
\fBipt_path\fR=\fB<string>\fR Specifies the location of the ipset(1) program and iptables(1), ip6tables(1), or firewall\-cmd(1) programs\. By default, tallow will look in "/usr/sbin" for them\.
.
.P
\fBexpires\fR=\fB<int>\fR The number of seconds that IP addresses are blocked for\. Note that due to the implementation, IP addresses may be blocked for much longer than this period\. If IP addresses are seen, but not blocked within this period, they are also removed from the watch list\. Defaults to 3600s\.
.
.P
\fBwhitelist\fR=\fB<ip address|pattern>\fR Specify an IP address or \fBpattern\fR that should never be blocked\. Multiple IP addresses can be included by repeating the \fBwhitelist\fR option several times\. By default, 127\.0\.0\.1, 192\.168\., and 10\. are whitelisted\. If you create a manual whitelist, you must include these entries if you want to continue them to be whitelisted as well, otherwise they will be omitted from the whitelist\.
.
.P
If the last character of the listed ip adress is a \fB\.\fR or a \fB:\fR, then the matching is only performed on the leftmost characters of an IP address against the whitelist entry\. For instance, if you whitelist \fB10\.\fR then all IP addresses in the \fB10/8\fR subnet mask will match this whitelist entry and never be blocked\.
.
.P
\fBipv6\fR=\fB<0|1>\fR Enable or disable ipv6 (ip6tables) support\. Ipv6 is disabled automatically on systems that do not appear to have ipv6 support and enabled when ipv6 is present\. Use this option to explicitly disable ipv6 support if your system does not have ipv6 or is missing ip6tables\. Even with ipv6 disabled, tallow will track and log ipv6 addresses\.
.
.P
\fBnocreate\fR=\fB<0|1>\fR Disable the creation of firewall rules and ipset sets\. By default, tallow will create new firewall\-cmd(1) or iptables(1) and ip6tables(1) rules when needed automatically\. If set to \fB1\fR, \fBtallow(1)\fR will not create any new firewall DROP rules or ipset sets that are needed work\. You should create them manually before tallow starts up and remove them afterwards using the sets of commands below\.
.
.P
Use the following commands if you\'re using iptables(1):
.
.IP "" 4
.
.nf
ipset create tallow hash:ip family inet timeout 3600
iptables \-t filter \-I INPUT 1 \-m set \-\-match\-set tallow src \-j DROP
ipset create tallow6 hash:ip family inet6 timeout 3600
ip6tables \-t filter \-I INPUT 1 \-m set \-\-match\-set tallow6 src \-j DROP
.
.fi
.
.IP "" 0
.
.P
Use the following commands if you\'re using firewalld(1):
.
.IP "" 4
.
.nf
firewall\-cmd \-\-permanent \-\-new\-ipset=tallow \-\-type=hash:ip \-\-family=inet \-\-option=timeout=3600
firewall\-cmd \-\-permanent \-\-direct \-\-add\-rule ipv4 filter INPUT 1 \-m set \-\-match\-set tallow src \-j DROP
firewall\-cmd \-\-permanent \-\-new\-ipset=tallow6 \-\-type=hash:ip \-\-family=inet6 \-\-option=timeout=3600
firewall\-cmd \-\-permanent \-\-direct \-\-add\-rule ipv6 filter INPUT 1 \-m set \-\-match\-set tallow6 src \-j DROP
.
.fi
.
.IP "" 0
.
.SH "SEE ALSO"
tallow(1)
.
.SH "AUTHOR"
Auke Kok \fIauke\-jan\.h\.kok@intel\.com\fR
+90
View File
@@ -0,0 +1,90 @@
## tallow.conf
The tallow configuration file
## NAME
tallow.conf - Tallow daemon configuration file
## SYNOPSIS
`/etc/tallow.conf`
## DESCRIPTION
This file is read on startup by the tallow(1) daemon, and can
be used to provide options to the tallow daemon. If not present,
tallow will operate with built-in defaults.
## OPTIONS
`ipt_path`=`<string>`
Specifies the location of the ipset(1) program and iptables(1),
ip6tables(1), or firewall-cmd(1) programs. By default, tallow will
look in "/usr/sbin" for them.
`expires`=`<int>`
The number of seconds that IP addresses are blocked for. Note that
due to the implementation, IP addresses may be blocked for much
longer than this period. If IP addresses are seen, but not
blocked within this period, they are also removed from the
watch list. Defaults to 3600s.
`whitelist`=`<ip address|pattern>`
Specify an IP address or `pattern` that should never be
blocked. Multiple IP addresses can be included by repeating the
`whitelist` option several times. By default, 127.0.0.1, 192.168., and
10. are whitelisted. If you create a manual whitelist, you must include
these entries if you want to continue them to be whitelisted as
well, otherwise they will be omitted from the whitelist.
If the last character of the listed ip adress is a `.` or a `:`, then
the matching is only performed on the leftmost characters of an IP
address against the whitelist entry. For instance, if you whitelist
`10.` then all IP addresses in the `10/8` subnet mask will match this
whitelist entry and never be blocked.
`ipv6`=`<0|1>`
Enable or disable ipv6 (ip6tables) support. Ipv6 is disabled
automatically on systems that do not appear to have ipv6 support
and enabled when ipv6 is present. Use this option to explicitly
disable ipv6 support if your system does not have ipv6 or is
missing ip6tables. Even with ipv6 disabled, tallow will track
and log ipv6 addresses.
`nocreate`=`<0|1>` Disable the creation of firewall rules and ipset sets. By
default, tallow will create new firewall-cmd(1) or iptables(1) and ip6tables(1)
rules when needed automatically. If set to `1`, `tallow(1)` will not create any
new firewall DROP rules or ipset sets that are needed work. You should create
them manually before tallow starts up and remove them afterwards using the sets
of commands below.
Use the following commands if you're using iptables(1):
```
ipset create tallow hash:ip family inet timeout 3600
iptables -t filter -I INPUT 1 -m set --match-set tallow src -j DROP
ipset create tallow6 hash:ip family inet6 timeout 3600
ip6tables -t filter -I INPUT 1 -m set --match-set tallow6 src -j DROP
```
Use the following commands if you're using firewalld(1):
```
firewall-cmd --permanent --new-ipset=tallow --type=hash:ip --family=inet --option=timeout=3600
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 -m set --match-set tallow src -j DROP
firewall-cmd --permanent --new-ipset=tallow6 --type=hash:ip --family=inet6 --option=timeout=3600
firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 1 -m set --match-set tallow6 src -j DROP
```
## SEE ALSO
tallow(1)
## AUTHOR
Auke Kok <auke-jan.h.kok@intel.com>
+175
View File
@@ -0,0 +1,175 @@
/*
* data.h - IP block sshd login abuse
*
* (C) Copyright 2019 Intel Corporation
* Authors:
* Auke Kok <auke-jan.h.kok@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/time.h>
#include <malloc.h>
#include <pcre.h>
#include "data.h"
struct block_struct *blocks;
struct pattern_struct *patterns;
struct filter_struct *filters;
struct whitelist_struct *whitelist;
void filter_add(const char *filter)
{
struct filter_struct *h = filters;
/* filter duplicates */
while (h) {
if (strcmp(h->filter, filter) == 0)
return;
h = h->next;
}
struct filter_struct *f = calloc(1, sizeof(struct filter_struct));
if (!f) {
perror("calloc()");
exit(EXIT_FAILURE);
}
f->filter = strdup(filter);
h = filters;
if (!h) {
filters = f;
} else {
while (h->next)
h = h->next;
h->next = f;
}
}
void pattern_add(const char *pattern, int ban, double score)
{
struct pattern_struct *p = calloc(1, sizeof(struct pattern_struct));
if (!p) {
perror("calloc()");
exit(EXIT_FAILURE);
}
p->pattern = strdup(pattern);
p->instant_block = ban;
p->weight = score;
const char *pcre_err;
int err;
p->re = pcre_compile(pattern, 0, &pcre_err, &err, NULL);
if (!p->re) {
fprintf(stderr, "PCRE compilation failed. Offset %d: %s\n",
err, pcre_err);
exit(EXIT_FAILURE);
}
struct pattern_struct *h = patterns;
if (!h) {
patterns = p;
} else {
while (h->next)
h = h->next;
h->next = p;
}
}
void whitelist_add(const char *ip)
{
struct whitelist_struct *w = calloc(1, sizeof(struct whitelist_struct));
if (!w) {
perror("calloc()");
exit(EXIT_FAILURE);
}
w->ip = strdup(ip);
size_t l = strlen(ip);
if ((ip[l-1] == '.') || (ip[l-1] == ':'))
w->len = l;
else
w->len = -1;
struct whitelist_struct *h = whitelist;
if (!h) {
whitelist = w;
} else {
while (h->next)
h = h->next;
h->next = w;
}
}
bool whitelist_find(const char *ip)
{
struct whitelist_struct *w = whitelist;
while (w) {
if (w->len > 0) {
if (!strncmp(w->ip, ip, w->len))
return (true);
} else {
if (!strcmp(w->ip, ip))
return (true);
}
w = w->next;
}
return (false);
}
void prune(int expires)
{
struct block_struct *s = blocks;
struct block_struct *p;
struct timeval tv;
(void) gettimeofday(&tv, NULL);
p = NULL;
while (s) {
/*
* Expire all records, but if they are blocked, make sure to
* expire them *before* the ipset rule expires, otherwise
* you might get an IP to bypass checks.
*/
time_t age = tv.tv_sec - s->time.tv_sec;
if ((age > expires) ||
((s->blocked) && (age > expires / 2))) {
dbg("Expired record for %s\n", s->ip);
if (p) {
p->next = s->next;
free(s->ip);
free(s);
s = p->next;
continue;
} else {
blocks = s->next;
free(s->ip);
free(s);
s = blocks;
p = NULL;
continue;
}
}
p = s;
s = s->next;
}
/* return some memory */
malloc_trim(0);
}
+60
View File
@@ -0,0 +1,60 @@
/*
* data.h - IP block sshd login abuse
*
* (C) Copyright 2019 Intel Corporation
* Authors:
* Auke Kok <auke-jan.h.kok@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/
#pragma once
#include <pcre.h>
#ifdef DEBUG
#define dbg(args...) fprintf(stderr, ##args)
#else
#define dbg(args...) do {} while (0)
#endif
struct block_struct {
char *ip;
float score;
struct timeval time;
struct block_struct *next;
bool blocked;
};
struct whitelist_struct {
char *ip;
size_t len;
struct whitelist_struct *next;
};
struct pattern_struct {
int instant_block;
float weight;
char *pattern;
pcre *re;
struct pattern_struct *next;
};
struct filter_struct {
char *filter;
struct filter_struct *next;
};
extern struct block_struct *blocks;
extern struct pattern_struct *patterns;
extern struct filter_struct *filters;
extern struct whitelist_struct *whitelist;
void filter_add(const char *filter);
void pattern_add(const char *pattern, int ban, double score);
void whitelist_add(const char *ip);
bool whitelist_find(const char *ip);
void prune(int expires);
+218
View File
@@ -0,0 +1,218 @@
/*
* json.c - IP block sshd login abuse
*
* (C) Copyright 2019 Intel Corporation
* Authors:
* Auke Kok <auke-jan.h.kok@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <fcntl.h>
#include <json-c/json.h>
#include "data.h"
static const char *s_pattern;
static const char *s_filter;
static int s_ban;
static double s_score;
static bool g_filter, g_ban, g_score, g_pattern = false;
static int json_parse(json_object *ob)
{
int i, count = 0;
bool l_filter, l_ban, l_score, l_pattern = false;
json_object_object_foreach(ob, key, val) {
enum json_type type;
type = json_object_get_type(val);
switch (type) {
case json_type_null:
break;
case json_type_boolean:
break;
case json_type_double:
if (strcmp(key, "score") == 0) {
l_score = g_score = true;
s_score = json_object_get_double(val);
} else {
fprintf(stderr, "Invalid JSON key \"%s\"\n", key);
return (0);
}
break;
case json_type_int:
if (strcmp(key, "ban") == 0) {
l_ban = g_ban = true;
s_ban = json_object_get_int(val);
} else {
fprintf(stderr, "Invalid JSON key \"%s\"\n", key);
return (0);
}
break;
case json_type_string:
if (strcmp(key, "filter") == 0) {
l_filter = g_filter = true;
s_filter = json_object_get_string(val);
} else if (strcmp(key, "pattern") == 0) {
l_pattern = g_pattern = true;
s_pattern = json_object_get_string(val);
} else {
fprintf(stderr, "Invalid JSON key \"%s\"\n", key);
return (0);
}
break;
case json_type_array:
ob = json_object_object_get(ob, key);
int len = json_object_array_length(ob);
json_object *val;
for (i = 0; i < len; i++) {
val = json_object_array_get_idx(ob, i);
count += json_parse(val);
}
break;
case json_type_object:
ob = json_object_object_get(ob, key);
count += json_parse(ob);
break;
}
}
/* check and finish if can */
if (g_score && g_ban && g_pattern && g_filter) {
#ifdef DEBUG
fprintf(stderr, "Adding: %s %s %d %lf\n", s_filter, s_pattern, s_ban, s_score);
#endif
filter_add(s_filter);
pattern_add(s_pattern, s_ban, s_score);
count++;
}
/* cleanup */
if (l_score) {
l_score = g_score = false;
} else if (l_ban) {
l_ban = g_ban = false;
} else if (l_pattern) {
l_pattern = g_pattern = false;
} else if (l_filter) {
l_filter = g_filter = false;
}
return (count);
}
static int json_load_file(const char* file)
{
int i, count = 0;
char *json;
int fd;
struct stat st;
fd = open(file, O_RDONLY);
fstat(fd, &st);
json = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
struct json_object *obj;
obj = json_tokener_parse(json);
if (json_object_is_type(obj,json_type_array)) {
int len = json_object_array_length(obj);
json_object *val;
for (i = 0; i < len; i++) {
val = json_object_array_get_idx(obj, i);
count += json_parse(val);
}
} else if (json_object_is_type(obj, json_type_object)) {
count += json_parse(obj);
} else {
fprintf(stderr, "This does not look like JSON: %s\n", file);
return (0);
}
json_object_put(obj);
munmap(json, st.st_size);
return (count);
}
static int json_load_dir(const char *dir)
{
int count = 0;
DIR *d = opendir(dir);
if (!d) {
fprintf(stderr, "Skipped reading %s: %s\n", dir, strerror(errno));
return (count);
}
struct dirent *entry;
for (;;) {
entry = readdir(d);
if (!entry)
break;
size_t l = strlen(entry->d_name);
if (l < strlen(".json"))
continue;
if (strcmp(entry->d_name + l - strlen(".json"), ".json") == 0) {
char *p;
if (!asprintf(&p, "%s/%s", dir, entry->d_name)) {
fprintf(stderr, "asprintf: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* allow /etc/tallow files to override /usr/share/tallow files */
if (strcmp(dir, DATADIR "/" PACKAGE_NAME) == 0) {
char *sp;
if (!asprintf(&sp, SYSCONFDIR "/" PACKAGE_NAME "/%s", entry->d_name)) {
fprintf(stderr, "asprintf: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
struct stat st;
if (stat(sp, &st) == 0) {
dbg("Skipped " SYSCONFDIR "/" PACKAGE_NAME "/%s\n", entry->d_name);
free(sp);
continue;
}
free(sp);
}
int c = json_load_file(p);
fprintf(stderr, "%s: %d patterns\n", p, c);
count += c;
free(p);
}
}
closedir(d);
return (count);
}
void json_load_patterns(void)
{
int count = 0;
count += json_load_dir(DATADIR "/" PACKAGE_NAME);
count += json_load_dir(SYSCONFDIR "/" PACKAGE_NAME);
fprintf(stderr, "Loaded %d patterns total\n", count);
if (count < 1) {
/* consider sending a special exit code here */
fprintf(stderr, "No patterns loaded, nothing to do!\n");
exit(EXIT_SUCCESS);
}
}
+16
View File
@@ -0,0 +1,16 @@
/*
* json.h - IP block sshd login abuse
*
* (C) Copyright 2019 Intel Corporation
* Authors:
* Auke Kok <auke-jan.h.kok@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/
#pragma once
void json_load_patterns(void);
+441
View File
@@ -0,0 +1,441 @@
/*
* tallow.c - IP block sshd login abuse
*
* (C) Copyright 2015-2019 Intel Corporation
* Authors:
* Auke Kok <auke-jan.h.kok@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <signal.h>
#include <unistd.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <pcre.h>
#include <systemd/sd-journal.h>
#include "json.h"
#include "data.h"
#define MAX_OFFSETS 30
static char ipt_path[PATH_MAX];
static int expires = 3600;
static int has_ipv6 = 0;
static bool nocreate = false;
static sd_journal *j;
static int ext(char *fmt, ...)
{
va_list args;
char cmd[1024];
int ret = 0;
va_start(args, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, args);
va_end(args);
ret = system(cmd);
if (ret)
fprintf(stderr, "Error executing \"%s\": returned %d\n", cmd, ret);
return (ret);
}
static void ext_ignore(char *fmt, ...)
{
va_list args;
char cmd[1024];
va_start(args, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, args);
va_end(args);
__attribute__((unused)) int ret = system(cmd);
}
static void reset_rules(void)
{
/* reset all rules in case the running fw changes */
ext_ignore("%s/firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 1 -m set --match-set tallow src -j DROP 2> /dev/null", ipt_path);
ext_ignore("%s/firewall-cmd --permanent --delete-ipset=tallow 2> /dev/null", ipt_path);
/* delete iptables ref to set before the ipset! */
ext_ignore("%s/iptables -t filter -D INPUT -m set --match-set tallow src -j DROP 2> /dev/null", ipt_path);
ext_ignore("%s/ipset destroy tallow 2> /dev/null", ipt_path);
if (has_ipv6) {
ext_ignore("%s/firewall-cmd --permanent --direct --remove-rule ipv6 filter INPUT 1 -m set --match-set tallow6 src -j DROP 2> /dev/null", ipt_path);
ext_ignore("%s/firewall-cmd --permanent --delete-ipset=tallow6 2> /dev/null", ipt_path);
/* delete iptables ref to set before the ipset! */
ext_ignore("%s/ip6tables -t filter -D INPUT -m set --match-set tallow6 src -j DROP 2> /dev/null", ipt_path);
ext_ignore("%s/ipset destroy tallow6 2> /dev/null", ipt_path);
}
}
static void setup(void)
{
static bool done = false;
if (done)
return;
done = true;
if (nocreate)
return;
/* firewalld */
char *fwd_path;
if (asprintf(&fwd_path, "%s/firewall-cmd", ipt_path) < 0) {
exit(EXIT_FAILURE);
}
if ((access(fwd_path, X_OK) == 0) && ext("%s/firewall-cmd --state --quiet", ipt_path) == 0) {
fprintf(stdout, "firewalld is running and will be used by tallow.\n");
reset_rules();
/* create ipv4 rule and ipset */
if (ext("%s/firewall-cmd --permanent --quiet --new-ipset=tallow --type=hash:ip --family=inet --option=timeout=%d", ipt_path, expires)) {
fprintf(stderr, "Unable to create ipv4 ipset with firewall-cmd.\n");
exit(EXIT_FAILURE);
}
if (ext("%s/firewall-cmd --permanent --direct --quiet --add-rule ipv4 filter INPUT 1 -m set --match-set tallow src -j DROP", ipt_path)) {
fprintf(stderr, "Unable to create ipv4 firewalld rule.\n");
exit(EXIT_FAILURE);
}
/* create ipv6 rule and ipset */
if (has_ipv6) {
if (ext("%s/firewall-cmd --permanent --quiet --new-ipset=tallow6 --type=hash:ip --family=inet6 --option=timeout=%d", ipt_path, expires)) {
fprintf(stderr, "Unable to create ipv6 ipset with firewall-cmd.\n");
exit(EXIT_FAILURE);
}
if (ext("%s/firewall-cmd --permanent --direct --quiet --add-rule ipv6 filter INPUT 1 -m set --match-set tallow6 src -j DROP ", ipt_path)) {
fprintf(stderr, "Unable to create ipv6 firewalld rule.\n");
exit(EXIT_FAILURE);
}
}
/* reload firewalld for ipsets to load */
if (ext("%s/firewall-cmd --reload --quiet", ipt_path, expires)) {
fprintf(stderr, "Unable to reload firewalld rules.\n");
exit(EXIT_FAILURE);
}
} else {
/* iptables */
reset_rules();
/* create ipv4 rule and ipset */
if (ext("%s/ipset create tallow hash:ip family inet timeout %d", ipt_path, expires)) {
fprintf(stderr, "Unable to create ipv4 ipset.\n");
exit(EXIT_FAILURE);
}
if (ext("%s/iptables -t filter -A INPUT -m set --match-set tallow src -j DROP", ipt_path)) {
fprintf(stderr, "Unable to create iptables rule.\n");
exit(EXIT_FAILURE);
}
/* create ipv6 rule and ipset */
if (has_ipv6) {
if (ext("%s/ipset create tallow6 hash:ip family inet6 timeout %d", ipt_path, expires)) {
fprintf(stderr, "Unable to create ipv6 ipset.\n");
exit(EXIT_FAILURE);
}
if (ext("%s/ip6tables -t filter -A INPUT -m set --match-set tallow6 src -j DROP", ipt_path)) {
fprintf(stderr, "Unable to create ipt6ables rule.\n");
exit(EXIT_FAILURE);
}
}
}
free(fwd_path);
}
static void block(struct block_struct *s, int instant_block)
{
setup();
if (strchr(s->ip, ':')) {
if (has_ipv6) {
if (instant_block > 0) {
(void) ext("%s/ipset -! add tallow6 %s timeout %d",
ipt_path, s->ip, instant_block);
} else {
(void) ext("%s/ipset -! add tallow6 %s", ipt_path, s->ip);
s->blocked = true;
}
}
} else {
if (instant_block > 0) {
(void) ext("%s/ipset -! add tallow %s timeout %d",
ipt_path, s->ip, instant_block);
} else {
(void) ext("%s/ipset -! add tallow %s", ipt_path, s->ip);
s->blocked = true;
}
}
if (s->blocked) {
fprintf(stderr, "Blocked %s\n", s->ip);
} else {
dbg("Throttled %s\n", s->ip);
}
}
void find(const char *ip, float weight, int instant_block)
{
struct block_struct *s = blocks;
struct block_struct *n;
if (!ip)
return;
/*
* not validating the IP address format here, just
* making sure we're not passing special characters
* to system().
*/
if (strspn(ip, "0123456789abcdef:.") != strlen(ip))
return;
if (whitelist_find(ip))
return;
/* walk and update entry */
while (s) {
if (!strcmp(s->ip, ip)) {
s->score += weight;
dbg("%s: %1.3f\n", s->ip, s->score);
(void) gettimeofday(&s->time, NULL);
if (s->blocked) {
return;
}
if (s->score >= 1.0) {
block(s, 0);
} else if (instant_block > 0) {
block(s, instant_block);
}
return;
}
if (s->next)
s = s->next;
else
break;
}
/* append */
n = calloc(1, sizeof(struct block_struct));
if (!n) {
fprintf(stderr, "Out of memory.\n");
exit(EXIT_FAILURE);
}
if (!blocks)
blocks = n;
else
s->next = n;
n->ip = strdup(ip);
n->score = weight;
n->next = NULL;
n->blocked = false;
(void) gettimeofday(&n->time, NULL);
dbg("%s: %1.3f\n", n->ip, n->score);
if (weight >= 1.0) {
block(n, 0);
} else if (instant_block > 0) {
block(n, instant_block);
}
return;
}
#ifdef DEBUG
static void sigusr1(int u __attribute__ ((unused)))
{
fprintf(stderr, "Dumping score list on request:\n");
struct block_struct *s = blocks;
while (s) {
fprintf(stderr, "%ld %s %1.3f\n", s->time.tv_sec, s->ip, s->score);
s = s->next;
}
}
#endif
int main(void)
{
int r;
FILE *f;
int timeout = 60;
long long int last_timestamp = 0;
json_load_patterns();
strcpy(ipt_path, "/usr/sbin");
#ifdef DEBUG
fprintf(stderr, "Debug output enabled. Send SIGUSR1 to dump internal state table\n");
struct sigaction s;
memset(&s, 0, sizeof(struct sigaction));
s.sa_handler = sigusr1;
sigaction(SIGUSR1, &s, NULL);
#endif
if (access("/proc/sys/net/ipv6", R_OK | X_OK) == 0)
has_ipv6 = 1;
f = fopen(SYSCONFDIR "/tallow.conf", "r");
if (f) {
char buf[256];
char *key;
char *val;
while (fgets(buf, 80, f) != NULL) {
char *c;
c = strchr(buf, '\n');
if (c) *c = 0; /* remove trailing \n */
if (buf[0] == '#')
continue; /* comment line */
key = strtok(buf, "=");
if (!key)
continue;
val = strtok(NULL, "=");
if (!val)
continue;
// todo: filter leading/trailing whitespace
if (!strcmp(key, "ipt_path"))
strncpy(ipt_path, val, PATH_MAX - 1);
if (!strcmp(key, "expires"))
expires = atoi(val);
if (!strcmp(key, "whitelist"))
whitelist_add(val);
if (!strcmp(key, "ipv6"))
has_ipv6 = atoi(val);
if (!strcmp(key, "nocreate"))
nocreate = (atoi(val) == 1);
}
fclose(f);
}
if (!has_ipv6)
fprintf(stdout, "ipv6 support disabled.\n");
if (!whitelist) {
whitelist_add("127.0.0.1");
whitelist_add("192.168.");
whitelist_add("10.");
}
r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
if (r < 0) {
fprintf(stderr, "Failed to open journal: %s\n", strerror(-r));
exit(EXIT_FAILURE);
}
/* add all filters */
struct filter_struct *flt = filters;
while (flt) {
sd_journal_add_match(j, flt->filter, 0);
flt = flt->next;
}
/* go to the tail and wait */
r = sd_journal_seek_tail(j);
sd_journal_wait(j, (uint64_t) 0);
dbg("sd_journal_seek_tail() returned %d\n", r);
while (sd_journal_next(j) != 0)
r++;
dbg("Forwarded through %d items in the journal to reach the end\n", r);
fprintf(stderr, PACKAGE_STRING " Started\n");
for (;;) {
const void *d, *dt;
size_t l, dl;
r = sd_journal_wait(j, (uint64_t) timeout * 1000000);
if (r == SD_JOURNAL_INVALIDATE) {
fprintf(stderr, "Journal was rotated, resetting\n");
sd_journal_seek_tail(j);
} else if (r == SD_JOURNAL_NOP) {
dbg("Timeout reached, waiting again\n");
continue;
}
while (sd_journal_next(j) != 0) {
char *m;
/*
* discard messages older than ones we've already seen before
* this happens when the journal rotates - we get replayed events
*/
if (sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &dt, &dl) == 0) {
long long int lt = atoi(dt + strlen("_SOURCE_REALTIME_TIMESTAMP="));
if (lt > last_timestamp)
last_timestamp = lt;
else if (lt < last_timestamp)
continue;
}
if (sd_journal_get_data(j, "MESSAGE", &d, &l) < 0) {
fprintf(stderr, "Failed to read message field: %s\n", strerror(-r));
break;
}
m = strndup(d, l+1);
m[l] = '\0';
struct pattern_struct *pat = patterns;
while (pat) {
int off[MAX_OFFSETS];
int ret = pcre_exec(pat->re, NULL, m, l, 0, 0, off, MAX_OFFSETS);
if (ret == 2) {
const char *s;
ret = pcre_get_substring(m, off, 2, 1, &s);
if (ret > 0) {
dbg("%s == %s\n", s, pat->pattern);
find(s, pat->weight, pat->instant_block);
pcre_free_substring(s);
}
}
pat = pat->next;
}
free(m);
}
prune(expires);
}
sd_journal_close(j);
exit(EXIT_SUCCESS);
}
-51
View File
@@ -1,51 +0,0 @@
## tallow
Reduce log clutter due to ssh login attempts.
## SYNOPSIS
`/usr/sbin/tallow`
## DESCRIPTION
`tallow` is a daemon that watches the systemd journal for
messages from the `sshd` service. It parses the messages
and looks for attempted random logins such as failed logins to the
root account and failed logins to invalid user accounts.
If such logins were detected, the offending IP address is stored
in a list. Items from this list are regularly purged, but if
the amount of times that a specific IP address is seen exceeds
a threshold (default 3), an ipset(1) entry is inserted in the
`tallow` or `tallow6` ipset, and further packets from that ip
address will be blocked by an `iptables(1)` or `ip6tables(1)`
rule that tallow creates at startup.
The system administrator needs to assure that the tallow
and tallow6 ipsets are left alone and that the inserted
iptables rules are properly matching on packets.
Care should be taken to assure that legitimate users are not
blocked inadvertently. You may wish to list any valid IP address
with the whitelist option in tallow.conf(5). Multiple addresses
can be whitelisted.
## OPTIONS
The `tallow` daemon itself has no runtime configuration. All
configuration is done through the tallow.conf(5) config file.
## SEE ALSO
systemd-journald(1), iptables(1), tallow.conf(5)
## BUGS
`tallow` is `NOT A SECURITY SOLUTION`, nor does it protect
against random password logins. A attacker may still be able to
logon to your systems if you allow password logins.
## AUTHOR
Auke Kok <auke-jan.h.kok@intel.com>
-383
View File
@@ -1,383 +0,0 @@
/*
* tallow.c - IP block sshd login abuse
*
* (C) Copyright 2015 Intel Corporation
* Authors:
* Auke Kok <auke-jan.h.kok@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <signal.h>
#include <unistd.h>
#include <limits.h>
#include <sys/time.h>
#include <systemd/sd-journal.h>
struct tallow_struct {
char *ip;
int count;
struct timeval time;
struct tallow_struct *next;
};
static struct tallow_struct *head;
static struct tallow_struct *whitelist;
#define FILTER_STRING "SYSLOG_IDENTIFIER=sshd"
static char ipt_path[PATH_MAX];
static int threshold = 3;
static int expires = 3600;
static int has_ipv6 = 0;
static sd_journal *j;
static int ext(char *fmt, ...)
{
va_list args;
char cmd[1024];
int ret = 0;
va_start(args, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, args);
va_end(args);
ret = system(cmd);
if (ret)
fprintf(stderr, "Error executing \"%s\": returned %d\n", cmd, ret);
return (ret);
}
static void ext_ignore(char *fmt, ...)
{
va_list args;
char cmd[1024];
va_start(args, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, args);
va_end(args);
__attribute__((unused)) int ret = system(cmd);
}
static void setup(void)
{
static bool done = false;
if (done)
return;
done = true;
/* init ipset and iptables */
/* delete iptables ref to set before the ipset! */
ext_ignore("%s/iptables -t filter -D INPUT -m set --match-set tallow src -j DROP 2> /dev/null", ipt_path);
ext_ignore("%s/ipset destroy tallow 2> /dev/null", ipt_path);
if (ext("%s/ipset create tallow hash:ip family inet timeout %d", ipt_path, expires)) {
fprintf(stderr, "Unable to create ipv4 ipset.\n");
exit(EXIT_FAILURE);
}
if (ext("%s/iptables -t filter -A INPUT -m set --match-set tallow src -j DROP", ipt_path)) {
fprintf(stderr, "Unable to create iptables rule.\n");
exit(EXIT_FAILURE);
}
if (has_ipv6) {
ext_ignore("%s/ip6tables -t filter -D INPUT -m set --match-set tallow6 src -j DROP 2> /dev/null", ipt_path);
ext_ignore("%s/ipset destroy tallow6 2> /dev/null", ipt_path);
if (ext("%s/ipset create tallow6 hash:ip family inet6 timeout %d", ipt_path, expires)) {
fprintf(stderr, "Unable to create ipv6 ipset.\n");
exit(EXIT_FAILURE);
}
if (ext("%s/ip6tables -t filter -A INPUT -m set --match-set tallow6 src -j DROP", ipt_path)) {
fprintf(stderr, "Unable to create ipt6ables rule.\n");
exit(EXIT_FAILURE);
}
}
}
static void block(struct tallow_struct *s)
{
if (s->count != threshold)
return;
setup();
if (strchr(s->ip, ':')) {
if (has_ipv6)
(void) ext("%s/ipset -A tallow6 %s", ipt_path, s->ip);
} else {
(void) ext("%s/ipset -A tallow %s", ipt_path, s->ip);
}
fprintf(stderr, "Blocked %s\n", s->ip);
}
static void whitelist_add(char *ip)
{
struct tallow_struct *w = whitelist;
struct tallow_struct *n;
while (w && w->next)
w = w->next;
n = malloc(sizeof(struct tallow_struct));
if (!n) {
fprintf(stderr, "Out of memory.\n");
exit(EXIT_FAILURE);
}
memset(n, 0, sizeof(struct tallow_struct));
n->ip = strdup(ip);
n->next = NULL;
if (!whitelist)
whitelist = n;
else
w->next = n;
}
static void find(char *ip)
{
struct tallow_struct *s = head;
struct tallow_struct *n;
struct tallow_struct *w = whitelist;
if (!ip)
return;
/*
* not validating the IP address format here, just
* making sure we're not passing special characters
* to system().
*/
if (strspn(ip, "0123456789abcdef:.") != strlen(ip))
return;
/* whitelist */
while (w) {
if (!strcmp(w->ip, ip))
return;
w = w->next;
}
/* walk and update entry */
while (s) {
if (!strcmp(s->ip, ip)) {
s->count++;
(void) gettimeofday(&s->time, NULL);
block(s);
return;
}
if (s->next)
s = s->next;
else
break;
}
/* append */
n = malloc(sizeof(struct tallow_struct));
if (!n) {
fprintf(stderr, "Out of memory.\n");
exit(EXIT_FAILURE);
}
memset(n, 0, sizeof(struct tallow_struct));
if (!head)
head = n;
else
s->next = n;
n->ip = strdup(ip);
n->count = 1;
n->next = NULL;
(void) gettimeofday(&n->time, NULL);
block(n);
return;
}
static void sig(int u __attribute__ ((unused)))
{
fprintf(stderr, "Exiting on request.\n");
sd_journal_close(j);
struct tallow_struct *s = head;
while (s) {
struct tallow_struct *n = NULL;
free(s->ip);
n = s;
s = s->next;
free(n);
}
exit(EXIT_SUCCESS);
}
static void prune(void)
{
struct tallow_struct *s = head;
struct tallow_struct *p;
struct timeval tv;
(void) gettimeofday(&tv, NULL);
p = NULL;
while (s) {
if ((tv.tv_sec - s->time.tv_sec) > expires) {
if (p) {
p->next = s->next;
free(s->ip);
free(s);
s = p->next;
continue;
} else {
head = s->next;
free(s->ip);
free(s);
s = head;
p = NULL;
continue;
}
}
p = s;
s = s->next;
}
}
int main(void)
{
int r;
FILE *f;
struct sigaction s;
int timeout = 60;
strcpy(ipt_path, "/usr/sbin");
memset(&s, 0, sizeof(struct sigaction));
s.sa_handler = sig;
sigaction(SIGHUP, &s, NULL);
sigaction(SIGTERM, &s, NULL);
sigaction(SIGINT, &s, NULL);
if (access("/proc/sys/net/ipv6", R_OK | X_OK) == 0)
has_ipv6 = 1;
f = fopen("/etc/tallow.conf", "r");
if (f) {
char buf[256];
char *key;
char *val;
while (fgets(buf, 80, f) != NULL) {
char *c;
c = strchr(buf, '\n');
if (c) *c = 0; /* remove trailing \n */
if (buf[0] == '#')
continue; /* comment line */
key = strtok(buf, "=");
if (!key)
continue;
val = strtok(NULL, "=");
if (!val)
continue;
// todo: filter leading/trailing whitespace
if (!strcmp(key, "ipt_path"))
strncpy(ipt_path, val, PATH_MAX - 1);
if (!strcmp(key, "threshold"))
threshold = atoi(val);
if (!strcmp(key, "expires"))
expires = atoi(val);
if (!strcmp(key, "whitelist"))
whitelist_add(val);
if (!strcmp(key, "ipv6"))
has_ipv6 = atoi(val);
}
fclose(f);
}
if (!has_ipv6)
fprintf(stdout, "ipv6 support disabled.\n");
if (!whitelist)
whitelist_add("127.0.0.1");
r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
if (r < 0) {
fprintf(stderr, "Failed to open journal: %s\n", strerror(-r));
exit(EXIT_FAILURE);
}
/* ffwd journal */
sd_journal_add_match(j, FILTER_STRING, 0);
r = sd_journal_seek_tail(j);
sd_journal_wait(j, (uint64_t) 0);
fprintf(stderr, "sd_journal_seek_tail() returned %d\n", r);
while (sd_journal_next(j) != 0)
r++;
fprintf(stderr, "Forwarded through %d items in the journal to reach the end\n", r);
fprintf(stderr, "Started\n");
for (;;) {
const void *d;
size_t l;
r = sd_journal_wait(j, (uint64_t) timeout * 1000000);
if (r == SD_JOURNAL_INVALIDATE) {
fprintf(stderr, "Journal was rotated, resetting\n");
sd_journal_seek_tail(j);
}
while (sd_journal_next(j) != 0) {
char *t;
char *m;
int i;
if (sd_journal_get_data(j, "MESSAGE", &d, &l) < 0) {
fprintf(stderr, "Failed to read message field: %s\n", strerror(-r));
continue;
}
m = strndup(d, l+1);
m[l] = '\0';
if (strstr(m, "MESSAGE=Failed password for invalid user ")) {
t = strtok(m, " ");
for (i = 0; i < 7; i++)
t = strtok(NULL, " ");
find(t);
}
if (strstr(m, "MESSAGE=Failed password for root ")) {
t = strtok(m, " ");
for (i = 0; i < 5; i++)
t = strtok(NULL, " ");
find(t);
}
free(m);
}
prune();
}
sd_journal_close(j);
exit(EXIT_SUCCESS);
}
+3 -2
View File
@@ -1,8 +1,9 @@
# tallow.conf - see `man tallow.conf` for more information
#path_iptables=/usr/sbin
#threshold=3
#ipt_path=/usr/sbin
#expires=3600
#whitelist=127.0.0.1
#whitelist=192.168.
#whitelist=10.
#ipv6=0
-57
View File
@@ -1,57 +0,0 @@
## tallow.conf
The tallow configuration file
## NAME
tallow.conf - Tallow daemon configuration file
## SYNOPSIS
`/etc/tallow.conf`
## DESCRIPTION
This file is read on startup by the tallow(1) daemon, and can
be used to provide options to the tallow daemon. If not present,
tallow will operate with built-in defaults.
## OPTIONS
`ipt_path`=`<string>`
Specifies the location of the ipset(1), iptables(1) or ip6tables(1)
program. By default, tallow will look in "/usr/sbin" for them.
`expires`=`<int>`
The number of seconds that IP addresses are blocked for. Note that
due to the implementation, IP addresses may be blocked for much
longer than this period. If IP addresses are seen, but not
blocked within this period, they are also removed from the
watch list. Defaults to 3600s.
`threshold`=`<int>`
Specifies the number of times an IP address may appear before it
is blocked. Defaults to 3.
`whitelist`=`<ipv4 address>`
Specify an IP address that should never be blocked. Multiple IP
addresses can be included by repeating the `whitelist`
option several times. By default, only 127.0.0.1 is whitelisted.
`ipv6`=`<0|1>`
Enable of disable ipv6 (ip6tables) support. Ipv6 is disabled
automatically on systems that do not appear to have ipv6 support
and enabled when ipv6 is present. Use this option to explicitly
disable ipv6 support if your system does not have ipv6 or is
missing ip6tables. Even with ipv6 disabled, tallow will track
and log ipv6 addresses.
## SEE ALSO
tallow(1), iptables(1)
## AUTHOR
Auke Kok <auke-jan.h.kok@intel.com>