2017-12-16 18:59:33 +01:00
|
|
|
|
# pyruse is intended as a replacement to both fail2ban and epylog
|
2018-01-31 08:28:05 +01:00
|
|
|
|
# Copyright © 2017–2018 Y. Gablin
|
2017-12-16 18:59:33 +01:00
|
|
|
|
# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing.
|
2017-12-15 19:36:50 +01:00
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
sys.path.insert(1, "..")
|
2018-01-31 12:04:21 +01:00
|
|
|
|
from pyruse import actions, config, module, workflow
|
2017-12-15 19:36:50 +01:00
|
|
|
|
|
|
|
|
|
def _clean():
|
|
|
|
|
for f in ['acted_on.log', 'action_nftBan.py.json', 'email.dump', 'nftBan.cmd', 'unfiltered.log']:
|
|
|
|
|
if os.path.exists(f):
|
|
|
|
|
os.remove(f)
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
global _microsec
|
|
|
|
|
conf = config.Config(os.curdir)
|
|
|
|
|
|
|
|
|
|
# Unit tests
|
2018-01-31 08:30:19 +01:00
|
|
|
|
import filter_equals, filter_greaterOrEquals, filter_in, filter_lowerOrEquals, filter_pcre, filter_pcreAny, filter_userExists
|
2017-12-15 19:36:50 +01:00
|
|
|
|
import action_counterRaise, action_counterReset, action_dailyReport, action_email, action_nftBan
|
|
|
|
|
|
2018-01-31 08:28:05 +01:00
|
|
|
|
filter_equals.unitTests()
|
|
|
|
|
filter_greaterOrEquals.unitTests()
|
2018-01-31 08:30:19 +01:00
|
|
|
|
filter_in.unitTests()
|
|
|
|
|
filter_lowerOrEquals.unitTests()
|
2018-01-31 08:28:05 +01:00
|
|
|
|
filter_pcre.unitTests()
|
|
|
|
|
filter_pcreAny.unitTests()
|
|
|
|
|
filter_userExists.unitTests()
|
|
|
|
|
action_counterRaise.unitTests()
|
|
|
|
|
action_counterReset.unitTests()
|
|
|
|
|
action_dailyReport.unitTests()
|
|
|
|
|
action_email.unitTests()
|
|
|
|
|
action_nftBan.unitTests()
|
2017-12-15 19:36:50 +01:00
|
|
|
|
|
|
|
|
|
# Integration test
|
|
|
|
|
wf = workflow.Workflow(conf.asMap().get("actions", {}))
|
|
|
|
|
_microsec = 0
|
|
|
|
|
test = [
|
|
|
|
|
entry("dmz", "ftp", "an ftp message", 0),
|
|
|
|
|
entry("dmz", "login", "Failed password for Unknown User from 1.2.3.4"),
|
|
|
|
|
entry("dmz", "login", "Failed password for nobody from 5.6.7.8"),
|
|
|
|
|
entry("dmz", "login", "End of session for root on localhost"),
|
|
|
|
|
entry("dmz", "login", "Failed password for User Unknown from 1.2.3.4"),
|
|
|
|
|
entry("bck", "ftp", "file requested"),
|
|
|
|
|
entry("dmz", "login", "Accepted password for root from 1.2.3.4"),
|
|
|
|
|
entry("bck", "login", "Failed password for root from 1.2.3.4"),
|
|
|
|
|
entry("bck", "login", "Failed password for nobody from 1.2.3.4"),
|
|
|
|
|
entry("dmz", "login", "Failed password for foobar from 1.2.3.4"),
|
|
|
|
|
entry("dmz", "login", "Failed password for nobody from 5.6.7.8")
|
|
|
|
|
]
|
|
|
|
|
_clean()
|
|
|
|
|
for e in test:
|
2018-01-31 15:13:03 +01:00
|
|
|
|
run(wf, e)
|
2017-12-15 19:36:50 +01:00
|
|
|
|
actions.action_dailyReport.Action._hour = 25
|
2018-01-31 15:13:03 +01:00
|
|
|
|
run(wf, entry("bck", "login", "Failed password for root from ::1", 11))
|
2017-12-15 19:36:50 +01:00
|
|
|
|
for f in ['acted_on.log', 'email.dump', 'nftBan.cmd', 'unfiltered.log']:
|
2018-01-31 08:28:05 +01:00
|
|
|
|
assert os.path.exists(f), "file should exist: " + f
|
2017-12-15 19:36:50 +01:00
|
|
|
|
try:
|
|
|
|
|
subprocess.run(
|
|
|
|
|
[ "/usr/bin/bash",
|
|
|
|
|
"-c",
|
|
|
|
|
"diff -U0 \"$0\"{,.test_ref} | grep -vE '^[-+@^]{2,3} |={5,}[0-9]+=='",
|
|
|
|
|
f],
|
|
|
|
|
check = True)
|
2017-12-16 18:31:06 +01:00
|
|
|
|
assert False, "differences found in " + f
|
2017-12-15 19:36:50 +01:00
|
|
|
|
except subprocess.CalledProcessError:
|
|
|
|
|
pass # OK, no difference found
|
|
|
|
|
_clean()
|
2017-12-16 18:31:06 +01:00
|
|
|
|
os.remove('action_dailyReport.py.journal')
|
2017-12-15 19:36:50 +01:00
|
|
|
|
|
|
|
|
|
def entry(host, service, message, microsecond = None):
|
|
|
|
|
global _microsec
|
|
|
|
|
if microsecond:
|
|
|
|
|
_microsec = microsecond
|
|
|
|
|
_microsec += 1
|
|
|
|
|
return {
|
|
|
|
|
"__REALTIME_TIMESTAMP": datetime(2118,1,1,8,1,1,_microsec),
|
|
|
|
|
"_HOSTNAME": host,
|
|
|
|
|
"service": service,
|
|
|
|
|
"MESSAGE": message
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-31 15:13:03 +01:00
|
|
|
|
def run(workflow, logEntry):
|
|
|
|
|
step = workflow.firstStep
|
|
|
|
|
while step is not None:
|
|
|
|
|
step = step.run(logEntry)
|
|
|
|
|
|
2017-12-15 19:36:50 +01:00
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|