Naposledy aktivní 1 month ago

Revize 8fd68a3fa89245e41610ce947b13e6629db22767

gistfile1.txt Raw
1#!/usr/bin/env bash
2
3# ============================================
4# Lifecycle
5# ============================================
6
7function cmd::block::on_load() {
8 flag::register --name
9 flag::register --type
10 flag::register --force
11 flag::register --quiet
12 flag::register --ip
13 flag::register --port
14 flag::register --proto
15 flag::register --subnet
16 flag::register --block-name
17
18 # System - NET Services
19 flag::register --service
20}
21
22# ============================================
23# Help
24# ============================================
25
26function cmd::block::help() {
27 cat <<EOF
28Usage: wgctl block --name <name> [options]
29
30Block a client entirely or restrict access to specific IPs/ports/subnets/services.
31All block rules are persisted and restored on WireGuard restart.
32Clients blocked via --ip/--port/--subnet/--service remain in WireGuard
33but have specific traffic restricted (shown as 'restricted' in list).
34
35Options:
36 --name <name> Client name (e.g. phone-nuno)
37 --type <type> Device type (optional, combines with --name)
38 --ip <ip> Block access to specific IP (repeatable)
39 --subnet <cidr> Block access to subnet (repeatable)
40 --port <ip:port:proto> Block specific port, e.g. 10.0.0.210:9000:tcp (repeatable)
41 --service <name> Block a named service (e.g. proxmox, truenas:web-ui) (repeatable)
42 --block-name <name> Optional name for this block rule
43 --quiet Suppress output (used by group block)
44
45Examples:
46 wgctl block --name phone-nuno
47 wgctl block --name nuno --type phone
48 wgctl block --name phone-nuno --ip 10.0.0.210
49 wgctl block --name phone-nuno --subnet 10.0.0.0/24
50 wgctl block --name phone-nuno --port 10.0.0.210:9000:tcp
51 wgctl block --name phone-nuno --service proxmox
52 wgctl block --name phone-nuno --service truenas:web-ui --block-name "no truenas ui"
53 wgctl ban --name phone-nuno
54EOF
55}
56
57# ============================================
58# Block Run
59# ============================================
60
61function cmd::block::run() {
62 local name="" type="" block_name=""
63 local ips=() subnets=() ports=() services=()
64 local quiet=false force=false
65 local changed=false
66
67 while [[ $# -gt 0 ]]; do
68 case "$1" in
69 --name) name="$2"; shift 2 ;;
70 --type) type="$2"; shift 2 ;;
71 --ip) ips+=("$2"); shift 2 ;;
72 --block-name) block_name="$2"; shift 2 ;;
73 --service) services+=("$2"); shift 2 ;;
74 --force) force=true; shift ;;
75 --quiet) quiet=true; shift ;;
76 --subnet) subnets+=("$2"); shift 2 ;;
77 --port) ports+=("$2"); shift 2 ;;
78 --help) cmd::block::help; return ;;
79 *)
80 log::error "Unknown flag: $1"
81 cmd::block::help
82 return 1 ;;
83 esac
84 done
85
86 [[ -z "$name" ]] && log::error "Missing required flag: --name" && \
87 cmd::block::help && return 1
88
89 name=$(peers::resolve_and_require "$name" "$type") || return 1
90
91 local client_ip
92 client_ip=$(peers::get_ip "$name") || return 1
93
94 # Full block if no specific targets
95 if [[ ${#ips[@]} -eq 0 && ${#ports[@]} -eq 0 && \
96 ${#subnets[@]} -eq 0 && ${#services[@]} -eq 0 ]]; then
97 if peers::is_blocked "$name"; then
98 log::wg_warning "Client is already blocked: ${name}"
99 return 0
100 fi
101 monitor::update_endpoint_cache
102 cmd::block::_block_all "$name" "$client_ip" "$quiet"
103 return 0
104 fi
105
106 # Specific rules — check if already fully blocked
107 if block::has_file "$name"; then
108 local direct
109 direct=$(block::is_blocked_direct "$name")
110 if [[ "$direct" == "true" ]]; then
111 log::wg_warning "${name} is fully blocked — unblock first to add specific rules"
112 return 1
113 fi
114 fi
115
116 # Block specific IPs
117 for ip in "${ips[@]}"; do
118 ip::require_valid "$ip"
119 fw::block_ip "$client_ip" "$ip"
120 block::add_rule "$name" "$client_ip" "ip" "${block_name:-}" "$ip"
121 $quiet || log::wg_success "${ip} has been blocked for ${name}"
122 done
123
124 # Block specific subnets
125 for subnet in "${subnets[@]}"; do
126 ip::require_valid "$subnet"
127 fw::block_subnet "$client_ip" "$subnet"
128 block::add_rule "$name" "$client_ip" "subnet" "${block_name:-}" "$subnet"
129 $quiet || log::wg_success "${subnet} has been blocked for ${name}"
130 done
131
132 # Block specific ports
133 for entry in "${ports[@]}"; do
134 local b_target b_port b_proto
135 IFS=":" read -r b_target b_port b_proto <<< "$entry"
136 ip::require_valid "$b_target"
137 fw::block_port "$client_ip" "$b_target" "$b_port" "${b_proto:-tcp}"
138 block::add_rule "$name" "$client_ip" "port" "${block_name:-}" \
139 "$b_target" "$b_port" "${b_proto:-tcp}"
140 $quiet || log::wg_success "${client_ip}:${b_port}:${b_proto:-tcp} has been blocked for ${name}"
141 done
142
143 # Block services
144 for svc in "${services[@]}"; do
145 local resolved_lines=()
146 mapfile -t resolved_lines < <(net::resolve "$svc" 2>/dev/null)
147 if [[ ${#resolved_lines[@]} -eq 0 ]]; then
148 log::error "Service not found or has no ports: ${svc}"
149 return 1
150 fi
151
152 # Check if already blocked
153 local already_blocked=true
154 for resolved in "${resolved_lines[@]}"; do
155 if [[ "$resolved" == *:*:* ]]; then
156 local b_ip b_port b_proto
157 IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
158 fw::has_block_rule "$client_ip" "$b_ip" "$b_port" "$b_proto" 2>/dev/null || \
159 { already_blocked=false; break; }
160 else
161 fw::has_block_rule "$client_ip" "$resolved" 2>/dev/null || \
162 { already_blocked=false; break; }
163 fi
164 done
165
166 if $already_blocked; then
167 $quiet || log::wg_warning "${svc} is already blocked for ${name}"
168 continue
169 fi
170
171 for resolved in "${resolved_lines[@]}"; do
172 if [[ "$resolved" == *:*:* ]]; then
173 local b_ip b_port b_proto
174 IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
175 fw::block_port "$client_ip" "$b_ip" "$b_port" "$b_proto"
176 block::add_rule "$name" "$client_ip" "port" "$svc" \
177 "$b_ip" "$b_port" "$b_proto"
178 else
179 fw::block_ip "$client_ip" "$resolved"
180 block::add_rule "$name" "$client_ip" "ip" "$svc" "$resolved"
181 fi
182 done
183
184 changed=true
185 $quiet || log::wg_success "${svc} has been blocked for ${name}"
186 done
187
188 [[ ${#ips[@]} -gt 0 || ${#ports[@]} -gt 0 || \
189 ${#subnets[@]} -gt 0 ]] && changed=true
190
191 # Reapply in correct order: rule ACCEPT first, then peer DROP rules
192 # Only reorder if rules were actually added
193 if $changed; then
194 local peer_rule
195 peer_rule=$(peers::get_meta "$name" "rule")
196 if [[ -n "$peer_rule" ]] && rule::exists "$peer_rule"; then
197 fw::flush_peer "$client_ip"
198 rule::apply "$peer_rule" "$client_ip" "$name"
199 block::restore_rules_for "$name" "$client_ip"
200 fi
201 fi
202
203 return 0
204}
205
206function cmd::block::_get_endpoint() {
207 local name="$1" public_key="$2"
208 local endpoint
209 endpoint=$(monitor::endpoint_for_key "$public_key")
210 if [[ -z "$endpoint" || "$endpoint" == "(none)" ]]; then
211 endpoint=$(monitor::get_cached_endpoint "$name")
212 fi
213 echo "$endpoint"
214}
215
216function cmd::block::_block_all() {
217 local name="${1:?name required}"
218 local client_ip="${2:?client_ip required}"
219 local quiet="${3:-false}"
220
221 # Apply fw rules and remove from server
222 block::apply_full "$name" "$client_ip"
223
224 # Mark as directly blocked
225 block::set_direct "$name" "$client_ip" "true"
226
227 $quiet || log::wg_success "${name} has been blocked."
228}