Fix nftables auto-merge syntax and block private/reserved ranges
auto-merge is a standalone set statement, not an nft "flags" value - "flags interval, auto-merge;" is a syntax error; fixed to "flags interval;" followed by "auto-merge" on its own line. Needed because large public blocklists (FireHOL, Spamhaus) contain overlapping CIDRs that nftables otherwise refuses as "conflicting intervals". Also add a hard guard: reject any "block" entry that overlaps a private/ reserved/bogon range (RFC1918, CGNAT, loopback, link-local, etc.) in both the single-entry and bulk-import paths. Public feeds like FireHOL level1 routinely include ranges like 10.0.0.0/8 and 172.16.0.0/12, meant for WAN-only edge firewalls - applied host-wide here (where WireGuard/LAN subnets legitimately live in that same private space), those entries would silently block a server's own internal/VPN traffic instead of actual bad actors. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a9649f6306
commit
00d084a188
+2
-2
@@ -33,13 +33,13 @@ func GenerateGlobalRuleset(entries []model.IPListEntry) string {
|
|||||||
|
|
||||||
fmt.Fprintf(&b, "table inet %s {\n", GlobalTableName)
|
fmt.Fprintf(&b, "table inet %s {\n", GlobalTableName)
|
||||||
|
|
||||||
fmt.Fprintf(&b, " set allowlist {\n type ipv4_addr; flags interval;\n")
|
fmt.Fprintf(&b, " set allowlist {\n type ipv4_addr; flags interval;\n auto-merge\n")
|
||||||
if len(allow) > 0 {
|
if len(allow) > 0 {
|
||||||
fmt.Fprintf(&b, " elements = { %s }\n", strings.Join(allow, ", "))
|
fmt.Fprintf(&b, " elements = { %s }\n", strings.Join(allow, ", "))
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&b, " }\n\n")
|
fmt.Fprintf(&b, " }\n\n")
|
||||||
|
|
||||||
fmt.Fprintf(&b, " set blocklist {\n type ipv4_addr; flags interval;\n")
|
fmt.Fprintf(&b, " set blocklist {\n type ipv4_addr; flags interval;\n auto-merge\n")
|
||||||
if len(block) > 0 {
|
if len(block) > 0 {
|
||||||
fmt.Fprintf(&b, " elements = { %s }\n", strings.Join(block, ", "))
|
fmt.Fprintf(&b, " elements = { %s }\n", strings.Join(block, ", "))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1789,6 +1789,53 @@ func FirewallListsPage() echo.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dangerousBlockRanges are private/reserved/bogon IPv4 supernets that public
|
||||||
|
// threat-intel feeds (FireHOL level1, some Spamhaus-derived lists, etc.)
|
||||||
|
// often include, since they're meaningless on a public WAN edge. On a host
|
||||||
|
// that also uses private address space internally (LAN, WireGuard client
|
||||||
|
// subnets - as this one does), blocking these at the global (host-wide)
|
||||||
|
// priority-10 hook would drop that host's own internal/VPN traffic. So
|
||||||
|
// "block" entries overlapping any of these are rejected outright; "allow"
|
||||||
|
// entries are never affected.
|
||||||
|
var dangerousBlockRanges = mustParseCIDRs([]string{
|
||||||
|
"0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8",
|
||||||
|
"169.254.0.0/16", "172.16.0.0/12", "192.168.0.0/16",
|
||||||
|
"192.0.0.0/24", "192.0.2.0/24", "198.18.0.0/15", "198.51.100.0/24",
|
||||||
|
"203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4",
|
||||||
|
})
|
||||||
|
|
||||||
|
func mustParseCIDRs(cidrs []string) []*net.IPNet {
|
||||||
|
nets := make([]*net.IPNet, 0, len(cidrs))
|
||||||
|
for _, c := range cidrs {
|
||||||
|
_, n, err := net.ParseCIDR(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
nets = append(nets, n)
|
||||||
|
}
|
||||||
|
return nets
|
||||||
|
}
|
||||||
|
|
||||||
|
// overlapsDangerousRange reports whether cidr (an IP or CIDR string)
|
||||||
|
// overlaps any private/reserved/bogon supernet in dangerousBlockRanges.
|
||||||
|
func overlapsDangerousRange(cidr string) bool {
|
||||||
|
var ipNet *net.IPNet
|
||||||
|
if ip, n, err := net.ParseCIDR(cidr); err == nil {
|
||||||
|
ipNet = n
|
||||||
|
_ = ip
|
||||||
|
} else if ip := net.ParseIP(cidr); ip != nil {
|
||||||
|
ipNet = &net.IPNet{IP: ip, Mask: net.CIDRMask(32, 32)}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, dangerous := range dangerousBlockRanges {
|
||||||
|
if dangerous.Contains(ipNet.IP) || ipNet.Contains(dangerous.IP) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func validateIPListEntry(entry model.IPListEntry) error {
|
func validateIPListEntry(entry model.IPListEntry) error {
|
||||||
if entry.ListType != "allow" && entry.ListType != "block" {
|
if entry.ListType != "allow" && entry.ListType != "block" {
|
||||||
return fmt.Errorf("list_type must be 'allow' or 'block'")
|
return fmt.Errorf("list_type must be 'allow' or 'block'")
|
||||||
@@ -1796,6 +1843,9 @@ func validateIPListEntry(entry model.IPListEntry) error {
|
|||||||
if !util.ValidateServerAddresses([]string{entry.CIDR}) && net.ParseIP(entry.CIDR) == nil {
|
if !util.ValidateServerAddresses([]string{entry.CIDR}) && net.ParseIP(entry.CIDR) == nil {
|
||||||
return fmt.Errorf("cidr must be a valid IP or CIDR")
|
return fmt.Errorf("cidr must be a valid IP or CIDR")
|
||||||
}
|
}
|
||||||
|
if entry.ListType == "block" && overlapsDangerousRange(entry.CIDR) {
|
||||||
|
return fmt.Errorf("refusing to block %s: overlaps a private/reserved range - this host uses private address space internally (LAN/WireGuard subnets), blocking it here would cut off your own traffic", entry.CIDR)
|
||||||
|
}
|
||||||
if len(entry.Comment) > 200 {
|
if len(entry.Comment) > 200 {
|
||||||
return fmt.Errorf("comment too long")
|
return fmt.Errorf("comment too long")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user