diff --git a/docs/releasenotes.rst b/docs/releasenotes.rst index c9651ee3e317b3099fc7ceced7502192b46c9172..490c8324a1d00fc19f26879309e541731c511337 100644 --- a/docs/releasenotes.rst +++ b/docs/releasenotes.rst @@ -44,6 +44,11 @@ Upgraded to Ansible 10.4.x. Dropped support for Debian 11 * Switched to using Paramiko + SFTP backend (instead of pexpect + SFTP), which should improve the backup performance. +* ``common`` role + + * TCP packets dropped due to incoming connection rate-limiting are + now logged with the ``RATELIMIT`` prefix in the system logs. + * ``ldap_server`` role * TLSv1.3 is now enabled by default (with RFC-defined mandatory diff --git a/docs/rolereference.rst b/docs/rolereference.rst index 5caa1691a96ea58d0ece5fd78b9e779c1f4a7306..c0609ca80f46a347e6a452d28a2ac1410e678c44 100644 --- a/docs/rolereference.rst +++ b/docs/rolereference.rst @@ -261,7 +261,10 @@ The role implements the following: which allows ICMP echo requests (PING), incoming connection on TCP port 22 (SSH), and also introduces rate-limitting for incoming ICMP echo request pacakges and (new) TCP connections. The rate-limitting is based on the source - IP address, using the ``iptables hashlimit`` module. + IP address, using the ``iptables hashlimit`` module. Incoming TCP packets + that have been dropped due to rate-limiting are logged in the system log with + message prefix ``RATELIMIT``. No logging is done for the dropped ICMP + packets. * Sets-up system for performing checks on certificates (currently only if they expire within less than 30 days). Roles that want their certificates checked should deploy a ``.conf`` to directory ``/etc/check_certificate/`` with paths diff --git a/roles/common/molecule/default/tests/test_default.py b/roles/common/molecule/default/tests/test_default.py index ce477074b3049a85dfe88123912b224990ac15b9..d72aa7cc55293684c9a68578a45e7ac38a4a68c9 100644 --- a/roles/common/molecule/default/tests/test_default.py +++ b/roles/common/molecule/default/tests/test_default.py @@ -1,3 +1,4 @@ +import ipaddress import os import re @@ -492,3 +493,31 @@ def test_legacy_iptables_removal_script(host): assert script.user == "root" assert script.group == "root" assert script.mode == 0o755 + + +@pytest.mark.parametrize('ip_protocol', [4, 6]) +def test_tcp_rate_limit_dropped_packet_message_tagging(host, ip_protocol): + """ + Tests log message tagging for dropped incoming TCP packets + (due to rate-limiting). + """ + + ansible_runner = testinfra.utils.ansible_runner.AnsibleRunner(os.environ['MOLECULE_INVENTORY_FILE']) + client = ansible_runner.get_host(ansible_runner.get_hosts('client-allowed')[0]) + + hostname = host.run("hostname").stdout.strip() + timestamp = host.run("date '+%b %d %H:%M:%S'").stdout.strip() + ip_address = client.run("getent ahostsv%s %s", str(ip_protocol), hostname).stdout.strip().split("\n")[-1].split()[0] + ip_address_exploded = ipaddress.ip_address(ip_address).exploded + + expected_message = re.compile(r"%s kernel: RATELIMIT .* DST=%s .* PROTO=TCP .* DPT=22" % (re.escape(hostname), re.escape(ip_address_exploded))) + + with host.sudo(): + + # This should trigger the firewall rules and produce log entries. + client.run("for i in $(seq 12); do nc.openbsd -%s -w 1 -z %s 22; done", str(ip_protocol), hostname) + + log = host.run("journalctl --dmesg --since %s", timestamp) + + assert log.rc == 0 + assert expected_message.search(log.stdout) is not None diff --git a/roles/common/templates/00-base.conf.j2 b/roles/common/templates/00-base.conf.j2 index f2d915dcb8aa1baf0785c20a0084bcc3bac230d5..2111cf3670a83a2a7b30237ec79ca677d921cc64 100644 --- a/roles/common/templates/00-base.conf.j2 +++ b/roles/common/templates/00-base.conf.j2 @@ -33,7 +33,7 @@ domain ip { proto tcp tcp-flags (FIN SYN RST ACK) SYN { mod hashlimit hashlimit {{ incoming_connection_limit }} hashlimit-burst {{ incoming_connection_limit_burst }} hashlimit-mode srcip hashlimit-name icmp RETURN; - LOG; + LOG log-prefix 'RATELIMIT '; DROP; } } @@ -89,7 +89,7 @@ domain ip6 { proto tcp tcp-flags (FIN SYN RST ACK) SYN { mod hashlimit hashlimit {{ incoming_connection_limit }} hashlimit-burst {{ incoming_connection_limit_burst }} hashlimit-mode srcip hashlimit-name icmp RETURN; - LOG; + LOG log-prefix 'RATELIMIT '; DROP; } }