Page Menu
Home
WMGMC Issues
搜索
Configure Global Search
登录
Files
F16304
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
订阅
标记用于日后
授予令牌
Size
18 KB
Referenced Files
None
订阅者
None
View Options
diff --git a/data/Dockerfiles/fail2ban/logwatch.py b/data/Dockerfiles/fail2ban/logwatch.py
index b7430988..5dec15e2 100644
--- a/data/Dockerfiles/fail2ban/logwatch.py
+++ b/data/Dockerfiles/fail2ban/logwatch.py
@@ -1,179 +1,202 @@
#!/usr/bin/env python2
import re
import os
import time
import atexit
import signal
import ipaddress
import subprocess
from threading import Thread
import docker
import redis
import time
import json
yes_regex = re.compile(r'([yY][eE][sS]|[yY])+$')
if re.search(yes_regex, os.getenv('SKIP_FAIL2BAN', 0)):
print "Skipping Fail2ban container..."
raise SystemExit
r = redis.StrictRedis(host='172.22.1.249', decode_responses=True, port=6379, db=0)
-RULES = {
- 'mailcowdockerized_postfix-mailcow_1': 'warning: .*\[([0-9a-f\.:]+)\]: SASL .* authentication failed',
- 'mailcowdockerized_dovecot-mailcow_1': '-login: Disconnected \(auth failed, .*\): user=.*, method=.*, rip=([0-9a-f\.:]+),',
- 'mailcowdockerized_sogo-mailcow_1': 'SOGo.* Login from \'([0-9a-f\.:]+)\' for user .* might not have worked',
- 'mailcowdockerized_php-fpm-mailcow_1': 'Mailcow UI: Invalid password for .* by ([0-9a-f\.:]+)',
-}
+client = docker.from_env()
+
+for container in client.containers.list():
+ if "postfix-mailcow" in container.name:
+ postfix_container = container.name
+ elif "dovecot-mailcow" in container.name:
+ dovecot_container = container.name
+ elif "sogo-mailcow" in container.name:
+ sogo_container = container.name
+ elif "php-fpm-mailcow" in container.name:
+ php_fpm_container = container.name
+
+RULES = {}
+
+RULES[postfix_container] = {}
+RULES[dovecot_container] = {}
+RULES[sogo_container] = {}
+RULES[php_fpm_container] = {}
+
+RULES[postfix_container][1] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .* authentication failed'
+RULES[dovecot_container][1] = '-login: Disconnected \(auth failed, .*\): user=.*, method=.*, rip=([0-9a-f\.:]+),'
+RULES[sogo_container][1] = 'SOGo.* Login from \'([0-9a-f\.:]+)\' for user .* might not have worked'
+RULES[php_fpm_container][1] = 'Mailcow UI: Invalid password for .* by ([0-9a-f\.:]+)'
+
r.setnx("F2B_BAN_TIME", "1800")
r.setnx("F2B_MAX_ATTEMPTS", "10")
r.setnx("F2B_RETRY_WINDOW", "600")
bans = {}
log = {}
quit_now = False
def ban(address):
BAN_TIME = int(r.get("F2B_BAN_TIME"))
MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS"))
RETRY_WINDOW = int(r.get("F2B_RETRY_WINDOW"))
WHITELIST = r.hgetall("F2B_WHITELIST")
ip = ipaddress.ip_address(address.decode('ascii'))
if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped:
ip = ip.ipv4_mapped
address = str(ip)
if ip.is_private or ip.is_loopback:
return
self_network = ipaddress.ip_network(address.decode('ascii'))
if WHITELIST:
for wl_key in WHITELIST:
wl_net = ipaddress.ip_network(wl_key.decode('ascii'), False)
if wl_net.overlaps(self_network):
log['time'] = int(round(time.time()))
log['priority'] = "info"
log['message'] = "Address %s is whitelisted by rule %s" % (self_network, wl_net)
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "Address %s is whitelisted by rule %s" % (self_network, wl_net)
return
net = ipaddress.ip_network((address + ('/24' if type(ip) is ipaddress.IPv4Address else '/64')).decode('ascii'), strict=False)
net = str(net)
if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW:
bans[net] = { 'attempts': 0 }
active_window = RETRY_WINDOW
else:
active_window = time.time() - bans[net]['last_attempt']
bans[net]['attempts'] += 1
bans[net]['last_attempt'] = time.time()
active_window = time.time() - bans[net]['last_attempt']
if bans[net]['attempts'] >= MAX_ATTEMPTS:
log['time'] = int(round(time.time()))
log['priority'] = "crit"
log['message'] = "Banning %s" % net
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "Banning %s for %d minutes" % (net, BAN_TIME / 60)
if type(ip) is ipaddress.IPv4Address:
subprocess.call(["iptables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
subprocess.call(["iptables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
else:
subprocess.call(["ip6tables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
subprocess.call(["ip6tables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
r.hset("F2B_ACTIVE_BANS", "%s" % net, log['time'] + BAN_TIME)
else:
log['time'] = int(round(time.time()))
log['priority'] = "warn"
log['message'] = "%d more attempts in the next %d seconds until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "%d more attempts in the next %d seconds until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
def unban(net):
log['time'] = int(round(time.time()))
log['priority'] = "info"
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
if not net in bans:
log['message'] = "%s is not banned, skipping unban and deleting from queue (if any)" % net
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "%s is not banned, skipping unban and deleting from queue (if any)" % net
r.hdel("F2B_QUEUE_UNBAN", "%s" % net)
return
log['message'] = "Unbanning %s" % net
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "Unbanning %s" % net
if type(ipaddress.ip_network(net.decode('ascii'))) is ipaddress.IPv4Network:
subprocess.call(["iptables", "-D", "INPUT", "-s", net, "-j", "REJECT"])
subprocess.call(["iptables", "-D", "FORWARD", "-s", net, "-j", "REJECT"])
else:
subprocess.call(["ip6tables", "-D", "INPUT", "-s", net, "-j", "REJECT"])
subprocess.call(["ip6tables", "-D", "FORWARD", "-s", net, "-j", "REJECT"])
r.hdel("F2B_ACTIVE_BANS", "%s" % net)
r.hdel("F2B_QUEUE_UNBAN", "%s" % net)
del bans[net]
def quit(signum, frame):
global quit_now
quit_now = True
def clear():
log['time'] = int(round(time.time()))
log['priority'] = "info"
log['message'] = "Clearing all bans"
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "Clearing all bans"
for net in bans.copy():
unban(net)
def watch(container):
log['time'] = int(round(time.time()))
log['priority'] = "info"
log['message'] = "Watching %s" % container
r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
print "Watching", container
- client = docker.from_env()
for msg in client.containers.get(container).attach(stream=True, logs=False):
- result = re.search(RULES[container], msg)
- if result:
- addr = result.group(1)
- ban(addr)
+ for rule_id, rule_regex in RULES[container].iteritems():
+ result = re.search(rule_regex, msg)
+ if result:
+ addr = result.group(1)
+ print "%s matched rule id %d in %s" % (addr, rule_id, container)
+ log['time'] = int(round(time.time()))
+ log['priority'] = "warn"
+ log['message'] = "%s matched rule id %d in %s" % (addr, rule_id, container)
+ r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
+ ban(addr)
def autopurge():
while not quit_now:
BAN_TIME = int(r.get("F2B_BAN_TIME"))
MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS"))
QUEUE_UNBAN = r.hgetall("F2B_QUEUE_UNBAN")
if QUEUE_UNBAN:
for net in QUEUE_UNBAN:
unban(str(net))
for net in bans.copy():
if bans[net]['attempts'] >= MAX_ATTEMPTS:
if time.time() - bans[net]['last_attempt'] > BAN_TIME:
unban(net)
time.sleep(30)
if __name__ == '__main__':
threads = []
for container in RULES:
threads.append(Thread(target=watch, args=(container,)))
threads[-1].daemon = True
threads[-1].start()
autopurge_thread = Thread(target=autopurge)
autopurge_thread.daemon = True
autopurge_thread.start()
signal.signal(signal.SIGTERM, quit)
atexit.register(clear)
while not quit_now:
for thread in threads:
if not thread.isAlive():
break
time.sleep(0.1)
clear()
diff --git a/docker-compose.yml b/docker-compose.yml
index c0388967..4ee191e7 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,367 +1,367 @@
version: '2.1'
services:
unbound-mailcow:
image: mailcow/unbound:1.0
build: ./data/Dockerfiles/unbound
command: /usr/sbin/unbound
depends_on:
mysql-mailcow:
condition: service_healthy
healthcheck:
test: ["CMD", "nslookup", "mailcow.email", "127.0.0.1"]
interval: 3s
timeout: 3s
retries: 5
volumes:
- ./data/conf/unbound/unbound.conf:/etc/unbound/unbound.conf:ro
restart: always
networks:
mailcow-network:
ipv4_address: 172.22.1.254
aliases:
- unbound
mysql-mailcow:
image: mariadb:10.1
command: mysqld --max_allowed_packet=128M
healthcheck:
test: ["CMD", "mysqladmin", "ping", "--host", "localhost", "--silent"]
interval: 10s
timeout: 30s
retries: 5
volumes:
- mysql-vol-1:/var/lib/mysql/
- ./data/conf/mysql/:/etc/mysql/conf.d/:ro
environment:
- MYSQL_ROOT_PASSWORD=${DBROOT}
- MYSQL_DATABASE=${DBNAME}
- MYSQL_USER=${DBUSER}
- MYSQL_PASSWORD=${DBPASS}
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
ipv4_address: 172.22.1.250
aliases:
- mysql
redis-mailcow:
image: redis:alpine
depends_on:
unbound-mailcow:
condition: service_healthy
volumes:
- redis-vol-1:/data/
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
ipv4_address: 172.22.1.249
aliases:
- redis
clamd-mailcow:
image: mailcow/clamd:1.0
build: ./data/Dockerfiles/clamd
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
aliases:
- clamd
rspamd-mailcow:
image: mailcow/rspamd:1.1
build: ./data/Dockerfiles/rspamd
command: >
/bin/bash -c "
sleep 5;
/usr/bin/rspamd -f -u _rspamd -g _rspamd
"
depends_on:
- nginx-mailcow
volumes:
- ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:ro
- ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:ro
- ./data/conf/rspamd/lua/:/etc/rspamd/lua/:ro
- dkim-vol-1:/data/dkim
- rspamd-vol-1:/var/lib/rspamd
restart: always
logging:
options:
max-size: "5m"
dns:
- 172.22.1.254
dns_search: mailcow-network
hostname: rspamd
networks:
mailcow-network:
ipv4_address: 172.22.1.253
aliases:
- rspamd
php-fpm-mailcow:
image: mailcow/phpfpm:1.0
build: ./data/Dockerfiles/phpfpm
command: "php-fpm -d date.timezone=${TZ}"
depends_on:
- redis-mailcow
volumes:
- ./data/web:/web:ro
- ./data/conf/rspamd/dynmaps:/dynmaps:ro
- dkim-vol-1:/data/dkim
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- IMAP_PORT=${IMAP_PORT:-143}
- IMAPS_PORT=${IMAPS_PORT:-993}
- POP_PORT=${POP_PORT:-110}
- POPS_PORT=${POPS_PORT:-995}
- SIEVE_PORT=${SIEVE_PORT:-4190}
- SUBMISSION_PORT=${SUBMISSION_PORT:-587}
- SMTPS_PORT=${SMTPS_PORT:-465}
- SMTP_PORT=${SMTP_PORT:-25}
restart: always
logging:
options:
max-size: "5m"
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
aliases:
- phpfpm
sogo-mailcow:
image: mailcow/sogo:1.0
build: ./data/Dockerfiles/sogo
depends_on:
unbound-mailcow:
condition: service_healthy
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
- TZ=${TZ}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
volumes:
- ./data/conf/sogo/:/etc/sogo/
- ./data/web/thunderbird-plugins:/thunderbird
restart: always
logging:
options:
max-size: "5m"
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
ipv4_address: 172.22.1.252
aliases:
- sogo
dovecot-mailcow:
image: mailcow/dovecot:1.0
build: ./data/Dockerfiles/dovecot
depends_on:
unbound-mailcow:
condition: service_healthy
volumes:
- ./data/conf/dovecot:/usr/local/etc/dovecot
- ./data/assets/ssl:/etc/ssl/mail/:ro
- ./data/conf/sogo/:/etc/sogo/
- vmail-vol-1:/var/vmail
- crypt-vol-1:/mail_crypt/
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
logging:
options:
max-size: "5m"
ports:
- "${IMAP_PORT:-143}:143"
- "${IMAPS_PORT:-993}:993"
- "${POP_PORT:-110}:110"
- "${POPS_PORT:-995}:995"
- "${SIEVE_PORT:-4190}:4190"
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
hostname: ${MAILCOW_HOSTNAME}
networks:
mailcow-network:
aliases:
- dovecot
postfix-mailcow:
image: mailcow/postfix:1.0
build: ./data/Dockerfiles/postfix
depends_on:
unbound-mailcow:
condition: service_healthy
volumes:
- ./data/conf/postfix:/opt/postfix/conf
- ./data/assets/ssl:/etc/ssl/mail/:ro
- postfix-vol-1:/var/spool/postfix
- crypt-vol-1:/var/lib/zeyple
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
ports:
- "${SMTP_PORT:-25}:25"
- "${SMTPS_PORT:-465}:465"
- "${SUBMISSION_PORT:-587}:587"
restart: always
logging:
options:
max-size: "5m"
dns:
- 172.22.1.254
dns_search: mailcow-network
hostname: ${MAILCOW_HOSTNAME}
networks:
mailcow-network:
aliases:
- postfix
memcached-mailcow:
image: memcached:alpine
depends_on:
unbound-mailcow:
condition: service_healthy
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
aliases:
- memcached
nginx-mailcow:
depends_on:
- sogo-mailcow
- php-fpm-mailcow
image: nginx:mainline-alpine
healthcheck:
test: ["CMD", "ping", "php-fpm-mailcow", "-c", "10"]
interval: 10s
timeout: 30s
retries: 5
command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active &&
envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active &&
envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active &&
nginx -g 'daemon off;'"
environment:
- HTTPS_PORT=${HTTPS_PORT:-443}
- HTTP_PORT=${HTTP_PORT:-80}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
volumes:
- ./data/web:/web:ro
- ./data/conf/rspamd/dynmaps:/dynmaps:ro
- ./data/assets/ssl/:/etc/ssl/mail/:ro
- ./data/conf/nginx/:/etc/nginx/conf.d/:rw
logging:
options:
max-size: "5m"
ports:
- "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
- "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
restart: always
dns:
- 172.22.1.254
dns_search: mailcow-network
networks:
mailcow-network:
ipv4_address: 172.22.1.251
aliases:
- nginx
acme-mailcow:
depends_on:
- nginx-mailcow
image: mailcow/acme:1.8
build: ./data/Dockerfiles/acme
dns:
- 172.22.1.254
dns_search: mailcow-network
environment:
- ADDITIONAL_SAN=${ADDITIONAL_SAN}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
- SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n}
volumes:
- ./data/web/.well-known/acme-challenge:/var/www/acme:rw
- ./data/assets/ssl:/var/lib/acme/:rw
- ./data/assets/ssl-example:/var/lib/ssl-example/:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
# do not restart the container too often. Things get worse when we hit let's encrypt's ratelimit.
restart: on-failure:1
networks:
mailcow-network:
aliases:
- acme
fail2ban-mailcow:
- image: mailcow/fail2ban:1.3
+ image: mailcow/fail2ban:1.5
build: ./data/Dockerfiles/fail2ban
depends_on:
- dovecot-mailcow
- postfix-mailcow
- sogo-mailcow
- php-fpm-mailcow
- redis-mailcow
restart: always
privileged: true
environment:
- TZ=${TZ}
- SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-no}
network_mode: "host"
dns:
- 172.22.1.254
dns_search: mailcow-network
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /lib/modules:/lib/modules:ro
ipv6nat:
image: robbertkl/ipv6nat
restart: always
privileged: true
network_mode: "host"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /lib/modules:/lib/modules:ro
networks:
mailcow-network:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 172.22.1.0/24
- subnet: fd4d:6169:6c63:6f77::/64
volumes:
vmail-vol-1:
mysql-vol-1:
dkim-vol-1:
redis-vol-1:
rspamd-vol-1:
postfix-vol-1:
crypt-vol-1:
File Metadata
详情
附加的
Mime Type
text/x-diff
Expires
9月 12 Fri, 3:03 AM (13 h, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5757
默认替代文本
(18 KB)
Attached To
Mode
rMAILCOW mailcow-tracking
附加的
Detach File
Event Timeline
Log In to Comment