mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-30 18:50:53 +00:00 
			
		
		
		
	Merge pull request #798 from mail-in-a-box/fail2banjails
add fail2ban jails for ownCloud, postfix submission, roundcube, and the Mail-in-a-Box management daemon
This commit is contained in:
		
						commit
						8844a9185f
					
				| @ -9,6 +9,10 @@ Mail: | |||||||
| * Roundcube is updated to version 1.2.0. | * Roundcube is updated to version 1.2.0. | ||||||
| * SSLv3 and RC4 are now no longer supported in incoming and outgoing mail (SMTP port 25). | * SSLv3 and RC4 are now no longer supported in incoming and outgoing mail (SMTP port 25). | ||||||
| 
 | 
 | ||||||
|  | System: | ||||||
|  | 
 | ||||||
|  | * fail2ban jails added for SMTP submission, Roundcube, ownCloud, the control panel, and munin. | ||||||
|  | 
 | ||||||
| v0.18c (June 2, 2016) | v0.18c (June 2, 2016) | ||||||
| --------------------- | --------------------- | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								conf/fail2ban/filter.d/miab-management-daemon.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								conf/fail2ban/filter.d/miab-management-daemon.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | # Fail2Ban filter Mail-in-a-Box management daemon | ||||||
|  | 
 | ||||||
|  | [INCLUDES] | ||||||
|  | 
 | ||||||
|  | before = common.conf | ||||||
|  | 
 | ||||||
|  | [Definition] | ||||||
|  | 
 | ||||||
|  | _daemon = mailinabox | ||||||
|  | 
 | ||||||
|  | failregex = Mail-in-a-Box Management Daemon: Failed login attempt from ip <HOST> - timestamp .* | ||||||
|  | ignoreregex = | ||||||
							
								
								
									
										7
									
								
								conf/fail2ban/filter.d/miab-munin.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								conf/fail2ban/filter.d/miab-munin.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | [INCLUDES] | ||||||
|  | 
 | ||||||
|  | before = common.conf | ||||||
|  | 
 | ||||||
|  | [Definition] | ||||||
|  | failregex=<HOST> - .*GET /admin/munin/.* HTTP/1.1\" 401.* | ||||||
|  | ignoreregex = | ||||||
							
								
								
									
										7
									
								
								conf/fail2ban/filter.d/miab-owncloud.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								conf/fail2ban/filter.d/miab-owncloud.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | [INCLUDES] | ||||||
|  | 
 | ||||||
|  | before = common.conf | ||||||
|  | 
 | ||||||
|  | [Definition] | ||||||
|  | failregex=Login failed: .*Remote IP: '<HOST>[\)'] | ||||||
|  | ignoreregex = | ||||||
							
								
								
									
										7
									
								
								conf/fail2ban/filter.d/miab-postfix-submission.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								conf/fail2ban/filter.d/miab-postfix-submission.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | [INCLUDES] | ||||||
|  | 
 | ||||||
|  | before = common.conf | ||||||
|  | 
 | ||||||
|  | [Definition] | ||||||
|  | failregex=postfix/submission/smtpd.*warning.*\[<HOST>\]: .* authentication (failed|aborted) | ||||||
|  | ignoreregex = | ||||||
							
								
								
									
										9
									
								
								conf/fail2ban/filter.d/miab-roundcube.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								conf/fail2ban/filter.d/miab-roundcube.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | [INCLUDES] | ||||||
|  | 
 | ||||||
|  | before = common.conf | ||||||
|  | 
 | ||||||
|  | [Definition] | ||||||
|  | 
 | ||||||
|  | failregex = IMAP Error: Login failed for .*? from <HOST>\. AUTHENTICATE.* | ||||||
|  | 
 | ||||||
|  | ignoreregex =  | ||||||
| @ -1,4 +1,5 @@ | |||||||
| # Fail2Ban configuration file for Mail-in-a-Box | # Fail2Ban configuration file for Mail-in-a-Box. Do not edit. | ||||||
|  | # This file is re-generated on updates. | ||||||
| 
 | 
 | ||||||
| [DEFAULT] | [DEFAULT] | ||||||
| # Whitelist our own IP addresses. 127.0.0.1/8 is the default. But our status checks | # Whitelist our own IP addresses. 127.0.0.1/8 is the default. But our status checks | ||||||
| @ -6,24 +7,52 @@ | |||||||
| # ours too. The string is substituted during installation. | # ours too. The string is substituted during installation. | ||||||
| ignoreip = 127.0.0.1/8 PUBLIC_IP | ignoreip = 127.0.0.1/8 PUBLIC_IP | ||||||
| 
 | 
 | ||||||
