[Netfilter] Fix chain order watching and other fixes
[Web] Fix perm ban displaymaster
parent
8b64db25c3
commit
f8283536ec
|
@ -24,8 +24,13 @@ RULES[4] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=(
|
||||||
RULES[5] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
|
RULES[5] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
|
||||||
RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
|
RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
|
||||||
|
|
||||||
def refresh_f2boptions():
|
bans = {}
|
||||||
|
log = {}
|
||||||
|
quit_now = False
|
||||||
|
|
||||||
|
def refreshF2boptions():
|
||||||
global f2boptions
|
global f2boptions
|
||||||
|
global quit_now
|
||||||
if not r.get('F2B_OPTIONS'):
|
if not r.get('F2B_OPTIONS'):
|
||||||
f2boptions = {}
|
f2boptions = {}
|
||||||
f2boptions['ban_time'] = int
|
f2boptions['ban_time'] = int
|
||||||
|
@ -45,38 +50,44 @@ def refresh_f2boptions():
|
||||||
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
print 'Error loading F2B options: F2B_OPTIONS is not json'
|
print 'Error loading F2B options: F2B_OPTIONS is not json'
|
||||||
global quit_now
|
|
||||||
quit_now = True
|
quit_now = True
|
||||||
|
|
||||||
if r.exists('F2B_LOG'):
|
if r.exists('F2B_LOG'):
|
||||||
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
||||||
|
|
||||||
bans = {}
|
|
||||||
log = {}
|
|
||||||
quit_now = False
|
|
||||||
|
|
||||||
def checkChainOrder():
|
def checkChainOrder():
|
||||||
filter4_table = iptc.Table(iptc.Table.FILTER)
|
global quit_now
|
||||||
filter6_table = iptc.Table6(iptc.Table6.FILTER)
|
while not quit_now:
|
||||||
for f in [filter4_table, filter6_table]:
|
time.sleep(20)
|
||||||
forward_chain = iptc.Chain(f, 'FORWARD')
|
filter4_table = iptc.Table(iptc.Table.FILTER)
|
||||||
for position, item in enumerate(forward_chain.rules):
|
filter6_table = iptc.Table6(iptc.Table6.FILTER)
|
||||||
if item.target.name == 'MAILCOW':
|
filter4_table.refresh()
|
||||||
mc_position = position
|
filter6_table.refresh()
|
||||||
if item.target.name == 'DOCKER':
|
for f in [filter4_table, filter6_table]:
|
||||||
docker_position = position
|
forward_chain = iptc.Chain(f, 'FORWARD')
|
||||||
if 'mc_position' in locals() and 'docker_position' in locals():
|
input_chain = iptc.Chain(f, 'INPUT')
|
||||||
if int(mc_position) > int(docker_position):
|
for chain in [forward_chain, input_chain]:
|
||||||
log['time'] = int(round(time.time()))
|
target_found = False
|
||||||
log['priority'] = 'crit'
|
for position, item in enumerate(chain.rules):
|
||||||
log['message'] = 'Error in chain order, restarting container'
|
if item.target.name == 'MAILCOW':
|
||||||
r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
|
target_found = True
|
||||||
print 'Error in chain order, restarting container...'
|
if position != 0:
|
||||||
global quit_now
|
log['time'] = int(round(time.time()))
|
||||||
quit_now = True
|
log['priority'] = 'crit'
|
||||||
|
log['message'] = 'Error in ' + chain.name + ' chain order, restarting container'
|
||||||
|
r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
|
||||||
|
print log['message']
|
||||||
|
quit_now = True
|
||||||
|
if not target_found:
|
||||||
|
log['time'] = int(round(time.time()))
|
||||||
|
log['priority'] = 'crit'
|
||||||
|
log['message'] = 'Error in ' + chain.name + ' chain: target not found, restarting container'
|
||||||
|
r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
|
||||||
|
print log['message']
|
||||||
|
quit_now = True
|
||||||
|
|
||||||
def ban(address):
|
def ban(address):
|
||||||
refresh_f2boptions()
|
refreshF2boptions()
|
||||||
BAN_TIME = int(f2boptions['ban_time'])
|
BAN_TIME = int(f2boptions['ban_time'])
|
||||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||||
RETRY_WINDOW = int(f2boptions['retry_window'])
|
RETRY_WINDOW = int(f2boptions['retry_window'])
|
||||||
|
@ -214,6 +225,7 @@ def clear():
|
||||||
filter_table.refresh()
|
filter_table.refresh()
|
||||||
filter_table.autocommit = True
|
filter_table.autocommit = True
|
||||||
r.delete('F2B_ACTIVE_BANS')
|
r.delete('F2B_ACTIVE_BANS')
|
||||||
|
r.delete('F2B_PERM_BANS')
|
||||||
pubsub.unsubscribe()
|
pubsub.unsubscribe()
|
||||||
|
|
||||||
def watch():
|
def watch():
|
||||||
|
@ -223,7 +235,7 @@ def watch():
|
||||||
r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
|
r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
|
||||||
pubsub.subscribe('F2B_CHANNEL')
|
pubsub.subscribe('F2B_CHANNEL')
|
||||||
print 'Subscribing to Redis channel F2B_CHANNEL'
|
print 'Subscribing to Redis channel F2B_CHANNEL'
|
||||||
while True:
|
while not quit_now:
|
||||||
for item in pubsub.listen():
|
for item in pubsub.listen():
|
||||||
for rule_id, rule_regex in RULES.iteritems():
|
for rule_id, rule_regex in RULES.iteritems():
|
||||||
if item['data'] and item['type'] == 'message':
|
if item['data'] and item['type'] == 'message':
|
||||||
|
@ -248,8 +260,8 @@ def snat(snat_target):
|
||||||
target = rule.create_target("SNAT")
|
target = rule.create_target("SNAT")
|
||||||
target.to_source = snat_target
|
target.to_source = snat_target
|
||||||
return rule
|
return rule
|
||||||
|
while not quit_now:
|
||||||
while True:
|
time.sleep(5)
|
||||||
table = iptc.Table('nat')
|
table = iptc.Table('nat')
|
||||||
table.refresh()
|
table.refresh()
|
||||||
table.autocommit = False
|
table.autocommit = False
|
||||||
|
@ -263,17 +275,16 @@ def snat(snat_target):
|
||||||
chain.insert_rule(get_snat_rule())
|
chain.insert_rule(get_snat_rule())
|
||||||
table.commit()
|
table.commit()
|
||||||
else:
|
else:
|
||||||
for i, rule in enumerate(chain.rules):
|
for position, item in enumerate(chain.rules):
|
||||||
if rule == get_snat_rule():
|
if item == get_snat_rule():
|
||||||
if i != 0:
|
if position != 0:
|
||||||
chain.delete_rule(get_snat_rule())
|
chain.delete_rule(get_snat_rule())
|
||||||
table.commit()
|
table.commit()
|
||||||
time.sleep(10)
|
|
||||||
|
|
||||||
def autopurge():
|
def autopurge():
|
||||||
while not quit_now:
|
while not quit_now:
|
||||||
checkChainOrder()
|
time.sleep(10)
|
||||||
refresh_f2boptions()
|
refreshF2boptions()
|
||||||
BAN_TIME = f2boptions['ban_time']
|
BAN_TIME = f2boptions['ban_time']
|
||||||
MAX_ATTEMPTS = f2boptions['max_attempts']
|
MAX_ATTEMPTS = f2boptions['max_attempts']
|
||||||
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
||||||
|
@ -284,7 +295,6 @@ def autopurge():
|
||||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||||
if time.time() - bans[net]['last_attempt'] > BAN_TIME:
|
if time.time() - bans[net]['last_attempt'] > BAN_TIME:
|
||||||
unban(net)
|
unban(net)
|
||||||
time.sleep(10)
|
|
||||||
|
|
||||||
def initChain():
|
def initChain():
|
||||||
print "Initializing mailcow netfilter chain"
|
print "Initializing mailcow netfilter chain"
|
||||||
|
@ -323,7 +333,13 @@ def initChain():
|
||||||
target = iptc.Target(rule, "REJECT")
|
target = iptc.Target(rule, "REJECT")
|
||||||
rule.target = target
|
rule.target = target
|
||||||
if rule not in chain.rules:
|
if rule not in chain.rules:
|
||||||
|
log['time'] = int(round(time.time()))
|
||||||
|
log['priority'] = 'crit'
|
||||||
|
log['message'] = 'Blacklisting host/network %s' % bl_key
|
||||||
|
r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
|
||||||
|
print log['message']
|
||||||
chain.insert_rule(rule)
|
chain.insert_rule(rule)
|
||||||
|
r.hset('F2B_PERM_BANS', '%s' % bl_key, int(round(time.time())))
|
||||||
else:
|
else:
|
||||||
chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')
|
chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')
|
||||||
rule = iptc.Rule6()
|
rule = iptc.Rule6()
|
||||||
|
@ -331,7 +347,14 @@ def initChain():
|
||||||
target = iptc.Target(rule, "REJECT")
|
target = iptc.Target(rule, "REJECT")
|
||||||
rule.target = target
|
rule.target = target
|
||||||
if rule not in chain.rules:
|
if rule not in chain.rules:
|
||||||
|
log['time'] = int(round(time.time()))
|
||||||
|
log['priority'] = 'crit'
|
||||||
|
log['message'] = 'Blacklisting host/network %s' % bl_key
|
||||||
|
r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False))
|
||||||
|
print log['message']
|
||||||
chain.insert_rule(rule)
|
chain.insert_rule(rule)
|
||||||
|
r.hset('F2B_PERM_BANS', '%s' % bl_key, int(round(time.time())))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
@ -359,6 +382,10 @@ if __name__ == '__main__':
|
||||||
autopurge_thread.daemon = True
|
autopurge_thread.daemon = True
|
||||||
autopurge_thread.start()
|
autopurge_thread.start()
|
||||||
|
|
||||||
|
chainwatch_thread = Thread(target=checkChainOrder)
|
||||||
|
chainwatch_thread.daemon = True
|
||||||
|
chainwatch_thread.start()
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, quit)
|
signal.signal(signal.SIGTERM, quit)
|
||||||
atexit.register(clear)
|
atexit.register(clear)
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,15 @@ function fail2ban($_action, $_data = null) {
|
||||||
else {
|
else {
|
||||||
$f2b_options['blacklist'] = "";
|
$f2b_options['blacklist'] = "";
|
||||||
}
|
}
|
||||||
|
$pb = $redis->hGetAll('F2B_PERM_BANS');
|
||||||
|
if (is_array($pb)) {
|
||||||
|
foreach ($pb as $key => $value) {
|
||||||
|
$f2b_options['perm_bans'][] = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$f2b_options['perm_bans'] = "";
|
||||||
|
}
|
||||||
$active_bans = $redis->hGetAll('F2B_ACTIVE_BANS');
|
$active_bans = $redis->hGetAll('F2B_ACTIVE_BANS');
|
||||||
$queue_unban = $redis->hGetAll('F2B_QUEUE_UNBAN');
|
$queue_unban = $redis->hGetAll('F2B_QUEUE_UNBAN');
|
||||||
if (is_array($active_bans)) {
|
if (is_array($active_bans)) {
|
||||||
|
|
|
@ -325,7 +325,7 @@ services:
|
||||||
- acme
|
- acme
|
||||||
|
|
||||||
netfilter-mailcow:
|
netfilter-mailcow:
|
||||||
image: mailcow/netfilter:1.15
|
image: mailcow/netfilter:1.16
|
||||||
build: ./data/Dockerfiles/netfilter
|
build: ./data/Dockerfiles/netfilter
|
||||||
stop_grace_period: 30s
|
stop_grace_period: 30s
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
Loading…
Reference in New Issue