Última atividade 1 month ago

gistfile1.txt Bruto
1#!/usr/bin/env bash
2
3# ============================================
4# Lifecycle
5# ============================================
6
7function cmd::watch::on_load() {
8 flag::register --type
9 flag::register --name
10 flag::register --blocked
11 flag::register --restricted
12 flag::register --allowed
13}
14
15# ============================================
16# Help
17# ============================================
18
19function cmd::watch::help() {
20 cat <<EOF
21Usage: wgctl watch [options]
22
23Live monitor of WireGuard activity.
24Shows handshakes from active clients and connection attempts from blocked clients.
25
26Options:
27 --type <type> Filter by device type
28 --name <name> Filter by client name
29 --allowed Show only allowed client handshakes
30 --restricted Show only restricted client events
31 --blocked Show only blocked client attempts
32
33Examples:
34 wgctl watch
35 wgctl watch --blocked
36 wgctl watch --allowed
37 wgctl watch --type phone
38 wgctl watch --name phone-nuno
39EOF
40}
41
42# ============================================
43# Helpers
44# ============================================
45
46function cmd::watch::format_event() {
47 local ts="$1"
48 local client="$2"
49 local endpoint="$3"
50 local event="$4"
51 local status="$5"
52
53 local event_color
54 case "$event" in
55 attempt) event_color="\033[1;31m" ;; # red
56 handshake) event_color="\033[1;32m" ;; # green
57 *) event_color="\033[0;37m" ;; # grey
58 esac
59
60 local status_str=""
61 if [[ -n "$status" ]]; then
62 case "$status" in
63 blocked) status_str=" \033[1;31mblocked\033[0m" ;;
64 allowed) status_str=" \033[1;32mallowed\033[0m" ;;
65 *) status_str="" ;;
66 esac
67 fi
68
69 printf " %-20s %-25s %-20s ${event_color}%-12s\033[0m%b\n" \
70 "$ts" "$client" "${endpoint:-—}" "$event" "$status_str"
71}
72
73function cmd::watch::header() {
74 log::section "wgctl — Live Monitor (Ctrl+C to stop)"
75 printf "\n %-20s %-25s %-20s %-12s %s\n" \
76 "TIME" "CLIENT" "ENDPOINT" "EVENT" "STATUS"
77 printf " %s\n\n" "$(printf '─%.0s' {1..85})"
78}
79
80# ============================================
81# Handshake Poller
82# ============================================
83
84declare -A _WATCH_LAST_HANDSHAKES=()
85
86function cmd::watch::poll_handshakes() {
87 local filter_name="$1"
88 local filter_type="$2"
89 local allowed_only="$3"
90
91 while IFS= read -r line; do
92 local public_key ts
93 public_key=$(echo "$line" | awk '{print $1}')
94 ts=$(echo "$line" | awk '{print $2}')
95
96 [[ -z "$ts" || "$ts" == "0" ]] && continue
97
98 # Find client by public key
99 local client_name=""
100 for conf in "$(ctx::clients)"/*.conf; do
101 [[ -f "$conf" ]] || continue
102 local name
103 name=$(basename "$conf" .conf)
104 local key
105 key=$(keys::public "$name" 2>/dev/null || echo "")
106 if [[ "$key" == "$public_key" ]]; then
107 client_name="$name"
108 break
109 fi
110 done
111
112 [[ -z "$client_name" ]] && continue
113
114 # Apply filters
115 [[ -n "$filter_name" && "$client_name" != "$filter_name" ]] && continue
116
117 if [[ -n "$filter_type" ]]; then
118 local ip
119 ip=$(grep "^Address" "$(ctx::clients)/${client_name}.conf" | awk '{print $3}' | cut -d'/' -f1)
120 local subnet
121 subnet=$(config::subnet_for "$filter_type")
122 string::starts_with "$ip" "$subnet" || continue
123 fi
124
125 # Only emit if handshake is new
126 local safe_key
127 safe_key=$(echo "$public_key" | md5sum | cut -d' ' -f1)
128 local prev_ts_file="/tmp/wgctl_hs_${safe_key}"
129 local prev_ts="0"
130 [[ -f "$prev_ts_file" ]] && prev_ts=$(cat "$prev_ts_file")
131 if [[ "$ts" != "$prev_ts" ]]; then
132 echo "$ts" > "$prev_ts_file"
133
134 local formatted_ts
135 formatted_ts=$(fmt::datetime "$ts")
136
137 local endpoint
138 endpoint=$(monitor::endpoint_for_key "$public_key")
139
140 cmd::watch::format_event \
141 "$formatted_ts" "$client_name" "${endpoint:-—}" "handshake" "allowed"
142 fi
143
144 done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
145}
146
147# ============================================
148# Event Tailer
149# ============================================
150
151function cmd::watch::tail_events() {
152 local filter_name="$1"
153 local filter_type="$2"
154 local blocked_only="$3"
155 local restricted_only="$4"
156 local allowed_only="$5"
157
158 declare -A _WATCH_LAST_ATTEMPT=()
159
160 tail -f "$(ctx::root)/daemon/events.log" 2>/dev/null | while IFS= read -r line; do
161 [[ -z "$line" ]] && continue
162
163 local event_data
164 event_data=$(json::parse_event "$line")
165
166 [[ -z "$event_data" ]] && continue
167
168 local ts client endpoint event
169 IFS="|" read -r ts client endpoint event <<< "$event_data"
170
171 if $restricted_only; then
172 local conf
173 conf="$(ctx::clients)/${client}.conf"
174 [[ -f "$conf" ]] || continue
175 cmd::list::is_restricted "$client" || continue
176 fi
177
178 # Apply filters
179 [[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
180
181 if [[ -n "$filter_type" ]]; then
182 local conf
183 conf="$(ctx::clients)/${client}.conf"
184 [[ -f "$conf" ]] || continue
185 local ip
186 ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
187 local subnet
188 subnet=$(config::subnet_for "$filter_type")
189 string::starts_with "$ip" "$subnet" || continue
190 fi
191
192 # Filter by status
193 if $allowed_only && [[ "$event" != "handshake" ]]; then
194 continue
195 fi
196
197 if $restricted_only; then
198 local conf
199 conf="$(ctx::clients)/${client}.conf"
200 [[ -f "$conf" ]] || continue
201 cmd::list::is_restricted "$client" || continue
202 fi
203
204 local formatted_ts
205 formatted_ts=$(fmt::datetime_iso "$ts")
206
207 # Before printing the event
208 local now
209 now=$(date +%s)
210 local safe_client="${client//[-.]/_}"
211 local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}"
212 local diff=$(( now - last ))
213 if (( diff < 30 )); then
214 continue
215 fi
216 _WATCH_LAST_ATTEMPT[$safe_client]="$now"
217
218 cmd::watch::format_event \
219 "$formatted_ts" "$client" "${endpoint:-—}" "$event" "blocked"
220 done
221}
222
223# ============================================
224# Run
225# ============================================
226
227function cmd::watch::run() {
228 local filter_name=""
229 local filter_type=""
230 local blocked_only=false
231 local allowed_only=false
232 local restricted_only=false
233
234 # Clean up any stale temp files from previous runs
235 rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true
236
237 while [[ $# -gt 0 ]]; do
238 case "$1" in
239 --name) filter_name="$2"; shift 2 ;;
240 --type) filter_type="$2"; shift 2 ;;
241 --blocked) blocked_only=true; shift ;;
242 --allowed) allowed_only=true; shift ;;
243 --restricted) restricted_only=true; shift ;;
244 --help) cmd::watch::help; return ;;
245 *)
246 log::error "Unknown flag: $1"
247 cmd::watch::help
248 return 1
249 ;;
250 esac
251 done
252
253 cmd::watch::header
254
255 # Start event tailer in background unless --blocked not set
256 if ! $blocked_only && ! $restricted_only; then
257 # Poll handshakes every 5 seconds in background
258 (
259 while true; do
260 cmd::watch::poll_handshakes "$filter_name" "$filter_type" "$allowed_only"
261 sleep 5
262 done
263 ) &
264 local poller_pid=$!
265 fi
266
267 # Tail events log for blocked attempts
268 cmd::watch::tail_events "$filter_name" "$filter_type" "$blocked_only" "$restricted_only" "$allowed_only" &
269 local tailer_pid=$!
270
271 # Trap Ctrl+C to clean up background processes
272 trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; echo ''; exit 0" INT TERM
273
274 # Keep main process alive
275 wait
276}
277