Initial role commit

This commit is contained in:
hiperman
2025-12-02 22:35:27 -05:00
parent 4566427755
commit 2268044797
9 changed files with 668 additions and 2 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# ---> Ansible
*.retry

405
README.md
View File

@@ -1,3 +1,404 @@
# ansible-role-fail2ban
# Ansible Role: fail2ban
An Ansible role for installing and configuring fail2ban.
An Ansible role for installing and configuring fail2ban on Linux systems. This role provides flexible configuration options for fail2ban's main settings, jail configurations, and custom filters/actions.
## Requirements
- Ansible 2.9 or higher
- Target systems: Debian/Ubuntu or RHEL-based distributions
- Python 3 on target hosts
## Role Variables
### Default Variables
Available variables are listed below, along with default values (see `defaults/main.yaml`):
```yaml
# Packages to install
fail2ban_dependencies:
- fail2ban
- python3-systemd
# Service configuration
fail2ban_service: fail2ban
fail2ban_loglevel: INFO
fail2ban_logtarget: /var/log/fail2ban.log
# Default jail settings
fail2ban_ignoreself: "true"
fail2ban_ignoreips:
- 127.0.0.1/8
- ::1
fail2ban_bantime: 10m
fail2ban_findtime: 10m
fail2ban_maxretry: 5
fail2ban_backend: auto
```
### Important Default Settings to Configure
Before deploying to production, consider adjusting these critical settings:
1. **IP Whitelist** (`fail2ban_ignoreips`): Add your management IPs to prevent lockouts
2. **Ban Duration** (`fail2ban_bantime`): Default is 10 minutes; consider longer for production (1h, 24h, 1w)
3. **Max Retry** (`fail2ban_maxretry`): Default is 5 attempts; lower for stricter security
4. **Find Time Window** (`fail2ban_findtime`): Default is 10 minutes; adjust based on your threat model
5. **Backend** (`fail2ban_backend`): Set to `systemd` for systemd-based distros for better performance
### Custom Configuration Variables
#### Using the INI Module Format
You can add custom configuration to `fail2ban.local` and `jail.local` by defining options in the INI module format:
```yaml
# Additional fail2ban.local configuration
fail2ban_configuration: []
- option: loglevel # The INI option name
value: "DEBUG" # The value to set
section: Definition # The INI section
# Additional jail.local configuration
fail2ban_jail_configuration:
- option: destemail
value: "admin@example.com"
section: DEFAULT
- option: sender
value: "fail2ban@example.com"
section: DEFAULT
- option: action
value: "%(action_mwl)s"
section: DEFAULT
```
**INI Format Explanation:**
- `section`: The configuration section in the INI file (e.g., `DEFAULT`, `Definition`, `sshd`)
- `option`: The configuration parameter name
- `value`: The value to assign (must be a string, number, or boolean)
This format allows you to set any fail2ban configuration option without modifying the role itself.
### Debian-Specific Configuration
#### SSH Service Name Issue
On Debian systems, there's a common issue where fail2ban's default SSH jail doesn't work out of the box. The problem:
- fail2ban's default configuration expects the service to be named `sshd`
- On many Debian systems, the actual systemd service is named `ssh.service` (not `sshd.service`)
- When using the `systemd` backend, fail2ban looks for logs from the `sshd` service and finds nothing
- Result: **SSH protection silently fails** - no errors, but no banning occurs
#### The Solution
This role provides the `fail2ban_default_debian_jail_configuration` variable to override Debian's default jail settings:
```yaml
fail2ban_default_debian_jail_configuration:
- option: backend
value: systemd
section: sshd
- option: journalmatch
value: "_SYSTEMD_UNIT=ssh.service" # Use ssh.service instead of sshd.service
section: sshd
```
This configuration:
- Explicitly sets the backend to `systemd` for better performance
- Corrects the journal match to look for `ssh.service` instead of the default `sshd.service`
- Is applied only on Debian systems (via `when: ansible_facts['distribution'] == 'Debian'`)
#### Usage Example
```yaml
- hosts: debian_servers
become: yes
roles:
- role: fail2ban
vars:
fail2ban_backend: systemd
# Fix SSH jail on Debian
fail2ban_default_debian_jail_configuration:
- option: backend
value: systemd
section: sshd
- option: journalmatch
value: "_SYSTEMD_UNIT=ssh.service"
section: sshd
fail2ban_jails:
- name: sshd
enabled: true
maxretry: 3
bantime: 1h
```
> [!NOTE] This only affects the default SSH jail in `/etc/fail2ban/jail.d/defaults-debian.conf`. If you're defining custom SSH jails using `fail2ban_jails`, make sure to specify the correct `journalmatch` or `logpath` for your system.
#### Verifying SSH Protection is Working
After applying the role, verify fail2ban is monitoring SSH:
```bash
# Check if the sshd jail is active
sudo fail2ban-client status sshd
# Check what logs fail2ban is monitoring
sudo fail2ban-client get sshd logpath
# Test with a failed login and check
sudo fail2ban-client status sshd
```
If you see "Currently banned: 0" and "Total banned: 0" after several failed login attempts, the jail may not be configured correctly for your system's SSH service name.
### Creating Custom Jails
#### Method 1: Define Jails as Dictionaries (Recommended for Simple Jails)
Create custom jails by defining them as dictionaries. This method uses a Jinja2 template to generate jail files:
```yaml
fail2ban_jails:
- name: sshd # Jail name (required)
enabled: true # Enable/disable jail
port: ssh # Service port(s)
logpath: /var/log/auth.log # Log file to monitor
maxretry: 3 # Override default maxretry
bantime: 1h # Override default bantime
findtime: 10m # Override default findtime
banaction: iptables-multiport # Ban action to use
filter: sshd # Filter to use
```
**Common jail parameters:**
- `name`: Jail identifier (required)
- `enabled`: true/false
- `port`: Service port (ssh, http, https, etc.)
- `filter`: Filter name (must exist in filter.d/)
- `logpath`: Path to log file(s) to monitor
- `maxretry`: Number of failures before ban
- `findtime`: Time window for counting failures
- `bantime`: Duration of ban
- `banaction`: Action to execute on ban
- `action`: Full action with parameters
#### Method 2: Copy Complete Jail Files (For Complex Configurations)
For more complex jail configurations that require specific options not supported by the template, copy complete jail files:
```yaml
# Path to directory containing custom jail files
fail2ban_jails_path: "{{ playbook_dir }}/files/fail2ban/jails"
```
Place your `.conf` or `.local` files in the specified directory. They will be copied to `/etc/fail2ban/jail.d/`.
### Custom Filters and Actions
#### Copying Custom Filters
Define custom filters for application-specific log patterns:
```yaml
# Path to directory containing custom filter files
fail2ban_filters_path: "{{ playbook_dir }}/files/fail2ban/filters"
```
Place your filter `.conf` files in the specified directory. They will be copied to `/etc/fail2ban/filter.d/`.
**Example custom filter** (`files/fail2ban/filters/myapp.conf`):
```ini
[Definition]
failregex = ^<HOST> .* "POST /login HTTP/.*" 401
^Authentication failure for .* from <HOST>
ignoreregex =
```
#### Copying Custom Actions
Define custom actions for specific ban/unban behaviors:
```yaml
# Path to directory containing custom action files
fail2ban_actions_path: "files/fail2ban/actions"
```
Place your action `.conf` files in the specified directory. They will be copied to `/etc/fail2ban/action.d/`.
To see an example of a custom action, I've included a [ufw-docker action](files/actions/ufw-docker.conf) with the role as I use it frequently. It is an enhanced UFW action that provides additional protection for Docker environments. To use it, just set `fail2ban_actions_path` to `actions`.
> [!WARNING]
> For this action to work properly, you'll need to configure UFW to work properly with Docker, either manually or using the [ufw-docker](https://github.com/chaifeng/ufw-docker) script.
## Dependencies
None.
## Example Playbook
### Basic Usage with Recommended Defaults
```yaml
- hosts: servers
become: yes
roles:
- role: fail2ban
vars:
# Add your management IPs to prevent lockouts
fail2ban_ignoreips:
- 127.0.0.1/8
- ::1
- 10.0.0.0/8 # Your private network
# Stricter default settings
fail2ban_bantime: 1h
fail2ban_maxretry: 3
fail2ban_backend: systemd
```
### Custom Configuration with INI Format
The role uses the `ini_file` module to edit the default configuration files. You may override or add to this configuration:
```yaml
- hosts: servers
become: yes
roles:
- role: fail2ban
vars:
# Override fail2ban.conf base settings using INI format
fail2ban_configuration:
- option: loglevel
value: "NOTICE"
section: Definition
- option: logtarget
value: "SYSLOG"
section: Definition
# Configure jail.conf email notifications
fail2ban_jail_configuration:
- option: destemail
value: "security@example.com"
section: DEFAULT
- option: sender
value: "fail2ban@example.com"
section: DEFAULT
- option: action
value: "%(action_mwl)s" # Mail with logs
section: DEFAULT
```
### Creating Custom Jails with Dictionaries
```yaml
- hosts: servers
become: yes
roles:
- role: fail2ban
vars:
fail2ban_bantime: 1h
fail2ban_maxretry: 3
fail2ban_jails:
# SSH protection
- name: sshd
enabled: true
port: ssh
logpath: /var/log/auth.log
maxretry: 3
bantime: 24h
# Nginx authentication failures
- name: nginx-http-auth
enabled: true
port: http,https
logpath: /var/log/nginx/error.log
# Recidive jail for repeat offenders
- name: recidive
enabled: true
maxretry: 3
findtime: 1w
bantime: 30d
logpath: /var/log/fail2ban.log
```
### Using Complete Jail Files for Complex Configurations
```yaml
- hosts: servers
become: yes
roles:
- role: fail2ban
vars:
# Use dictionaries for simple jails
fail2ban_jails:
- name: sshd
enabled: true
maxretry: 3
# For complex jails, copy complete jail files
fail2ban_jails_path: "{{ playbook_dir }}/files/fail2ban/jails"
```
## Role Tasks
This role performs the following tasks:
1. Installs fail2ban and required dependencies
2. Configures `fail2ban.local` with base and custom settings
3. Configures `jail.local` with default jail parameters
4. Configures Debian-specific jail settings (when applicable)
5. Copies custom filter configurations (if provided)
6. Copies custom action configurations (if provided)
7. Copies or generates jail configurations
8. Starts and enables the fail2ban service
## Handlers
- `Restart fail2ban`: Restarts the fail2ban service when configuration changes are made
## File Organization
Organize your custom files in the following structure:
```
playbook_directory/
├── files/
│ └── fail2ban/
│ ├── filters/
│ │ ├── myapp.conf
│ │ └── custom-filter.conf
│ ├── actions/
│ │ ├── slack-notify.conf
│ │ └── custom-action.conf
│ └── jails/
│ └── complex-jail.local
├── roles/
│ └── fail2ban/
└── playbook.yml
```
## Tips and Best Practices
1. **Always whitelist management IPs** in `fail2ban_ignoreips` to prevent lockouts
2. **Start with lenient settings** (higher maxretry, shorter bantime) and adjust based on logs
3. **Use systemd backend** when available for better performance: `fail2ban_backend: systemd`
4. **Monitor fail2ban logs** at `/var/log/fail2ban.log` to tune your rules
5. **Test custom filters** before deploying to production using `fail2ban-regex`
6. **Use dictionaries for simple jails**, copy files for complex ones requiring specific options
7. **Keep custom filters and actions in version control** alongside your playbooks
## Testing Custom Filters
Test your custom filters before deployment:
```bash
fail2ban-regex /var/log/myapp.log /etc/fail2ban/filter.d/myapp.conf
```
## License
MIT

