mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-31 19:00:54 +00:00 
			
		
		
		
	Enable fail2ban for z-push and add a test for it
This commit is contained in:
		
							parent
							
								
									71b8b94276
								
							
						
					
					
						commit
						27dcb5d7ca
					
				
							
								
								
									
										15
									
								
								conf/fail2ban/filter.d/z-push.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								conf/fail2ban/filter.d/z-push.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | # Source: https://kb.kopano.io/display/ZP/Fail2Ban+support | ||||||
|  | [INCLUDES] | ||||||
|  | before = common.conf | ||||||
|  | [Definition] | ||||||
|  | # Option:  failregex | ||||||
|  | # Notes.:  regex to match the password failures messages in the logfile. The | ||||||
|  | #          host must be matched by a group named "host". The tag "<HOST>" can | ||||||
|  | #          be used for standard IP/hostname matching and is only an alias for | ||||||
|  | #          (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) | ||||||
|  | # Values:  TEXT | ||||||
|  | # | ||||||
|  | failregex = IP: <HOST> failed to authenticate user | ||||||
|  | ignoreregex = | ||||||
|  | [Init] | ||||||
|  | journalmatch = _SYSTEMD_UNIT=fail2ban.service | ||||||
| @ -88,3 +88,10 @@ bantime = 3600 | |||||||
| [slapd] | [slapd] | ||||||
| enabled = true | enabled = true | ||||||
| logpath = /var/log/ldap/slapd.log | logpath = /var/log/ldap/slapd.log | ||||||
|  | 
 | ||||||
|  | [z-push] | ||||||
|  | enabled = true | ||||||
|  | port     = http,https | ||||||
|  | filter   = z-push | ||||||
|  | logpath  = /var/log/z-push/z-push-error.log | ||||||
|  | maxretry = 20 | ||||||
|  | |||||||
| @ -75,6 +75,7 @@ rm -f /usr/local/lib/z-push/autodiscover/config.php | |||||||
| cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php | cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php | ||||||
| sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php | sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php | ||||||
| sed -i "s^define('TIMEZONE', .*^define('TIMEZONE', '$(cat /etc/timezone)');^" /usr/local/lib/z-push/autodiscover/config.php | sed -i "s^define('TIMEZONE', .*^define('TIMEZONE', '$(cat /etc/timezone)');^" /usr/local/lib/z-push/autodiscover/config.php | ||||||
|  | sed -i "s/define('LOGAUTHFAIL', .*/define('LOGAUTHFAIL', true);/" /usr/local/lib/z-push/config.php | ||||||
| 
 | 
 | ||||||
| # Some directories it will use. | # Some directories it will use. | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ set +eu | |||||||
| 
 | 
 | ||||||
| MIAB_DIR=".." | MIAB_DIR=".." | ||||||
| PYMAIL="./test_mail.py" | PYMAIL="./test_mail.py" | ||||||
| 
 | EDITCONF="../tools/editconf.py" | ||||||
| 
 | 
 | ||||||