| # JAILS |  | ||||||
| 
 |  | ||||||
| [ssh] |  | ||||||
| maxretry = 7 |  | ||||||
| bantime = 3600 |  | ||||||
| 
 |  | ||||||
| [ssh-ddos] |  | ||||||
| enabled  = true |  | ||||||
| 
 |  | ||||||
| [sasl] |  | ||||||
| enabled  = true |  | ||||||
| 
 |  | ||||||
| [dovecot] | [dovecot] | ||||||
| enabled = true | enabled = true | ||||||
| filter  = dovecotimap | filter  = dovecotimap | ||||||
|  | logpath = /var/log/mail.log | ||||||
| findtime = 30 | findtime = 30 | ||||||
| maxretry = 20 | maxretry = 20 | ||||||
|  | 
 | ||||||
|  | [miab-management] | ||||||
|  | enabled = true | ||||||
|  | filter = miab-management-daemon | ||||||
|  | port = http,https | ||||||
|  | logpath = /var/log/syslog | ||||||
|  | maxretry = 20 | ||||||
|  | findtime = 30 | ||||||
|  | 
 | ||||||
|  | [miab-munin] | ||||||
|  | enabled  = true | ||||||
|  | port     = http,https | ||||||
|  | filter   = miab-munin | ||||||
|  | logpath  = /var/log/nginx/access.log | ||||||
|  | maxretry = 20 | ||||||
|  | findtime = 30 | ||||||
|  | 
 | ||||||
|  | [miab-owncloud] | ||||||
|  | enabled  = true | ||||||
|  | port     = http,https | ||||||
|  | filter   = miab-owncloud | ||||||
|  | logpath  = STORAGE_ROOT/owncloud/owncloud.log | ||||||
|  | maxretry = 20 | ||||||
|  | findtime = 120 | ||||||
|  | 
 | ||||||
|  | [miab-postfix587] | ||||||
|  | enabled  = true | ||||||
|  | port     = 587 | ||||||
|  | filter   = miab-postfix-submission | ||||||
| logpath  = /var/log/mail.log | logpath  = /var/log/mail.log | ||||||
|  | maxretry = 20 | ||||||
|  | findtime = 30 | ||||||
|  | 
 | ||||||
|  | [miab-roundcube] | ||||||
|  | enabled  = true | ||||||
|  | port     = http,https | ||||||
|  | filter   = miab-roundcube | ||||||
|  | logpath  = /var/log/roundcubemail/errors | ||||||
|  | maxretry = 20 | ||||||
|  | findtime = 30 | ||||||
| 
 | 
 | ||||||
| [recidive] | [recidive] | ||||||
| enabled  = true | enabled  = true | ||||||
| @ -39,3 +68,13 @@ action   = iptables-allports[name=recidive] | |||||||
| # By default we don't configure this address and no action is required from the admin anyway. | # By default we don't configure this address and no action is required from the admin anyway. | ||||||
| # So the notification is ommited. This will prevent message appearing in the mail.log that mail | # So the notification is ommited. This will prevent message appearing in the mail.log that mail | ||||||
| # can't be delivered to fail2ban@$HOSTNAME. | # can't be delivered to fail2ban@$HOSTNAME. | ||||||
|  | 
 | ||||||
|  | [sasl] | ||||||
|  | enabled  = true | ||||||
|  | 
 | ||||||
|  | [ssh] | ||||||
|  | maxretry = 7 | ||||||
|  | bantime = 3600 | ||||||
|  | 
 | ||||||
|  | [ssh-ddos] | ||||||
|  | enabled  = true | ||||||
| @ -1,7 +1,8 @@ | |||||||
| #!/usr/bin/python3 | #!/usr/bin/python3 | ||||||
| 
 | 
 | ||||||
| import os, os.path, re, json | import os, os.path, re, json, time | ||||||
| import subprocess | import subprocess | ||||||
|  | 
 | ||||||
| from functools import wraps | from functools import wraps | ||||||
| 
 | 
 | ||||||
