Dernière activité 1 month ago

audit.command.sh Brut
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 and peer-specific blocks)
15vs actual iptables state.
16
17Options:
18 --peer <name> Audit specific peer only
19 --type <type> Audit peers of specific device type only
20 --fix Attempt to auto-repair missing or extra rules
21
22Output:
23 ✅ pass — peer has correct rule count
24 ❌ fail — peer has missing rules (run --fix to repair)
25 ⚠️ warn — peer has extra rules or configuration issues
26
27Notes:
28 Fully blocked peers (removed from WireGuard) show 0 expected fw rules.
29 Restricted peers (peer-specific blocks) have their block rules included
30 in the expected count.
31
32Examples:
33 wgctl audit
34 wgctl audit --peer phone-nuno
35 wgctl audit --type guest
36 wgctl audit --fix
37EOF
38}
39
40function cmd::audit::run() {
41 local fix=false peer="" type=""
42
43 while [[ $# -gt 0 ]]; do
44 case "$1" in
45 --fix) fix=true; shift ;;
46 --peer)
47 if [[ -z "${2:-}" ]]; then
48 log::error "Flag --peer requires a value"
49 cmd::audit::help
50 return 1
51 fi
52 peer="$2"
53 shift 2
54 ;;
55 --type) type="$2"; shift 2 ;;
56 --help) cmd::audit::help; return ;;
57 *)
58 log::error "Unknown flag: $1"
59 return 1
60 ;;
61 esac
62 done
63
64 test::reset
65 log::section "WireGuard Audit"
66
67 # Precompute iptables counts via single Python call
68 declare -A peer_fw_counts
69 while IFS=":" read -r peer_name count; do
70 [[ -n "$peer_name" ]] && peer_fw_counts["$peer_name"]="$count"
71 done < <(json::audit_fw_counts "$(ctx::clients)")
72
73 test::section "Peer Rules"
74 while IFS= read -r peer_name; do
75 [[ -n "$peer" && "$peer_name" != "$peer" ]] && continue
76 [[ -n "$type" && "$peer_name" != "${type}-"* ]] && continue
77 cmd::audit::check_peer "$peer_name" "$fix" "${peer_fw_counts[$peer_name]:-0}"
78 done < <(peers::all)
79
80 test::section "WireGuard Server"
81 cmd::audit::check_wg "$fix" "$peer" "$type"
82
83 test::section "Rules"
84 cmd::audit::check_rules
85
86 test::summary
87}
88
89function cmd::audit::check_peer() {
90 local peer_name="$1" fix="$2" actual="${3:-0}"
91 local ip rule
92
93 ip=$(peers::get_ip "$peer_name")
94 rule=$(peers::get_meta "$peer_name" "rule")
95
96 if [[ -z "$rule" ]]; then
97 test::warn "$peer_name — no rule assigned (effective: $(peers::default_rule "$peer_name"))"
98 return 0
99 fi
100
101 if ! rule::exists "$rule"; then
102 test::fail "$peer_name — assigned rule '$rule' does not exist"
103 return 0
104 fi
105
106 # Blocked peers have no fw rules (removed from wg0)
107 if block::is_blocked "$peer_name" 2>/dev/null; then
108 if [[ "$actual" -eq 0 ]]; then
109 test::pass "$(printf "%-28s rule=%-15s blocked (no fw rules)" \
110 "$peer_name" "$rule")"
111 else
112 test::warn "$(printf "%-28s rule=%-15s blocked but has %s fw rules" \
113 "$peer_name" "$rule" "$actual")"
114 fi
115 return 0
116 fi
117
118 # Count expected rules from assigned rule (includes inheritance)
119 local block_ports block_ips allow_ports allow_ips
120 block_ports=$(json::count_resolved "$rule" "block_ports")
121 block_ips=$(json::count_resolved "$rule" "block_ips")
122 allow_ports=$(json::count_resolved "$rule" "allow_ports")
123 allow_ips=$(json::count_resolved "$rule" "allow_ips")
124 local expected=$(( (block_ports + block_ips) * 2 + allow_ports + allow_ips ))
125
126 # Add peer-specific block rules (each ip/port/subnet = 2 rules: NFLOG+DROP)
127 if block::has_specific_rules "$peer_name" 2>/dev/null; then
128 while IFS="|" read -r bname btype target port proto; do
129 [[ -z "$btype" || "$btype" == "full" ]] && continue
130 (( expected += 2 )) || true
131 done < <(block::get_rules "$peer_name")
132 fi
133
134 if [[ "$actual" -eq "$expected" ]]; then
135 test::pass "$(printf "%-28s rule=%-15s fw: %s/%s" \
136 "$peer_name" "$rule" "$actual" "$expected")"
137 elif [[ "$actual" -gt "$expected" ]]; then
138 test::warn "$(printf "%-28s rule=%-15s fw: %s/%s (extra rules)" \
139 "$peer_name" "$rule" "$actual" "$expected")"
140 if $fix; then
141 fw::flush_peer "$ip"
142 rule::apply "$rule" "$ip" "$peer_name"
143 block::restore_rules_for "$peer_name" "$ip"
144 test::pass " Fixed: $peer_name"
145 fi
146 else
147 test::fail "$(printf "%-28s rule=%-15s fw: %s/%s (missing rules)" \
148 "$peer_name" "$rule" "$actual" "$expected")"
149 if $fix; then
150 rule::apply "$rule" "$ip" "$peer_name"
151 block::restore_rules_for "$peer_name" "$ip"
152 test::pass " Fixed: $peer_name"
153 fi
154 fi
155 return 0
156}
157
158function cmd::audit::check_wg() {
159 local fix="$1" filter_peer="$2" filter_type="$3"
160 local wg_peers
161 wg_peers=$(wg show wg0 peers 2>/dev/null)
162
163 while IFS= read -r peer_name; do
164 [[ -n "$filter_peer" && "$peer_name" != "$filter_peer" ]] && continue
165 [[ -n "$filter_type" && "$peer_name" != "${filter_type}-"* ]] && continue
166
167 local pub_key
168 pub_key=$(keys::public "$peer_name" 2>/dev/null)
169 [[ -z "$pub_key" ]] && test::fail "$peer_name — missing public key" && continue
170
171 if peers::is_blocked "$peer_name"; then
172 if echo "$wg_peers" | grep -q "$pub_key"; then
173 test::fail "$peer_name — blocked but still in wg0"
174 $fix && peers::remove_from_server "$peer_name" && peers::reload
175 else
176 test::pass "$peer_name — correctly blocked (not in wg0)"
177 fi
178 else
179 if echo "$wg_peers" | grep -q "$pub_key"; then
180 test::pass "$peer_name — present in wg0"
181 else
182 test::fail "$peer_name — missing from wg0"
183 if $fix; then
184 local ip
185 ip=$(peers::get_ip "$peer_name")
186 peers::add_to_server "$peer_name" "$pub_key" "$ip"
187 peers::reload
188 fi
189 fi
190 fi
191 done < <(peers::all)
192}
193
194function cmd::audit::check_rules() {
195 local rules_dir
196 rules_dir="$(ctx::rules)"
197
198 # Precompute peer counts
199 declare -A rule_counts
200 while IFS= read -r peer_name; do
201 local r
202 r=$(peers::get_meta "$peer_name" "rule")
203 [[ -z "$r" ]] && continue
204 rule_counts["$r"]=$(( ${rule_counts["$r"]:-0} + 1 ))
205 done < <(peers::all)
206
207 for rule_file in "${rules_dir}"/*.rule; do
208 [[ -f "$rule_file" ]] || continue
209 local name
210 name=$(json::get "$rule_file" "name")
211 local count=${rule_counts["$name"]:-0}
212 test::pass "Rule '${name}' — ${count} peers assigned"
213 done
214}