host_firewall: handshake meters per source reject over-limit connections with fw-rl-hs:, SYN meters log fw-rl-addr: and fw-rl-range: as evidence, conntrack accounting on
This commit is contained in:
@@ -103,6 +103,12 @@ table inet host {
|
||||
set rl_{{ svc.name }}_v6r {
|
||||
type ipv6_addr; flags dynamic; timeout 10m; size 16384;
|
||||
}
|
||||
set rl_hs_{{ svc.name }}_v4 {
|
||||
type ipv4_addr; flags dynamic; timeout 10m; size 65535;
|
||||
}
|
||||
set rl_hs_{{ svc.name }}_v6 {
|
||||
type ipv6_addr; flags dynamic; timeout 10m; size 65535;
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
chain input {
|
||||
@@ -119,6 +125,11 @@ table inet host {
|
||||
ip saddr @firehol_v4 jump firehol_in
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% if rl_input %}
|
||||
# Before the established rule: the packet that completes a handshake is the second original-direction packet
|
||||
# (needs net.netfilter.nf_conntrack_acct).
|
||||
meta l4proto tcp ct state established ct direction original ct original packets 2 jump ratelimit_hs_in
|
||||
{% endif %}
|
||||
ct state established,related accept
|
||||
{% if firewall.scanners %}
|
||||
@@ -182,7 +193,10 @@ table inet host {
|
||||
# range (IPv4 /24, IPv6 /48; the per-source rate times range_factor). Admin, cluster, VPN, guest and internal sources
|
||||
# and the public addresses of all inventory hosts (rl_exempt_v4, rl_exempt_v6) are exempt: cluster nodes reach other hosts
|
||||
# with their public address (ArgoCD git fetches, mail submission). The public set is checked only here, no other chain
|
||||
# trusts it. The excess drops with its own log prefix, which CrowdSec reads as a ratelimit event and not as a port scan.
|
||||
# trusts it. The SYN meters drop the excess with the log prefixes fw-rl-syn-addr: (per address) and fw-rl-syn-range: (per range),
|
||||
# which a spoofed source can trigger: evidence only. The handshake meters count the packet that completes a handshake
|
||||
# (the second original-direction packet of an established connection), which a spoofed source cannot send: the excess is
|
||||
# logged with fw-rl-hs: and reset. Only the address tier exists there; ranges are left to CrowdSec.
|
||||
{% macro rl_exempt() %}
|
||||
iifname "wt0" return
|
||||
{% for iface in cluster_ifaces + internal_ifaces + guest_ifaces %}
|
||||
@@ -204,19 +218,47 @@ table inet host {
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
{% for svc in rl_services if svc.input or svc.forward %}
|
||||
chain rl_{{ svc.name }} {
|
||||
update @rl_{{ svc.name }}_v4 { ip saddr limit rate over {{ svc.rate }}/{{ svc.per }} burst {{ svc.burst }} packets } counter jump rl_drop
|
||||
update @rl_{{ svc.name }}_v6 { ip6 saddr & ffff:ffff:ffff:ffff:: limit rate over {{ svc.rate }}/{{ svc.per }} burst {{ svc.burst }} packets } counter jump rl_drop
|
||||
update @rl_{{ svc.name }}_v4r { ip saddr & 255.255.255.0 limit rate over {{ svc.rate * svc.f4 }}/{{ svc.per }} burst {{ svc.burst * svc.f4 }} packets } counter jump rl_drop
|
||||
update @rl_{{ svc.name }}_v6r { ip6 saddr & ffff:ffff:ffff:: limit rate over {{ svc.rate * svc.f6 }}/{{ svc.per }} burst {{ svc.burst * svc.f6 }} packets } counter jump rl_drop
|
||||
{% for hook, svcs in [('in', rl_input), ('fwd', rl_forward)] if svcs %}
|
||||
chain ratelimit_hs_{{ hook }} {
|
||||
{{ rl_exempt() }}
|
||||
{% for svc in svcs %}
|
||||
meta l4proto tcp ct original proto-dst { {{ svc.ports }} } counter jump rl_hs_{{ svc.name }}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
chain rl_drop {
|
||||
limit rate {{ firewall.log.ratelimit_rate }} log prefix "fw-ratelimit: "
|
||||
{% for svc in rl_services if svc.input or svc.forward %}
|
||||
chain rl_{{ svc.name }} {
|
||||
update @rl_{{ svc.name }}_v4 { ip saddr limit rate over {{ svc.rate }}/{{ svc.per }} burst {{ svc.burst }} packets } counter jump rl_drop_addr
|
||||
update @rl_{{ svc.name }}_v6 { ip6 saddr & ffff:ffff:ffff:ffff:: limit rate over {{ svc.rate }}/{{ svc.per }} burst {{ svc.burst }} packets } counter jump rl_drop_addr
|
||||
update @rl_{{ svc.name }}_v4r { ip saddr & 255.255.255.0 limit rate over {{ svc.rate * svc.f4 }}/{{ svc.per }} burst {{ svc.burst * svc.f4 }} packets } counter jump rl_drop_range
|
||||
update @rl_{{ svc.name }}_v6r { ip6 saddr & ffff:ffff:ffff:: limit rate over {{ svc.rate * svc.f6 }}/{{ svc.per }} burst {{ svc.burst * svc.f6 }} packets } counter jump rl_drop_range
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
{% for svc in rl_services if svc.input or svc.forward %}
|
||||
chain rl_hs_{{ svc.name }} {
|
||||
update @rl_hs_{{ svc.name }}_v4 { ip saddr limit rate over {{ svc.rate }}/{{ svc.per }} burst {{ svc.burst }} packets } counter jump rl_hs_reject
|
||||
update @rl_hs_{{ svc.name }}_v6 { ip6 saddr & ffff:ffff:ffff:ffff:: limit rate over {{ svc.rate }}/{{ svc.per }} burst {{ svc.burst }} packets } counter jump rl_hs_reject
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
# One log limit per prefix, so a flood of spoofed SYNs cannot use up the budget of the handshake evidence.
|
||||
chain rl_drop_addr {
|
||||
limit rate {{ firewall.log.ratelimit_rate }} log prefix "fw-rl-syn-addr: "
|
||||
drop
|
||||
}
|
||||
|
||||
chain rl_drop_range {
|
||||
limit rate {{ firewall.log.ratelimit_rate }} log prefix "fw-rl-syn-range: "
|
||||
drop
|
||||
}
|
||||
|
||||
# The handshake is complete, so the connection has to be ended actively.
|
||||
chain rl_hs_reject {
|
||||
limit rate {{ firewall.log.ratelimit_rate }} log prefix "fw-rl-hs: "
|
||||
reject with tcp reset
|
||||
}
|
||||
{% endif %}
|
||||
{% if firewall.spamhaus %}
|
||||
|
||||
@@ -339,6 +381,7 @@ table inet host {
|
||||
|
||||
# Docker-published ports and port forwards arrive here after DNAT; pod and guest egress is not DNATed.
|
||||
ct status dnat ct state new jump ratelimit_fwd
|
||||
meta l4proto tcp ct status dnat ct state established ct direction original ct original packets 2 jump ratelimit_hs_fwd
|
||||
{% endif %}
|
||||
{% if firewall.spamhaus %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user