| from flask import Flask, request, render_template, abort, Response, send_from_directory, make_response | from flask import Flask, request, render_template, abort, Response, send_from_directory, make_response | ||||||
| @ -45,6 +46,9 @@ def authorized_personnel_only(viewfunc): | |||||||
| 			privs = [] | 			privs = [] | ||||||
| 			error = "Incorrect username or password" | 			error = "Incorrect username or password" | ||||||
| 
 | 
 | ||||||
|  | 			# Write a line in the log recording the failed login | ||||||
|  | 			log_failed_login(request) | ||||||
|  | 
 | ||||||
| 		# Authorized to access an API view? | 		# Authorized to access an API view? | ||||||
| 		if "admin" in privs: | 		if "admin" in privs: | ||||||
| 			# Call view func. | 			# Call view func. | ||||||
| @ -117,6 +121,9 @@ def me(): | |||||||
| 	try: | 	try: | ||||||
| 		email, privs = auth_service.authenticate(request, env) | 		email, privs = auth_service.authenticate(request, env) | ||||||
| 	except ValueError as e: | 	except ValueError as e: | ||||||
|  | 		# Log the failed login | ||||||
|  | 		log_failed_login(request) | ||||||
|  | 
 | ||||||
| 		return json_response({ | 		return json_response({ | ||||||
| 			"status": "invalid", | 			"status": "invalid", | ||||||
| 			"reason": "Incorrect username or password", | 			"reason": "Incorrect username or password", | ||||||
| @ -583,6 +590,22 @@ def munin_cgi(filename): | |||||||
| 		app.logger.warning("munin_cgi: munin-cgi-graph returned 404 status code. PATH_INFO=%s", env['PATH_INFO']) | 		app.logger.warning("munin_cgi: munin-cgi-graph returned 404 status code. PATH_INFO=%s", env['PATH_INFO']) | ||||||
| 	return response | 	return response | ||||||
| 
 | 
 | ||||||
|  | def log_failed_login(request): | ||||||
|  | 	# We need to figure out the ip to list in the message, all our calls are routed | ||||||
|  | 	# through nginx who will put the original ip in X-Forwarded-For. | ||||||
|  | 	# During setup we call the management interface directly to determine the user | ||||||
|  | 	# status. So we can't always use X-Forwarded-For because during setup that header | ||||||
|  | 	# will not be present. | ||||||
|  | 	if request.headers.getlist("X-Forwarded-For"): | ||||||
|  | 		ip = request.headers.getlist("X-Forwarded-For")[0] | ||||||
|  | 	else: | ||||||
|  | 		ip = request.remote_addr | ||||||
|  | 
 | ||||||
|  | 	# We need to add a timestamp to the log message, otherwise /dev/log will eat the "duplicate" | ||||||
|  | 	# message. | ||||||
|  | 	app.logger.warning( "Mail-in-a-Box Management Daemon: Failed login attempt from ip %s - timestamp %s" % (ip, time.time())) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| # APP | # APP | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|  | |||||||
| @ -92,7 +92,6 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then | |||||||
| 	mkdir -p $STORAGE_ROOT/owncloud | 	mkdir -p $STORAGE_ROOT/owncloud | ||||||
| 
 | 
 | ||||||
| 	# Create an initial configuration file. | 	# Create an initial configuration file. | ||||||
| 	TIMEZONE=$(cat /etc/timezone) |  | ||||||
| 	instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1) | 	instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1) | ||||||
| 	cat > $STORAGE_ROOT/owncloud/config.php <<EOF; | 	cat > $STORAGE_ROOT/owncloud/config.php <<EOF; | ||||||
| <?php | <?php | ||||||
| @ -125,7 +124,6 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then | |||||||
|   'mail_smtppassword' => '', |   'mail_smtppassword' => '', | ||||||
|   'mail_from_address' => 'owncloud', |   'mail_from_address' => 'owncloud', | ||||||
|   'mail_domain' => '$PRIMARY_HOSTNAME', |   'mail_domain' => '$PRIMARY_HOSTNAME', | ||||||
|   'logtimezone' => '$TIMEZONE', |  | ||||||
| ); | ); | ||||||
| ?> | ?> | ||||||
| EOF | EOF | ||||||
| @ -163,7 +161,11 @@ fi | |||||||
| #   so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so | #   so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so | ||||||
| #   this will make sure it has the right value. | #   this will make sure it has the right value. | ||||||
| # * Some settings weren't included in previous versions of Mail-in-a-Box. | # * Some settings weren't included in previous versions of Mail-in-a-Box. | ||||||
|  | # * We need to set the timezone to the system timezone to allow fail2ban to ban | ||||||
|  | #   users within the proper timeframe | ||||||
|  | # * We need to set the logdateformat to something that will work correctly with fail2ban | ||||||
| # Use PHP to read the settings file, modify it, and write out the new settings array. | # Use PHP to read the settings file, modify it, and write out the new settings array. | ||||||
|  | TIMEZONE=$(cat /etc/timezone) | ||||||
| CONFIG_TEMP=$(/bin/mktemp) | CONFIG_TEMP=$(/bin/mktemp) | ||||||
| php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php; | php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php; | ||||||
| <?php | <?php | ||||||
| @ -175,6 +177,9 @@ include("$STORAGE_ROOT/owncloud/config.php"); | |||||||
| \$CONFIG['overwrite.cli.url'] = '/cloud'; | \$CONFIG['overwrite.cli.url'] = '/cloud'; | ||||||
| \$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address | \$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address | ||||||
| 
 | 
 | ||||||