42
defaults/main.yaml Normal file
View File

@@ -0,0 +1,42 @@
fail2ban_dependencies:
- fail2ban
- python3-systemd
fail2ban_service: fail2ban
fail2ban_loglevel: INFO
fail2ban_logtarget: /var/log/fail2ban.log
fail2ban_ignoreself: "true"
fail2ban_ignoreips:
- 127.0.0.1/8
- ::1
fail2ban_bantime: 10m
fail2ban_findtime: 10m
fail2ban_maxretry: 5
fail2ban_backend: auto
fail2ban_configuration: []
# - option: loglevel
# value: "INFO"
# section: Definition
fail2ban_jail_configuration: []
# - option: ignoreself
# value: "true"
# section: DEFAULT
fail2ban_default_debian_jail_configuration: []
# - option: backend
# value: systemd
# section: sshd
fail2ban_jails:
# - name: recidive
# enabled: true
# maxretry: 3
# findtime: 1w
# bantime: 30d
# banaction: ufw
# logpath = /var/log/fail2ban.log

View File

@@ -0,0 +1,48 @@
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = ufw <add> <blocktype> from <ip> comment "<comment>"
ufw <add> <blocktype> out to <ip> comment "<comment>"
iptables -I DOCKER-USER 1 -s <ip> -j REJECT
<kill>
actionunban = ufw delete <blocktype> from <ip>
ufw delete <blocktype> out to <ip>
iptables -D DOCKER-USER -s <ip> -j REJECT
# Option: kill-mode
# Notes.: can be set to ss or conntrack (may be extended later with other modes) to immediately drop all connections from banned IP, default empty (no kill)
# Example: banaction = ufw[kill-mode=ss]
kill-mode =
# intern conditional parameter used to provide killing mode after ban:
_kill_ =
_kill_ss = ss -K dst "[<ip>]"
_kill_conntrack = conntrack -D -s "<ip>"
# Option: kill
# Notes.: can be used to specify custom killing feature, by default depending on option kill-mode
# Examples: banaction = ufw[kill='ss -K "( sport = :http || sport = :https )" dst "[<ip>]"']
# banaction = ufw[kill='cutter "<ip>"']
kill = <_kill_<kill-mode>>
[Init]
# Option: add
# Notes.: can be set to "insert 1" to insert a rule at certain position (here 1):
add = prepend
# Option: blocktype
# Notes.: reject or deny
blocktype = deny
# Option: comment
# Notes.: comment for rule added by fail2ban
comment = by Fail2Ban after <failures> attempts against <name>

