feat(api, web): enhance port ACL handling and documentation
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 2m20s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped

- Updated the `evofw-firewall.sh` script to refine the port ACL logic, ensuring the correct order of operations for deny and allow rules.
- Introduced a new structure for port ACL rows in the UI, allowing for better management of system and EvoFW rules.
- Enhanced the documentation to clarify the new port ACL behavior, including implicit drops for open ports and the distinction between EvoFW and system rules.
- Improved the handling of port ranges and source addresses in the UI, ensuring accurate representation of firewall rules.

These changes improve the functionality and clarity of port ACL management, enhancing user experience and system reliability.
This commit is contained in:
Denozordec
2026-08-16 16:28:11 +07:00
parent d552f4f326
commit a2ad637a38
4 changed files with 491 additions and 92 deletions
+45 -18
View File
@@ -438,7 +438,7 @@ apply_nft() {
done
((${#batch[@]})) && nft_add_chunk "$table" "$name" allow_v4 "${batch[@]}"
# Unified chain: deny → allow → default_action
# Unified chain: deny → Port ACL (close/open/implicit) → allow → default_action
if [[ "$DEFAULT_ACTION" == "drop" ]]; then
nft add chain "$table" "$name" input '{ type filter hook input priority 0; policy drop; }'
else
@@ -461,9 +461,9 @@ apply_nft() {
else
nft add rule "$table" "$name" input ip saddr @deny_v4 counter drop
fi
nft add rule "$table" "$name" input ip saddr @allow_v4 counter accept
# Port ACL: close (drop) then open (accept), before default.
# Port ACL before L3 allow so open+list is exclusive (implicit drop per open port).
apply_nft_port_acl "$table" "$name"
nft add rule "$table" "$name" input ip saddr @allow_v4 counter accept
if [[ "$DEFAULT_ACTION" == "drop" ]]; then
nft add rule "$table" "$name" input counter drop
else
@@ -499,41 +499,68 @@ try:
except Exception:
rules = []
safe_id = re.compile(r"[^a-zA-Z0-9_]")
for r in rules:
def parse_rule(r):
rid = safe_id.sub("_", str(r.get("id") or "x"))[:40]
action = r.get("action") or "open"
proto = r.get("protocol") or "tcp"
if proto not in ("tcp", "udp"):
continue
return None
ps = int(r.get("port_start") or 0)
pe = int(r.get("port_end") or ps)
if ps < 1 or pe > 65535 or pe < ps:
continue
return None
cidrs = [c for c in (r.get("src_cidrs") or []) if c and ":" not in c]
if not cidrs:
continue
verdict = "drop" if action == "close" else "accept"
return None
dport = f"{ps}" if ps == pe else f"{ps}-{pe}"
comment = f"evofw-port-{rid}"
is_all = any(c in ("0.0.0.0/0", "0.0.0.0") for c in cidrs)
if is_all:
return {
"rid": rid,
"action": action,
"proto": proto,
"dport": dport,
"cidrs": cidrs,
"is_all": any(c in ("0.0.0.0/0", "0.0.0.0") for c in cidrs),
}
def emit(p):
verdict = "drop" if p["action"] == "close" else "accept"
comment = f"evofw-port-{p['rid']}"
proto, dport = p["proto"], p["dport"]
if p["is_all"]:
print(f'nft add rule inet evofw input {proto} dport {dport} counter {verdict} comment "{comment}"')
continue
setname = f"port_src_{rid}"
return
setname = f"port_src_{p['rid']}_{p['proto']}"
print(f"nft add set inet evofw {setname} '{{ type ipv4_addr; flags interval; }}'")
chunk = []
for c in cidrs:
for c in p["cidrs"]:
chunk.append(c)
if len(chunk) >= 32:
joined = ", ".join(chunk)
print(f"nft add element inet evofw {setname} '{{ {joined} }}'")
print(f"nft add element inet evofw {setname} '{{ {', '.join(chunk)} }}'")
chunk = []
if chunk:
joined = ", ".join(chunk)
print(f"nft add element inet evofw {setname} '{{ {joined} }}'")
print(f"nft add element inet evofw {setname} '{{ {', '.join(chunk)} }}'")
print(
f'nft add rule inet evofw input ip saddr @{setname} {proto} dport {dport} counter {verdict} comment "{comment}"'
)
parsed = [p for p in (parse_rule(r) for r in rules) if p]
closes = [p for p in parsed if p["action"] == "close"]
opens = [p for p in parsed if p["action"] != "close"]
for p in closes:
emit(p)
for p in opens:
emit(p)
seen = set()
for p in opens:
key = (p["proto"], p["dport"])
if key in seen:
continue
seen.add(key)
comment = f"evofw-port-implicit-{p['proto']}-{p['dport']}"
print(
f'nft add rule inet evofw input {p["proto"]} dport {p["dport"]} counter drop comment "{comment}"'
)
PY
local cmd
while IFS= read -r cmd; do