|  | \$CONFIG['logtimezone'] = '$TIMEZONE'; | ||||||
|  | \$CONFIG['logdateformat'] = 'Y-m-d H:i:s'; | ||||||
|  | 
 | ||||||
| echo "<?php\n\\\$CONFIG = "; | echo "<?php\n\\\$CONFIG = "; | ||||||
| var_export(\$CONFIG); | var_export(\$CONFIG); | ||||||
| echo ";"; | echo ";"; | ||||||
|  | |||||||
| @ -291,10 +291,12 @@ restart_service resolvconf | |||||||
| 
 | 
 | ||||||
| # ### Fail2Ban Service | # ### Fail2Ban Service | ||||||
| 
 | 
 | ||||||
| # Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix and ssh | # Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix, ssh, etc. | ||||||
| cat conf/fail2ban/jail.local \ | rm -f /etc/fail2ban/jail.local # we used to use this file but don't anymore | ||||||
|  | cat conf/fail2ban/jails.conf \ | ||||||
| 	| sed "s/PUBLIC_IP/$PUBLIC_IP/g" \ | 	| sed "s/PUBLIC_IP/$PUBLIC_IP/g" \ | ||||||
| 	> /etc/fail2ban/jail.local | 	| sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \ | ||||||
| cp conf/fail2ban/dovecotimap.conf /etc/fail2ban/filter.d/dovecotimap.conf | 	> /etc/fail2ban/jail.d/mailinabox.conf | ||||||
|  | cp -f conf/fail2ban/filter.d/* /etc/fail2ban/filter.d/ | ||||||
| 
 | 
 | ||||||
| restart_service fail2ban | restart_service fail2ban | ||||||
|  | |||||||
							
								
								
									
										195
									
								
								tests/fail2ban.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								tests/fail2ban.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,195 @@ | |||||||
|  | # Test that a box's fail2ban setting are working | ||||||
|  | # correctly by attempting a bunch of failed logins. | ||||||
|  | # Specify SSH login information the command line - | ||||||
|  | # we use that to reset fail2ban after each test, | ||||||
|  | # and we extract the hostname from that to open | ||||||
|  | # connections to. | ||||||
|  | ###################################################################### | ||||||
|  | 
 | ||||||
|  | import sys, os, time, functools | ||||||
|  | 
 | ||||||
|  | # parse command line | ||||||
|  | 
 | ||||||
|  | if len(sys.argv) < 2: | ||||||
|  | 	print("Usage: tests/fail2ban.py user@hostname") | ||||||
|  | 	sys.exit(1) | ||||||
|  | 
 | ||||||
|  | ssh_user, hostname = sys.argv[1].split("@", 1) | ||||||
|  | 
 | ||||||
|  | # define some test types | ||||||
|  | 
 | ||||||
|  | import socket | ||||||
|  | socket.setdefaulttimeout(10) | ||||||
|  | 
 | ||||||
|  | class IsBlocked(Exception): | ||||||
|  | 	"""Tests raise this exception when it appears that a fail2ban | ||||||
|  | 	jail is in effect, i.e. on a connection refused error.""" | ||||||
|  | 	pass | ||||||
|  | 
 | ||||||
|  | def smtp_test(): | ||||||
|  | 	import smtplib | ||||||
|  | 
 | ||||||
|  | 	try: | ||||||
|  | 		server = smtplib.SMTP(hostname, 587) | ||||||
|  | 	except ConnectionRefusedError: | ||||||
|  | 		# looks like fail2ban worked | ||||||
|  | 		raise IsBlocked() | ||||||
|  | 	server.starttls() | ||||||
|  | 	server.ehlo_or_helo_if_needed() | ||||||
|  | 
 | ||||||
|  | 	try: | ||||||
|  | 		server.login("fakeuser", "fakepassword") | ||||||
|  | 		raise Exception("authentication didn't fail") | ||||||
|  | 	except smtplib.SMTPAuthenticationError: | ||||||
|  | 		# athentication should fail | ||||||
|  | 		pass | ||||||
|  | 
 | ||||||
|  | 	try: | ||||||
|  | 		server.quit() | ||||||
|  | 	except: | ||||||
|  | 		# ignore errors here | ||||||
|  | 		pass | ||||||
|  | 
 | ||||||
|  | def imap_test(): | ||||||
|  | 	import imaplib | ||||||
|  | 
 | ||||||
|  | 	try: | ||||||
|  | 		M = imaplib.IMAP4_SSL(hostname) | ||||||
|  | 	except ConnectionRefusedError: | ||||||
|  | 		# looks like fail2ban worked | ||||||
|  | 		raise IsBlocked() | ||||||
|  | 
 | ||||||
|  | 	try: | ||||||
|  | 		M.login("fakeuser", "fakepassword") | ||||||
|  | 		raise Exception("authentication didn't fail") | ||||||
|  | 	except imaplib.IMAP4.error: | ||||||
|  | 		# authentication should fail | ||||||
|  | 		pass | ||||||
|  | 	finally: | ||||||
|  | 		M.logout() # shuts down connection, has nothing to do with login() | ||||||
|  | 
 | ||||||
|  | def http_test(url, expected_status, postdata=None, qsargs=None, auth=None): | ||||||
|  | 	import urllib.parse | ||||||
|  | 	import requests | ||||||
|  | 	from requests.auth import HTTPBasicAuth | ||||||
|  | 
 | ||||||
|  | 	# form request | ||||||
|  | 	url = urllib.parse.urljoin("https://" + hostname, url) | ||||||
|  | 	if qsargs: url += "?" + urllib.parse.urlencode(qsargs) | ||||||
|  | 	urlopen = requests.get if not postdata else requests.post | ||||||
|  | 
 | ||||||
|  | 	try: | ||||||
|  | 		# issue request | ||||||
|  | 		r = urlopen( | ||||||
|  | 			url, | ||||||
|  | 			auth=HTTPBasicAuth(*auth) if auth else None, | ||||||
|  | 			data=postdata, | ||||||
|  | 			headers={'User-Agent': 'Mail-in-a-Box fail2ban tester'}, | ||||||
|  | 			timeout=8) | ||||||
|  | 	except requests.exceptions.ConnectTimeout as e: | ||||||
|  | 		raise IsBlocked() | ||||||
|  | 	except requests.exceptions.ConnectionError as e: | ||||||
|  | 		if "Connection refused" in str(e): | ||||||
|  | 			raise IsBlocked() | ||||||
|  | 		raise # some other unexpected condition | ||||||
|  | 
 | ||||||
|  | 	# return response status code | ||||||
|  | 	if r.status_code != expected_status: | ||||||
|  | 		r.raise_for_status() # anything but 200 | ||||||
|  | 		raise IOError("Got unexpected status code %s." % r.status_code) | ||||||
|  | 
 | ||||||
|  | # define how to run a test | ||||||
|  | 
 | ||||||
|  | def restart_fail2ban_service(final=False): | ||||||
|  | 	# Log in over SSH to restart fail2ban. | ||||||
|  | 	command = "sudo fail2ban-client reload" | ||||||
|  | 	if not final: | ||||||
|  | 		# Stop recidive jails during testing. | ||||||
|  | 		command += " && sudo fail2ban-client stop recidive" | ||||||
|  | 	os.system("ssh %s@%s \"%s\"" % (ssh_user, hostname, command)) | ||||||
|  | 
 | ||||||
|  | def testfunc_runner(i, testfunc, *args): | ||||||
|  | 	print(i+1, end=" ", flush=True) | ||||||
|  | 	testfunc(*args) | ||||||
|  | 
 | ||||||
|  | def run_test(testfunc, args, count, within_seconds, parallel): | ||||||
|  | 	# Run testfunc count times in within_seconds seconds (and actually | ||||||
|  | 	# within a little less time so we're sure we're under the limit). | ||||||
|  | 	# | ||||||
|  | 	# Because some services are slow, like IMAP, we can't necessarily | ||||||
|  | 	# run testfunc sequentially and still get to count requests within | ||||||
|  | 	# the required time. So we split the requests across threads. | ||||||
|  | 
 | ||||||
|  | 	import requests.exceptions | ||||||
|  | 	from multiprocessing import Pool | ||||||
|  | 
 | ||||||
|  | 	restart_fail2ban_service() | ||||||
|  | 
 | ||||||
|  | 	# Log. | ||||||
|  | 	print(testfunc.__name__, " ".join(str(a) for a in args), "...") | ||||||
|  | 
 | ||||||
|  | 	# Record the start time so we can know how to evenly space our | ||||||
|  | 	# calls to testfunc. | ||||||
|  | 	start_time = time.time() | ||||||
|  | 
 | ||||||
|  | 	with Pool(parallel) as p: | ||||||
|  | 		# Distribute the requests across the pool. | ||||||
|  | 		asyncresults = [] | ||||||
|  | 		for i in range(count): | ||||||
|  | 			ar = p.apply_async(testfunc_runner, [i, testfunc] + list(args)) | ||||||
|  | 			asyncresults.append(ar) | ||||||
|  | 
 | ||||||
|  | 		# Wait for all runs to finish. | ||||||
|  | 		p.close() | ||||||
|  | 		p.join() | ||||||
|  | 
 | ||||||
|  | 		# Check for errors. | ||||||
|  | 		for ar in asyncresults: | ||||||
|  | 			try: | ||||||
|  | 				ar.get() | ||||||
|  | 			except IsBlocked: | ||||||
|  | 				print("Test machine prematurely blocked!") | ||||||
|  | 				return False | ||||||
|  | 
 | ||||||
|  | 	# Did we make enough requests within the limit? | ||||||
|  | 	if (time.time()-start_time) > within_seconds: | ||||||
|  | 		raise Exception("Test failed to make %s requests in %d seconds." % (count, within_seconds)) | ||||||
|  | 
 | ||||||
|  | 	# Wait a moment for the block to be put into place. | ||||||
|  | 	time.sleep(4) | ||||||
|  | 
 | ||||||
|  | 	# The next call should fail. | ||||||
|  | 	print("*", end=" ", flush=True) | ||||||
|  | 	try: | ||||||
|  | 		testfunc(*args) | ||||||
|  | 	except IsBlocked: | ||||||
|  | 		# Success -- this one is supposed to be refused. | ||||||
|  | 		print("blocked [OK]") | ||||||
|  | 		return True # OK | ||||||
|  | 
 | ||||||
|  | 	print("not blocked!") | ||||||
|  | 	return False | ||||||
|  | 
 | ||||||
|  | ###################################################################### | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  | 	# run tests | ||||||
|  | 
 | ||||||
|  | 	# SMTP bans at 10 even though we say 20 in the config because we get | ||||||
|  | 	# doubled-up warnings in the logs, we'll let that be for now | ||||||
|  | 	run_test(smtp_test, [], 10, 30, 8) | ||||||
|  | 
 | ||||||
|  | 	# IMAP | ||||||
|  | 	run_test(imap_test, [], 20, 30, 4) | ||||||
|  | 
 | ||||||
|  | 	# Mail-in-a-Box control panel | ||||||
|  | 	run_test(http_test, ["/admin/me", 200], 20, 30, 1) | ||||||
|  | 
 | ||||||
|  | 	# Munin via the Mail-in-a-Box control panel | ||||||
|  | 	run_test(http_test, ["/admin/munin/", 401], 20, 30, 1) | ||||||
|  | 
 | ||||||
|  | 	# ownCloud | ||||||
|  | 	run_test(http_test, ["/cloud/remote.php/webdav", 401, None, None, ["aa", "aa"]], 20, 120, 1) | ||||||
|  | 
 | ||||||
|  | 	# restart fail2ban so that this client machine is no longer blocked | ||||||
|  | 	restart_fail2ban_service(final=True) | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user