6
handlers/main.yaml Normal file
View File

@@ -0,0 +1,6 @@
---
- name: Restart fail2ban
ansible.builtin.service:
name: "{{ fail2ban_service }}"
state: restarted
listen: Restart fail2ban

0
meta/main.yaml Normal file
View File

106
tasks/main.yaml Normal file
View File

@@ -0,0 +1,106 @@
---
- name: Install fail2ban and dependencies
ansible.builtin.package:
name: "{{ item }}"
state: latest
with_items: "{{ fail2ban_dependencies }}"
- name: Configure fail2ban.local
community.general.ini_file:
path: /etc/fail2ban/fail2ban.local
section: "{{ item.section }}"
option: "{{ item.option }}"
value: "{{ item.value }}"
owner: root
group: root
mode: 0644
loop: "{{ fail2ban_base_configuration + fail2ban_configuration }}"
loop_control:
label: "{{ item.option }}"
notify: Restart fail2ban
- name: Configure jail.local
community.general.ini_file:
path: /etc/fail2ban/jail.local
section: "{{ item.section }}"
option: "{{ item.option }}"
value: "{{ item.value }}"
owner: root
group: root
mode: 0644
loop: "{{ fail2ban_base_jail_configuration + fail2ban_jail_configuration }}"
loop_control:
label: "{{ item.option }}"
notify: Restart fail2ban
- name: Configure Debian default jail config
community.general.ini_file:
path: /etc/fail2ban/jail.d/defaults-debian.conf
section: "{{ item.section }}"
option: "{{ item.option }}"
value: "{{ item.value }}"
owner: root
group: root
mode: 0644
loop: "{{ fail2ban_default_debian_jail_configuration }}"
loop_control:
label: "{{ item.option }}"
notify: Restart fail2ban
when: ansible_facts['distribution'] == 'Debian'
- name: Copy filter configs
community.general.ini_file:
src: "{{ item }}"
dest: /etc/fail2ban/filter.d/
owner: root
group: root
mode: 0644
with_fileglob:
- "{{ fail2ban_filters_path }}/*"
when: fail2ban_filters_path is defined
notify: Restart fail2ban
- name: Copy action configs
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/fail2ban/action.d/
owner: root
group: root
mode: 0644
with_fileglob:
- "{{ fail2ban_actions_path }}/*"
when: fail2ban_actions_path is defined
notify: Restart fail2ban
- name: Copy jail configs
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/fail2ban/jail.d/
owner: root
group: root
mode: 0644
with_fileglob:
- "{{ fail2ban_jails_path }}/*"
when: fail2ban_jails_path is defined
notify: Restart fail2ban
- name: Create jail configs
ansible.builtin.template:
src: jail.local.j2
dest: /etc/fail2ban/jail.d/{{ jail.name }}.local
owner: root
group: root
mode: 0644
loop: "{{ fail2ban_jails }}"
loop_control:
label: "{{ jail.name }}"
loop_var: jail
when: fail2ban_jails
notify: Restart fail2ban
- name: Start and enable service
systemd:
name: fail2ban
state: started
enabled: true

