feat: net command, service annotations, block::restore_rules_for, fw refactor, restricted status, block system cleanup
This commit is contained in:
parent
cf90ab22db
commit
9a3ac2ae47
14 changed files with 1476 additions and 506 deletions
|
|
@ -14,6 +14,9 @@ function cmd::block::on_load() {
|
||||||
flag::register --proto
|
flag::register --proto
|
||||||
flag::register --subnet
|
flag::register --subnet
|
||||||
flag::register --block-name
|
flag::register --block-name
|
||||||
|
|
||||||
|
# System - NET Services
|
||||||
|
flag::register --service
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
@ -51,94 +54,119 @@ EOF
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
function cmd::block::run() {
|
function cmd::block::run() {
|
||||||
local name=""
|
local name="" type="" block_name=""
|
||||||
local type=""
|
local ips=() subnets=() ports=() services=()
|
||||||
local block_name=""
|
local quiet=false force=false
|
||||||
local ips=()
|
|
||||||
local subnets=()
|
|
||||||
local ports=()
|
|
||||||
local quiet=false
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--name) name="$2"; shift 2 ;;
|
--name) name="$2"; shift 2 ;;
|
||||||
--type) type="$2"; shift 2 ;;
|
--type) type="$2"; shift 2 ;;
|
||||||
--ip) ips+=("$2"); shift 2 ;;
|
--ip) ips+=("$2"); shift 2 ;;
|
||||||
--block-name) block_name="$2"; shift 2 ;;
|
--block-name) block_name="$2"; shift 2 ;;
|
||||||
--force) force=true; shift ;;
|
--service) services+=("$2"); shift 2 ;;
|
||||||
--quiet) quiet=true; shift ;;
|
--force) force=true; shift ;;
|
||||||
--subnet) subnets+=("$2"); shift 2 ;;
|
--quiet) quiet=true; shift ;;
|
||||||
--port) ports+=("$2"); shift 2 ;;
|
--subnet) subnets+=("$2"); shift 2 ;;
|
||||||
--help) cmd::block::help; return ;;
|
--port) ports+=("$2"); shift 2 ;;
|
||||||
|
--help) cmd::block::help; return ;;
|
||||||
*)
|
*)
|
||||||
log::error "Unknown flag: $1"
|
log::error "Unknown flag: $1"
|
||||||
cmd::block::help
|
cmd::block::help
|
||||||
return 1
|
return 1 ;;
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$name" ]]; then
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && \
|
||||||
log::error "Missing required flag: --name"
|
cmd::block::help && return 1
|
||||||
cmd::block::help
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
name=$(peers::resolve_and_require "$name" "$type") || return 1
|
name=$(peers::resolve_and_require "$name" "$type") || return 1
|
||||||
|
|
||||||
# Check if actually blocked
|
|
||||||
if peers::is_blocked "$name" || block::has_file "$name"; then
|
|
||||||
log::wg_warning "Client is already blocked: ${name}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update cache first
|
|
||||||
monitor::update_endpoint_cache
|
|
||||||
|
|
||||||
local public_key
|
|
||||||
public_key=$(keys::public "$name") || return 1
|
|
||||||
|
|
||||||
local endpoint
|
|
||||||
endpoint=$(cmd::block::_get_endpoint "$name" "$public_key")
|
|
||||||
|
|
||||||
local client_ip
|
local client_ip
|
||||||
client_ip=$(peers::get_ip "$name") || return 1
|
client_ip=$(peers::get_ip "$name") || return 1
|
||||||
|
|
||||||
# No specific target — block everything
|
# Full block if no specific targets
|
||||||
# Only full block if no specific targets provided
|
if [[ ${#ips[@]} -eq 0 && ${#ports[@]} -eq 0 && \
|
||||||
if [[ ${#ips[@]} -eq 0 && ${#ports[@]} -eq 0 && ${#subnets[@]} -eq 0 ]]; then
|
${#subnets[@]} -eq 0 && ${#services[@]} -eq 0 ]]; then
|
||||||
|
if peers::is_blocked "$name" || block::has_file "$name"; then
|
||||||
|
log::wg_warning "Client is already blocked: ${name}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
monitor::update_endpoint_cache
|
||||||
cmd::block::_block_all "$name" "$client_ip" "$quiet"
|
cmd::block::_block_all "$name" "$client_ip" "$quiet"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Specific rules — don't full block
|
# Specific rules — check if already fully blocked
|
||||||
block::set_direct "$name" "$client_ip" "false" # ensure not marked as full block
|
if block::has_file "$name"; then
|
||||||
|
local direct
|
||||||
|
direct=$(block::is_blocked_direct "$name")
|
||||||
|
if [[ "$direct" == "true" ]]; then
|
||||||
|
log::wg_warning "${name} is fully blocked — unblock first to add specific rules"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Block specific IPs
|
# Block specific IPs
|
||||||
for ip in "${ips[@]}"; do
|
for ip in "${ips[@]}"; do
|
||||||
ip::require_valid "$ip"
|
ip::require_valid "$ip"
|
||||||
fw::block_ip "$client_ip" "$ip"
|
fw::block_ip "$client_ip" "$ip"
|
||||||
block::add_rule "$name" "$client_ip" "ip" "" "$ip"
|
block::add_rule "$name" "$client_ip" "ip" "${block_name:-}" "$ip"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Block specific subnets
|
# Block specific subnets
|
||||||
for subnet in "${subnets[@]}"; do
|
for subnet in "${subnets[@]}"; do
|
||||||
ip::require_valid "$subnet"
|
ip::require_valid "$subnet"
|
||||||
fw::block_subnet "$client_ip" "$subnet"
|
fw::block_subnet "$client_ip" "$subnet"
|
||||||
block::add_rule "$name" "$client_ip" "subnet" "" "$target_ip"
|
block::add_rule "$name" "$client_ip" "subnet" "${block_name:-}" "$subnet"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Block specific ports
|
# Block specific ports
|
||||||
for entry in "${ports[@]}"; do
|
for entry in "${ports[@]}"; do
|
||||||
local target port proto
|
local b_target b_port b_proto
|
||||||
IFS=":" read -r target port proto <<< "$entry"
|
IFS=":" read -r b_target b_port b_proto <<< "$entry"
|
||||||
ip::require_valid "$target"
|
ip::require_valid "$b_target"
|
||||||
|
fw::block_port "$client_ip" "$b_target" "$b_port" "${b_proto:-tcp}"
|
||||||
fw::block_port "$client_ip" "$target" "$port" "${proto:-tcp}"
|
block::add_rule "$name" "$client_ip" "port" "${block_name:-}" \
|
||||||
block::add_rule "$name" "$client_ip" "port" "" "$target" "$port" "${proto:-tcp}"
|
"$b_target" "$b_port" "${b_proto:-tcp}"
|
||||||
done
|
done
|
||||||
|
|
||||||
log::debug "Block rules applied for: ${name}"
|
# Block 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 or has no ports: ${svc}"
|
||||||
|
return 1
|
||||||
|
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::block_port "$client_ip" "$b_ip" "$b_port" "$b_proto"
|
||||||
|
block::add_rule "$name" "$client_ip" "port" "$svc" \
|
||||||
|
"$b_ip" "$b_port" "$b_proto"
|
||||||
|
else
|
||||||
|
fw::block_ip "$client_ip" "$resolved"
|
||||||
|
block::add_rule "$name" "$client_ip" "ip" "$svc" "$resolved"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Reapply in correct order: rule ACCEPT first, then peer DROP rules
|
||||||
|
local peer_rule
|
||||||
|
peer_rule=$(peers::get_meta "$name" "rule")
|
||||||
|
if [[ -n "$peer_rule" ]] && rule::exists "$peer_rule"; then
|
||||||
|
fw::flush_peer "$client_ip"
|
||||||
|
rule::apply "$peer_rule" "$client_ip" "$name"
|
||||||
|
block::restore_rules_for "$name" "$client_ip"
|
||||||
|
else
|
||||||
|
# No rule assigned — peer blocks are the only fw rules, order is fine
|
||||||
|
: # no-op
|
||||||
|
fi
|
||||||
|
|
||||||
|
$quiet || log::wg_success "${name} — access restricted"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmd::block::_get_endpoint() {
|
function cmd::block::_get_endpoint() {
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,12 @@ function cmd::inspect::_peer_info() {
|
||||||
peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
|
peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
|
||||||
last_ts=$(monitor::last_attempt "$name")
|
last_ts=$(monitor::last_attempt "$name")
|
||||||
|
|
||||||
|
local is_restricted="false"
|
||||||
|
block::has_specific_rules "$name" 2>/dev/null && is_restricted="true"
|
||||||
|
|
||||||
local status last_seen endpoint
|
local status last_seen endpoint
|
||||||
status=$(peers::format_status "$name" "$public_key" \
|
status=$(peers::format_status "$name" "$public_key" \
|
||||||
"$is_blocked" "false" "$handshake_ts" "$last_ts")
|
"$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
|
||||||
last_seen=$(peers::format_last_seen "$name" "$public_key" \
|
last_seen=$(peers::format_last_seen "$name" "$public_key" \
|
||||||
"$is_blocked" "$last_ts" "" "$handshake_ts")
|
"$is_blocked" "$last_ts" "" "$handshake_ts")
|
||||||
endpoint=$(monitor::get_cached_endpoint "$name")
|
endpoint=$(monitor::get_cached_endpoint "$name")
|
||||||
|
|
@ -109,6 +112,107 @@ function cmd::inspect::_peer_info() {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# function cmd::inspect::_rule_info() {
|
||||||
|
# local name="${1:-}"
|
||||||
|
# local rule
|
||||||
|
# rule=$(peers::get_meta "$name" "rule")
|
||||||
|
# [[ -z "$rule" ]] && return 0
|
||||||
|
# rule::exists "$rule" || return 0
|
||||||
|
|
||||||
|
# cmd::inspect::_section "Rule: ${rule}"
|
||||||
|
|
||||||
|
# local rule_file
|
||||||
|
# rule_file="$(rule::path "$rule")"
|
||||||
|
|
||||||
|
# # Check for inheritance
|
||||||
|
# local extends_raw=()
|
||||||
|
# mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
||||||
|
|
||||||
|
# if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
|
||||||
|
# # Show inheritance tree
|
||||||
|
# for base_name in "${extends_raw[@]}"; do
|
||||||
|
# [[ -z "$base_name" ]] && continue
|
||||||
|
# printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
||||||
|
|
||||||
|
# local base_allows base_blocks
|
||||||
|
# base_allows=$(rule::get "$base_name" "allow_ports")$'\n'$(rule::get "$base_name" "allow_ips")
|
||||||
|
# base_blocks=$(rule::get "$base_name" "block_ips")$'\n'$(rule::get "$base_name" "block_ports")
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "+" "$e"
|
||||||
|
# done <<< "$base_allows"
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "-" "$e"
|
||||||
|
# done <<< "$base_blocks"
|
||||||
|
|
||||||
|
# local base_dns
|
||||||
|
# base_dns=$(rule::get_own "$base_name" "dns_redirect")
|
||||||
|
# [[ "${base_dns,,}" == "true" ]] && \
|
||||||
|
# printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||||
|
# done
|
||||||
|
|
||||||
|
# # Own rules
|
||||||
|
# local own_allows own_blocks
|
||||||
|
# own_allows=$(json::get "$rule_file" "allow_ports" 2>/dev/null)$'\n'$(json::get "$rule_file" "allow_ips" 2>/dev/null)
|
||||||
|
# own_blocks=$(json::get "$rule_file" "block_ips" 2>/dev/null)$'\n'$(json::get "$rule_file" "block_ports" 2>/dev/null)
|
||||||
|
# local has_own=false
|
||||||
|
|
||||||
|
# while IFS= read -r e; do [[ -n "$e" ]] && has_own=true && break; done <<< "$own_allows$own_blocks"
|
||||||
|
|
||||||
|
# if $has_own; then
|
||||||
|
# printf "\n \033[0;37mOwn:\033[0m\n"
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "+" "$e"
|
||||||
|
# done <<< "$own_allows"
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "-" "$e"
|
||||||
|
# done <<< "$own_blocks"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# else
|
||||||
|
# # No inheritance — flat view
|
||||||
|
# local allow_ports allow_ips block_ips block_ports
|
||||||
|
# allow_ports=$(rule::get "$rule" "allow_ports")
|
||||||
|
# allow_ips=$(rule::get "$rule" "allow_ips")
|
||||||
|
# block_ips=$(rule::get "$rule" "block_ips")
|
||||||
|
# block_ports=$(rule::get "$rule" "block_ports")
|
||||||
|
|
||||||
|
# if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
||||||
|
# printf "\n"
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "+" "$e"
|
||||||
|
# done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
||||||
|
# printf "\n"
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "-" "$e"
|
||||||
|
# done <<< "$block_ips"$'\n'"$block_ports"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# if [[ -z "$allow_ports" && -z "$allow_ips" && \
|
||||||
|
# -z "$block_ips" && -z "$block_ports" ]]; then
|
||||||
|
# printf "\n full access (no restrictions)\n"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# local dns_redirect
|
||||||
|
# dns_redirect=$(rule::get_own "$rule" "dns_redirect")
|
||||||
|
# [[ "${dns_redirect,,}" == "true" ]] && \
|
||||||
|
# printf "\n \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# return 0
|
||||||
|
# }
|
||||||
|
|
||||||
function cmd::inspect::_rule_info() {
|
function cmd::inspect::_rule_info() {
|
||||||
local name="${1:-}"
|
local name="${1:-}"
|
||||||
local rule
|
local rule
|
||||||
|
|
@ -118,93 +222,12 @@ function cmd::inspect::_rule_info() {
|
||||||
|
|
||||||
cmd::inspect::_section "Rule: ${rule}"
|
cmd::inspect::_section "Rule: ${rule}"
|
||||||
|
|
||||||
local rule_file
|
if rule::render_extends_tree "$rule"; then
|
||||||
rule_file="$(rule::path "$rule")"
|
printf "\n"
|
||||||
|
|
||||||
# Check for inheritance
|
|
||||||
local extends_raw=()
|
|
||||||
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
|
|
||||||
# Show inheritance tree
|
|
||||||
for base_name in "${extends_raw[@]}"; do
|
|
||||||
[[ -z "$base_name" ]] && continue
|
|
||||||
printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
|
||||||
|
|
||||||
local base_allows base_blocks
|
|
||||||
base_allows=$(rule::get "$base_name" "allow_ports")$'\n'$(rule::get "$base_name" "allow_ips")
|
|
||||||
base_blocks=$(rule::get "$base_name" "block_ips")$'\n'$(rule::get "$base_name" "block_ports")
|
|
||||||
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$base_allows"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$base_blocks"
|
|
||||||
|
|
||||||
local base_dns
|
|
||||||
base_dns=$(rule::get_own "$base_name" "dns_redirect")
|
|
||||||
[[ "${base_dns,,}" == "true" ]] && \
|
|
||||||
printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Own rules
|
|
||||||
local own_allows own_blocks
|
|
||||||
own_allows=$(json::get "$rule_file" "allow_ports" 2>/dev/null)$'\n'$(json::get "$rule_file" "allow_ips" 2>/dev/null)
|
|
||||||
own_blocks=$(json::get "$rule_file" "block_ips" 2>/dev/null)$'\n'$(json::get "$rule_file" "block_ports" 2>/dev/null)
|
|
||||||
local has_own=false
|
|
||||||
|
|
||||||
while IFS= read -r e; do [[ -n "$e" ]] && has_own=true && break; done <<< "$own_allows$own_blocks"
|
|
||||||
|
|
||||||
if $has_own; then
|
|
||||||
printf "\n \033[0;37mOwn:\033[0m\n"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_allows"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_blocks"
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
else
|
||||||
# No inheritance — flat view
|
# No inheritance — flat view
|
||||||
local allow_ports allow_ips block_ips block_ports
|
rule::render_flat "$rule"
|
||||||
allow_ports=$(rule::get "$rule" "allow_ports")
|
|
||||||
allow_ips=$(rule::get "$rule" "allow_ips")
|
|
||||||
block_ips=$(rule::get "$rule" "block_ips")
|
|
||||||
block_ports=$(rule::get "$rule" "block_ports")
|
|
||||||
|
|
||||||
if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
|
||||||
printf "\n"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$allow_ports"$'\n'"$allow_ips"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
|
||||||
printf "\n"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$block_ips"$'\n'"$block_ports"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$allow_ports" && -z "$allow_ips" && \
|
|
||||||
-z "$block_ips" && -z "$block_ports" ]]; then
|
|
||||||
printf "\n full access (no restrictions)\n"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local dns_redirect
|
|
||||||
dns_redirect=$(rule::get_own "$rule" "dns_redirect")
|
|
||||||
[[ "${dns_redirect,,}" == "true" ]] && \
|
|
||||||
printf "\n \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,11 +340,13 @@ function cmd::inspect::_firewall_info() {
|
||||||
"$(color::red "-${drops}")" \
|
"$(color::red "-${drops}")" \
|
||||||
"$(printf '─%.0s' {1..28})"
|
"$(printf '─%.0s' {1..28})"
|
||||||
|
|
||||||
if [[ ${#rules_output[@]} -gt 0 ]]; then
|
fw::list_peer_rules "$ip" false
|
||||||
for line in "${rules_output[@]}"; do
|
|
||||||
fw::format_rule "$line"
|
# if [[ ${#rules_output[@]} -gt 0 ]]; then
|
||||||
done
|
# for line in "${rules_output[@]}"; do
|
||||||
fi
|
# fw::format_rule "$line"
|
||||||
|
# done
|
||||||
|
# fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -381,22 +381,7 @@ function cmd::list::_precompute_all() {
|
||||||
|
|
||||||
# Block/restricted status
|
# Block/restricted status
|
||||||
declare -gA p_blocked=() p_restricted=()
|
declare -gA p_blocked=() p_restricted=()
|
||||||
local wg_peers
|
cmd::list::_precompute_block_status p_blocked p_restricted
|
||||||
wg_peers=$(wg show "$(config::interface)" peers 2>/dev/null)
|
|
||||||
while IFS= read -r name; do
|
|
||||||
if block::is_blocked "$name" 2>/dev/null; then
|
|
||||||
p_restricted["$name"]=true
|
|
||||||
else
|
|
||||||
p_restricted["$name"]=false
|
|
||||||
fi
|
|
||||||
local pubkey
|
|
||||||
pubkey=$(keys::public "$name" 2>/dev/null || echo "")
|
|
||||||
if [[ -n "$pubkey" ]] && ! echo "$wg_peers" | grep -qF "$pubkey"; then
|
|
||||||
p_blocked["$name"]=true
|
|
||||||
else
|
|
||||||
p_blocked["$name"]=false
|
|
||||||
fi
|
|
||||||
done < <(peers::all)
|
|
||||||
|
|
||||||
# Public keys
|
# Public keys
|
||||||
declare -gA p_pubkeys=()
|
declare -gA p_pubkeys=()
|
||||||
|
|
@ -440,11 +425,10 @@ function cmd::list::_precompute_block_status() {
|
||||||
wg_peers=$(wg show "$(config::interface)" peers 2>/dev/null)
|
wg_peers=$(wg show "$(config::interface)" peers 2>/dev/null)
|
||||||
|
|
||||||
while IFS= read -r name; do
|
while IFS= read -r name; do
|
||||||
# Restricted = has block rules but still in server (partial block)
|
if block::has_specific_rules "$name" 2>/dev/null; then
|
||||||
if block::is_blocked "$name" 2>/dev/null; then
|
_restricted["$name"]=true
|
||||||
_restricted["$name"]=true
|
else
|
||||||
else
|
_restricted["$name"]=false
|
||||||
_restricted["$name"]=false
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Blocked = removed from WG server
|
# Blocked = removed from WG server
|
||||||
|
|
|
||||||
299
commands/net.command.sh
Normal file
299
commands/net.command.sh
Normal file
|
|
@ -0,0 +1,299 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function cmd::net::on_load() {
|
||||||
|
flag::register --name
|
||||||
|
flag::register --ip
|
||||||
|
flag::register --port
|
||||||
|
flag::register --desc
|
||||||
|
flag::register --tag
|
||||||
|
flag::register --detailed
|
||||||
|
flag::register --force
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::net::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: wgctl net <subcommand> [options]
|
||||||
|
|
||||||
|
Manage named network services for use with block/allow rules.
|
||||||
|
Services map names to IPs and ports, making rules more readable.
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
list List all services
|
||||||
|
show --name <name> Show service details
|
||||||
|
add --name <name> --ip <ip> Add a service
|
||||||
|
add --name <svc:port-name> --port <port:proto>
|
||||||
|
Add a port to a service
|
||||||
|
rm --name <name> Remove service or port
|
||||||
|
rm --name <svc:ports> Remove all ports from service
|
||||||
|
|
||||||
|
Options for add (service):
|
||||||
|
--name <name> Service name (e.g. proxmox)
|
||||||
|
--ip <ip> Service IP address
|
||||||
|
--desc <description> Optional description
|
||||||
|
--tag <tag> Optional tag (repeatable)
|
||||||
|
|
||||||
|
Options for add (port):
|
||||||
|
--name <svc:port-name> Service:port-name (e.g. proxmox:web-ui)
|
||||||
|
--port <port:proto> Port and protocol (e.g. 8006:tcp)
|
||||||
|
--desc <description> Optional description
|
||||||
|
|
||||||
|
Options for list:
|
||||||
|
--detailed Show ports for each service
|
||||||
|
--tag <tag> Filter by tag
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
wgctl net list
|
||||||
|
wgctl net list --detailed
|
||||||
|
wgctl net list --tag admin
|
||||||
|
wgctl net show --name proxmox
|
||||||
|
wgctl net add --name proxmox --ip 10.0.0.100 --desc "Proxmox VE"
|
||||||
|
wgctl net add --name proxmox:web-ui --port 8006:tcp --desc "Web UI"
|
||||||
|
wgctl net add --name proxmox:ssh --port 22:tcp
|
||||||
|
wgctl net rm --name proxmox:web-ui
|
||||||
|
wgctl net rm --name proxmox:ports
|
||||||
|
wgctl net rm --name proxmox
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::net::run() {
|
||||||
|
local subcmd="${1:-list}"
|
||||||
|
shift || true
|
||||||
|
case "$subcmd" in
|
||||||
|
list) cmd::net::list "$@" ;;
|
||||||
|
show) cmd::net::show "$@" ;;
|
||||||
|
add) cmd::net::add "$@" ;;
|
||||||
|
rm|remove|del) cmd::net::rm "$@" ;;
|
||||||
|
help) cmd::net::help ;;
|
||||||
|
*)
|
||||||
|
log::error "Unknown subcommand: '${subcmd}'"
|
||||||
|
cmd::net::help
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# List
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::net::list() {
|
||||||
|
local detailed=false filter_tag=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--detailed) detailed=true; shift ;;
|
||||||
|
--tag) filter_tag="$2"; shift 2 ;;
|
||||||
|
--help) cmd::net::help; return ;;
|
||||||
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
local net_file
|
||||||
|
net_file="$(ctx::net)"
|
||||||
|
|
||||||
|
if [[ ! -f "$net_file" ]]; then
|
||||||
|
log::wg_warning "No services configured. Use 'wgctl net add' to add one."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log::section "Network Services"
|
||||||
|
printf "\n %-20s %-16s %-6s %s\n" "NAME" "IP" "PORTS" "DESCRIPTION"
|
||||||
|
local divider
|
||||||
|
divider=$(printf '─%.0s' {1..72})
|
||||||
|
printf " %s\n" "$divider"
|
||||||
|
|
||||||
|
local found=false
|
||||||
|
while IFS="|" read -r name ip desc tags ports; do
|
||||||
|
[[ -z "$name" ]] && continue
|
||||||
|
|
||||||
|
# Tag filter
|
||||||
|
if [[ -n "$filter_tag" ]]; then
|
||||||
|
[[ "$tags" != *"$filter_tag"* ]] && continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
found=true
|
||||||
|
local tag_display=""
|
||||||
|
[[ -n "$tags" ]] && tag_display=" \033[0;37m[${tags}]\033[0m"
|
||||||
|
|
||||||
|
printf " %-20s %-16s %-6s %s%b\n" \
|
||||||
|
"$name" "$ip" "${ports}p" "${desc:-—}" "$tag_display"
|
||||||
|
|
||||||
|
if $detailed; then
|
||||||
|
local has_ports=false
|
||||||
|
# Show ports inline
|
||||||
|
while IFS="|" read -r ptype pname pport pproto pdesc; do
|
||||||
|
[[ "$ptype" != "port" ]] && continue
|
||||||
|
has_ports=true
|
||||||
|
local ann
|
||||||
|
ann=$(net::annotation "$ip" "$pport" "$pproto")
|
||||||
|
printf " \033[0;37m%-18s %s:%s%s\033[0m\n" \
|
||||||
|
"${pname}" "$pport" "$pproto" \
|
||||||
|
"${pdesc:+ # $pdesc}"
|
||||||
|
done < <(json::net_show "$net_file" "$name")
|
||||||
|
$has_ports && printf "\n" # newline after each service with ports
|
||||||
|
fi
|
||||||
|
|
||||||
|
done < <(json::net_list "$net_file")
|
||||||
|
|
||||||
|
if ! $found; then
|
||||||
|
[[ -n "$filter_tag" ]] && \
|
||||||
|
log::wg_warning "No services with tag: ${filter_tag}" || \
|
||||||
|
log::wg_warning "No services configured"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Show
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::net::show() {
|
||||||
|
local name=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--name) util::require_flag "--name" "${2:-}" || return 1
|
||||||
|
name="$2"; shift 2 ;;
|
||||||
|
--help) cmd::net::help; return ;;
|
||||||
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
||||||
|
net::require_exists "$name" || return 1
|
||||||
|
|
||||||
|
log::section "Service: ${name}"
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
while IFS="|" read -r key val1 val2 val3 val4; do
|
||||||
|
case "$key" in
|
||||||
|
name) ui::row "Name" "$val1" ;;
|
||||||
|
ip) ui::row "IP" "$val1" ;;
|
||||||
|
desc) ui::row "Description" "${val1:-—}" ;;
|
||||||
|
tags) ui::row "Tags" "${val1:-—}" ;;
|
||||||
|
port)
|
||||||
|
# val1=port_name val2=port val3=proto val4=desc
|
||||||
|
local ann
|
||||||
|
ann=$(net::annotation "$(json::net_resolve "$(ctx::net)" "$name")" \
|
||||||
|
"$val2" "$val3" 2>/dev/null || true)
|
||||||
|
printf " %-20s \033[0;36m%s\033[0m %s:%s%s\n" \
|
||||||
|
"${val1}:" "" "$val2" "$val3" \
|
||||||
|
"${val4:+ # $val4}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < <(json::net_show "$(ctx::net)" "$name")
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Add
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::net::add() {
|
||||||
|
local name="" ip="" port="" desc="" tags=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--name) util::require_flag "--name" "${2:-}" || return 1
|
||||||
|
name="$2"; shift 2 ;;
|
||||||
|
--ip) ip="$2"; shift 2 ;;
|
||||||
|
--port) port="$2"; shift 2 ;;
|
||||||
|
--desc) desc="$2"; shift 2 ;;
|
||||||
|
--tag) tags+=("$2"); shift 2 ;;
|
||||||
|
--help) cmd::net::help; return ;;
|
||||||
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
||||||
|
|
||||||
|
if [[ "$name" == *:* ]]; then
|
||||||
|
# Port mode: proxmox:web-ui
|
||||||
|
local svc_name="${name%%:*}"
|
||||||
|
local port_name="${name##*:}"
|
||||||
|
|
||||||
|
[[ -z "$port" ]] && log::error "Missing required flag: --port" && return 1
|
||||||
|
net::require_exists "$svc_name" || return 1
|
||||||
|
|
||||||
|
local port_num proto
|
||||||
|
if [[ "$port" == *:* ]]; then
|
||||||
|
port_num="${port%%:*}"
|
||||||
|
proto="${port##*:}"
|
||||||
|
else
|
||||||
|
port_num="$port"
|
||||||
|
proto="tcp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
json::net_add_port "$(ctx::net)" "$svc_name" "$port_name" \
|
||||||
|
"$port_num" "$proto" "$desc"
|
||||||
|
|
||||||
|
log::wg_success "Added port: ${svc_name}:${port_name} → ${port_num}/${proto}"
|
||||||
|
else
|
||||||
|
# Service mode: proxmox
|
||||||
|
[[ -z "$ip" ]] && log::error "Missing required flag: --ip" && return 1
|
||||||
|
|
||||||
|
local tags_str
|
||||||
|
tags_str=$(IFS=','; echo "${tags[*]}")
|
||||||
|
|
||||||
|
json::net_add_service "$(ctx::net)" "$name" "$ip" "$desc" "$tags_str"
|
||||||
|
|
||||||
|
log::wg_success "Service added: ${name} → ${ip}"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Remove
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::net::rm() {
|
||||||
|
local name="" force=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--name) util::require_flag "--name" "${2:-}" || return 1
|
||||||
|
name="$2"; shift 2 ;;
|
||||||
|
--force) force=true; shift ;;
|
||||||
|
--help) cmd::net::help; return ;;
|
||||||
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
||||||
|
|
||||||
|
# Validate existence
|
||||||
|
if [[ "$name" == *:* ]]; then
|
||||||
|
local svc_name="${name%%:*}"
|
||||||
|
local port_name="${name##*:}"
|
||||||
|
if [[ "$port_name" != "ports" ]]; then
|
||||||
|
# Check specific port exists
|
||||||
|
local exists
|
||||||
|
exists=$(json::net_exists "$(ctx::net)" "$name")
|
||||||
|
if [[ "$exists" != "true" ]]; then
|
||||||
|
log::error "Port not found: ${name}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
net::require_exists "$svc_name" || return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
net::require_exists "$name" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! $force; then
|
||||||
|
local what="service '${name}'"
|
||||||
|
[[ "$name" == *:ports ]] && what="all ports from '${name%%:*}'"
|
||||||
|
[[ "$name" == *:* && "$name" != *:ports ]] && what="port '${name}'"
|
||||||
|
read -r -p "Remove ${what}? [y/N] " confirm
|
||||||
|
case "$confirm" in
|
||||||
|
[yY]*) ;;
|
||||||
|
*) log::info "Aborted"; return 0 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
json::net_remove "$(ctx::net)" "$name"
|
||||||
|
log::wg_success "Removed: ${name}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,8 @@ function cmd::rule::on_load() {
|
||||||
flag::register --remove-extends
|
flag::register --remove-extends
|
||||||
flag::register --block-ip
|
flag::register --block-ip
|
||||||
flag::register --allow-ip
|
flag::register --allow-ip
|
||||||
|
flag::register --block-service
|
||||||
|
flag::register --allow-service
|
||||||
flag::register --block-port
|
flag::register --block-port
|
||||||
flag::register --allow-port
|
flag::register --allow-port
|
||||||
flag::register --remove-block-ip
|
flag::register --remove-block-ip
|
||||||
|
|
@ -311,17 +313,225 @@ function cmd::rule::list() {
|
||||||
# Show
|
# Show
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
|
# function cmd::rule::show() {
|
||||||
|
# local name="" show_peers=true color=false show_resolved=false
|
||||||
|
|
||||||
|
# while [[ $# -gt 0 ]]; do
|
||||||
|
# case "$1" in
|
||||||
|
# --name) util::require_flag "--name" "${2:-}" || return 1
|
||||||
|
# name="$2"; shift 2 ;;
|
||||||
|
# --no-peers) show_peers=false; shift ;;
|
||||||
|
# --color) color=true; shift ;;
|
||||||
|
# --resolved) show_resolved=true; shift ;;
|
||||||
|
# --help) cmd::rule::help; return ;;
|
||||||
|
# *) log::error "Unknown flag: $1"; return 1 ;;
|
||||||
|
# esac
|
||||||
|
# done
|
||||||
|
|
||||||
|
# [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
|
||||||
|
# rule::require_exists "$name" || return 1
|
||||||
|
|
||||||
|
# local rule_file
|
||||||
|
# rule_file="$(rule::path "$name")"
|
||||||
|
|
||||||
|
# local dns_redirect
|
||||||
|
# dns_redirect=$(rule::get_own "$name" "dns_redirect")
|
||||||
|
# dns_redirect="${dns_redirect:-false}"
|
||||||
|
|
||||||
|
# local resolved_dns
|
||||||
|
# resolved_dns=$(rule::get "$name" "dns_redirect")
|
||||||
|
# resolved_dns="${resolved_dns:-false}"
|
||||||
|
|
||||||
|
# local dns_display
|
||||||
|
# if [[ "${resolved_dns,,}" == "true" && "${dns_redirect,,}" != "true" ]]; then
|
||||||
|
# dns_display="true (inherited)"
|
||||||
|
# elif [[ "${dns_redirect,,}" == "true" ]]; then
|
||||||
|
# dns_display="true"
|
||||||
|
# else
|
||||||
|
# dns_display="false"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
|
||||||
|
# log::section "Rule: ${name}"
|
||||||
|
# printf "\n"
|
||||||
|
|
||||||
|
# # ── Header info ──────────────────────────────
|
||||||
|
# local desc group dns_redirect
|
||||||
|
# desc=$(json::get "$rule_file" "desc")
|
||||||
|
# group=$(json::get "$rule_file" "group")
|
||||||
|
|
||||||
|
# ui::row "Description" "${desc:-—}"
|
||||||
|
# ui::row "Group" "${group:-—}"
|
||||||
|
# ui::row "DNS" "$dns_display"
|
||||||
|
|
||||||
|
# # ── Extends section ──────────────────────────
|
||||||
|
# local extends_raw=()
|
||||||
|
# mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
||||||
|
|
||||||
|
# if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then
|
||||||
|
# cmd::rule::_show_section "Extends"
|
||||||
|
|
||||||
|
# for base_name in "${extends_raw[@]}"; do
|
||||||
|
# [[ -z "$base_name" ]] && continue
|
||||||
|
# printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
||||||
|
|
||||||
|
# # Show resolved entries for this base
|
||||||
|
# local base_allow_ports base_allow_ips base_block_ips base_block_ports base_dns
|
||||||
|
# base_allow_ports=$(rule::get "$base_name" "allow_ports" 2>/dev/null || true)
|
||||||
|
# base_allow_ips=$(rule::get "$base_name" "allow_ips" 2>/dev/null || true)
|
||||||
|
# base_block_ips=$(rule::get "$base_name" "block_ips" 2>/dev/null || true)
|
||||||
|
# base_block_ports=$(rule::get "$base_name" "block_ports" 2>/dev/null || true)
|
||||||
|
# base_dns=$(json::get "$(rule::path "$base_name")" "dns_redirect" 2>/dev/null || true)
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "+" "$e"
|
||||||
|
# done <<< "$base_allow_ports"
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "-" "$e"
|
||||||
|
# done <<< "$base_allow_ips"
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "-" "$e"
|
||||||
|
# done <<< "$base_block_ips"
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "-" "$e"
|
||||||
|
# done <<< "$base_block_ports"
|
||||||
|
|
||||||
|
# [[ "$base_dns" == "true" ]] && \
|
||||||
|
# printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||||
|
# done
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# # ── Own Rules section ─────────────────────────
|
||||||
|
# local own_allow_ports own_allow_ips own_block_ips own_block_ports
|
||||||
|
# own_allow_ports=$(json::get "$rule_file" "allow_ports")
|
||||||
|
# own_allow_ips=$(json::get "$rule_file" "allow_ips")
|
||||||
|
# own_block_ips=$(json::get "$rule_file" "block_ips")
|
||||||
|
# own_block_ports=$(json::get "$rule_file" "block_ports")
|
||||||
|
|
||||||
|
# local has_own=false
|
||||||
|
# [[ -n "$own_allow_ports" || -n "$own_allow_ips" || \
|
||||||
|
# -n "$own_block_ips" || -n "$own_block_ports" ]] && has_own=true
|
||||||
|
|
||||||
|
# if $has_own; then
|
||||||
|
# if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then
|
||||||
|
# cmd::rule::_show_section "Own Rules"
|
||||||
|
# printf "\n"
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "+" "$e"
|
||||||
|
# done <<< "$own_allow_ports"
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "+" "$e"
|
||||||
|
# done <<< "$own_allow_ips"
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "-" "$e"
|
||||||
|
# done <<< "$own_block_ips"
|
||||||
|
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# net::print_entry "-" "$e"
|
||||||
|
# done <<< "$own_block_ports"
|
||||||
|
|
||||||
|
# [[ "$dns_redirect" == "true" ]] && \
|
||||||
|
# printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
||||||
|
# else
|
||||||
|
# # No inheritance — use Allow/Block sections
|
||||||
|
# if [[ -n "$own_allow_ports" || -n "$own_allow_ips" ]]; then
|
||||||
|
# cmd::rule::_show_section "Allow"
|
||||||
|
# printf "\n"
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||||
|
# done <<< "$own_allow_ports"
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# printf " \033[0;32m+\033[0m %s\n" "$e"
|
||||||
|
# done <<< "$own_allow_ips"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# if [[ -n "$own_block_ips" || -n "$own_block_ports" ]]; then
|
||||||
|
# cmd::rule::_show_section "Block"
|
||||||
|
# printf "\n"
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||||
|
# done <<< "$own_block_ips"
|
||||||
|
# while IFS= read -r e; do
|
||||||
|
# [[ -z "$e" ]] && continue
|
||||||
|
# printf " \033[0;31m-\033[0m %s\n" "$e"
|
||||||
|
# done <<< "$own_block_ports"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# if [[ "$dns_redirect" == "true" ]]; then
|
||||||
|
# cmd::rule::_show_section "DNS"
|
||||||
|
# printf "\n \033[0;36m↺\033[0m Redirect all DNS → %s\n" "$(config::dns)"
|
||||||
|
# fi
|
||||||
|
# fi
|
||||||
|
# elif [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]}" ]]; then
|
||||||
|
# printf "\n"
|
||||||
|
# ui::row "Access" "full (no restrictions)"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# # ── Resolved section (optional) ──────────────
|
||||||
|
# if $show_resolved; then
|
||||||
|
# cmd::rule::_show_section "Resolved (applied to peers)"
|
||||||
|
# local res_allow_ports res_allow_ips res_block_ips res_block_ports
|
||||||
|
# res_allow_ports=$(rule::get "$name" "allow_ports")
|
||||||
|
# res_allow_ips=$(rule::get "$name" "allow_ips")
|
||||||
|
# res_block_ips=$(rule::get "$name" "block_ips")
|
||||||
|
# res_block_ports=$(rule::get "$name" "block_ports")
|
||||||
|
|
||||||
|
# while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||||
|
# printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ports"
|
||||||
|
# while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||||
|
# printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ips"
|
||||||
|
# while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||||
|
# printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ips"
|
||||||
|
# while IFS= read -r e; do [[ -n "$e" ]] && \
|
||||||
|
# printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ports"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# # ── Peers section ─────────────────────────────
|
||||||
|
# cmd::rule::_show_section "Peers"
|
||||||
|
|
||||||
|
# local peer_list=()
|
||||||
|
# mapfile -t peer_list < <(peers::with_rule "$name")
|
||||||
|
# local peer_count=${#peer_list[@]}
|
||||||
|
# ui::row "Assigned" "$peer_count"
|
||||||
|
|
||||||
|
# if $show_peers && [[ $peer_count -gt 0 ]]; then
|
||||||
|
# printf "\n"
|
||||||
|
# for peer_name in "${peer_list[@]}"; do
|
||||||
|
# local ip
|
||||||
|
# ip=$(peers::get_ip "$peer_name")
|
||||||
|
# printf " %-28s %s\n" "$peer_name" "$ip"
|
||||||
|
# done
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# printf "\n"
|
||||||
|
# }
|
||||||
|
|
||||||
function cmd::rule::show() {
|
function cmd::rule::show() {
|
||||||
local name="" show_peers=true color=false show_resolved=false
|
local name="" show_peers=true show_resolved=false
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--name) util::require_flag "--name" "${2:-}" || return 1
|
--name) util::require_flag "--name" "${2:-}" || return 1
|
||||||
name="$2"; shift 2 ;;
|
name="$2"; shift 2 ;;
|
||||||
--no-peers) show_peers=false; shift ;;
|
--no-peers) show_peers=false; shift ;;
|
||||||
--color) color=true; shift ;;
|
|
||||||
--resolved) show_resolved=true; shift ;;
|
--resolved) show_resolved=true; shift ;;
|
||||||
--help) cmd::rule::help; return ;;
|
--help) cmd::rule::help; return ;;
|
||||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
@ -332,15 +542,13 @@ function cmd::rule::show() {
|
||||||
local rule_file
|
local rule_file
|
||||||
rule_file="$(rule::path "$name")"
|
rule_file="$(rule::path "$name")"
|
||||||
|
|
||||||
local dns_redirect
|
# ── DNS display ───────────────────────────────
|
||||||
|
local dns_redirect resolved_dns dns_display
|
||||||
dns_redirect=$(rule::get_own "$name" "dns_redirect")
|
dns_redirect=$(rule::get_own "$name" "dns_redirect")
|
||||||
dns_redirect="${dns_redirect:-false}"
|
dns_redirect="${dns_redirect:-false}"
|
||||||
|
|
||||||
local resolved_dns
|
|
||||||
resolved_dns=$(rule::get "$name" "dns_redirect")
|
resolved_dns=$(rule::get "$name" "dns_redirect")
|
||||||
resolved_dns="${resolved_dns:-false}"
|
resolved_dns="${resolved_dns:-false}"
|
||||||
|
|
||||||
local dns_display
|
|
||||||
if [[ "${resolved_dns,,}" == "true" && "${dns_redirect,,}" != "true" ]]; then
|
if [[ "${resolved_dns,,}" == "true" && "${dns_redirect,,}" != "true" ]]; then
|
||||||
dns_display="true (inherited)"
|
dns_display="true (inherited)"
|
||||||
elif [[ "${dns_redirect,,}" == "true" ]]; then
|
elif [[ "${dns_redirect,,}" == "true" ]]; then
|
||||||
|
|
@ -349,157 +557,46 @@ function cmd::rule::show() {
|
||||||
dns_display="false"
|
dns_display="false"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
log::section "Rule: ${name}"
|
log::section "Rule: ${name}"
|
||||||
printf "\n"
|
printf "\n"
|
||||||
|
|
||||||
# ── Header info ──────────────────────────────
|
local desc group
|
||||||
local desc group dns_redirect
|
|
||||||
desc=$(json::get "$rule_file" "desc")
|
desc=$(json::get "$rule_file" "desc")
|
||||||
group=$(json::get "$rule_file" "group")
|
group=$(json::get "$rule_file" "group")
|
||||||
|
|
||||||
ui::row "Description" "${desc:-—}"
|
ui::row "Description" "${desc:-—}"
|
||||||
ui::row "Group" "${group:-—}"
|
ui::row "Group" "${group:-—}"
|
||||||
ui::row "DNS" "$dns_display"
|
ui::row "DNS" "$dns_display"
|
||||||
|
|
||||||
# ── Extends section ──────────────────────────
|
# ── Extends + own rules ────────────────────────
|
||||||
local extends_raw=()
|
if rule::render_extends_tree "$name"; then
|
||||||
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
# Has inheritance — tree already rendered
|
||||||
|
|
||||||
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then
|
|
||||||
cmd::rule::_show_section "Extends"
|
|
||||||
|
|
||||||
for base_name in "${extends_raw[@]}"; do
|
|
||||||
[[ -z "$base_name" ]] && continue
|
|
||||||
printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
|
||||||
|
|
||||||
# Show resolved entries for this base
|
|
||||||
local base_allow_ports base_allow_ips base_block_ips base_block_ports base_dns
|
|
||||||
base_allow_ports=$(rule::get "$base_name" "allow_ports" 2>/dev/null || true)
|
|
||||||
base_allow_ips=$(rule::get "$base_name" "allow_ips" 2>/dev/null || true)
|
|
||||||
base_block_ips=$(rule::get "$base_name" "block_ips" 2>/dev/null || true)
|
|
||||||
base_block_ports=$(rule::get "$base_name" "block_ports" 2>/dev/null || true)
|
|
||||||
base_dns=$(json::get "$(rule::path "$base_name")" "dns_redirect" 2>/dev/null || true)
|
|
||||||
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$base_allow_ports"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$base_allow_ips"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$base_block_ips"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$base_block_ports"
|
|
||||||
[[ "$base_dns" == "true" ]] && \
|
|
||||||
printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Own Rules section ─────────────────────────
|
|
||||||
local own_allow_ports own_allow_ips own_block_ips own_block_ports
|
|
||||||
own_allow_ports=$(json::get "$rule_file" "allow_ports")
|
|
||||||
own_allow_ips=$(json::get "$rule_file" "allow_ips")
|
|
||||||
own_block_ips=$(json::get "$rule_file" "block_ips")
|
|
||||||
own_block_ports=$(json::get "$rule_file" "block_ports")
|
|
||||||
|
|
||||||
local has_own=false
|
|
||||||
[[ -n "$own_allow_ports" || -n "$own_allow_ips" || \
|
|
||||||
-n "$own_block_ips" || -n "$own_block_ports" ]] && has_own=true
|
|
||||||
|
|
||||||
if $has_own; then
|
|
||||||
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then
|
|
||||||
cmd::rule::_show_section "Own Rules"
|
|
||||||
printf "\n"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_allow_ports"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_allow_ips"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_block_ips"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_block_ports"
|
|
||||||
[[ "$dns_redirect" == "true" ]] && \
|
|
||||||
printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)"
|
|
||||||
else
|
|
||||||
# No inheritance — use Allow/Block sections
|
|
||||||
if [[ -n "$own_allow_ports" || -n "$own_allow_ips" ]]; then
|
|
||||||
cmd::rule::_show_section "Allow"
|
|
||||||
printf "\n"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_allow_ports"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_allow_ips"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$own_block_ips" || -n "$own_block_ports" ]]; then
|
|
||||||
cmd::rule::_show_section "Block"
|
|
||||||
printf "\n"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_block_ips"
|
|
||||||
while IFS= read -r e; do
|
|
||||||
[[ -z "$e" ]] && continue
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"
|
|
||||||
done <<< "$own_block_ports"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$dns_redirect" == "true" ]]; then
|
|
||||||
cmd::rule::_show_section "DNS"
|
|
||||||
printf "\n \033[0;36m↺\033[0m Redirect all DNS → %s\n" "$(config::dns)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
elif [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]}" ]]; then
|
|
||||||
printf "\n"
|
printf "\n"
|
||||||
ui::row "Access" "full (no restrictions)"
|
else
|
||||||
|
# No inheritance — flat view
|
||||||
|
rule::render_flat "$name"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Resolved section (optional) ──────────────
|
# ── Resolved ──────────────────────────────────
|
||||||
if $show_resolved; then
|
if $show_resolved; then
|
||||||
cmd::rule::_show_section "Resolved (applied to peers)"
|
cmd::rule::_show_section "Resolved (applied to peers)"
|
||||||
|
printf "\n"
|
||||||
local res_allow_ports res_allow_ips res_block_ips res_block_ports
|
local res_allow_ports res_allow_ips res_block_ips res_block_ports
|
||||||
res_allow_ports=$(rule::get "$name" "allow_ports")
|
res_allow_ports=$(rule::get "$name" "allow_ports")
|
||||||
res_allow_ips=$(rule::get "$name" "allow_ips")
|
res_allow_ips=$(rule::get "$name" "allow_ips")
|
||||||
res_block_ips=$(rule::get "$name" "block_ips")
|
res_block_ips=$(rule::get "$name" "block_ips")
|
||||||
res_block_ports=$(rule::get "$name" "block_ports")
|
res_block_ports=$(rule::get "$name" "block_ports")
|
||||||
|
while IFS= read -r e; do [[ -n "$e" ]] && net::print_entry "+" "$e"; done \
|
||||||
while IFS= read -r e; do [[ -n "$e" ]] && \
|
<<< "$res_allow_ports"$'\n'"$res_allow_ips"
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ports"
|
while IFS= read -r e; do [[ -n "$e" ]] && net::print_entry "-" "$e"; done \
|
||||||
while IFS= read -r e; do [[ -n "$e" ]] && \
|
<<< "$res_block_ips"$'\n'"$res_block_ports"
|
||||||
printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ips"
|
|
||||||
while IFS= read -r e; do [[ -n "$e" ]] && \
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ips"
|
|
||||||
while IFS= read -r e; do [[ -n "$e" ]] && \
|
|
||||||
printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ports"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Peers section ─────────────────────────────
|
# ── Peers ─────────────────────────────────────
|
||||||
cmd::rule::_show_section "Peers"
|
cmd::rule::_show_section "Peers"
|
||||||
|
|
||||||
local peer_list=()
|
local peer_list=()
|
||||||
mapfile -t peer_list < <(peers::with_rule "$name")
|
mapfile -t peer_list < <(peers::with_rule "$name")
|
||||||
local peer_count=${#peer_list[@]}
|
local peer_count=${#peer_list[@]}
|
||||||
ui::row "Assigned" "$peer_count"
|
ui::row "Assigned" "$peer_count"
|
||||||
|
|
||||||
if $show_peers && [[ $peer_count -gt 0 ]]; then
|
if $show_peers && [[ $peer_count -gt 0 ]]; then
|
||||||
printf "\n"
|
printf "\n"
|
||||||
for peer_name in "${peer_list[@]}"; do
|
for peer_name in "${peer_list[@]}"; do
|
||||||
|
|
@ -508,8 +605,8 @@ function cmd::rule::show() {
|
||||||
printf " %-28s %s\n" "$peer_name" "$ip"
|
printf " %-28s %s\n" "$peer_name" "$ip"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf "\n"
|
printf "\n"
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
@ -520,6 +617,7 @@ function cmd::rule::add() {
|
||||||
local name="" desc="" group=""
|
local name="" desc="" group=""
|
||||||
local extends=()
|
local extends=()
|
||||||
local allow_ips=() block_ips=() block_ports=() allow_ports=()
|
local allow_ips=() block_ips=() block_ports=() allow_ports=()
|
||||||
|
local block_services=() allow_services=()
|
||||||
local dns_redirect=false
|
local dns_redirect=false
|
||||||
local is_base=false
|
local is_base=false
|
||||||
|
|
||||||
|
|
@ -537,6 +635,8 @@ function cmd::rule::add() {
|
||||||
--allow-port) allow_ports+=("$2"); shift 2 ;;
|
--allow-port) allow_ports+=("$2"); shift 2 ;;
|
||||||
--block-ip) block_ips+=("$2"); shift 2 ;;
|
--block-ip) block_ips+=("$2"); shift 2 ;;
|
||||||
--block-port) block_ports+=("$2"); shift 2 ;;
|
--block-port) block_ports+=("$2"); shift 2 ;;
|
||||||
|
--block-service) block_services+=("$2"); shift 2 ;;
|
||||||
|
--allow-service) allow_services+=("$2"); shift 2 ;;
|
||||||
--dns-redirect) dns_redirect=true; shift ;;
|
--dns-redirect) dns_redirect=true; shift ;;
|
||||||
--help) cmd::rule::help; return ;;
|
--help) cmd::rule::help; return ;;
|
||||||
*)
|
*)
|
||||||
|
|
@ -566,6 +666,26 @@ function cmd::rule::add() {
|
||||||
rule_dir="$(ctx::rules)"
|
rule_dir="$(ctx::rules)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
for svc in "${block_services[@]}"; do
|
||||||
|
while IFS= read -r resolved; do
|
||||||
|
if [[ "$resolved" == *:*:* ]]; then
|
||||||
|
block_ports+=("${resolved}")
|
||||||
|
else
|
||||||
|
block_ips+=("${resolved}/32")
|
||||||
|
fi
|
||||||
|
done < <(net::resolve "$svc")
|
||||||
|
done
|
||||||
|
|
||||||
|
for svc in "${allow_services[@]}"; do
|
||||||
|
while IFS= read -r resolved; do
|
||||||
|
if [[ "$resolved" == *:*:* ]]; then
|
||||||
|
allow_ports+=("${resolved}")
|
||||||
|
else
|
||||||
|
allow_ips+=("${resolved}/32")
|
||||||
|
fi
|
||||||
|
done < <(net::resolve "$svc")
|
||||||
|
done
|
||||||
|
|
||||||
local rule_file="${rule_dir}/${name}.rule"
|
local rule_file="${rule_dir}/${name}.rule"
|
||||||
|
|
||||||
local allow_str block_str port_str allow_port_str extends_str
|
local allow_str block_str port_str allow_port_str extends_str
|
||||||
|
|
@ -844,4 +964,4 @@ function cmd::rule::_show_section() {
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
printf "\n ${color_code}── %s ──────────────────────────────────\033[0m\n" "$title"
|
printf "\n ${color_code}── %s ──────────────────────────────────\033[0m\n" "$title"
|
||||||
}
|
}
|
||||||
|
|
@ -204,6 +204,34 @@ function cmd::test::section_fw() {
|
||||||
cmd::test::run_cmd "fw nat" "PREROUTING" fw nat
|
cmd::test::run_cmd "fw nat" "PREROUTING" fw nat
|
||||||
cmd::test::run_cmd "fw count" "TOTAL" fw count
|
cmd::test::run_cmd "fw count" "TOTAL" fw count
|
||||||
}
|
}
|
||||||
|
function cmd::test::section_net() {
|
||||||
|
test::section "Net"
|
||||||
|
|
||||||
|
"$WGCTL_BINARY" net rm --name test-svc --force > /dev/null 2>&1 || true
|
||||||
|
|
||||||
|
cmd::test::run_cmd "net add service" "added" \
|
||||||
|
net add --name test-svc --ip 10.0.0.99 --desc "Test service"
|
||||||
|
cmd::test::run_cmd "net add port" "Added" \
|
||||||
|
net add --name test-svc:web --port 9999:tcp
|
||||||
|
cmd::test::run_cmd "net list" "test-svc" \
|
||||||
|
net list
|
||||||
|
cmd::test::run_cmd "net list --detailed" "web" \
|
||||||
|
net list --detailed
|
||||||
|
cmd::test::run_cmd "net show" "9999" \
|
||||||
|
net show --name test-svc
|
||||||
|
cmd::test::run_cmd "net rm port" "Removed" \
|
||||||
|
net rm --name test-svc:web --force
|
||||||
|
cmd::test::run_cmd "net add port again" "Added" \
|
||||||
|
net add --name test-svc:web --port 9999:tcp
|
||||||
|
cmd::test::run_cmd "net rm all ports" "Removed" \
|
||||||
|
net rm --name test-svc:ports --force
|
||||||
|
cmd::test::run_cmd "net rm service" "Removed" \
|
||||||
|
net rm --name test-svc --force
|
||||||
|
cmd::test::run_cmd_fails "net show nonexistent" \
|
||||||
|
net show --name nonexistent-svc
|
||||||
|
cmd::test::run_cmd_fails "net add port no service" \
|
||||||
|
net add --name nonexistent:web --port 80:tcp
|
||||||
|
}
|
||||||
|
|
||||||
function cmd::test::section_destructive() {
|
function cmd::test::section_destructive() {
|
||||||
test::section "Destructive (modifying state)"
|
test::section "Destructive (modifying state)"
|
||||||
|
|
@ -432,6 +460,7 @@ function cmd::test::run() {
|
||||||
audit) cmd::test::section_audit ;;
|
audit) cmd::test::section_audit ;;
|
||||||
logs) cmd::test::section_logs ;;
|
logs) cmd::test::section_logs ;;
|
||||||
fw) cmd::test::section_fw ;;
|
fw) cmd::test::section_fw ;;
|
||||||
|
net) cmd::test::section_net ;;
|
||||||
destructive) cmd::test::section_destructive ;;
|
destructive) cmd::test::section_destructive ;;
|
||||||
*)
|
*)
|
||||||
log::error "Unknown section: $section"
|
log::error "Unknown section: $section"
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ _CTX_GROUPS="${_CTX_DATA}/groups"
|
||||||
_CTX_BLOCKS="${_CTX_DATA}/blocks"
|
_CTX_BLOCKS="${_CTX_DATA}/blocks"
|
||||||
_CTX_META="${_CTX_DATA}/meta"
|
_CTX_META="${_CTX_DATA}/meta"
|
||||||
_CTX_DAEMON="${_CTX_DATA}/daemon"
|
_CTX_DAEMON="${_CTX_DATA}/daemon"
|
||||||
|
_CTX_NET="${_CTX_DATA}/services.json"
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
|
|
@ -41,6 +42,7 @@ function ctx::groups() { echo "$_CTX_GROUPS"; }
|
||||||
function ctx::blocks() { echo "$_CTX_BLOCKS"; }
|
function ctx::blocks() { echo "$_CTX_BLOCKS"; }
|
||||||
function ctx::meta() { echo "$_CTX_META"; }
|
function ctx::meta() { echo "$_CTX_META"; }
|
||||||
function ctx::daemon() { echo "$_CTX_DAEMON"; }
|
function ctx::daemon() { echo "$_CTX_DAEMON"; }
|
||||||
|
function ctx::net() { echo "$_CTX_NET"; }
|
||||||
function ctx::events_log() { echo "$(ctx::daemon)/events.log"; }
|
function ctx::events_log() { echo "$(ctx::daemon)/events.log"; }
|
||||||
function ctx::fw_events_log() { echo "$(ctx::daemon)/fw_events.log"; }
|
function ctx::fw_events_log() { echo "$(ctx::daemon)/fw_events.log"; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,15 @@ function json::block_remove_rule() { python3 "$JSON_HELPER" block_remove_ru
|
||||||
function json::block_get_rules() { python3 "$JSON_HELPER" block_get_rules "$@" </dev/null; }
|
function json::block_get_rules() { python3 "$JSON_HELPER" block_get_rules "$@" </dev/null; }
|
||||||
function json::block_get_groups() { python3 "$JSON_HELPER" block_get_groups "$@" </dev/null; }
|
function json::block_get_groups() { python3 "$JSON_HELPER" block_get_groups "$@" </dev/null; }
|
||||||
function json::block_get_direct() { python3 "$JSON_HELPER" block_get_direct "$@" </dev/null; }
|
function json::block_get_direct() { python3 "$JSON_HELPER" block_get_direct "$@" </dev/null; }
|
||||||
|
function json::net_list() { python3 "$JSON_HELPER" net_list "$@" </dev/null; }
|
||||||
|
function json::net_show() { python3 "$JSON_HELPER" net_show "$@" </dev/null; }
|
||||||
|
function json::net_exists() { python3 "$JSON_HELPER" net_exists "$@" </dev/null; }
|
||||||
|
function json::net_add_service() { python3 "$JSON_HELPER" net_add_service "$@" </dev/null; }
|
||||||
|
function json::net_add_port() { python3 "$JSON_HELPER" net_add_port "$@" </dev/null; }
|
||||||
|
function json::net_remove() { python3 "$JSON_HELPER" net_remove "$@" </dev/null; }
|
||||||
|
function json::net_resolve() { python3 "$JSON_HELPER" net_resolve "$@" </dev/null; }
|
||||||
|
function json::net_reverse_lookup() { python3 "$JSON_HELPER" net_reverse_lookup "$@" </dev/null; }
|
||||||
|
function json::block_is_empty() { python3 "$JSON_HELPER" block_is_empty "$@" </dev/null; }
|
||||||
|
|
||||||
function json::peer_transfer() {
|
function json::peer_transfer() {
|
||||||
ACTIVITY_TOTAL_LOW="$(config::activity_total_low)" \
|
ACTIVITY_TOTAL_LOW="$(config::activity_total_low)" \
|
||||||
|
|
|
||||||
|
|
@ -1312,6 +1312,164 @@ def block_get_direct(file):
|
||||||
return
|
return
|
||||||
print('true' if data.get('blocked_direct', False) else 'false')
|
print('true' if data.get('blocked_direct', False) else 'false')
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Net / Services
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
def _net_read(file):
|
||||||
|
"""Read services.json, return dict or empty dict"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(file):
|
||||||
|
return {}
|
||||||
|
with open(file) as f:
|
||||||
|
content = f.read().strip()
|
||||||
|
if not content:
|
||||||
|
return {}
|
||||||
|
return json.loads(content)
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _net_write(file, data):
|
||||||
|
"""Write services.json"""
|
||||||
|
os.makedirs(os.path.dirname(file), exist_ok=True)
|
||||||
|
with open(file, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
def net_list(file):
|
||||||
|
"""List all service names with IP and port count"""
|
||||||
|
data = _net_read(file)
|
||||||
|
for name, svc in sorted(data.items()):
|
||||||
|
ip = svc.get('ip', '')
|
||||||
|
desc = svc.get('desc', '')
|
||||||
|
tags = ','.join(svc.get('tags', []))
|
||||||
|
ports = len(svc.get('ports', {}))
|
||||||
|
print(f"{name}|{ip}|{desc}|{tags}|{ports}")
|
||||||
|
|
||||||
|
def net_show(file, name):
|
||||||
|
"""Show full service details"""
|
||||||
|
data = _net_read(file)
|
||||||
|
if name not in data:
|
||||||
|
print(f"Error: Service not found: {name}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
svc = data[name]
|
||||||
|
print(f"name|{name}")
|
||||||
|
print(f"ip|{svc.get('ip','')}")
|
||||||
|
print(f"desc|{svc.get('desc','')}")
|
||||||
|
print(f"tags|{','.join(svc.get('tags',[]))}")
|
||||||
|
for port_name, port_def in svc.get('ports', {}).items():
|
||||||
|
port = port_def.get('port', '')
|
||||||
|
proto = port_def.get('proto', 'tcp')
|
||||||
|
desc = port_def.get('desc', '')
|
||||||
|
print(f"port|{port_name}|{port}|{proto}|{desc}")
|
||||||
|
|
||||||
|
def net_exists(file, name):
|
||||||
|
"""Check if service exists"""
|
||||||
|
data = _net_read(file)
|
||||||
|
# Handle service:port syntax
|
||||||
|
if ':' in name:
|
||||||
|
svc_name, port_name = name.split(':', 1)
|
||||||
|
if port_name == 'ports':
|
||||||
|
print('true' if svc_name in data else 'false')
|
||||||
|
else:
|
||||||
|
svc = data.get(svc_name, {})
|
||||||
|
print('true' if port_name in svc.get('ports', {}) else 'false')
|
||||||
|
else:
|
||||||
|
print('true' if name in data else 'false')
|
||||||
|
|
||||||
|
def net_add_service(file, name, ip, desc='', tags=''):
|
||||||
|
"""Add or update a service"""
|
||||||
|
data = _net_read(file)
|
||||||
|
if name not in data:
|
||||||
|
data[name] = {'ip': ip, 'ports': {}}
|
||||||
|
else:
|
||||||
|
data[name]['ip'] = ip
|
||||||
|
if desc:
|
||||||
|
data[name]['desc'] = desc
|
||||||
|
if tags:
|
||||||
|
data[name]['tags'] = [t.strip() for t in tags.split(',') if t.strip()]
|
||||||
|
_net_write(file, data)
|
||||||
|
|
||||||
|
def net_add_port(file, service, port_name, port, proto='tcp', desc=''):
|
||||||
|
"""Add or update a port on a service"""
|
||||||
|
data = _net_read(file)
|
||||||
|
if service not in data:
|
||||||
|
print(f"Error: Service not found: {service}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if 'ports' not in data[service]:
|
||||||
|
data[service]['ports'] = {}
|
||||||
|
entry = {'port': int(port), 'proto': proto}
|
||||||
|
if desc:
|
||||||
|
entry['desc'] = desc
|
||||||
|
data[service]['ports'][port_name] = entry
|
||||||
|
_net_write(file, data)
|
||||||
|
|
||||||
|
def net_remove(file, name):
|
||||||
|
"""Remove service or port"""
|
||||||
|
data = _net_read(file)
|
||||||
|
if ':' in name:
|
||||||
|
svc_name, port_name = name.split(':', 1)
|
||||||
|
if svc_name not in data:
|
||||||
|
print(f"Error: Service not found: {svc_name}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if port_name == 'ports':
|
||||||
|
# Remove all ports
|
||||||
|
data[svc_name]['ports'] = {}
|
||||||
|
else:
|
||||||
|
if port_name not in data[svc_name].get('ports', {}):
|
||||||
|
print(f"Error: Port not found: {port_name}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
del data[svc_name]['ports'][port_name]
|
||||||
|
else:
|
||||||
|
if name not in data:
|
||||||
|
print(f"Error: Service not found: {name}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
del data[name]
|
||||||
|
_net_write(file, data)
|
||||||
|
|
||||||
|
def net_resolve(file, name):
|
||||||
|
"""Resolve service name to ip or ip:port:proto lines"""
|
||||||
|
data = _net_read(file)
|
||||||
|
if ':' in name:
|
||||||
|
svc_name, port_name = name.split(':', 1)
|
||||||
|
if svc_name not in data:
|
||||||
|
print(f"Error: Service not found: {svc_name}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
svc = data[svc_name]
|
||||||
|
ip = svc.get('ip', '')
|
||||||
|
if port_name == 'ports':
|
||||||
|
# All ports
|
||||||
|
for pname, pdef in svc.get('ports', {}).items():
|
||||||
|
print(f"{ip}:{pdef['port']}:{pdef.get('proto','tcp')}")
|
||||||
|
else:
|
||||||
|
if port_name not in svc.get('ports', {}):
|
||||||
|
print(f"Error: Port not found: {port_name}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
pdef = svc['ports'][port_name]
|
||||||
|
print(f"{ip}:{pdef['port']}:{pdef.get('proto','tcp')}")
|
||||||
|
else:
|
||||||
|
if name not in data:
|
||||||
|
print(f"Error: Service not found: {name}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
print(data[name].get('ip', ''))
|
||||||
|
|
||||||
|
def net_reverse_lookup(file, ip, port='', proto=''):
|
||||||
|
"""Reverse lookup IP/port to service name"""
|
||||||
|
data = _net_read(file)
|
||||||
|
for svc_name, svc in data.items():
|
||||||
|
if svc.get('ip') != ip:
|
||||||
|
continue
|
||||||
|
if not port:
|
||||||
|
print(svc_name)
|
||||||
|
return
|
||||||
|
for port_name, port_def in svc.get('ports', {}).items():
|
||||||
|
if (str(port_def.get('port','')) == str(port) and
|
||||||
|
port_def.get('proto','tcp') == proto):
|
||||||
|
print(f"{svc_name}:{port_name}")
|
||||||
|
return
|
||||||
|
# IP matched but no port match — return service name
|
||||||
|
print(svc_name)
|
||||||
|
return
|
||||||
|
|
||||||
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]),
|
||||||
|
|
@ -1381,6 +1539,27 @@ commands = {
|
||||||
'block_get_rules': lambda args: block_get_rules(args[0]),
|
'block_get_rules': lambda args: block_get_rules(args[0]),
|
||||||
'block_get_groups': lambda args: block_get_groups(args[0]),
|
'block_get_groups': lambda args: block_get_groups(args[0]),
|
||||||
'block_get_direct': lambda args: block_get_direct(args[0]),
|
'block_get_direct': lambda args: block_get_direct(args[0]),
|
||||||
|
'net_list': lambda args: net_list(args[0]),
|
||||||
|
'net_show': lambda args: net_show(args[0], args[1]),
|
||||||
|
'net_exists': lambda args: net_exists(args[0], args[1]),
|
||||||
|
'net_add_service': lambda args: net_add_service(
|
||||||
|
args[0], args[1], args[2],
|
||||||
|
args[3] if len(args) > 3 else '',
|
||||||
|
args[4] if len(args) > 4 else ''
|
||||||
|
),
|
||||||
|
'net_add_port': lambda args: net_add_port(
|
||||||
|
args[0], args[1], args[2], args[3],
|
||||||
|
args[4] if len(args) > 4 else 'tcp',
|
||||||
|
args[5] if len(args) > 5 else ''
|
||||||
|
),
|
||||||
|
'net_remove': lambda args: net_remove(args[0], args[1]),
|
||||||
|
'net_resolve': lambda args: net_resolve(args[0], args[1]),
|
||||||
|
'net_reverse_lookup': lambda args: net_reverse_lookup(
|
||||||
|
args[0], args[1],
|
||||||
|
args[2] if len(args) > 2 else '',
|
||||||
|
args[3] if len(args) > 3 else ''
|
||||||
|
),
|
||||||
|
'block_is_empty': lambda args: block_is_empty(args[0]),
|
||||||
}
|
}
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,16 @@ function block::has_file() {
|
||||||
[[ -f "$(block::file "$name")" ]]
|
[[ -f "$(block::file "$name")" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function block::has_specific_rules() {
|
||||||
|
local name="${1:?}"
|
||||||
|
block::has_file "$name" || return 1
|
||||||
|
while IFS="|" read -r bname btype target port proto; do
|
||||||
|
[[ -z "$btype" ]] && continue
|
||||||
|
[[ "$btype" != "full" ]] && return 0
|
||||||
|
done < <(block::get_rules "$name")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
function block::is_blocked() {
|
function block::is_blocked() {
|
||||||
local name="${1:?}"
|
local name="${1:?}"
|
||||||
block::has_file "$name" || return 1
|
block::has_file "$name" || return 1
|
||||||
|
|
@ -128,6 +138,19 @@ function block::restore_peer() {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function block::restore_rules_for() {
|
||||||
|
local name="${1:?}" client_ip="${2:?}"
|
||||||
|
while IFS="|" read -r bname btype target port proto; do
|
||||||
|
[[ -z "$btype" ]] && continue
|
||||||
|
case "$btype" in
|
||||||
|
ip) fw::block_ip "$client_ip" "$target" "append" ;;
|
||||||
|
port) fw::block_port "$client_ip" "$target" "$port" \
|
||||||
|
"${proto:-tcp}" "append" ;;
|
||||||
|
subnet) fw::block_subnet "$client_ip" "$target" "append" ;;
|
||||||
|
esac
|
||||||
|
done < <(block::get_rules "$name")
|
||||||
|
}
|
||||||
|
|
||||||
function block::restore_all() {
|
function block::restore_all() {
|
||||||
while IFS= read -r peer_name; do
|
while IFS= read -r peer_name; do
|
||||||
block::has_file "$peer_name" || continue
|
block::has_file "$peer_name" || continue
|
||||||
|
|
@ -151,17 +174,68 @@ function block::restore_all() {
|
||||||
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
|
||||||
|
|
||||||
while IFS="|" read -r bname btype target port proto; do
|
while IFS="|" read -r bname btype target port proto; do
|
||||||
[[ -z "$btype" ]] && continue
|
[[ -z "$btype" ]] && continue
|
||||||
local display
|
|
||||||
|
local display="" ann=""
|
||||||
|
|
||||||
case "$btype" in
|
case "$btype" in
|
||||||
full) display="all traffic" ;;
|
full)
|
||||||
ip) display="$target" ;;
|
display="all traffic"
|
||||||
port) display="${target}:${port}:${proto}" ;;
|
;;
|
||||||
subnet) display="$target" ;;
|
ip)
|
||||||
|
display="$target"
|
||||||
|
ann=$(net::annotate "$target")
|
||||||
|
;;
|
||||||
|
port)
|
||||||
|
display="${target}:${port}:${proto}"
|
||||||
|
ann=$(net::annotate "${target}:${port}:${proto}")
|
||||||
|
;;
|
||||||
|
subnet)
|
||||||
|
display="$target"
|
||||||
|
ann=$(net::annotate "${target%%/*}")
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
local label="${bname:-$btype}"
|
|
||||||
printf " \033[0;31m-\033[0m %-30s \033[0;37m%s\033[0m\n" \
|
local label="$bname"
|
||||||
"$display" "$label"
|
|
||||||
|
# If bname wasn't set (equals type default), clear it
|
||||||
|
case "$label" in
|
||||||
|
full|ip|port|subnet|"") label="" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Suppress label if it matches annotation
|
||||||
|
if [[ -n "$ann" && -n "$label" && \
|
||||||
|
("$ann" == "$label" || "$ann" == "${label}:"*) ]]; then
|
||||||
|
label=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# log::debug "label='$label' ann='$ann' match=$([ "$ann" == "$label" ] && echo yes || echo no)"
|
||||||
|
|
||||||
|
printf " \033[0;31m-\033[0m %-20s \033[0;37m%s%s\033[0m\n" \
|
||||||
|
"$display" \
|
||||||
|
"${label:+${label} }" \
|
||||||
|
"${ann:+→ ${ann}}"
|
||||||
|
|
||||||
done < <(block::get_rules "$name")
|
done < <(block::get_rules "$name")
|
||||||
}
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# function block::format_rules() {
|
||||||
|
# local name="${1:?}"
|
||||||
|
# block::has_file "$name" || return 0
|
||||||
|
# while IFS="|" read -r bname btype target port proto; do
|
||||||
|
# [[ -z "$btype" ]] && continue
|
||||||
|
# local display
|
||||||
|
# case "$btype" in
|
||||||
|
# full) display="all traffic" ;;
|
||||||
|
# ip) display="$target" ;;
|
||||||
|
# port) display="${target}:${port}:${proto}" ;;
|
||||||
|
# subnet) display="$target" ;;
|
||||||
|
# esac
|
||||||
|
# local label="${bname:-$btype}"
|
||||||
|
# printf " \033[0;31m-\033[0m %-30s \033[0;37m%s\033[0m\n" \
|
||||||
|
# "$display" "$label"
|
||||||
|
# done < <(block::get_rules "$name")
|
||||||
|
# }
|
||||||
|
|
@ -12,143 +12,108 @@ function fw::on_load() {
|
||||||
# Rule Management
|
# Rule Management
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Block / Unblock
|
||||||
|
|
||||||
function fw::block_ip() {
|
function fw::block_ip() {
|
||||||
local client_ip="${1:-}" target_ip="${2:-}"
|
local client_ip="${1:-}" target_ip="${2:-}" mode="${3:-insert}"
|
||||||
|
fw::_block_pair "$mode" -s "$client_ip" -d "$target_ip"
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j DROP \
|
|
||||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j DROP
|
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
|
||||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::unblock_ip() {
|
function fw::unblock_ip() {
|
||||||
local client_ip="${1:-}" target_ip="${2:-}"
|
local client_ip="${1:-}" target_ip="${2:-}"
|
||||||
|
fw::_unblock_pair -s "$client_ip" -d "$target_ip"
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j DROP \
|
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j DROP 2>/dev/null || true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::block_port() {
|
function fw::block_port() {
|
||||||
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" \
|
||||||
|
proto="${4:-tcp}" mode="${5:-insert}"
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP \
|
fw::_block_pair "$mode" \
|
||||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP
|
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
|
||||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::unblock_port() {
|
function fw::unblock_port() {
|
||||||
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
||||||
|
fw::_unblock_pair \
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP \
|
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
function fw::block_all() {
|
|
||||||
local client_ip="${1:-}" client_name="${2:-}"
|
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -j DROP \
|
|
||||||
|| iptables -A FORWARD -s "$client_ip" -j DROP
|
|
||||||
|
|
||||||
log::debug "Blocked all traffic from: ${client_ip}"
|
|
||||||
}
|
|
||||||
|
|
||||||
function fw::unblock_all() {
|
|
||||||
local client_ip="${1:-}"
|
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -j DROP \
|
|
||||||
&& iptables -D FORWARD -s "$client_ip" -j DROP 2>/dev/null || true
|
|
||||||
|
|
||||||
monitor::unwatch "$client_ip"
|
|
||||||
log::debug "Unblocked all traffic from: ${client_ip}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::block_subnet() {
|
function fw::block_subnet() {
|
||||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
local client_ip="${1:-}" target_subnet="${2:-}" mode="${3:-append}"
|
||||||
|
fw::_block_pair "$mode" -s "$client_ip" -d "$target_subnet"
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
|
||||||
|| iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j DROP \
|
|
||||||
|| iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j DROP
|
|
||||||
|
|
||||||
log::wg_block "Blocked ${client_ip} → subnet ${target_subnet}"
|
log::wg_block "Blocked ${client_ip} → subnet ${target_subnet}"
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::unblock_subnet() {
|
function fw::unblock_subnet() {
|
||||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
local client_ip="${1:-}" target_subnet="${2:-}"
|
||||||
|
fw::_unblock_pair -s "$client_ip" -d "$target_subnet"
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j DROP \
|
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j DROP 2>/dev/null || true
|
|
||||||
|
|
||||||
log::wg_unblock "Unblocked ${client_ip} → subnet ${target_subnet}"
|
log::wg_unblock "Unblocked ${client_ip} → subnet ${target_subnet}"
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::allow_subnet() {
|
function fw::block_all() {
|
||||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
local client_ip="${1:-}" client_name="${2:-}"
|
||||||
|
fw::_forward_exists -s "$client_ip" -j DROP \
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j ACCEPT \
|
|| iptables -A FORWARD -s "$client_ip" -j DROP
|
||||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_subnet" -j ACCEPT
|
log::debug "Blocked all traffic from: ${client_ip}"
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::unallow_subnet() {
|
function fw::unblock_all() {
|
||||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
local client_ip="${1:-}"
|
||||||
|
fw::_forward_exists -s "$client_ip" -j DROP \
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j ACCEPT \
|
&& iptables -D FORWARD -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j ACCEPT 2>/dev/null || true
|
monitor::unwatch "$client_ip"
|
||||||
|
log::debug "Unblocked all traffic from: ${client_ip}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Allow / Unallow
|
||||||
|
|
||||||
function fw::allow_ip() {
|
function fw::allow_ip() {
|
||||||
local client_ip="${1:-}" target_ip="${2:-}"
|
local client_ip="${1:-}" target_ip="${2:-}"
|
||||||
|
fw::_accept_insert -s "$client_ip" -d "$target_ip"
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j ACCEPT \
|
|
||||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j ACCEPT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::unallow_ip() {
|
function fw::unallow_ip() {
|
||||||
local client_ip="${1:-}" target_ip="${2:-}"
|
local client_ip="${1:-}" target_ip="${2:-}"
|
||||||
|
fw::_accept_remove -s "$client_ip" -d "$target_ip"
|
||||||
|
}
|
||||||
|
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j ACCEPT \
|
function fw::allow_subnet() {
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j ACCEPT 2>/dev/null || true
|
local client_ip="${1:-}" target_subnet="${2:-}"
|
||||||
|
fw::_accept_insert -s "$client_ip" -d "$target_subnet"
|
||||||
|
}
|
||||||
|
|
||||||
|
function fw::unallow_subnet() {
|
||||||
|
local client_ip="${1:-}" target_subnet="${2:-}"
|
||||||
|
fw::_accept_remove -s "$client_ip" -d "$target_subnet"
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::allow_port() {
|
function fw::allow_port() {
|
||||||
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
||||||
|
fw::_accept_insert \
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT \
|
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
||||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::unallow_port() {
|
function fw::unallow_port() {
|
||||||
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
||||||
|
fw::_accept_remove \
|
||||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT \
|
-s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port"
|
||||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null || true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Flush
|
||||||
|
# ============================================
|
||||||
|
|
||||||
function fw::flush_peer() {
|
function fw::flush_peer() {
|
||||||
local client_ip="${1:?client_ip required}"
|
local client_ip="${1:?client_ip required}"
|
||||||
log::debug "flush_peer: starting for $client_ip"
|
log::debug "flush_peer: starting for $client_ip"
|
||||||
|
|
||||||
# Collect line numbers into array
|
|
||||||
local linenums=()
|
local linenums=()
|
||||||
while IFS= read -r linenum; do
|
while IFS= read -r linenum; do
|
||||||
[[ -n "$linenum" ]] && linenums+=("$linenum")
|
[[ -n "$linenum" ]] && linenums+=("$linenum")
|
||||||
done < <(iptables -L FORWARD -n --line-numbers | grep -F "$client_ip" | awk '{print $1}')
|
done < <(iptables -L FORWARD -n --line-numbers \
|
||||||
|
| grep -F "$client_ip" | awk '{print $1}')
|
||||||
|
|
||||||
# Delete in reverse order (highest number first)
|
|
||||||
local count=0
|
local count=0
|
||||||
local i
|
local i
|
||||||
for (( i=${#linenums[@]}-1; i>=0; i-- )); do
|
for (( i=${#linenums[@]}-1; i>=0; i-- )); do
|
||||||
|
|
@ -159,118 +124,6 @@ function fw::flush_peer() {
|
||||||
log::debug "flush_peer: removed $count FORWARD rules for: $client_ip"
|
log::debug "flush_peer: removed $count FORWARD rules for: $client_ip"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Guest Subnet Rules
|
|
||||||
# ============================================
|
|
||||||
|
|
||||||
function fw::apply_dns_redirect() {
|
|
||||||
if iptables -t nat -C PREROUTING -s "$(config::subnet_for guest).0/24" -p udp --dport 53 -j DNAT --to-destination "$(config::dns):53" 2>/dev/null; then
|
|
||||||
log::wg "Guest DNS redirect already applied"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local guest_subnet dns
|
|
||||||
guest_subnet="$(config::subnet_for "guest").0/24"
|
|
||||||
dns="$(config::dns)"
|
|
||||||
|
|
||||||
# Log DNS bypass attempts (queries not directed at Pi-hole)
|
|
||||||
iptables -t nat -A PREROUTING -s "$guest_subnet" -p udp --dport 53 \
|
|
||||||
! -d "$dns" \
|
|
||||||
-j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
|
|
||||||
iptables -t nat -A PREROUTING -s "$guest_subnet" -p tcp --dport 53 \
|
|
||||||
! -d "$dns" \
|
|
||||||
-j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
|
|
||||||
|
|
||||||
# Redirect all DNS to Pi-hole
|
|
||||||
iptables -t nat -A PREROUTING -s "$guest_subnet" -p udp --dport 53 -j DNAT --to-destination "${dns}:53"
|
|
||||||
iptables -t nat -A PREROUTING -s "$guest_subnet" -p tcp --dport 53 -j DNAT --to-destination "${dns}:53"
|
|
||||||
|
|
||||||
log::wg_block "Guest DNS redirected to Pi-hole (${dns}), bypass attempts will be logged"
|
|
||||||
}
|
|
||||||
|
|
||||||
function fw::remove_dns_redirect() {
|
|
||||||
local guest_subnet dns
|
|
||||||
guest_subnet="$(config::subnet_for "guest").0/24"
|
|
||||||
dns="$(config::dns)"
|
|
||||||
|
|
||||||
iptables -t nat -D PREROUTING -s "$guest_subnet" -p udp --dport 53 \
|
|
||||||
! -d "$dns" \
|
|
||||||
-j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4 2>/dev/null || true
|
|
||||||
iptables -t nat -D PREROUTING -s "$guest_subnet" -p tcp --dport 53 \
|
|
||||||
! -d "$dns" \
|
|
||||||
-j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4 2>/dev/null || true
|
|
||||||
iptables -t nat -D PREROUTING -s "$guest_subnet" -p udp --dport 53 -j DNAT --to-destination "${dns}:53" 2>/dev/null || true
|
|
||||||
iptables -t nat -D PREROUTING -s "$guest_subnet" -p tcp --dport 53 -j DNAT --to-destination "${dns}:53" 2>/dev/null || true
|
|
||||||
|
|
||||||
log::debug "Removed guest DNS redirect"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Peer related
|
|
||||||
# ============================================
|
|
||||||
|
|
||||||
function fw::list_peer_rules() {
|
|
||||||
local ip="${1:-}" show_nflog="${2:-false}"
|
|
||||||
|
|
||||||
fw::forward_rules_for_ip "$ip" | while IFS= read -r line; do
|
|
||||||
[[ -z "$line" ]] && continue
|
|
||||||
! $show_nflog && [[ "$line" =~ NFLOG ]] && continue
|
|
||||||
fw::format_rule "$line"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
function fw::format_rule() {
|
|
||||||
local line="${1:-}"
|
|
||||||
[[ -z "$line" ]] && return 0
|
|
||||||
|
|
||||||
# Parse verbose iptables format:
|
|
||||||
# pkts bytes target prot opt in out src dst [extra]
|
|
||||||
local target prot src dst extra
|
|
||||||
target=$(awk '{print $3}' <<< "$line")
|
|
||||||
prot=$(awk '{print $4}' <<< "$line")
|
|
||||||
src=$(awk '{print $8}' <<< "$line")
|
|
||||||
dst=$(awk '{print $9}' <<< "$line")
|
|
||||||
extra=$(awk '{for(i=10;i<=NF;i++) printf $i" "}' <<< "$line" | xargs)
|
|
||||||
|
|
||||||
local prot_name
|
|
||||||
prot_name=$(fw::proto_name "$prot")
|
|
||||||
|
|
||||||
local dst_fmt="$dst"
|
|
||||||
if [[ "$extra" =~ dpt:([0-9]+) ]]; then
|
|
||||||
local port="${BASH_REMATCH[1]}"
|
|
||||||
dst_fmt="${dst}:${port}:${prot_name}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local formatted
|
|
||||||
formatted=$(printf " %-8s %-15s → %s" "$target" "$src" "$dst_fmt")
|
|
||||||
ui::firewall_rule "$formatted"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Helpers
|
|
||||||
# ============================================
|
|
||||||
|
|
||||||
function fw::_nat_exists() {
|
|
||||||
fw::_rule_exists nat PREROUTING "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
function fw::forward_rules_for_ip() {
|
|
||||||
local ip="${1:-}"
|
|
||||||
iptables -L FORWARD -n -v </dev/null | grep -F "$ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
function fw::proto_name() {
|
|
||||||
local proto="${1:-0}"
|
|
||||||
case "$proto" in
|
|
||||||
6) echo "tcp" ;;
|
|
||||||
17) echo "udp" ;;
|
|
||||||
1) echo "icmp" ;;
|
|
||||||
0) echo "all" ;;
|
|
||||||
tcp|udp|icmp|all) echo "$proto" ;;
|
|
||||||
*) echo "$proto" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
function fw::flush_forward() {
|
function fw::flush_forward() {
|
||||||
iptables -F FORWARD
|
iptables -F FORWARD
|
||||||
log::debug "Flushed FORWARD chain"
|
log::debug "Flushed FORWARD chain"
|
||||||
|
|
@ -286,6 +139,10 @@ function fw::flush_all() {
|
||||||
fw::flush_nat
|
fw::flush_nat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# NAT / DNS Redirect
|
||||||
|
# ============================================
|
||||||
|
|
||||||
function fw::nat_add_dns_redirect() {
|
function fw::nat_add_dns_redirect() {
|
||||||
local subnet="${1:-}" dns="${2:-}" interface="${3:-wg0}"
|
local subnet="${1:-}" dns="${2:-}" interface="${3:-wg0}"
|
||||||
iptables -t nat -A PREROUTING -i "$interface" -s "$subnet" \
|
iptables -t nat -A PREROUTING -i "$interface" -s "$subnet" \
|
||||||
|
|
@ -311,16 +168,137 @@ function fw::nat_remove_dns_redirect() {
|
||||||
--to-destination "${dns}:53" 2>/dev/null || true
|
--to-destination "${dns}:53" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Display
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function fw::forward_rules_for_ip() {
|
||||||
|
local ip="${1:-}"
|
||||||
|
iptables -L FORWARD -n -v </dev/null | grep -F "$ip"
|
||||||
|
}
|
||||||
|
|
||||||
|
function fw::proto_name() {
|
||||||
|
local proto="${1:-0}"
|
||||||
|
case "$proto" in
|
||||||
|
6) echo "tcp" ;;
|
||||||
|
17) echo "udp" ;;
|
||||||
|
1) echo "icmp" ;;
|
||||||
|
0) echo "all" ;;
|
||||||
|
tcp|udp|icmp|all) echo "$proto" ;;
|
||||||
|
*) echo "$proto" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function fw::format_rule() {
|
||||||
|
local line="${1:-}"
|
||||||
|
[[ -z "$line" ]] && return 0
|
||||||
|
|
||||||
|
local target prot src dst extra
|
||||||
|
target=$(awk '{print $3}' <<< "$line")
|
||||||
|
prot=$(awk '{print $4}' <<< "$line")
|
||||||
|
src=$(awk '{print $8}' <<< "$line")
|
||||||
|
dst=$(awk '{print $9}' <<< "$line")
|
||||||
|
extra=$(awk '{for(i=10;i<=NF;i++) printf $i" "}' <<< "$line" | xargs)
|
||||||
|
|
||||||
|
local prot_name
|
||||||
|
prot_name=$(fw::proto_name "$prot")
|
||||||
|
|
||||||
|
local dst_fmt="$dst"
|
||||||
|
if [[ "$extra" =~ dpt:([0-9]+) ]]; then
|
||||||
|
local port="${BASH_REMATCH[1]}"
|
||||||
|
dst_fmt="${dst}:${port}:${prot_name}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local formatted
|
||||||
|
formatted=$(printf " %-8s %-15s → %s" "$target" "$src" "$dst_fmt")
|
||||||
|
ui::firewall_rule "$formatted"
|
||||||
|
}
|
||||||
|
|
||||||
|
function fw::list_peer_rules() {
|
||||||
|
local ip="${1:-}" show_nflog="${2:-false}"
|
||||||
|
fw::forward_rules_for_ip "$ip" | while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
! $show_nflog && [[ "$line" =~ NFLOG ]] && continue
|
||||||
|
fw::format_rule "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Counts
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function fw::count_peer_rules() {
|
||||||
|
local ip="${1:-}"
|
||||||
|
local total=0 accepts=0 drops=0
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
[[ "$line" =~ NFLOG ]] && continue
|
||||||
|
(( total++ )) || true
|
||||||
|
[[ "$line" =~ ACCEPT ]] && (( accepts++ )) || true
|
||||||
|
[[ "$line" =~ DROP ]] && (( drops++ )) || true
|
||||||
|
done < <(fw::forward_rules_for_ip "$ip")
|
||||||
|
echo "${total}|${accepts}|${drops}"
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# private
|
# private
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
|
function fw::_forward_exists() {
|
||||||
|
iptables -C FORWARD "$@" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
function fw::_rule_exists() {
|
function fw::_rule_exists() {
|
||||||
local table="${1:-filter}" chain="${2:-FORWARD}"
|
local table="${1:-filter}" chain="${2:-FORWARD}"
|
||||||
shift 2
|
shift 2
|
||||||
iptables -t "$table" -C "$chain" "$@" 2>/dev/null
|
iptables -t "$table" -C "$chain" "$@" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
function fw::_forward_exists() {
|
function fw::_nat_exists() {
|
||||||
iptables -C FORWARD "$@" 2>/dev/null
|
fw::_rule_exists nat PREROUTING "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Core NFLOG+DROP block pair — insert or append
|
||||||
|
function fw::_block_pair() {
|
||||||
|
local mode="${1:-insert}" # insert | append
|
||||||
|
shift
|
||||||
|
# $@ = match args (no -j)
|
||||||
|
if [[ "$mode" == "insert" ]]; then
|
||||||
|
# insert: DROP first at pos 1, NFLOG second at pos 1 → NFLOG ends above DROP
|
||||||
|
fw::_forward_exists "$@" -j DROP \
|
||||||
|
|| iptables -I FORWARD 1 "$@" -j DROP
|
||||||
|
fw::_forward_exists "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||||
|
|| iptables -I FORWARD 1 "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||||
|
else
|
||||||
|
# append: NFLOG first, DROP second → NFLOG ends above DROP
|
||||||
|
fw::_forward_exists "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||||
|
|| iptables -A FORWARD "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||||
|
fw::_forward_exists "$@" -j DROP \
|
||||||
|
|| iptables -A FORWARD "$@" -j DROP
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Core NFLOG+DROP removal
|
||||||
|
function fw::_unblock_pair() {
|
||||||
|
shift 0 # no mode needed for deletion
|
||||||
|
# $@ = match args
|
||||||
|
fw::_forward_exists "$@" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||||
|
&& iptables -D FORWARD "$@" -j NFLOG \
|
||||||
|
--nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
||||||
|
fw::_forward_exists "$@" -j DROP \
|
||||||
|
&& iptables -D FORWARD "$@" -j DROP 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Core ACCEPT insert
|
||||||
|
function fw::_accept_insert() {
|
||||||
|
# $@ = match args
|
||||||
|
fw::_forward_exists "$@" -j ACCEPT \
|
||||||
|
|| iptables -I FORWARD 1 "$@" -j ACCEPT
|
||||||
|
}
|
||||||
|
|
||||||
|
# Core ACCEPT removal
|
||||||
|
function fw::_accept_remove() {
|
||||||
|
fw::_forward_exists "$@" -j ACCEPT \
|
||||||
|
&& iptables -D FORWARD "$@" -j ACCEPT 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
102
modules/net.module.sh
Normal file
102
modules/net.module.sh
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function net::exists() {
|
||||||
|
local name="${1:?}"
|
||||||
|
local result
|
||||||
|
result=$(json::net_exists "$(ctx::net)" "$name")
|
||||||
|
[[ "$result" == "true" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
function net::require_exists() {
|
||||||
|
local name="${1:?}"
|
||||||
|
if ! net::exists "$name"; then
|
||||||
|
log::error "Service not found: ${name}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function net::resolve() {
|
||||||
|
local name="${1:?}"
|
||||||
|
json::net_resolve "$(ctx::net)" "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
function net::reverse_lookup() {
|
||||||
|
local ip="${1:-}" port="${2:-}" proto="${3:-}"
|
||||||
|
[[ -z "$ip" ]] && return 0
|
||||||
|
json::net_reverse_lookup "$(ctx::net)" "$ip" "$port" "$proto"
|
||||||
|
}
|
||||||
|
|
||||||
|
function net::annotation() {
|
||||||
|
# Returns " → service:port" or "" — for display use
|
||||||
|
local ip="${1:-}" port="${2:-}" proto="${3:-}"
|
||||||
|
local match
|
||||||
|
match=$(net::reverse_lookup "$ip" "$port" "$proto")
|
||||||
|
[[ -n "$match" ]] && echo " → ${match}" || echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function net::annotate() {
|
||||||
|
# Returns " → service:port-name" or "" for display use
|
||||||
|
local entry="${1:-}"
|
||||||
|
[[ -z "$entry" ]] && return 0
|
||||||
|
|
||||||
|
local ann=""
|
||||||
|
if [[ "$entry" == *:*:* ]]; then
|
||||||
|
# ip:port:proto
|
||||||
|
local b_ip b_port b_proto
|
||||||
|
IFS=":" read -r b_ip b_port b_proto <<< "$entry"
|
||||||
|
ann=$(net::reverse_lookup "$b_ip" "$b_port" "$b_proto")
|
||||||
|
else
|
||||||
|
# ip or ip/cidr
|
||||||
|
local ip="${entry%%/*}"
|
||||||
|
ann=$(net::reverse_lookup "$ip")
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$ann" ]] && echo "${ann}" || echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# function net::print_entry() {
|
||||||
|
# local sign="${1:-}" entry="${2:-}" indent="${3:-6}"
|
||||||
|
|
||||||
|
# local ann
|
||||||
|
# ann=$(net::annotate "$entry")
|
||||||
|
|
||||||
|
# local color
|
||||||
|
# [[ "$sign" == "+" ]] && color="\033[0;32m" || color="\033[0;31m"
|
||||||
|
|
||||||
|
# local spaces
|
||||||
|
# spaces=$(printf '%*s' "$indent" '')
|
||||||
|
# printf "%s%b%s\033[0m %s\033[0;37m%s\033[0m\n" \
|
||||||
|
# "$spaces" "$color" "$sign" "$entry" "${ann:+ → ${ann}}"
|
||||||
|
# }
|
||||||
|
|
||||||
|
function net::print_entry() {
|
||||||
|
local sign="${1:-}" entry="${2:-}" indent="${3:-6}"
|
||||||
|
local ann
|
||||||
|
ann=$(net::annotate "$entry")
|
||||||
|
local color
|
||||||
|
[[ "$sign" == "+" ]] && color="\033[0;32m" || color="\033[0;31m"
|
||||||
|
local spaces
|
||||||
|
spaces=$(printf '%*s' "$indent" '')
|
||||||
|
printf "%s%b%s\033[0m %-20s\033[0;37m%s\033[0m\n" \
|
||||||
|
"$spaces" "$color" "$sign" "$entry" \
|
||||||
|
"${ann:+ → ${ann}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function net::print_dns_redirect() {
|
||||||
|
local ip="${1:-}" indent="${2:-6}" label="${3:-DNS}"
|
||||||
|
local spaces
|
||||||
|
spaces=$(printf '%*s' "$indent" '')
|
||||||
|
local ann
|
||||||
|
ann=$(net::annotate "$ip")
|
||||||
|
printf "%s\033[0;36m↺\033[0m %s → %s\033[0;37m%s\033[0m\n" \
|
||||||
|
"$spaces" "$label" "$ip" "${ann:+ → ${ann}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function net::print_dns_redirect_full() {
|
||||||
|
# For rule::show — slightly different prefix
|
||||||
|
local ip="${1:-}"
|
||||||
|
local ann
|
||||||
|
ann=$(net::annotate "$ip")
|
||||||
|
printf " \033[0;36m↺\033[0m Redirect all DNS → %s\033[0;37m%s\033[0m\n" \
|
||||||
|
"$ip" "${ann:+ → ${ann}}"
|
||||||
|
}
|
||||||
|
|
@ -288,6 +288,143 @@ function rule::restore_all() {
|
||||||
log::wg "Rules restored for all peers"
|
log::wg "Rules restored for all peers"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Rendering
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function rule::render_flat() {
|
||||||
|
local rule_name="${1:-}"
|
||||||
|
|
||||||
|
local allow_ports allow_ips block_ips block_ports dns
|
||||||
|
allow_ports=$(rule::get "$rule_name" "allow_ports")
|
||||||
|
allow_ips=$(rule::get "$rule_name" "allow_ips")
|
||||||
|
block_ips=$(rule::get "$rule_name" "block_ips")
|
||||||
|
block_ports=$(rule::get "$rule_name" "block_ports")
|
||||||
|
dns=$(rule::get_own "$rule_name" "dns_redirect")
|
||||||
|
|
||||||
|
local has_content=false
|
||||||
|
[[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && \
|
||||||
|
has_content=true
|
||||||
|
|
||||||
|
if ! $has_content; then
|
||||||
|
printf "\n full access (no restrictions)\n"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
||||||
|
printf "\n"
|
||||||
|
while IFS= read -r e; do
|
||||||
|
[[ -z "$e" ]] && continue
|
||||||
|
net::print_entry "+" "$e" 2
|
||||||
|
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
||||||
|
printf "\n"
|
||||||
|
while IFS= read -r e; do
|
||||||
|
[[ -z "$e" ]] && continue
|
||||||
|
net::print_entry "-" "$e" 2
|
||||||
|
done <<< "$block_ips"$'\n'"$block_ports"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "${dns,,}" == "true" ]] && \
|
||||||
|
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function rule::render_entries() {
|
||||||
|
# Renders allow/block entries for a rule name with annotations and DNS
|
||||||
|
# Usage: rule::render_entries <rule_name> <indent>
|
||||||
|
# indent: 4 for rule show, 4 for inspect (same)
|
||||||
|
local rule_name="${1:-}" indent="${2:-4}"
|
||||||
|
local rule_file
|
||||||
|
rule_file="$(rule::path "$rule_name")"
|
||||||
|
|
||||||
|
local allow_ports allow_ips block_ips block_ports dns
|
||||||
|
allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
|
||||||
|
allow_ips=$(rule::get "$rule_name" "allow_ips" 2>/dev/null || true)
|
||||||
|
block_ips=$(rule::get "$rule_name" "block_ips" 2>/dev/null || true)
|
||||||
|
block_ports=$(rule::get "$rule_name" "block_ports" 2>/dev/null || true)
|
||||||
|
dns=$(rule::get_own "$rule_name" "dns_redirect")
|
||||||
|
|
||||||
|
while IFS= read -r e; do
|
||||||
|
[[ -z "$e" ]] && continue
|
||||||
|
net::print_entry "+" "$e"
|
||||||
|
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||||
|
|
||||||
|
while IFS= read -r e; do
|
||||||
|
[[ -z "$e" ]] && continue
|
||||||
|
net::print_entry "-" "$e"
|
||||||
|
done <<< "$block_ips"$'\n'"$block_ports"
|
||||||
|
|
||||||
|
[[ "${dns,,}" == "true" ]] && \
|
||||||
|
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||||
|
}
|
||||||
|
|
||||||
|
function rule::render_own_entries() {
|
||||||
|
# Renders own (non-inherited) entries for a rule
|
||||||
|
local rule_name="${1:-}"
|
||||||
|
local rule_file
|
||||||
|
rule_file="$(rule::path "$rule_name")"
|
||||||
|
|
||||||
|
local allow_ports allow_ips block_ips block_ports dns
|
||||||
|
allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
|
||||||
|
allow_ips=$(json::get "$rule_file" "allow_ips" 2>/dev/null || true)
|
||||||
|
block_ips=$(json::get "$rule_file" "block_ips" 2>/dev/null || true)
|
||||||
|
block_ports=$(json::get "$rule_file" "block_ports" 2>/dev/null || true)
|
||||||
|
dns=$(json::get "$rule_file" "dns_redirect" 2>/dev/null || true)
|
||||||
|
|
||||||
|
local has_own=false
|
||||||
|
local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
|
||||||
|
[[ -n "${combined//[$'\n']/}" ]] && has_own=true
|
||||||
|
|
||||||
|
$has_own || return 0
|
||||||
|
|
||||||
|
while IFS= read -r e; do
|
||||||
|
[[ -z "$e" ]] && continue
|
||||||
|
net::print_entry "+" "$e"
|
||||||
|
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||||
|
|
||||||
|
while IFS= read -r e; do
|
||||||
|
[[ -z "$e" ]] && continue
|
||||||
|
net::print_entry "-" "$e"
|
||||||
|
done <<< "$block_ips"$'\n'"$block_ports"
|
||||||
|
|
||||||
|
[[ "${dns,,}" == "true" ]] && \
|
||||||
|
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function rule::render_extends_tree() {
|
||||||
|
# Renders full inheritance tree for a rule
|
||||||
|
local rule_name="${1:-}"
|
||||||
|
local rule_file
|
||||||
|
rule_file="$(rule::path "$rule_name")"
|
||||||
|
|
||||||
|
local extends_raw=()
|
||||||
|
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
|
||||||
|
|
||||||
|
[[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]:-}" ]] && return 1
|
||||||
|
|
||||||
|
for base_name in "${extends_raw[@]}"; do
|
||||||
|
[[ -z "$base_name" ]] && continue
|
||||||
|
printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
|
||||||
|
rule::render_entries "$base_name"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Own rules after inherited
|
||||||
|
local own_output
|
||||||
|
own_output=$(rule::render_own_entries "$rule_name")
|
||||||
|
if [[ -n "$own_output" ]]; then
|
||||||
|
printf "\n \033[0;37mOwn:\033[0m\n"
|
||||||
|
printf "%s\n" "$own_output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# DNS Redirect
|
# DNS Redirect
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
|
||||||
4
wgctl
4
wgctl
|
|
@ -18,6 +18,7 @@ load_module firewall
|
||||||
load_module monitor
|
load_module monitor
|
||||||
load_module rule
|
load_module rule
|
||||||
load_module block
|
load_module block
|
||||||
|
load_module net
|
||||||
load_module group
|
load_module group
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
@ -51,6 +52,9 @@ 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
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue