feat: block/unblock --service, fw::has_rule wrappers, restricted status, net annotations, block system tests, 64 tests passing

This commit is contained in:
Nuno Duque Nunes 2026-05-15 11:36:05 +00:00
parent c1d0a9ddd4
commit 16b4351313
5 changed files with 80 additions and 51 deletions

View file

@ -113,6 +113,7 @@ function cmd::block::run() {
ip::require_valid "$ip"
fw::block_ip "$client_ip" "$ip"
block::add_rule "$name" "$client_ip" "ip" "${block_name:-}" "$ip"
$quiet || log::wg_success "${ip} has been blocked for ${name}"
done
# Block specific subnets
@ -120,6 +121,7 @@ function cmd::block::run() {
ip::require_valid "$subnet"
fw::block_subnet "$client_ip" "$subnet"
block::add_rule "$name" "$client_ip" "subnet" "${block_name:-}" "$subnet"
$quiet || log::wg_success "${subnet} has been blocked for ${name}"
done
# Block specific ports
@ -130,6 +132,7 @@ function cmd::block::run() {
fw::block_port "$client_ip" "$b_target" "$b_port" "${b_proto:-tcp}"
block::add_rule "$name" "$client_ip" "port" "${block_name:-}" \
"$b_target" "$b_port" "${b_proto:-tcp}"
$quiet || log::wg_success "${client_ip}:${b_port}:${b_proto:-tcp} has been blocked for ${name}"
done
# Block services
@ -147,7 +150,7 @@ function cmd::block::run() {
if [[ "$resolved" == *:*:* ]]; then
local b_ip b_port b_proto
IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
fw::has_block_rule "$client_ip" "$b_ip" "$b_proto" "$b_port" 2>/dev/null || \
fw::has_block_rule "$client_ip" "$b_ip" "$b_port" "$b_proto" 2>/dev/null || \
{ already_blocked=false; break; }
else
fw::has_block_rule "$client_ip" "$resolved" 2>/dev/null || \

View file

@ -130,18 +130,16 @@ function cmd::list::show_client() {
subtype=$(peers::get_meta "$name" "subtype")
local endpoint="—"
local ep
ep=$(monitor::endpoint_for_key "$public_key")
[[ -z "$ep" ]] && ep=$(monitor::last_endpoint "$name")
[[ -n "$ep" ]] && endpoint="$ep"
local is_blocked="false"
peers::is_blocked "$name" && is_blocked="true"
if [[ "$is_blocked" == "true" ]]; then
local ep
ep=$(monitor::last_endpoint "$name")
[[ -n "$ep" ]] && endpoint="$ep"
else
local ep
ep=$(monitor::endpoint_for_key "$public_key")
[[ -n "$ep" ]] && endpoint="$ep"
fi
local is_restricted="false"
peers::is_restricted "$name" && is_restricted="true"
local handshake_ts
handshake_ts=$(monitor::get_handshake_ts "$public_key")
@ -151,7 +149,7 @@ function cmd::list::show_client() {
local status
status=$(peers::format_status "$name" "$public_key" \
"$is_blocked" "false" "$handshake_ts" "$last_ts")
"$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
local last_seen
last_seen=$(peers::format_last_seen "$name" "$public_key" \

View file

@ -67,7 +67,9 @@ function cmd::test::run_cmd() {
fi
if [[ -n "$expected" ]] && ! grep -qF "$expected" "$tmp"; then
test::fail "${desc} (expected '${expected}' in output)"
local actual
actual=$(head -3 "$tmp" | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-100)
test::fail "${desc} (expected '${expected}', got: '${actual}')"
rm -f "$tmp"
return 1
fi
@ -240,18 +242,44 @@ function cmd::test::section_destructive() {
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" group remove --name testgroup --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" group remove --name testgroup2 --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" net rm --name test-block-svc --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" unblock --name phone-testunit > /dev/null 2>&1 || true
# ── Add test peer ──────────────────────────
cmd::test::run_cmd "add phone peer" "added successfully" \
add --name testunit --type phone
# ── Direct block/unblock ───────────────────
cmd::test::run_cmd "block peer" "blocked" \
block --name phone-testunit
cmd::test::run_cmd "list shows blocked" "blocked" \
list --blocked
cmd::test::run_cmd "unblock peer" "unblocked" \
unblock --name phone-testunit
cmd::test::run_cmd "block peer" "blocked" block --name phone-testunit
cmd::test::run_cmd "list shows blocked" "blocked" list --blocked
cmd::test::run_cmd "unblock peer" "unblocked" unblock --name phone-testunit
# ── Specific IP block/unblock ──────────────
cmd::test::run_cmd "block peer --ip" "blocked for" \
block --name phone-testunit --ip 10.0.0.99
cmd::test::run_cmd "list shows restricted" "restricted" \
list --name phone-testunit
cmd::test::run_cmd "unblock peer --ip" "unblocked" \
unblock --name phone-testunit --ip 10.0.0.99
# ── Service block/unblock ──────────────────
"$WGCTL_BINARY" net add --name test-block-svc \
--ip 10.0.0.99 > /dev/null 2>&1
"$WGCTL_BINARY" net add --name test-block-svc:web \
--port 9999:tcp > /dev/null 2>&1
cmd::test::run_cmd "block peer --service (ip)" "blocked" \
block --name phone-testunit --service test-block-svc
cmd::test::run_cmd "block already blocked service" "already" \
block --name phone-testunit --service test-block-svc
cmd::test::run_cmd "unblock peer --service (ip)" "unblocked" \
unblock --name phone-testunit --service test-block-svc
cmd::test::run_cmd "unblock not blocked service" "not blocked" \
unblock --name phone-testunit --service test-block-svc
cmd::test::run_cmd "block peer --service (port)" "blocked" \
block --name phone-testunit --service test-block-svc:web
cmd::test::run_cmd "unblock peer --service (port)" "unblocked" \
unblock --name phone-testunit --service test-block-svc:web
"$WGCTL_BINARY" net rm --name test-block-svc --force > /dev/null 2>&1 || true
# ── Rule assign/unassign ───────────────────
cmd::test::run_cmd "rule assign" "Assigned" \
@ -272,24 +300,20 @@ function cmd::test::section_destructive() {
group unblock --name testgroup
# ── M:N group block tracking ───────────────
# Setup: add testunit to a second group
"$WGCTL_BINARY" group add --name testgroup2 \
--desc "Test group 2" > /dev/null 2>&1
"$WGCTL_BINARY" group peer add --name testgroup2 \
--peer phone-testunit > /dev/null 2>&1
# Block from both groups
cmd::test::run_cmd "group block first group" "blocked" \
cmd::test::run_cmd "group block first group" "blocked" \
group block --name testgroup
cmd::test::run_cmd "group block second group" "blocked" \
group block --name testgroup2
# Unblock from first — should stay blocked (second group still blocking)
"$WGCTL_BINARY" group unblock --name testgroup > /dev/null 2>&1
cmd::test::run_cmd "peer stays blocked after partial unblock" "blocked" \
list --blocked
# Unblock from second — should now be fully unblocked
"$WGCTL_BINARY" group unblock --name testgroup2 > /dev/null 2>&1
cmd::test::run_cmd "peer unblocked after all groups unblock" "phone-testunit" \
list --allowed
@ -299,12 +323,11 @@ function cmd::test::section_destructive() {
cmd::test::run_cmd "direct unblock overrides group block" "unblocked" \
unblock --name phone-testunit
# ── Cleanup groups ─────────────────────────
# ── Cleanup ────────────────────────────────
cmd::test::run_cmd "group remove" "removed" \
group remove --name testgroup --force
"$WGCTL_BINARY" group remove --name testgroup2 --force > /dev/null 2>&1 || true
# ── Remove test peer ───────────────────────
cmd::test::run_cmd "remove phone peer" "removed" \
remove --name phone-testunit --force
}
@ -406,9 +429,6 @@ function cmd::test::fn_rule_assign() {
"$WGCTL_BINARY" remove --name phone-testunit --force > /dev/null 2>&1 || true
"$WGCTL_BINARY" add --name testunit --type phone > /dev/null 2>&1
# Verify peer exists
echo "DEBUG peer exists: $("$WGCTL_BINARY" list | grep phone-testunit)"
cmd::test::run_cmd "rule assign" "Assigned" \
rule assign --name admin --peer phone-testunit

View file

@ -110,6 +110,30 @@ function cmd::unblock::run() {
return 0
fi
# Unblock specific IPs
for ip in "${ips[@]}"; do
fw::unblock_ip "$client_ip" "$ip"
block::remove_rule "$name" "ip" "$ip"
$quiet || log::wg_success "${ip} has been unblocked for ${name}"
done
# Unblock specific subnets
for subnet in "${subnets[@]}"; do
fw::unblock_subnet "$client_ip" "$subnet"
block::remove_rule "$name" "subnet" "$subnet"
$quiet || log::wg_success "${subnet} has been unblocked for ${name}"
done
# Unblock specific ports
for entry in "${ports[@]}"; do
local target port proto
IFS=":" read -r target port proto <<< "$entry"
proto="${proto:-tcp}"
fw::unblock_port "$client_ip" "$target" "$port" "$proto"
block::remove_rule "$name" "port" "$b_target" "$b_port" "$b_proto"
$quiet || log::wg_success "${client_ip}:${b_port}:${b_proto:-tcp} has been unblocked for ${name}"
done
# Unblock services
for svc in "${services[@]}"; do
local resolved_lines=()
@ -125,7 +149,7 @@ function cmd::unblock::run() {
if [[ "$resolved" == *:*:* ]]; then
local b_ip b_port b_proto
IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
fw::has_block_rule "$client_ip" "$b_ip" "$b_proto" "$b_port" 2>/dev/null && \
fw::has_block_rule "$client_ip" "$b_ip" "$b_port" "$b_proto" 2>/dev/null && \
{ is_blocked=true; break; }
else
fw::has_block_rule "$client_ip" "$resolved" 2>/dev/null && \
@ -153,27 +177,6 @@ function cmd::unblock::run() {
$quiet || log::wg_success "${svc} has been unblocked for ${name}"
done
# Unblock specific IPs
for ip in "${ips[@]}"; do
fw::unblock_ip "$client_ip" "$ip"
block::remove_rule "$name" "ip" "$ip"
done
# Unblock specific subnets
for subnet in "${subnets[@]}"; do
fw::unblock_subnet "$client_ip" "$subnet"
block::remove_rule "$name" "subnet" "$subnet"
done
# Unblock specific ports
for entry in "${ports[@]}"; do
local target port proto
IFS=":" read -r target port proto <<< "$entry"
proto="${proto:-tcp}"
fw::unblock_port "$client_ip" "$target" "$port" "$proto"
block::remove_rule "$name" "port" "$b_target" "$b_port" "$b_proto"
done
# Clean up block file if now empty
block::cleanup "$name"

View file

@ -170,6 +170,11 @@ function peers::is_blocked() {
block::is_blocked "$name"
}
function peers::is_restricted() {
local name="${1:-}"
block::has_specific_rules "$name" 2>/dev/null
}
# ============================================
# Default Rule
# ============================================