33
templates/jail.local.j2 Normal file
View File

@@ -0,0 +1,33 @@
# /etc/fail2ban/jail.d/{{ jail.name }}.local
# {{ ansible_managed }}
[{{ jail.name }}]
enabled = {{ jail.enabled }}
filter = {{ jail.filter }}
{% if jail.port is defined %}
port = {{ jail.port }}
{% endif %}
{% if jail.logpath is defined %}
logpath = {{ jail.logpath }}
{% endif %}
{% if jail.maxretry is defined %}
maxretry = {{ jail.maxretry }}
{% endif %}
{% if jail.findtime is defined %}
findtime = {{ jail.findtime }}
{% endif %}
{% if jail.bantime is defined %}
bantime = {{ jail.bantime }}
{% endif %}
{% if jail.banaction is defined %}
banaction = {{ jail.banaction }}
{% endif %}
{% if jail.ignoreip is defined %}
ignoreip = {{ jail.ignoreip | join(' ') }}
{% endif %}
{% if jail.backend is defined %}
backend = {{ jail.backend }}
{% endif %}
{% if jail.action is defined %}
action = {{ jail.action }}
{% endif %}

27
vars/main.yaml Normal file
View File

@@ -0,0 +1,27 @@
fail2ban_base_configuration:
- option: loglevel
value: "{{ fail2ban_loglevel }}"
section: Definition
- option: logtarget
value: "{{ fail2ban_logtarget }}"
section: Definition
fail2ban_base_jail_configuration:
- option: ignoreself
value: "{{ fail2ban_ignoreself }}"
section: DEFAULT
- option: ignoreip
value: "{{ fail2ban_ignoreips | join(' ') }}"
section: DEFAULT
- option: bantime
value: "{{ fail2ban_bantime }}"
section: DEFAULT
- option: findtime
value: "{{ fail2ban_findtime }}"
section: DEFAULT
- option: maxretry
value: "{{ fail2ban_maxretry }}"
section: DEFAULT
- option: backend
section: DEFAULT
value: "{{ fail2ban_backend }}"