From 16b43513139b4f0dfd42966d44088eee08347a45 Mon Sep 17 00:00:00 2001 From: Nuno Duque Nunes Date: Fri, 15 May 2026 11:36:05 +0000 Subject: [PATCH] feat: block/unblock --service, fw::has_rule wrappers, restricted status, net annotations, block system tests, 64 tests passing --- commands/block.command.sh | 5 +++- commands/list.command.sh | 18 ++++++------ commands/test.command.sh | 56 +++++++++++++++++++++++++------------ commands/unblock.command.sh | 47 ++++++++++++++++--------------- modules/peers.module.sh | 5 ++++ 5 files changed, 80 insertions(+), 51 deletions(-) diff --git a/commands/block.command.sh b/commands/block.command.sh index f7590ef..35f0609 100644 --- a/commands/block.command.sh +++ b/commands/block.command.sh @@ -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 || \ diff --git a/commands/list.command.sh b/commands/list.command.sh index 542b054..5992f2f 100644 --- a/commands/list.command.sh +++ b/commands/list.command.sh @@ -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" \ diff --git a/commands/test.command.sh b/commands/test.command.sh index 4c4014f..866421c 100644 --- a/commands/test.command.sh +++ b/commands/test.command.sh @@ -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 diff --git a/commands/unblock.command.sh b/commands/unblock.command.sh index c8e6097..1322388 100644 --- a/commands/unblock.command.sh +++ b/commands/unblock.command.sh @@ -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" diff --git a/modules/peers.module.sh b/modules/peers.module.sh index 7672db3..d32dc12 100644 --- a/modules/peers.module.sh +++ b/modules/peers.module.sh @@ -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 # ============================================