feat: block/unblock --service, fw::has_rule/has_block_rule/has_allow_rule, block::cleanup, restricted status in inspect, net service annotations

This commit is contained in:
Nuno Duque Nunes 2026-05-15 09:22:17 +00:00
parent 9a3ac2ae47
commit c1d0a9ddd4
7 changed files with 140 additions and 20 deletions

View file

@ -57,6 +57,7 @@ function cmd::block::run() {
local name="" type="" block_name="" local name="" type="" block_name=""
local ips=() subnets=() ports=() services=() local ips=() subnets=() ports=() services=()
local quiet=false force=false local quiet=false force=false
local changed=false
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
@ -139,6 +140,26 @@ function cmd::block::run() {
log::error "Service not found or has no ports: ${svc}" log::error "Service not found or has no ports: ${svc}"
return 1 return 1
fi fi
# Check if already blocked
local already_blocked=true
for resolved in "${resolved_lines[@]}"; do
if [[ "$resolved" == *:*:* ]]; then
local b_ip b_port b_proto
IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
fw::has_block_rule "$client_ip" "$b_ip" "$b_proto" "$b_port" 2>/dev/null || \
{ already_blocked=false; break; }
else
fw::has_block_rule "$client_ip" "$resolved" 2>/dev/null || \
{ already_blocked=false; break; }
fi
done
if $already_blocked; then
$quiet || log::wg_warning "${svc} is already blocked for ${name}"
continue
fi
for resolved in "${resolved_lines[@]}"; do for resolved in "${resolved_lines[@]}"; do
if [[ "$resolved" == *:*:* ]]; then if [[ "$resolved" == *:*:* ]]; then
local b_ip b_port b_proto local b_ip b_port b_proto
@ -151,21 +172,26 @@ function cmd::block::run() {
block::add_rule "$name" "$client_ip" "ip" "$svc" "$resolved" block::add_rule "$name" "$client_ip" "ip" "$svc" "$resolved"
fi fi
done done
changed=true
$quiet || log::wg_success "${svc} has been blocked for ${name}"
done done
[[ ${#ips[@]} -gt 0 || ${#ports[@]} -gt 0 || \
${#subnets[@]} -gt 0 ]] && changed=true
# Reapply in correct order: rule ACCEPT first, then peer DROP rules # Reapply in correct order: rule ACCEPT first, then peer DROP rules
# Only reorder if rules were actually added
if $changed; then
local peer_rule local peer_rule
peer_rule=$(peers::get_meta "$name" "rule") peer_rule=$(peers::get_meta "$name" "rule")
if [[ -n "$peer_rule" ]] && rule::exists "$peer_rule"; then if [[ -n "$peer_rule" ]] && rule::exists "$peer_rule"; then
fw::flush_peer "$client_ip" fw::flush_peer "$client_ip"
rule::apply "$peer_rule" "$client_ip" "$name" rule::apply "$peer_rule" "$client_ip" "$name"
block::restore_rules_for "$name" "$client_ip" block::restore_rules_for "$name" "$client_ip"
else fi
# No rule assigned — peer blocks are the only fw rules, order is fine
: # no-op
fi fi
$quiet || log::wg_success "${name} — access restricted"
return 0 return 0
} }

View file

@ -14,6 +14,9 @@ function cmd::unblock::on_load() {
flag::register --proto flag::register --proto
flag::register --subnet flag::register --subnet
flag::register --all flag::register --all
# System - NET Services
flag::register --service
} }
# ============================================ # ============================================
@ -54,6 +57,7 @@ function cmd::unblock::run() {
local ips=() local ips=()
local subnets=() local subnets=()
local ports=() local ports=()
local services=()
local all=false local all=false
local quiet=false local quiet=false
@ -66,6 +70,7 @@ function cmd::unblock::run() {
--quiet) quiet=true; shift ;; --quiet) quiet=true; shift ;;
--subnet) subnets+=("$2"); shift 2 ;; --subnet) subnets+=("$2"); shift 2 ;;
--port) ports+=("$2"); shift 2 ;; --port) ports+=("$2"); shift 2 ;;
--service) services+=("$2"); shift 2 ;;
--all) all=true; shift ;; --all) all=true; shift ;;
--help) cmd::unblock::help; return ;; --help) cmd::unblock::help; return ;;
*) *)
@ -91,7 +96,7 @@ function cmd::unblock::run() {
fi fi
# Default to full unblock if no specific flags given # Default to full unblock if no specific flags given
if [[ ${#ips[@]} -eq 0 && ${#subnets[@]} -eq 0 && ${#ports[@]} -eq 0 ]]; then if [[ ${#ips[@]} -eq 0 && ${#subnets[@]} -eq 0 && ${#ports[@]} -eq 0 && ${#services[@]} -eq 0 ]]; then
all=true all=true
fi fi
@ -105,6 +110,49 @@ function cmd::unblock::run() {
return 0 return 0
fi fi
# Unblock services
for svc in "${services[@]}"; do
local resolved_lines=()
mapfile -t resolved_lines < <(net::resolve "$svc" 2>/dev/null)
if [[ ${#resolved_lines[@]} -eq 0 ]]; then
log::error "Service not found: ${svc}"
return 1
fi
# Check if actually blocked
local is_blocked=false
for resolved in "${resolved_lines[@]}"; do
if [[ "$resolved" == *:*:* ]]; then
local b_ip b_port b_proto
IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
fw::has_block_rule "$client_ip" "$b_ip" "$b_proto" "$b_port" 2>/dev/null && \
{ is_blocked=true; break; }
else
fw::has_block_rule "$client_ip" "$resolved" 2>/dev/null && \
{ is_blocked=true; break; }
fi
done
if ! $is_blocked; then
$quiet || log::wg_warning "${svc} is not blocked for ${name}"
continue
fi
for resolved in "${resolved_lines[@]}"; do
if [[ "$resolved" == *:*:* ]]; then
local b_ip b_port b_proto
IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
fw::unblock_port "$client_ip" "$b_ip" "$b_port" "$b_proto"
block::remove_rule "$name" "port" "$b_ip" "$b_port" "$b_proto"
else
fw::unblock_ip "$client_ip" "$resolved"
block::remove_rule "$name" "ip" "$resolved"
fi
done
$quiet || log::wg_success "${svc} has been unblocked for ${name}"
done
# Unblock specific IPs # Unblock specific IPs
for ip in "${ips[@]}"; do for ip in "${ips[@]}"; do
fw::unblock_ip "$client_ip" "$ip" fw::unblock_ip "$client_ip" "$ip"
@ -114,6 +162,7 @@ function cmd::unblock::run() {
# Unblock specific subnets # Unblock specific subnets
for subnet in "${subnets[@]}"; do for subnet in "${subnets[@]}"; do
fw::unblock_subnet "$client_ip" "$subnet" fw::unblock_subnet "$client_ip" "$subnet"
block::remove_rule "$name" "subnet" "$subnet"
done done
# Unblock specific ports # Unblock specific ports
@ -122,9 +171,11 @@ function cmd::unblock::run() {
IFS=":" read -r target port proto <<< "$entry" IFS=":" read -r target port proto <<< "$entry"
proto="${proto:-tcp}" proto="${proto:-tcp}"
fw::unblock_port "$client_ip" "$target" "$port" "$proto" fw::unblock_port "$client_ip" "$target" "$port" "$proto"
block::remove_rule "$name" "port" "$b_target" "$b_port" "$b_proto"
done done
$quiet || log::wg_success "Unblock rules applied for: ${name}" # Clean up block file if now empty
block::cleanup "$name"
return 0 return 0
} }

View file

@ -1470,6 +1470,19 @@ def net_reverse_lookup(file, ip, port='', proto=''):
print(svc_name) print(svc_name)
return return
def block_is_empty(file):
data = _block_read(file)
if not data:
print("true")
return
empty = (
not data.get("blocked_direct", False) and
not data.get("blocked_by_groups", []) and
not data.get("rules", []) and
not data.get("services", [])
)
print("true" if empty else "false")
commands = { commands = {
'get': lambda args: get(args[0], args[1]), 'get': lambda args: get(args[0], args[1]),
'set': lambda args: set_key(args[0], args[1], args[2]), 'set': lambda args: set_key(args[0], args[1], args[2]),

View file

@ -222,6 +222,16 @@ function block::format_rules() {
return 0 return 0
} }
function block::cleanup() {
local name="${1:?}"
block::has_file "$name" || return 0
local result
result=$(json::block_is_empty "$(block::file "$name")")
[[ "$result" == "true" ]] && block::remove_file "$name"
return 0
}
# function block::format_rules() { # function block::format_rules() {
# local name="${1:?}" # local name="${1:?}"
# block::has_file "$name" || return 0 # block::has_file "$name" || return 0

View file

@ -12,6 +12,30 @@ function fw::on_load() {
# Rule Management # Rule Management
# ============================================ # ============================================
# ============================================
# Public Interfaces
function fw::has_rule() {
# Generic rule existence check
# fw::has_rule <target> <client_ip> <dest> [port] [proto]
local action="${1:-DROP}" client_ip="${2:-}" target="${3:-}" \
port="${4:-}" proto="${5:-}"
if [[ -n "$port" ]]; then
fw::_forward_exists -s "$client_ip" -d "$target" \
-p "$proto" --dport "$port" -j "$action"
else
fw::_forward_exists -s "$client_ip" -d "$target" -j "$action"
fi
}
function fw::has_block_rule() {
fw::has_rule "DROP" "$@"
}
function fw::has_allow_rule() {
fw::has_rule "ACCEPT" "$@"
}
# ============================================ # ============================================
# Block / Unblock # Block / Unblock
@ -174,7 +198,7 @@ function fw::nat_remove_dns_redirect() {
function fw::forward_rules_for_ip() { function fw::forward_rules_for_ip() {
local ip="${1:-}" local ip="${1:-}"
iptables -L FORWARD -n -v </dev/null | grep -F "$ip" iptables -L FORWARD -n -v </dev/null | grep -F "$ip" || true
} }
function fw::proto_name() { function fw::proto_name() {

View file

@ -70,15 +70,14 @@ function rule::is_applied() {
local target port proto local target port proto
IFS=":" read -r target port proto <<< "$first_port" IFS=":" read -r target port proto <<< "$first_port"
proto="${proto:-tcp}" proto="${proto:-tcp}"
fw::_forward_exists -s "$client_ip" -d "$target" \ fw::has_block_rule "$client_ip" "$target" "$proto" "$port"
-p "$proto" --dport "$port" -j DROP
return $? return $?
fi fi
local first_ip local first_ip
first_ip=$(rule::get "$rule_name" "block_ips" | head -1) first_ip=$(rule::get "$rule_name" "block_ips" | head -1)
if [[ -n "$first_ip" ]]; then if [[ -n "$first_ip" ]]; then
fw::_forward_exists -s "$client_ip" -d "$first_ip" -j DROP fw::has_block_rule "$client_ip" "$first_ip"
return $? return $?
fi fi

3
wgctl
View file

@ -52,9 +52,6 @@ declare -A CMD_ALIASES=(
[disable]=service [disable]=service
) )
block::has_specific_rules "phone-test" && echo "HAS SPECIFIC" || echo "NO SPECIFIC"
block::get_rules "phone-test"
# ============================================ # ============================================
# Dispatch # Dispatch
# ============================================ # ============================================