2017-12-16 18:59:33 +01:00
|
|
|
|
# pyruse is intended as a replacement to both fail2ban and epylog
|
2018-01-31 13:07: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 datetime
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
from pyruse import base, config
|
|
|
|
|
|
|
|
|
|
class Action(base.Action):
|
|
|
|
|
_storage = config.Config().asMap().get("storage", "/var/lib/pyruse") \
|
|
|
|
|
+ "/" + os.path.basename(__file__) + ".json"
|
|
|
|
|
_nft = config.Config().asMap().get("nftBan", {}).get("nft", ["/usr/bin/nft"])
|
|
|
|
|
|
|
|
|
|
def __init__(self, args):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.ipv4Set = args["nftSetIPv4"]
|
|
|
|
|
self.ipv6Set = args["nftSetIPv6"]
|
|
|
|
|
self.field = args["IP"]
|
|
|
|
|
self.banSeconds = args.get("banSeconds", None)
|
|
|
|
|
|
|
|
|
|
def act(self, entry):
|
|
|
|
|
ip = entry[self.field]
|
|
|
|
|
nftSet = self.ipv6Set if ":" in ip else self.ipv4Set
|
|
|
|
|
newBan = {"IP": ip, "nftSet": nftSet}
|
|
|
|
|
|
|
|
|
|
now = datetime.datetime.utcnow()
|
|
|
|
|
bans = []
|
|
|
|
|
previousTS = None
|
|
|
|
|
try:
|
|
|
|
|
with open(Action._storage) as dataFile:
|
|
|
|
|
for ban in json.load(dataFile):
|
|
|
|
|
if ban["timestamp"] <= now.timestamp():
|
|
|
|
|
continue
|
|
|
|
|
elif {k: ban[k] for k in newBan.keys()} == newBan:
|
|
|
|
|
# should not happen, since the IP is banned…
|
|
|
|
|
previousTS = ban["timestamp"]
|
|
|
|
|
else:
|
|
|
|
|
bans.append(ban)
|
|
|
|
|
except IOError:
|
|
|
|
|
pass # new file
|
|
|
|
|
|
2017-12-16 20:26:59 +01:00
|
|
|
|
if previousTS:
|
2018-02-02 08:12:04 +01:00
|
|
|
|
try:
|
|
|
|
|
cmd = list(Action._nft)
|
|
|
|
|
cmd.append("delete element %s {%s}" % (nftSet, ip))
|
|
|
|
|
subprocess.run(cmd)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass # too late: not a problem
|
2017-12-16 20:26:59 +01:00
|
|
|
|
|
|
|
|
|
until = self._doBan(now, ip, nftSet)
|
|
|
|
|
|
|
|
|
|
newBan["timestamp"] = until.timestamp()
|
|
|
|
|
bans.append(newBan)
|
|
|
|
|
with open(Action._storage, "w") as dataFile:
|
|
|
|
|
json.dump(bans, dataFile)
|
|
|
|
|
|
|
|
|
|
def boot(self):
|
|
|
|
|
now = datetime.datetime.utcnow()
|
|
|
|
|
bans = []
|
|
|
|
|
try:
|
|
|
|
|
with open(Action._storage) as dataFile:
|
|
|
|
|
for ban in json.load(dataFile):
|
|
|
|
|
if ban["timestamp"] <= now.timestamp():
|
|
|
|
|
continue
|
|
|
|
|
else:
|
|
|
|
|
bans.append(ban)
|
|
|
|
|
self._doBan(now, ip, nftSet)
|
|
|
|
|
except IOError:
|
|
|
|
|
pass # no file
|
|
|
|
|
|
|
|
|
|
with open(Action._storage, "w") as dataFile:
|
|
|
|
|
json.dump(bans, dataFile)
|
|
|
|
|
|
|
|
|
|
def _doBan(self, now, ip, nftSet):
|
2017-12-15 19:36:50 +01:00
|
|
|
|
if self.banSeconds:
|
|
|
|
|
until = now + datetime.timedelta(seconds = self.banSeconds)
|
|
|
|
|
timeout = " timeout %ss" % str(self.banSeconds)
|
|
|
|
|
else:
|
|
|
|
|
until = now + datetime.timedelta(days = 365)
|
|
|
|
|
timeout = ""
|
|
|
|
|
|
|
|
|
|
cmd = list(Action._nft)
|
|
|
|
|
cmd.append("add element %s {%s%s}" % (nftSet, ip, timeout))
|
|
|
|
|
subprocess.run(cmd)
|
|
|
|
|
|
2017-12-16 20:26:59 +01:00
|
|
|
|
return until
|