Ansible Role linuxfabrik.lfops.fail2ban¶
This role installs and configures fail2ban.
Filters and jails are defined in the inventory (fail2ban__filters__*_var / fail2ban__jails__*_var). Each entry either references one of the templates shipped with the role, or uses the raw template to deploy an arbitrary filter or jail definition.
This role provides four additional filters:
- apache-404: Matches HTTP 404 responses in Apache access logs (combined, combinedio, common, fail2ban, linuxfabrikio, matomo, vhost_common). Can be used to ban IPs causing excessive 404 errors.
Important: in order to capture the client ip for all formats, this filter requires the ServerName to be a domain instead of an ip address when using a LogFormat where the canonical ServerName
%vprecedes the client IP%h(matomo, vhost_common). - apache-dos: Matches all incoming requests to Apache. Can be used to limit the number of allowed requests per client.
- portscan: Instantly blocks an IP if it accesses a non-permitted port. Only packets that open a connection count: a TCP SYN without ACK, PSH, RST or FIN, or any UDP packet. Reply traffic of a TCP connection the firewall no longer tracks, such as the late FIN of a half-closed connection, an RST or a mid-connection ACK, is ignored, and so are ICMP error messages, so a server that a local proxy talks to is not banned. TCP scans that send no plain SYN, such as FIN, NULL, Xmas, ACK and SYN/FIN scans, are therefore not banned either; they find no open port on a stateful firewall. A late UDP reply, for example a DNS answer that arrives after the firewall has forgotten the query, is still banned, since the kernel log cannot tell it apart from a UDP scan.
- wordpress-login: Matches failed WordPress logins in Apache access logs (combined, common, linuxfabrikio, matomo, vhost_common), also for WordPress in a sub-path such as
/blog, which WordPress answers with the login form again (HTTP 200) instead of a redirect. Thez10-wordpress-loginjail bans IPs that fail too often. It bans the address Apache logs as the client, so behind a reverse proxy it belongs on the proxy, where that is the visitor's address; on the WordPress host it would ban the proxy.
Available since LFOps 2.0.0.
How the Role Behaves¶
The role deploys its [DEFAULT] section as jail.d/z00-defaults.conf. fail2ban reads jail.d/ in alphabetical order, so this file is read after the 00-firewalld.conf that the fail2ban-firewalld package ships on the Red Hat family, and banaction ends up as fail2ban__jail_default_banaction instead of the packaged firewalld action. banaction_allports is not touched and keeps the packaged value.
Jails are read from a z10-<template>.conf.j2 source but written to jail.d/<filename>.conf, so the destination name, and with it the order in which fail2ban reads the jail, is chosen freely per entry. Filters have no such prefix and are written to filter.d/<filename>.conf.
Dependent Roles¶
Any LFOps playbook that installs this role runs these for you. Optional ones can be disabled via the playbook's skip variables.
- On Rocky 9 and newer, the CRB repository must be enabled, since EPEL builds against it (role: linuxfabrik.lfops.repo_baseos).
- The
python3-policycoreutilsmodule must be installed (required for the SELinux Ansible tasks) (role: linuxfabrik.lfops.policycoreutils). - On RHEL-compatible systems, the EPEL repository must be enabled (role: linuxfabrik.lfops.repo_epel).
- On RHEL-compatible systems, the
nis_enabledSELinux boolean must be enabled (role: linuxfabrik.lfops.selinux). - sshd must log at
VERBOSElevel, otherwise the sshd jail does not see the failed logins (role: linuxfabrik.lfops.sshd). - The firewall must be one the
iptables-multiportbanaction can insert its chains into (role: linuxfabrik.lfops.firewall).
Requirements¶
- Optional: The
apache-*jails read the Apache logs below/var/log/httpd/. - Optional: The
portscanfilter matches the kernel log of an iptables firewall that logs denied packets, as fwbuilder generates it, read through the systemd journal. Without such a firewall the jail never bans.
Tags¶
fail2ban
- Installs and configures fail2ban.
- Triggers: fail2ban.service restart.
fail2ban:configure
- Deploys the actions, filters and jails without touching the packages.
- Triggers: fail2ban.service restart.
fail2ban:state
- Manages the state of the fail2ban service.
- Triggers: none.
Optional Role Variables¶
fail2ban__filter_apache_404_ignoreregex
- A list of regular expressions. Log lines matching any of these patterns will be ignored by the
apache-404filter, even if they match thefailregex. Useful for excluding known missing resources like/favicon.icoor/assets/style.css. - Type: List of strings.
- Default:
[]
fail2ban__filters__group_var / fail2ban__filters__host_var
- The fail2ban filter definition. For the usage in
host_vars/group_vars(can only be used in one group at a time). - Type: List of dictionaries.
- Default:
apache-404,apache-dos,portscan,wordpress-login -
Subkeys:
-
filename:- Mandatory. Destination filename in
filter.d/, and normally is equal to the name of the sourcetemplateused. Will be suffixed with.conf. - Type: String.
- Mandatory. Destination filename in
-
raw:- Optional. Raw content for the filter. Only used if
templateisraw. - Type: String.
- Optional. Raw content for the filter. Only used if
-
state:- Optional.
presentorabsent. - Type: String.
- Default:
'present'
- Optional.
-
template:- Mandatory. Name of the Jinja template source file to use. Have a look at the possible options here, or
raw. - Type: String.
- Mandatory. Name of the Jinja template source file to use. Have a look at the possible options here, or
-
fail2ban__jail_apache_404_bantime
- The ban duration for the apache-404 jail.
- Type: String.
- Default:
'8h'
fail2ban__jail_apache_404_findtime
- The find time for the apache-404 jail. An IP is banned if it causes more than
fail2ban__jail_apache_404_maxretry404 errors within this duration. - Type: String.
- Default:
'10s'
fail2ban__jail_apache_404_maxretry
- The number of 404 errors within
fail2ban__jail_apache_404_findtimebefore an IP is banned. - Type: Integer.
- Default:
10
fail2ban__jail_default_action
- The default action. This will be used in all jails which do not overwrite it.
- Type: String.
- Default:
'%(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]'
fail2ban__jail_default_banaction
- The default banaction, which will be executed as defined in
fail2ban__jail_default_action(assuming the jail does not overwrite it). - Type: String.
- Default:
'iptables-multiport'
fail2ban__jail_default_ignoreip
- List of IP addresses (in CIDR notation) that will be ignored from all jails (assuming the jail does not overwrite it).
- Type: List of strings.
- Default:
[]
fail2ban__jail_default_rocketchat_hook
- The incoming Rocket.Chat hook which will be used to send a notification on bans. For this to work
rocketchathas to be in the action, have a look atfail2ban__jail_default_action(example below). - Type: String.
- Default:
''
fail2ban__jail_portscan_allowed_ports
- Ports on which a connection attempt that the firewall denies does not lead to a ban in the portscan jail. The jail only sees packets the firewall denied, so a port that is open to everyone never triggers it, whether it is listed or not. List the ports that the firewall opens to some sources only, such as SSH restricted to an admin network, so that others trying them are blocked without being banned. The ports are matched exactly and apply to TCP and UDP alike, so
22exempts port 22 and not 2222. An empty list exempts no port. - Type: List of numbers.
- Default:
[22]
fail2ban__jail_portscan_bantime
- The ban duration for the portscan jail.
- Type: String.
- Default:
'8h'
fail2ban__jail_portscan_server_ips
- A list of IP addresses of the server. Only traffic destined for these IPs will be considered. This prevents accidental banning due to traffic which is passing by the server, but not destined for it. Note: This setting is for the portscan jail.
- Type: List of strings.
- Default:
'{{ ansible_facts["all_ipv4_addresses"] }}'
fail2ban__jail_sshd_bantime
- The ban duration for the sshd jail.
- Type: String.
- Default:
'7d'
fail2ban__jail_wordpress_login_bantime
- The ban duration for the wordpress-login jail.
- Type: String.
- Default:
'8h'
fail2ban__jail_wordpress_login_findtime
- The find time for the wordpress-login jail. An IP is banned if it fails to log in
fail2ban__jail_wordpress_login_maxretrytimes within this duration. - Type: String.
- Default:
'10m'
fail2ban__jail_wordpress_login_maxretry
- The number of failed WordPress logins within
fail2ban__jail_wordpress_login_findtimebefore an IP is banned. - Type: Integer.
- Default:
5
fail2ban__jails__group_var / fail2ban__jails__host_var
- The fail2ban jail definition. For the usage in
host_vars/group_vars(can only be used in one group at a time). - Type: List of dictionaries.
- Default:
z10-portscan,z10-sshd -
Subkeys:
-
filename:- Mandatory. Destination filename in
jail.d/, and normally is equal to the name of the sourcetemplateused. Will be suffixed with.conf. - Type: String.
- Mandatory. Destination filename in
-
raw:- Optional. Raw content for the jail. Only used if
templateisraw. - Type: String.
- Optional. Raw content for the jail. Only used if
-
state:- Optional.
presentorabsent. - Type: String.
- Default:
'present'
- Optional.
-
template:- Mandatory. Name of the Jinja template source file to use. Have a look at the possible options here, or
raw. - Type: String.
- Mandatory. Name of the Jinja template source file to use. Have a look at the possible options here, or
-
fail2ban__service_enabled
- Enables or disables the fail2ban service, analogous to
systemctl enable/disable. - Type: Bool.
- Default:
true
fail2ban__service_state
- Changes the state of the fail2ban service, analogous to
systemctl start/stop/restart/reload. - Type: String. One of
reloaded,restarted,started,stopped. - Default:
'started'iffail2ban__service_enabledistrue, else'stopped'
Example:
# optional
fail2ban__filter_apache_404_ignoreregex:
- '^<HOST> [^"]*"GET /favicon\.ico '
- '^<HOST> [^"]*"GET /assets/style\.css '
fail2ban__filters__host_var:
- filename: 'numishare-admin'
state: 'present'
template: 'raw'
raw: |-
[Definition]
failregex = ^<HOST> .*"POST /admin/j_security_check HTTP/[\d.]+" (401|403)
ignoreregex =
fail2ban__jail_apache_404_bantime: '8h'
fail2ban__jail_apache_404_findtime: '10s'
fail2ban__jail_apache_404_maxretry: 10
fail2ban__jail_default_action: |-
%(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
rocketchat[name=%(__name__)s, rocketchat-hook="%(rocketchat-hook)s"]
fail2ban__jail_default_banaction: 'iptables-multiport'
fail2ban__jail_default_ignoreip:
- '192.0.2.1/32' # ansible deployment host
fail2ban__jail_default_rocketchat_hook: ''
fail2ban__jail_portscan_allowed_ports:
- 22
fail2ban__jail_portscan_bantime: '8h'
fail2ban__jail_portscan_server_ips:
- '192.0.2.5'
- '198.51.100.100'
fail2ban__jail_sshd_bantime: '7d'
fail2ban__jail_wordpress_login_bantime: '8h'
fail2ban__jail_wordpress_login_findtime: '10m'
fail2ban__jail_wordpress_login_maxretry: 5
fail2ban__jails__host_var:
- filename: 'z10-apache-dos'
state: 'absent'
template: 'apache-dos'
- filename: 'z10-wordpress-login'
state: 'present'
template: 'wordpress-login'
- filename: 'z20-custom-apache-dos'
state: 'present'
template: 'raw'
raw: |-
[apache-dos]
bantime = 5m
enabled = true
findtime = 10s
logpath = /var/log/httpd/*access?log
maxretry = 600
port = http,https
fail2ban__service_enabled: true
fail2ban__service_state: 'started'