Zuletzt aktiv 1 month ago

gistfile1.txt Originalformat
1#!/usr/bin/env bash
2
3function cmd::audit::on_load() {
4 flag::register --fix
5 flag::register --peer
6 flag::register --type
7}
8
9function cmd::audit::help() {
10 cat <<EOF
11Usage: wgctl audit [options]
12
13Verify that all peers have the correct iptables firewall rules applied.
14Checks expected rule count (including inherited rules) vs actual iptables state.
15
16Options:
17 --peer <name> Audit specific peer only
18 --type <type> Audit peers of specific device type only
19 --fix Attempt to auto-repair missing or extra rules
20
21Output:
22 ✅ pass — peer has correct rule count
23 ❌ fail — peer has missing rules (run --fix to repair)
24 ⚠️ warn — peer has extra rules (e.g. blocked peers with base rules)
25
26Examples:
27 wgctl audit
28 wgctl audit --peer phone-nuno
29 wgctl audit --type guest
30 wgctl audit --fix
31EOF
32}
33
34function cmd::audit::run() {
35 local fix=false peer="" type=""
36
37 while [[ $# -gt 0 ]]; do
38 case "$1" in
39 --fix) fix=true; shift ;;
40 --peer)
41 if [[ -z "${2:-}" ]]; then
42 log::error "Flag --peer requires a value"
43 cmd::audit::help
44 return 1
45 fi
46 peer="$2"
47 shift 2
48 ;;
49 --type) type="$2"; shift 2 ;;
50 --help) cmd::audit::help; return ;;
51 *)
52 log::error "Unknown flag: $1"
53 return 1
54 ;;
55 esac
56 done
57
58 test::reset
59 log::section "WireGuard Audit"
60
61 # Precompute iptables counts via single Python call
62 declare -A peer_fw_counts
63 while IFS=":" read -r peer_name count; do
64 [[ -n "$peer_name" ]] && peer_fw_counts["$peer_name"]="$count"
65 done < <(json::audit_fw_counts "$(ctx::clients)")
66
67 test::section "Peer Rules"
68 while IFS= read -r peer_name; do
69 [[ -n "$peer" && "$peer_name" != "$peer" ]] && continue
70 [[ -n "$type" && "$peer_name" != "${type}-"* ]] && continue
71 cmd::audit::check_peer "$peer_name" "$fix" "${peer_fw_counts[$peer_name]:-0}"
72 done < <(peers::all)
73
74 test::section "WireGuard Server"
75 cmd::audit::check_wg "$fix" "$peer" "$type"
76
77 test::section "Rules"
78 cmd::audit::check_rules
79
80 test::summary
81}
82
83function cmd::audit::check_peer() {
84 local peer_name="$1"
85 local fix="$2"
86 local actual="${3:-0}"
87 local ip rule
88
89 ip=$(peers::get_ip "$peer_name")
90 rule=$(peers::get_meta "$peer_name" "rule")
91
92 # Check rule assigned
93 if [[ -z "$rule" ]]; then
94 test::warn "$peer_name — no rule assigned (effective: $(peers::default_rule "$peer_name"))"
95 return
96 fi
97
98 # Check rule exists
99 if ! rule::exists "$rule"; then
100 test::fail "$peer_name — assigned rule '$rule' does not exist"
101 return
102 fi
103
104 # Count expected iptables rules from rule file
105 local rule_file
106 rule_file="$(ctx::rule::path "${rule}.rule")"
107 local block_ports block_ips allow_ports allow_ips expected
108 block_ports=$(json::count_resolved "$rule" "block_ports")
109 block_ips=$(json::count_resolved "$rule" "block_ips")
110 allow_ports=$(json::count_resolved "$rule" "allow_ports")
111 allow_ips=$(json::count_resolved "$rule" "allow_ips")
112 expected=$(( (block_ports + block_ips) * 2 + allow_ports + allow_ips ))
113
114 # actual is passed in as $3 — no iptables call here
115 if [[ "$actual" -eq "$expected" ]]; then
116 test::pass "$(printf "%-28s rule=%-15s fw: %s/%s" "$peer_name" "$rule" "$actual" "$expected")"
117 elif [[ "$actual" -gt "$expected" ]]; then
118 test::warn "$(printf "%-28s rule=%-15s fw: %s/%s (extra rules)" "$peer_name" "$rule" "$actual" "$expected")"
119 if $fix; then
120 fw::flush_peer "$ip"
121 rule::apply "$rule" "$ip" "$peer_name"
122 test::pass " Fixed: $peer_name"
123 fi
124 else
125 test::fail "$(printf "%-28s rule=%-15s fw: %s/%s (missing rules)" "$peer_name" "$rule" "$actual" "$expected")"
126 if $fix; then
127 rule::apply "$rule" "$ip" "$peer_name"
128 test::pass " Fixed: $peer_name"
129 fi
130 fi
131}
132
133function cmd::audit::check_wg() {
134 local fix="$1" filter_peer="$2" filter_type="$3"
135 local wg_peers
136 wg_peers=$(wg show wg0 peers 2>/dev/null)
137
138 while IFS= read -r peer_name; do
139 [[ -n "$filter_peer" && "$peer_name" != "$filter_peer" ]] && continue
140 [[ -n "$filter_type" && "$peer_name" != "${filter_type}-"* ]] && continue
141
142 local pub_key
143 pub_key=$(keys::public "$peer_name" 2>/dev/null)
144 [[ -z "$pub_key" ]] && test::fail "$peer_name — missing public key" && continue
145
146 if peers::is_blocked "$peer_name"; then
147 if echo "$wg_peers" | grep -q "$pub_key"; then
148 test::fail "$peer_name — blocked but still in wg0"
149 $fix && peers::remove_from_server "$peer_name" && peers::reload
150 else
151 test::pass "$peer_name — correctly blocked (not in wg0)"
152 fi
153 else
154 if echo "$wg_peers" | grep -q "$pub_key"; then
155 test::pass "$peer_name — present in wg0"
156 else
157 test::fail "$peer_name — missing from wg0"
158 if $fix; then
159 local ip
160 ip=$(peers::get_ip "$peer_name")
161 peers::add_to_server "$peer_name" "$pub_key" "$ip"
162 peers::reload
163 fi
164 fi
165 fi
166 done < <(peers::all)
167}
168
169function cmd::audit::check_rules() {
170 local rules_dir
171 rules_dir="$(ctx::rules)"
172
173 # Precompute peer counts
174 declare -A rule_counts
175 while IFS= read -r peer_name; do
176 local r
177 r=$(peers::get_meta "$peer_name" "rule")
178 [[ -z "$r" ]] && continue
179 rule_counts["$r"]=$(( ${rule_counts["$r"]:-0} + 1 ))
180 done < <(peers::all)
181
182 for rule_file in "${rules_dir}"/*.rule; do
183 [[ -f "$rule_file" ]] || continue
184 local name
185 name=$(json::get "$rule_file" "name")
186 local count=${rule_counts["$name"]:-0}
187 test::pass "Rule '${name}' — ${count} peers assigned"
188 done
189}