From f32ca5c0a18795f21c446626844501223e1152fe Mon Sep 17 00:00:00 2001 From: Nuno Duque Nunes Date: Fri, 15 May 2026 12:36:38 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20complete=20wgctl=20v2=20=E2=80=94=20net?= =?UTF-8?q?=20services,=20block=20system=20M:N,=20rule=20inheritance,=20se?= =?UTF-8?q?rvice=20annotations,=20restricted=20status,=2064=20tests=20pass?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commands/audit.command.sh | 61 ++++++++++---- commands/block.command.sh | 23 ++++-- commands/group.command.sh | 89 ++------------------ commands/inspect.command.sh | 159 +++--------------------------------- commands/list.command.sh | 11 ++- commands/rule.command.sh | 12 ++- commands/shell.command.sh | 11 ++- commands/unblock.command.sh | 21 +++-- wgctl | 17 +++- 9 files changed, 122 insertions(+), 282 deletions(-) diff --git a/commands/audit.command.sh b/commands/audit.command.sh index ca7c6ac..cdf370e 100644 --- a/commands/audit.command.sh +++ b/commands/audit.command.sh @@ -11,7 +11,8 @@ function cmd::audit::help() { Usage: wgctl audit [options] Verify that all peers have the correct iptables firewall rules applied. -Checks expected rule count (including inherited rules) vs actual iptables state. +Checks expected rule count (including inherited rules and peer-specific blocks) +vs actual iptables state. Options: --peer Audit specific peer only @@ -21,7 +22,12 @@ Options: Output: ✅ pass — peer has correct rule count ❌ fail — peer has missing rules (run --fix to repair) - ⚠️ warn — peer has extra rules (e.g. blocked peers with base rules) + ⚠️ warn — peer has extra rules or configuration issues + +Notes: + Fully blocked peers (removed from WireGuard) show 0 expected fw rules. + Restricted peers (peer-specific blocks) have their block rules included + in the expected count. Examples: wgctl audit @@ -81,53 +87,72 @@ function cmd::audit::run() { } function cmd::audit::check_peer() { - local peer_name="$1" - local fix="$2" - local actual="${3:-0}" + local peer_name="$1" fix="$2" actual="${3:-0}" local ip rule ip=$(peers::get_ip "$peer_name") rule=$(peers::get_meta "$peer_name" "rule") - # Check rule assigned if [[ -z "$rule" ]]; then test::warn "$peer_name — no rule assigned (effective: $(peers::default_rule "$peer_name"))" - return + return 0 fi - # Check rule exists if ! rule::exists "$rule"; then test::fail "$peer_name — assigned rule '$rule' does not exist" - return + return 0 fi - # Count expected iptables rules from rule file - local rule_file - rule_file="$(ctx::rule::path "${rule}.rule")" - local block_ports block_ips allow_ports allow_ips expected + # Blocked peers have no fw rules (removed from wg0) + if block::is_blocked "$peer_name" 2>/dev/null; then + if [[ "$actual" -eq 0 ]]; then + test::pass "$(printf "%-28s rule=%-15s blocked (no fw rules)" \ + "$peer_name" "$rule")" + else + test::warn "$(printf "%-28s rule=%-15s blocked but has %s fw rules" \ + "$peer_name" "$rule" "$actual")" + fi + return 0 + fi + + # Count expected rules from assigned rule (includes inheritance) + local block_ports block_ips allow_ports allow_ips block_ports=$(json::count_resolved "$rule" "block_ports") block_ips=$(json::count_resolved "$rule" "block_ips") allow_ports=$(json::count_resolved "$rule" "allow_ports") allow_ips=$(json::count_resolved "$rule" "allow_ips") - expected=$(( (block_ports + block_ips) * 2 + allow_ports + allow_ips )) + local expected=$(( (block_ports + block_ips) * 2 + allow_ports + allow_ips )) + + # Add peer-specific block rules (each ip/port/subnet = 2 rules: NFLOG+DROP) + if block::has_specific_rules "$peer_name" 2>/dev/null; then + while IFS="|" read -r bname btype target port proto; do + [[ -z "$btype" || "$btype" == "full" ]] && continue + (( expected += 2 )) || true + done < <(block::get_rules "$peer_name") + fi - # actual is passed in as $3 — no iptables call here if [[ "$actual" -eq "$expected" ]]; then - test::pass "$(printf "%-28s rule=%-15s fw: %s/%s" "$peer_name" "$rule" "$actual" "$expected")" + test::pass "$(printf "%-28s rule=%-15s fw: %s/%s" \ + "$peer_name" "$rule" "$actual" "$expected")" elif [[ "$actual" -gt "$expected" ]]; then - test::warn "$(printf "%-28s rule=%-15s fw: %s/%s (extra rules)" "$peer_name" "$rule" "$actual" "$expected")" + test::warn "$(printf "%-28s rule=%-15s fw: %s/%s (extra rules)" \ + "$peer_name" "$rule" "$actual" "$expected")" if $fix; then fw::flush_peer "$ip" rule::apply "$rule" "$ip" "$peer_name" + block::restore_rules_for "$peer_name" "$ip" test::pass " Fixed: $peer_name" fi else - test::fail "$(printf "%-28s rule=%-15s fw: %s/%s (missing rules)" "$peer_name" "$rule" "$actual" "$expected")" + test::fail "$(printf "%-28s rule=%-15s fw: %s/%s (missing rules)" \ + "$peer_name" "$rule" "$actual" "$expected")" if $fix; then rule::apply "$rule" "$ip" "$peer_name" + block::restore_rules_for "$peer_name" "$ip" test::pass " Fixed: $peer_name" fi fi + return 0 } function cmd::audit::check_wg() { diff --git a/commands/block.command.sh b/commands/block.command.sh index 35f0609..4798a5e 100644 --- a/commands/block.command.sh +++ b/commands/block.command.sh @@ -27,17 +27,20 @@ function cmd::block::help() { cat < [options] -Block a client entirely or restrict access to specific IPs/ports/subnets. -Block rules are persisted and restored on WireGuard restart. +Block a client entirely or restrict access to specific IPs/ports/subnets/services. +All block rules are persisted and restored on WireGuard restart. +Clients blocked via --ip/--port/--subnet/--service remain in WireGuard +but have specific traffic restricted (shown as 'restricted' in list). Options: - --name Client name (e.g. phone-nuno) - --type Device type (optional, combines with --name) - --ip Block access to specific IP (repeatable) - --subnet Block access to subnet (repeatable) - --port Block specific port, e.g. 10.0.0.210:9000:tcp (repeatable) - --force Skip confirmation prompt - --quiet Suppress output (used by group block) + --name Client name (e.g. phone-nuno) + --type Device type (optional, combines with --name) + --ip Block access to specific IP (repeatable) + --subnet Block access to subnet (repeatable) + --port Block specific port, e.g. 10.0.0.210:9000:tcp (repeatable) + --service Block a named service (e.g. proxmox, truenas:web-ui) (repeatable) + --block-name Optional name for this block rule + --quiet Suppress output (used by group block) Examples: wgctl block --name phone-nuno @@ -45,6 +48,8 @@ Examples: wgctl block --name phone-nuno --ip 10.0.0.210 wgctl block --name phone-nuno --subnet 10.0.0.0/24 wgctl block --name phone-nuno --port 10.0.0.210:9000:tcp + wgctl block --name phone-nuno --service proxmox + wgctl block --name phone-nuno --service truenas:web-ui --block-name "no truenas ui" wgctl ban --name phone-nuno EOF } diff --git a/commands/group.command.sh b/commands/group.command.sh index 78b2d56..35cc8b4 100644 --- a/commands/group.command.sh +++ b/commands/group.command.sh @@ -22,14 +22,16 @@ function cmd::group::help() { cat < [options] -Manage peer groups. Groups are organizational — a peer can belong -to multiple groups. Operations like block/unblock act on all peers in a group. +Manage peer groups. Operations like block/unblock act on all peers in a group. +A peer can belong to multiple groups (M:N relationship). +Group blocks track which groups blocked a peer — unblocking one group won't +unblock a peer still blocked by another group. Subcommands: - list, ls List all groups with status + list, ls List all groups show Show group members and their status add, new, create Create a new group - remove, rm, del Remove a group definition (not the peers) + remove, rm, del Remove a group definition rename Rename a group peer add Add a peer to a group peer remove, peer rm Remove a peer from a group @@ -55,7 +57,6 @@ Examples: wgctl group list wgctl group add --name family --desc "Family devices" wgctl group peer add --name family --peer phone-nuno - wgctl group peer remove --name family --peer phone-nuno wgctl group show --name family wgctl group block --name family wgctl group unblock --name family @@ -147,79 +148,6 @@ function cmd::group::list() { printf "\n" } -# function cmd::group::list() { -# local groups_dir -# groups_dir="$(ctx::groups)" - -# local groups=("${groups_dir}"/*.group) -# if [[ ! -f "${groups[0]}" ]]; then -# log::wg "No groups configured" -# return 0 -# fi - -# log::section "Groups" - -# printf "\n %-20s %-35s %-8s %s\n" "NAME" "DESCRIPTION" "PEERS" "STATUS" -# printf " %s\n" "$(printf '─%.0s' {1..75})" - -# for group_file in "${groups_dir}"/*.group; do -# [[ -f "$group_file" ]] || continue - -# local name desc -# name=$(json::get "$group_file" "name") -# desc=$(json::get "$group_file" "desc") - -# # Count peers -# local peers_list=() -# mapfile -t peers_list < <(json::get "$group_file" "peers") -# # Filter empty entries -# local filtered=() -# for p in "${peers_list[@]:-}"; do -# [[ -n "$p" ]] && filtered+=("$p") -# done -# peers_list=("${filtered[@]:-}") -# local peer_count=${#peers_list[@]} - -# [[ -z "${peers_list[0]}" ]] && peer_count=0 - -# # Check block status -# local blocked=0 total=0 -# for peer_name in "${peers_list[@]}"; do -# [[ -z "$peer_name" ]] && continue -# (( total++ )) || true -# peers::is_blocked "$peer_name" && (( blocked++ )) || true -# done - -# local status_color="" -# local status_str="active" -# if [[ "$total" -gt 0 ]]; then -# if [[ "$blocked" -eq "$total" ]]; then -# status_color="\033[1;31m" -# status_str="blocked" -# elif [[ "$blocked" -gt 0 ]]; then -# status_color="\033[1;33m" -# status_str="blocked (${blocked}/${total})" -# # status_color="\033[1;33m" -# # status_str="${blocked}/${total} blocked" -# else -# status_color="\033[1;32m" -# status_str="active" -# fi -# fi - -# local short_desc="${desc:0:33}" -# [[ ${#desc} -gt 33 ]] && short_desc="${short_desc}..." - -# printf " %-20s %-35s %-8s %b\n" \ -# "$name" \ -# "${short_desc:-—}" \ -# "$peer_count" \ -# "${status_color}${status_str}\033[0m" -# done - -# printf "\n" -# } - # ============================================ # Show # ============================================ @@ -731,17 +659,14 @@ function cmd::group::_unblock_peer() { return 1 fi - log::debug "_unblock_peer: restoring $peer_name" block::restore_peer "$peer_name" "$client_ip" block::remove_file "$peer_name" local rule rule=$(peers::get_meta "$peer_name" "rule") - + [[ -n "$rule" ]] && rule::exists "$rule" && \ rule::apply "$rule" "$client_ip" "$peer_name" - - log::debug "_unblock_peer: done" } # ============================================ diff --git a/commands/inspect.command.sh b/commands/inspect.command.sh index c541cbb..9690dca 100644 --- a/commands/inspect.command.sh +++ b/commands/inspect.command.sh @@ -12,12 +12,18 @@ function cmd::inspect::help() { Usage: wgctl inspect --name [options] wgctl inspect -Show detailed information for a WireGuard client including -status, rule with inheritance, groups, firewall rules, and activity. +Show detailed information for a WireGuard client. + +Sections shown: + Client — IP, type, rule, status, activity + Groups — group memberships + Rule — firewall rule with inheritance tree and service annotations + Peer Blocks — peer-specific restrictions (beyond the assigned rule) + Firewall — active iptables rules with ACCEPT/DROP counts Options: --name Client name (e.g. phone-nuno) - --type Device type — combines with --name (e.g. --name nuno --type phone) + --type Device type — combines with --name --config Also show raw WireGuard client config --qr Also show QR code @@ -112,107 +118,6 @@ function cmd::inspect::_peer_info() { 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() { local name="${1:-}" local rule @@ -252,48 +157,6 @@ function cmd::inspect::_blocks_info() { 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 - -# local rule_file -# rule_file="$(ctx::rule::path "${rule}.rule")" - -# ui::section "Rule: ${rule}" - -# local desc dns_redirect -# desc=$(json::get "$rule_file" "desc") -# dns_redirect=$(json::get "$rule_file" "dns_redirect") -# ui::row "Description" "${desc:-—}" -# ui::row "DNS Redirect" "${dns_redirect:-false}" - -# local allow_ports allow_ips block_ips block_ports -# allow_ports=$(json::get "$rule_file" "allow_ports") -# allow_ips=$(json::get "$rule_file" "allow_ips") -# block_ips=$(json::get "$rule_file" "block_ips") -# block_ports=$(json::get "$rule_file" "block_ports") - -# if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then -# printf " %-20s\n" "Allows:" -# ui::print_list "+" "$allow_ports" -# ui::print_list "+" "$allow_ips" -# else -# ui::row "Allows" "—" -# fi - -# if [[ -n "$block_ips" || -n "$block_ports" ]]; then -# printf " %-20s\n" "Blocks:" -# ui::print_list "-" "$block_ips" -# ui::print_list "-" "$block_ports" -# else -# ui::row "Blocks" "—" -# fi -# } - function cmd::inspect::_group_info() { local name="$1" @@ -401,10 +264,10 @@ function cmd::inspect::run() { log::section "Inspect: ${name}" cmd::inspect::_peer_info "$name" - cmd::inspect::_rule_info "$name" cmd::inspect::_group_info "$name" - cmd::inspect::_firewall_info "$name" + cmd::inspect::_rule_info "$name" cmd::inspect::_blocks_info "$name" + cmd::inspect::_firewall_info "$name" if $show_config; then cmd::inspect::_config "$name" diff --git a/commands/list.command.sh b/commands/list.command.sh index 5992f2f..f9010e5 100644 --- a/commands/list.command.sh +++ b/commands/list.command.sh @@ -33,12 +33,18 @@ Options: --group Filter by group membership --online Show only connected clients --offline Show only disconnected clients - --blocked Show only blocked clients - --restricted Show only restricted clients + --blocked Show only fully blocked clients (removed from WireGuard) + --restricted Show only restricted clients (specific IP/port blocks applied) --allowed Show only unrestricted clients --detailed Show full detail cards for all clients --name Show detail card for a single client +Status values: + online Connected (recent handshake) + offline Not connected + blocked Removed from WireGuard server (wgctl block --name) + restricted In WireGuard but with specific access rules (wgctl block --ip/--service) + Examples: wgctl list wgctl list --type guest @@ -46,6 +52,7 @@ Examples: wgctl list --group family wgctl list --online wgctl list --blocked + wgctl list --restricted wgctl list --detailed wgctl list --name phone-nuno EOF diff --git a/commands/rule.command.sh b/commands/rule.command.sh index e2ae595..a009251 100644 --- a/commands/rule.command.sh +++ b/commands/rule.command.sh @@ -43,6 +43,7 @@ Usage: wgctl rule [options] Manage firewall rules with inheritance support. Rules can extend base rules to compose reusable access policies. +Service names from 'wgctl net' can be used instead of raw IPs/ports. Subcommands: list, ls List all rules @@ -71,6 +72,8 @@ Options for add: --allow-port Allow specific port (repeatable) --block-ip Block IP or subnet (repeatable) --block-port Block specific port (repeatable) + --block-service Block named service — resolved to IP/port at creation (repeatable) + --allow-service Allow named service — resolved to IP/port at creation (repeatable) --dns-redirect Force DNS through Pi-hole Options for update: @@ -95,16 +98,13 @@ Examples: wgctl rule list wgctl rule list --tree wgctl rule list --group "VM Rules" - wgctl rule list --base wgctl rule show --name guest wgctl rule show --name moonlight-02 --resolved + wgctl rule add --name no-proxmox --base --block-service proxmox wgctl rule add --name dev-01 --desc "Dev access" --group "Dev" --extends no-lan - wgctl rule add --name no-npm --base --block-ip 10.0.0.101/32 + wgctl rule add --name restricted-dns --allow-service pihole:dns --block-service pihole wgctl rule update --name user --add-extends no-nginx - wgctl rule update --name dev-01 --allow-ip 10.0.0.50/32 wgctl rule assign --name dev-01 --peer laptop-nuno - wgctl rule unassign --peer laptop-nuno - wgctl rule reapply --name user wgctl rule reapply --all EOF } @@ -845,9 +845,7 @@ function cmd::rule::assign() { local ip ip=$(peers::get_ip "$peer") - log::debug "rule::assign: peer=$peer ip=$ip" [[ -z "$ip" ]] && log::error "Could not resolve IP for: $peer" && return 1 - log::debug "assign: peer=$peer ip=$ip clients=$(ctx::clients)" if [[ -n "$existing_rule" && "$existing_rule" != "$name" ]]; then rule::unapply "$existing_rule" "$ip" diff --git a/commands/shell.command.sh b/commands/shell.command.sh index f87c7e8..8737ba7 100644 --- a/commands/shell.command.sh +++ b/commands/shell.command.sh @@ -18,7 +18,7 @@ function cmd::shell::_is_wgctl_command() { local known=( list add remove rm inspect block unblock rule group audit logs watch fw config qr - rename keys ip service shell help test + rename keys ip net service shell help test ) local c for c in "${known[@]}"; do @@ -85,12 +85,17 @@ function cmd::shell::_banner() { printf " \033[1;37mCommon commands:\033[0m\n" printf " list List all peers\n" printf " list --blocked Show blocked peers\n" + printf " list --restricted Show restricted peers\n" printf " list --rule user Filter by rule\n" printf " inspect --name Full peer details\n" - printf " block/unblock --name Block or restore a peer\n" + printf " block --name Block a peer entirely\n" + printf " block --name --service proxmox Restrict service\n" + printf " unblock --name Restore full access\n" printf " rule list Show firewall rules\n" - printf " rule list --tree Show with inheritance tree\n" + printf " rule list --tree Show with inheritance\n" printf " rule show --name Rule details\n" + printf " net list Show network services\n" + printf " net list --detailed Show services with ports\n" printf " group list Show groups\n" printf " group block --name Block all peers in group\n" printf " logs --follow Live activity log\n" diff --git a/commands/unblock.command.sh b/commands/unblock.command.sh index 1322388..cf1e5e2 100644 --- a/commands/unblock.command.sh +++ b/commands/unblock.command.sh @@ -27,22 +27,25 @@ function cmd::unblock::help() { cat < [options] -Remove block rules for a client. +Remove block rules for a client. Without specific flags, performs a full unblock. +Direct unblock overrides any group blocks. Options: - --name Client name (e.g. phone-nuno) - --type Device type (optional, combines with --name) - --ip Unblock specific IP (repeatable) - --subnet Unblock specific subnet (repeatable) - --port Unblock specific port (repeatable) - --all Remove all block rules for this client - --force Skip confirmation prompt - --quiet Suppress output (used by group unblock) + --name Client name (e.g. phone-nuno) + --type Device type (optional, combines with --name) + --ip Unblock specific IP (repeatable) + --subnet Unblock specific subnet (repeatable) + --port Unblock specific port (repeatable) + --service Unblock a named service (repeatable) + --all Remove all block rules (same as no flags) + --quiet Suppress output (used by group unblock) Examples: wgctl unblock --name phone-nuno wgctl unblock --name nuno --type phone wgctl unblock --name phone-nuno --ip 10.0.0.210 + wgctl unblock --name phone-nuno --service proxmox + wgctl unblock --name phone-nuno --service truenas:web-ui wgctl unban --name phone-nuno EOF } diff --git a/wgctl b/wgctl index c9f9b80..3cbd939 100755 --- a/wgctl +++ b/wgctl @@ -107,21 +107,26 @@ Usage: wgctl [options] add, new Add a new client (--type, --subtype, --rule, --group) remove, rm Remove a client rename, mv Rename a client - list, ls List all clients (--type, --rule, --group, --blocked...) + list, ls List all clients (--type, --rule, --group, --blocked, --restricted...) inspect Show detailed client info (--config, --qr) config Show client config qr Show QR code for a client Access Control: - block, ban Block a client entirely or restrict specific IPs/ports - unblock, unban Restore client access + block, ban Block a client entirely or restrict specific IPs/ports/services + unblock, unban Restore client access (overrides group blocks) rule Manage firewall rules with inheritance - list, show, inspect, add, update, assign, reapply... + list, show, add, update, assign, reapply... Organization: group Manage peer groups list, show, add, block, unblock, watch, logs... + Network Services: + net Manage named network services + list, show, add, rm + Used with block/unblock --service and rule --block-service + Monitoring: watch Live monitor — handshakes, fw drops, blocked attempts logs Show and manage activity logs @@ -140,12 +145,16 @@ Usage: wgctl [options] Common examples: wgctl add --name nuno --type phone --rule admin --group family wgctl list --blocked + wgctl list --restricted wgctl list --rule user --group family wgctl block --name phone-nuno + wgctl block --name phone-nuno --service proxmox wgctl inspect --name phone-nuno wgctl rule list --tree wgctl rule show --name guest wgctl rule add --name dev-01 --group vm-rules --extends no-lan + wgctl net add --name proxmox --ip 10.0.0.100 + wgctl net add --name proxmox:web-ui --port 8006:tcp wgctl group block --name family wgctl logs --follow wgctl logs rotate --days 7