Utoljára aktív 1 month ago

Revízió 78501b63643d528d702352c252443535a4823b08

rule.module.sh Eredeti
1#!/usr/bin/env bash
2
3# ============================================
4# Rule File Parsing
5# ============================================
6
7function rule::is_base() {
8 local name="${1:-}"
9 [[ -f "$(ctx::rules::base)/${name}.rule" ]]
10}
11
12function rule::exists() {
13 local name="${1:-}"
14 local path
15 path=$(json::find_rule_file "$(ctx::rules)" "$name")
16 [[ -n "$path" ]]
17}
18
19function rule::require_assignable() {
20 local name="${1:-}"
21 if rule::is_base "$name"; then
22 log::error "Cannot assign base rule '${name}' — base rules cannot be assigned directly"
23 return 1
24 fi
25}
26
27function rule::require_exists() {
28 local name="${1:-}"
29 if ! rule::exists "$name"; then
30 log::error "Rule not found: ${name}"
31 return 1
32 fi
33}
34
35function rule::get() {
36 local name="${1:-}" key="${2:-}"
37 json::rule_resolve_field "$(ctx::rules)" "$name" "$key"
38}
39
40function rule::get_own() {
41 local name="${1:-}" key="${2:-}"
42 local file
43 file=$(rule::path "$name") || return 0
44 json::get_raw "$file" "$key"
45}
46
47function rule::get_resolved() {
48 local name="${1:-}"
49 json::rule_resolve "$(ctx::rules)" "$name"
50}
51
52function rule::path() {
53 local name="${1:-}"
54 local path
55 path=$(json::find_rule_file "$(ctx::rules)" "$name")
56 [[ -n "$path" ]] && echo "$path" || return 1
57}
58
59function rule::get_all() {
60 local name="${1:-}"
61 rule::get_resolved "$name"
62}
63
64function rule::is_applied() {
65 local rule_name="${1:-}" client_ip="${2:-}"
66
67 local first_port
68 first_port=$(rule::get "$rule_name" "block_ports" | head -1)
69 if [[ -n "$first_port" ]]; then
70 local target port proto
71 IFS=":" read -r target port proto <<< "$first_port"
72 proto="${proto:-tcp}"
73 fw::has_block_rule "$client_ip" "$target" "$proto" "$port"
74 return $?
75 fi
76
77 local first_ip
78 first_ip=$(rule::get "$rule_name" "block_ips" | head -1)
79 if [[ -n "$first_ip" ]]; then
80 fw::has_block_rule "$client_ip" "$first_ip"
81 return $?
82 fi
83
84 local first_allow
85 first_allow=$(rule::get "$rule_name" "allow_ports" | head -1)
86 if [[ -n "$first_allow" ]]; then
87 local target port proto
88 IFS=":" read -r target port proto <<< "$first_allow"
89 proto="${proto:-tcp}"
90 fw::_forward_exists -s "$client_ip" -d "$target" \
91 -p "$proto" --dport "$port" -j ACCEPT
92 return $?
93 fi
94
95 return 1
96}
97
98# ============================================
99# Rule Application
100# ============================================
101
102function rule::apply() {
103 local rule_name="${1:?rule_name required}"
104 local client_ip="${2:?client_ip required}"
105 local peer_name="${3:-}"
106
107 rule::require_exists "$rule_name" || return 1
108
109 if [[ -z "$peer_name" ]]; then
110 peer_name=$(peers::find_by_ip "$client_ip")
111 fi
112
113 log::debug "rule::apply: peer_name=$peer_name ip=$client_ip"
114
115 # Check if already applied
116 if rule::is_applied "$rule_name" "$client_ip"; then
117 log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
118 [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" "$rule_name"
119 return 0
120 fi
121
122 # Process block_ips
123 while IFS= read -r block_ip; do
124 [[ -z "$block_ip" ]] && continue
125 fw::block_ip "$client_ip" "$block_ip"
126 done < <(rule::get "$rule_name" "block_ips")
127
128 # Process block_ports
129 while IFS= read -r entry; do
130 [[ -z "$entry" ]] && continue
131 local target port proto
132 IFS=":" read -r target port proto <<< "$entry"
133 proto="${proto:-tcp}"
134 fw::block_port "$client_ip" "$target" "$port" "$proto"
135 done < <(rule::get "$rule_name" "block_ports")
136
137 # Process allow_ips (inserted before blocks)
138 while IFS= read -r allow_ip; do
139 [[ -z "$allow_ip" ]] && continue
140 fw::allow_ip "$client_ip" "$allow_ip"
141 done < <(rule::get "$rule_name" "allow_ips")
142
143 # Process allow_ports (highest priority)
144 while IFS= read -r entry; do
145 [[ -z "$entry" ]] && continue
146 local target port proto
147 IFS=":" read -r target port proto <<< "$entry"
148 proto="${proto:-tcp}"
149 fw::allow_port "$client_ip" "$target" "$port" "$proto"
150 done < <(rule::get "$rule_name" "allow_ports")
151
152 # Persist rule assignment
153 [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" "$rule_name"
154
155 # DNS redirect
156 local dns_redirect
157 dns_redirect=$(rule::get "$rule_name" "dns_redirect")
158 if [[ "$dns_redirect" == "true" ]]; then
159 local peer_subnet
160 peer_subnet=$(peers::get_ip "$peer_name" | cut -d'.' -f1-3)
161 if ! fw::_nat_exists -i wg0 -s "${peer_subnet}.0/24" \
162 -p udp --dport 53 -j DNAT \
163 --to-destination "$(config::dns):53" 2>/dev/null; then
164 rule::apply_dns_redirect "${peer_subnet}.0/24"
165 log::debug "dns_redirect: applied for ${peer_subnet}.0/24"
166 else
167 log::debug "dns_redirect: already applied for ${peer_subnet}.0/24"
168 fi
169 fi
170
171 log::debug "Applied rule '${rule_name}' to: ${client_ip}"
172}
173
174function rule::unapply() {
175 local rule_name="${1:-}" client_ip="${2:-}"
176
177 rule::require_exists "$rule_name" || return 1
178
179 local peer_name
180 peer_name=$(peers::find_by_ip "$client_ip")
181
182 # Remove allow_ports first (reverse order of apply)
183 while IFS= read -r entry; do
184 [[ -z "$entry" ]] && continue
185 local target port proto
186 IFS=":" read -r target port proto <<< "$entry"
187 proto="${proto:-tcp}"
188 fw::unallow_port "$client_ip" "$target" "$port" "$proto"
189 done < <(rule::get "$rule_name" "allow_ports")
190
191 # Remove allow_ips
192 while IFS= read -r allow_ip; do
193 [[ -z "$allow_ip" ]] && continue
194 if [[ "$allow_ip" == *"/"* ]]; then
195 fw::unallow_subnet "$client_ip" "$allow_ip"
196 else
197 fw::unallow_ip "$client_ip" "$allow_ip"
198 fi
199 done < <(rule::get "$rule_name" "allow_ips")
200
201 # Remove block_ports
202 while IFS= read -r entry; do
203 [[ -z "$entry" ]] && continue
204 local target port proto
205 IFS=":" read -r target port proto <<< "$entry"
206 proto="${proto:-tcp}"
207 fw::unblock_port "$client_ip" "$target" "$port" "$proto"
208 done < <(rule::get "$rule_name" "block_ports")
209
210 # Remove block_ips
211 while IFS= read -r block_ip; do
212 [[ -z "$block_ip" ]] && continue
213 if [[ "$block_ip" == *"/"* ]]; then
214 fw::unblock_subnet "$client_ip" "$block_ip"
215 else
216 fw::unblock_ip "$client_ip" "$block_ip"
217 fi
218 done < <(rule::get "$rule_name" "block_ips")
219
220 # Remove DNS redirect if applicable
221 local dns_redirect
222 dns_redirect=$(rule::get "$rule_name" "dns_redirect")
223 if [[ "$dns_redirect" == "true" ]]; then
224 local subtype
225 subtype=$(peers::get_meta "$peer_name" "subtype")
226 local subnet
227 if [[ -n "$subtype" ]]; then
228 subnet=$(config::subnet_for "$subtype")
229 else
230 local peer_type
231 peer_type=$(peers::get_type "$peer_name") || true
232 [[ -z "$peer_type" ]] && peer_type="phone"
233 subnet=$(config::subnet_for "$peer_type")
234 fi
235 rule::remove_dns_redirect "${subnet}.0/24"
236 fi
237
238 # Clear rule from meta
239 [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" ""
240
241 log::debug "Removed rule '${rule_name}' from: ${client_ip}"
242}
243
244function rule::reapply_all() {
245 local rule_name="${1:-}"
246 rule::require_exists "$rule_name" || return 1
247
248 local peers=()
249 mapfile -t peers < <(peers::with_rule "$rule_name")
250 [[ ${#peers[@]} -eq 0 ]] && return 0
251
252 local count=0
253 for peer_name in "${peers[@]}"; do
254 local client_ip
255 client_ip=$(peers::get_ip "$peer_name")
256 [[ -z "$client_ip" ]] && continue
257 # FLUSH first to ensure clean ordering
258 fw::flush_peer "$client_ip"
259 rule::apply "$rule_name" "$client_ip" "$peer_name"
260 (( count++ )) || true
261 done
262
263 log::wg_success "Rule '${rule_name}' re-applied to ${count} peers"
264}
265
266function rule::restore_all() {
267 while IFS= read -r peer_name; do
268 # Skip blocked peers - no fw rules needed when blocked
269 block::is_blocked "$peer_name" && continue
270
271 local rule_name
272 rule_name=$(peers::get_meta "$peer_name" "rule")
273
274 [[ -z "$rule_name" ]] && continue
275
276 if ! rule::exists "$rule_name"; then
277 log::wg_warning "Rule '${rule_name}' not found for peer '${peer_name}', skipping"
278 continue
279 fi
280
281 local client_ip
282 client_ip=$(peers::get_ip "$peer_name")
283 [[ -z "$client_ip" ]] && continue
284
285 rule::apply "$rule_name" "$client_ip"
286 done < <(peers::all)
287 log::wg "Rules restored for all peers"
288}
289
290# ============================================
291# Rendering
292# ============================================
293
294function rule::render_flat() {
295 local rule_name="${1:-}"
296
297 local allow_ports allow_ips block_ips block_ports dns
298 allow_ports=$(rule::get "$rule_name" "allow_ports")
299 allow_ips=$(rule::get "$rule_name" "allow_ips")
300 block_ips=$(rule::get "$rule_name" "block_ips")
301 block_ports=$(rule::get "$rule_name" "block_ports")
302 dns=$(rule::get_own "$rule_name" "dns_redirect")
303
304 local has_content=false
305 [[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && \
306 has_content=true
307
308 if ! $has_content; then
309 printf "\n full access (no restrictions)\n"
310 return 0
311 fi
312
313 if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
314 printf "\n"
315 while IFS= read -r e; do
316 [[ -z "$e" ]] && continue
317 net::print_entry "+" "$e" 2
318 done <<< "$allow_ports"$'\n'"$allow_ips"
319 fi
320
321 if [[ -n "$block_ips" || -n "$block_ports" ]]; then
322 printf "\n"
323 while IFS= read -r e; do
324 [[ -z "$e" ]] && continue
325 net::print_entry "-" "$e" 2
326 done <<< "$block_ips"$'\n'"$block_ports"
327 fi
328
329 [[ "${dns,,}" == "true" ]] && \
330 net::print_dns_redirect "$(config::dns)" 6 "DNS"
331
332 return 0
333}
334
335function rule::render_entries() {
336 # Renders allow/block entries for a rule name with annotations and DNS
337 # Usage: rule::render_entries <rule_name> <indent>
338 # indent: 4 for rule show, 4 for inspect (same)
339 local rule_name="${1:-}" indent="${2:-4}"
340 local rule_file
341 rule_file="$(rule::path "$rule_name")"
342
343 local allow_ports allow_ips block_ips block_ports dns
344 allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
345 allow_ips=$(rule::get "$rule_name" "allow_ips" 2>/dev/null || true)
346 block_ips=$(rule::get "$rule_name" "block_ips" 2>/dev/null || true)
347 block_ports=$(rule::get "$rule_name" "block_ports" 2>/dev/null || true)
348 dns=$(rule::get_own "$rule_name" "dns_redirect")
349
350 while IFS= read -r e; do
351 [[ -z "$e" ]] && continue
352 net::print_entry "+" "$e"
353 done <<< "$allow_ports"$'\n'"$allow_ips"
354
355 while IFS= read -r e; do
356 [[ -z "$e" ]] && continue
357 net::print_entry "-" "$e"
358 done <<< "$block_ips"$'\n'"$block_ports"
359
360 [[ "${dns,,}" == "true" ]] && \
361 net::print_dns_redirect "$(config::dns)" 6 "DNS"
362}
363
364function rule::render_own_entries() {
365 # Renders own (non-inherited) entries for a rule
366 local rule_name="${1:-}"
367 local rule_file
368 rule_file="$(rule::path "$rule_name")"
369
370 local allow_ports allow_ips block_ips block_ports dns
371 allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
372 allow_ips=$(json::get "$rule_file" "allow_ips" 2>/dev/null || true)
373 block_ips=$(json::get "$rule_file" "block_ips" 2>/dev/null || true)
374 block_ports=$(json::get "$rule_file" "block_ports" 2>/dev/null || true)
375 dns=$(json::get "$rule_file" "dns_redirect" 2>/dev/null || true)
376
377 local has_own=false
378 local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
379 [[ -n "${combined//[$'\n']/}" ]] && has_own=true
380
381 $has_own || return 0
382
383 while IFS= read -r e; do
384 [[ -z "$e" ]] && continue
385 net::print_entry "+" "$e"
386 done <<< "$allow_ports"$'\n'"$allow_ips"
387
388 while IFS= read -r e; do
389 [[ -z "$e" ]] && continue
390 net::print_entry "-" "$e"
391 done <<< "$block_ips"$'\n'"$block_ports"
392
393 [[ "${dns,,}" == "true" ]] && \
394 net::print_dns_redirect "$(config::dns)" 6 "DNS"
395
396 return 0
397}
398
399function rule::render_extends_tree() {
400 # Renders full inheritance tree for a rule
401 local rule_name="${1:-}"
402 local rule_file
403 rule_file="$(rule::path "$rule_name")"
404
405 local extends_raw=()
406 mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
407
408 [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]:-}" ]] && return 1
409
410 for base_name in "${extends_raw[@]}"; do
411 [[ -z "$base_name" ]] && continue
412 printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
413 rule::render_entries "$base_name"
414 done
415
416 # Own rules after inherited
417 local own_output
418 own_output=$(rule::render_own_entries "$rule_name")
419 if [[ -n "$own_output" ]]; then
420 printf "\n \033[0;37mOwn:\033[0m\n"
421 printf "%s\n" "$own_output"
422 fi
423
424 return 0
425}
426
427# ============================================
428# DNS Redirect
429# ============================================
430
431function rule::apply_dns_redirect() {
432 local client_subnet="${1:-}"
433 fw::nat_add_dns_redirect "$client_subnet" "$(config::dns)" "$(config::interface)"
434}
435
436function rule::remove_dns_redirect() {
437 local client_subnet="${1:-}"
438 fw::nat_remove_dns_redirect "$client_subnet" "$(config::dns)" "$(config::interface)"
439}
440