| # options | # options | ||||||
| SKIP_REMOTE_SMTP_TESTS=no | SKIP_REMOTE_SMTP_TESTS=no | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| test_zpush_logon() { | test_zpush_logon() { | ||||||
|     test_start "zpush-logon" |     test_start "logon" | ||||||
| 
 | 
 | ||||||
|     # create regular user alice |     # create regular user alice | ||||||
|     local alice="alice@somedomain.com" |     local alice="alice@somedomain.com" | ||||||
| @ -62,9 +62,143 @@ test_zpush_logon() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | test_zpush_fail2ban() { | ||||||
|  |     test_start "fail2ban" | ||||||
|  |      | ||||||
|  |     # create regular user with password "alice" | ||||||
|  |     local alice="alice@somedomain.com" | ||||||
|  |     local alice_pw="alice" | ||||||
|  |     create_user "$alice" "$alice_pw" | ||||||
|  | 
 | ||||||
|  |     # The default fail2ban configuration ignores failed logins coming | ||||||
|  |     # from our private ip and localhost. Change it so that it does not | ||||||
|  |     # ignore the private ip in the z-push configuration only. Also | ||||||
|  |     # change the allowed number of failures to a lower value to speed | ||||||
|  |     # up the tests. | ||||||
|  |      | ||||||
|  |     record "[override default fail2ban options]" | ||||||
|  |     local fail2ban_conf_temp="/tmp/runner_zpush_fail2ban.conf" | ||||||
|  |     if [ -e "$fail2ban_conf_temp" ]; then | ||||||
|  |         # if this test was somehow interrupted, the temp still exists | ||||||
|  |         record "1. restore /etc/fail2ban/jail.d/mailinabox.conf" | ||||||
|  |         cp "$fail2ban_conf_temp" "/etc/fail2ban/jail.d/mailinabox.conf" 1>>$TEST_OF 2>&1 || test_failure "Unable to setup test - could not restore fail2ban config" | ||||||
|  |     else | ||||||
|  |         record "1. duplicate /etc/fail2ban/jail.d/mailinabox.conf" | ||||||
|  |         cp --no-clobber /etc/fail2ban/jail.d/mailinabox.conf $fail2ban_conf_temp 1>>$TEST_OF 2>&1 || test_failure "Unable to setup test - could not copy fail2ban config" | ||||||
|  |     fi     | ||||||
|  | 
 | ||||||
|  |     if ! have_test_failures; then | ||||||
|  |         record "2. edit /etc/fail2ban/jail.d/mailinabox.conf" | ||||||
|  |         $EDITCONF /etc/fail2ban/jail.d/mailinabox.conf \ | ||||||
|  |                   -ini-section z-push \ | ||||||
|  |                   "ignoreip=127.0.0.1/8 ::1" \ | ||||||
|  |                   "maxretry=5" >>$TEST_OF 2>&1 || | ||||||
|  |             test_failure "Unable to setup test - changing fail2ban config failed" | ||||||
|  |         if ! have_test_failures; then | ||||||
|  |             record "3. reload fail2ban" | ||||||
|  |             systemctl reload fail2ban >>$TEST_OF 2>&1 || test_failure "Unable to setup test - reloading fail2ban failed" | ||||||
|  |         fi | ||||||
|  |          | ||||||
|  |         # reset fail2ban - unban all | ||||||
|  |         if ! have_test_failures; then | ||||||
|  |             record "4. unban all" | ||||||
|  |             fail2ban-client unban --all >>$TEST_OF 2>&1 || | ||||||
|  |                 test_failure "Unable to setup test - executing unban --all failed" | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     if have_test_failures; then | ||||||
|  |         test_end | ||||||
|  |         return | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     # log in a bunch of times with wrong password | ||||||
|  |     local devid="device1" | ||||||
|  |     local devtype="iPhone" | ||||||
|  |     local n=0 t1 t2 t | ||||||
|  |     local total=10 | ||||||
|  |     local banned=no | ||||||
|  |     local code=0 | ||||||
|  | 
 | ||||||
|  |     start_log_capture | ||||||
|  |      | ||||||
|  |     record "[log in $total times with wrong password]" | ||||||
|  |     while ! have_test_failures && [ $n -lt $total ]; do | ||||||
|  |         t1=$(date +%s) | ||||||
|  |         rest_urlencoded POST "https://$PRIVATE_IP/Microsoft-Server-ActiveSync?Cmd=Ping&DeviceId=$devid&DeviceType=$devtype" "$alice" "bad-alice" --insecure 2>>$TEST_OF | ||||||
|  |         code=$? | ||||||
|  |         t2=$(date +%s) | ||||||
|  |         let t="$t2 - $t1" | ||||||
|  |         record "TRY $n (${t}s): result code $code" | ||||||
|  |         if [ $code -eq 0 ]; then | ||||||
|  |             test_failure "Unexpected logon success" | ||||||
|  |             continue | ||||||
|  |         elif grep -F 'code 7' <<<"$REST_ERROR" >/dev/null; then | ||||||
|  |             # curl error for connection refused | ||||||
|  |             record "BANNED!" | ||||||
|  |             banned=yes | ||||||
|  |             break | ||||||
|  |         elif [ $REST_HTTP_CODE -eq 401 ]; then | ||||||
|  |             # assume a logon failure, reset log monitor | ||||||
|  |             check_logs false zpush nginx_access | ||||||
|  |             start_log_capture | ||||||
|  |         else | ||||||
|  |             test_failure "Error in REST call to z-push: $REST_ERROR" | ||||||
|  |             assert_check_logs zpush nginx_access | ||||||
|  |             continue | ||||||
|  |         fi | ||||||
|  |         record "$REST_OUTPUT" | ||||||
|  |         let n+=1 | ||||||
|  |     done | ||||||
|  | 
 | ||||||
|  |     if ! have_test_failures; then | ||||||
|  |         if [ "$banned" == "no" ]; then | ||||||
|  |             test_failure "Multiple failed logons did not ban ip" | ||||||
|  | 
 | ||||||
|  |         else | ||||||
|  |             record "[logging in with correct password should also fail]" | ||||||
|  |             rest_urlencoded POST "https://$PRIVATE_IP/Microsoft-Server-ActiveSync?Cmd=Ping&DeviceId=$devid&DeviceType=$devtype" "$alice" "$alice_pw" --insecure 2>>$TEST_OF | ||||||
|  |             code=$? | ||||||
|  |             record "result: $code" | ||||||
|  |             if [ $code -eq 0 ]; then | ||||||
|  |                 test_failure "Expected user logon to fail due to ban" | ||||||
|  |             elif grep -F 'code 7' <<<"$REST_ERROR" >/dev/null; then | ||||||
|  |                 # curl error for connection refused | ||||||
|  |                 record "OK: banned: $REST_ERROR" | ||||||
|  |             else | ||||||
|  |                 test_failure "Error in REST call to z-push: $REST_ERROR" | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     # delete alice | ||||||
|  |     delete_user "$alice" | ||||||
|  |      | ||||||
|  |     # reset fail2ban | ||||||
|  |     record "[reset fail2ban config changes]" | ||||||
|  |     record "restore /etc/fail2ban/jail.d/mailinabox.conf" | ||||||
|  |     cp $fail2ban_conf_temp /etc/fail2ban/jail.d/mailinabox.conf | ||||||
|  |     if [ $? -ne 0 ]; then | ||||||
|  |         test_failure "Unable to restore fail2ban config" | ||||||
|  |     else | ||||||
|  |         systemctl reload fail2ban >>$TEST_OF 2>&1 || | ||||||
|  |             test_failure "Unable reload fail2ban" | ||||||
|  |     fi | ||||||
|  |     rm -f $fail2ban_conf_temp | ||||||
|  | 
 | ||||||
|  |     fail2ban-client unban --all >>$TEST_OF 2>&1 || | ||||||
|  |         test_failure "Unable to execute unban --all" | ||||||
|  |          | ||||||
|  |     # done | ||||||
|  |     test_end | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| suite_start "z-push" zpush_start | suite_start "z-push" zpush_start | ||||||
| 
 | 
 | ||||||
| test_zpush_logon | test_zpush_logon | ||||||
|  | test_zpush_fail2ban | ||||||
| 
 | 
 | ||||||
| suite_end zpush_end | suite_end zpush_end | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -40,9 +40,11 @@ settings = sys.argv[2:] | |||||||
| delimiter = "=" | delimiter = "=" | ||||||
| delimiter_re = r"\s*=\s*" | delimiter_re = r"\s*=\s*" | ||||||
| erase_setting = False | erase_setting = False | ||||||
|  | erase_setting_via_comment = True | ||||||
| comment_char = "#" | comment_char = "#" | ||||||
| folded_lines = False | folded_lines = False | ||||||
| testing = False | testing = False | ||||||
|  | ini_section = None | ||||||
| while settings[0][0] == "-" and settings[0] != "--": | while settings[0][0] == "-" and settings[0] != "--": | ||||||
| 	opt = settings.pop(0) | 	opt = settings.pop(0) | ||||||
| 	if opt == "-s": | 	if opt == "-s": | ||||||
| @ -52,12 +54,18 @@ while settings[0][0] == "-" and settings[0] != "--": | |||||||
| 	elif opt == "-e": | 	elif opt == "-e": | ||||||
| 		# Erase settings that have empty values. | 		# Erase settings that have empty values. | ||||||
| 		erase_setting = True | 		erase_setting = True | ||||||
|  | 	elif opt == "-E": | ||||||
|  | 		# Erase settings (remove from file) that have empty values. | ||||||
|  | 		erase_setting = True | ||||||
|  | 		erase_setting_via_comment = False | ||||||
| 	elif opt == "-w": | 	elif opt == "-w": | ||||||
| 		# Line folding is possible in this file. | 		# Line folding is possible in this file. | ||||||
| 		folded_lines = True | 		folded_lines = True | ||||||
| 	elif opt == "-c": | 	elif opt == "-c": | ||||||
| 		# Specifies a different comment character. | 		# Specifies a different comment character. | ||||||
| 		comment_char = settings.pop(0) | 		comment_char = settings.pop(0) | ||||||
|  | 	elif opt == "-ini-section": | ||||||
|  | 		ini_section = settings.pop(0) | ||||||
| 	elif opt == "-t": | 	elif opt == "-t": | ||||||
| 		testing = True | 		testing = True | ||||||
| 	else: | 	else: | ||||||
| @ -77,6 +85,7 @@ for setting in settings: | |||||||
| found = set() | found = set() | ||||||
| buf = "" | buf = "" | ||||||
| input_lines = list(open(filename)) | input_lines = list(open(filename)) | ||||||
|  | cur_section = None | ||||||
| 
 | 
 | ||||||
| while len(input_lines) > 0: | while len(input_lines) > 0: | ||||||
| 	line = input_lines.pop(0) | 	line = input_lines.pop(0) | ||||||
| @ -87,6 +96,24 @@ while len(input_lines) > 0: | |||||||
| 		while len(input_lines) > 0 and input_lines[0][0] in " \t": | 		while len(input_lines) > 0 and input_lines[0][0] in " \t": | ||||||
| 			line += input_lines.pop(0) | 			line += input_lines.pop(0) | ||||||
| 
 | 
 | ||||||
|  | 	# If an ini file, keep track of what section we're in | ||||||
|  | 	if ini_section and line.startswith('[') and line.strip().endswith(']'): | ||||||
|  | 		if cur_section == ini_section.lower(): | ||||||
|  | 			# Put any settings we didn't see at the end of the section. | ||||||
|  | 			for i in range(len(settings)): | ||||||
|  | 				if i not in found: | ||||||
|  | 					name, val = settings[i].split("=", 1) | ||||||
|  | 					if not (not val and erase_setting): | ||||||
|  | 					        buf += name + delimiter + val + "\n" | ||||||
|  | 		cur_section = line.strip()[1:-1].strip().lower() | ||||||
|  | 		buf += line | ||||||
|  | 		continue | ||||||
|  | 
 | ||||||
|  | 	if ini_section and cur_section != ini_section.lower(): | ||||||
|  | 		# we're not processing the desired section, just append | ||||||
|  | 		buf += line | ||||||
|  | 		continue | ||||||
|  | 
 | ||||||
| 	# See if this line is for any settings passed on the command line. | 	# See if this line is for any settings passed on the command line. | ||||||
| 	for i in range(len(settings)): | 	for i in range(len(settings)): | ||||||
| 		# Check if this line contain this setting from the command-line arguments. | 		# Check if this line contain this setting from the command-line arguments. | ||||||
| @ -112,6 +139,7 @@ while len(input_lines) > 0: | |||||||
| 		 | 		 | ||||||
| 		# comment-out the existing line (also comment any folded lines) | 		# comment-out the existing line (also comment any folded lines) | ||||||
| 		if is_comment is None: | 		if is_comment is None: | ||||||
|  | 			if val or not erase_setting or erase_setting_via_comment: | ||||||
| 				buf += comment_char + line.rstrip().replace("\n", "\n" + comment_char) + "\n" | 				buf += comment_char + line.rstrip().replace("\n", "\n" + comment_char) + "\n" | ||||||
| 		else: | 		else: | ||||||
| 			# the line is already commented, pass it through | 			# the line is already commented, pass it through | ||||||
| @ -135,7 +163,8 @@ while len(input_lines) > 0: | |||||||
| 		 | 		 | ||||||
| # Put any settings we didn't see at the end of the file, | # Put any settings we didn't see at the end of the file, | ||||||
| # except settings being cleared. | # except settings being cleared. | ||||||
| for i in range(len(settings)): | if not ini_section or cur_section == ini_section.lower(): | ||||||
|  |         for i in range(len(settings)): | ||||||
|                 if (i not in found): |                 if (i not in found): | ||||||
|                         name, val = settings[i].split("=", 1) |                         name, val = settings[i].split("=", 1) | ||||||
|                         if not (not val and erase_setting): |                         if not (not val and erase_setting): | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user