Son aktivite 1 month ago

gistfile1.txt Ham
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 if rule::is_applied "$rule_name" "$client_ip"; then
116 log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
117 [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" "$rule_name"
118 return 0
119 fi
120
121 # Process block_ips
122 while IFS= read -r block_ip; do
123 [[ -z "$block_ip" ]] && continue
124 fw::block_ip "$client_ip" "$block_ip"
125 done < <(rule::get "$rule_name" "block_ips")
126
127 # Process block_ports
128 while IFS= read -r entry; do
129 [[ -z "$entry" ]] && continue
130 local target port proto
131 IFS=":" read -r target port proto <<< "$entry"
132 proto="${proto:-tcp}"
133 fw::block_port "$client_ip" "$target" "$port" "$proto"
134 done < <(rule::get "$rule_name" "block_ports")
135
136 # Process allow_ips (inserted before blocks)
137 while IFS= read -r allow_ip; do
138 [[ -z "$allow_ip" ]] && continue
139 fw::allow_ip "$client_ip" "$allow_ip"
140 done < <(rule::get "$rule_name" "allow_ips")
141
142 # Process allow_ports (highest priority)
143 while IFS= read -r entry; do
144 [[ -z "$entry" ]] && continue
145 local target port proto
146 IFS=":" read -r target port proto <<< "$entry"
147 proto="${proto:-tcp}"
148 fw::allow_port "$client_ip" "$target" "$port" "$proto"
149 done < <(rule::get "$rule_name" "allow_ports")
150
151 [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" "$rule_name"
152
153 # DNS redirect
154 local dns_redirect
155 dns_redirect=$(rule::get "$rule_name" "dns_redirect")
156 if [[ "$dns_redirect" == "true" ]]; then
157 local peer_subnet
158 peer_subnet=$(peers::get_ip "$peer_name" | cut -d'.' -f1-3)
159 if ! fw::_nat_exists -i wg0 -s "${peer_subnet}.0/24" \
160 -p udp --dport 53 -j DNAT \
161 --to-destination "$(config::dns):53" 2>/dev/null; then
162 rule::apply_dns_redirect "${peer_subnet}.0/24"
163 log::debug "dns_redirect: applied for ${peer_subnet}.0/24"
164 else
165 log::debug "dns_redirect: already applied for ${peer_subnet}.0/24"
166 fi
167 fi
168
169 log::debug "Applied rule '${rule_name}' to: ${client_ip}"
170}
171
172function rule::unapply() {
173 local rule_name="${1:-}" client_ip="${2:-}"
174
175 rule::require_exists "$rule_name" || return 1
176
177 local peer_name
178 peer_name=$(peers::find_by_ip "$client_ip")
179
180 # Remove allow_ports first (reverse order of apply)
181 while IFS= read -r entry; do
182 [[ -z "$entry" ]] && continue
183 local target port proto
184 IFS=":" read -r target port proto <<< "$entry"
185 proto="${proto:-tcp}"
186 fw::unallow_port "$client_ip" "$target" "$port" "$proto"
187 done < <(rule::get "$rule_name" "allow_ports")
188
189 # Remove allow_ips
190 while IFS= read -r allow_ip; do
191 [[ -z "$allow_ip" ]] && continue
192 if [[ "$allow_ip" == *"/"* ]]; then
193 fw::unallow_subnet "$client_ip" "$allow_ip"
194 else
195 fw::unallow_ip "$client_ip" "$allow_ip"
196 fi
197 done < <(rule::get "$rule_name" "allow_ips")
198
199 # Remove block_ports
200 while IFS= read -r entry; do
201 [[ -z "$entry" ]] && continue
202 local target port proto
203 IFS=":" read -r target port proto <<< "$entry"
204 proto="${proto:-tcp}"
205 fw::unblock_port "$client_ip" "$target" "$port" "$proto"
206 done < <(rule::get "$rule_name" "block_ports")
207
208 # Remove block_ips
209 while IFS= read -r block_ip; do
210 [[ -z "$block_ip" ]] && continue
211 if [[ "$block_ip" == *"/"* ]]; then
212 fw::unblock_subnet "$client_ip" "$block_ip"
213 else
214 fw::unblock_ip "$client_ip" "$block_ip"
215 fi
216 done < <(rule::get "$rule_name" "block_ips")
217
218 # Remove DNS redirect if applicable
219 local dns_redirect
220 dns_redirect=$(rule::get "$rule_name" "dns_redirect")
221 if [[ "$dns_redirect" == "true" ]]; then
222 local peer_ip peer_subnet
223 peer_ip=$(peers::get_ip "$peer_name")
224 peer_subnet=$(echo "$peer_ip" | cut -d'.' -f1-3)
225 rule::remove_dns_redirect "${peer_subnet}.0/24"
226 fi
227
228 [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" ""
229
230 log::debug "Removed rule '${rule_name}' from: ${client_ip}"
231}
232
233# ============================================
234# Bulk Operations
235# ============================================
236
237function rule::_apply_identity_rule() {
238 local peer_name="${1:-}" client_ip="${2:-}"
239
240 local identity_name
241 identity_name=$(identity::get_name "$peer_name")
242 [[ -z "$identity_name" ]] && return 0
243
244 local rules
245 rules=$(identity::rules "$identity_name")
246 [[ -z "$rules" ]] && return 0
247
248 local strict
249 strict=$(identity::rule_flags "$identity_name" "strict_rule")
250
251 if [[ "$strict" == "true" ]]; then
252 # Strict: flush and apply only identity rules — peer rule ignored
253 fw::flush_peer "$client_ip"
254 while IFS= read -r rule_name; do
255 [[ -z "$rule_name" ]] && continue
256 rule::exists "$rule_name" && rule::apply "$rule_name" "$client_ip" "$peer_name" || true
257 done <<< "$rules"
258 else
259 # Additive: apply identity rules on top of peer rule
260 while IFS= read -r rule_name; do
261 [[ -z "$rule_name" ]] && continue
262 rule::exists "$rule_name" && rule::apply "$rule_name" "$client_ip" "$peer_name" || true
263 done <<< "$rules"
264 fi
265}
266
267# rule::full_restore_peer <peer_name> <client_ip>
268# Flush and fully restore all fw rules for a peer — rule rules + block rules.
269# Use this instead of calling rule::apply + block::restore_rules_for separately
270# to ensure block rules are never left missing after a flush.
271function rule::full_restore_peer() {
272 local peer_name="${1:-}" client_ip="${2:-}"
273 [[ -z "$peer_name" || -z "$client_ip" ]] && return 1
274
275 fw::flush_peer "$client_ip"
276
277 local peer_rule
278 peer_rule=$(peers::get_meta "$peer_name" "rule")
279
280 local strict
281 strict=$(rule::_get_identity_strict "$peer_name")
282
283 if [[ "$strict" == "true" ]]; then
284 # Strict mode: only identity rules apply
285 rule::_apply_identity_rule "$peer_name" "$client_ip"
286 else
287 # Normal mode: peer rule + identity rules (additive)
288 [[ -n "$peer_rule" ]] && rule::apply "$peer_rule" "$client_ip" "$peer_name"
289 rule::_apply_identity_rule "$peer_name" "$client_ip"
290 fi
291
292 block::restore_rules_for "$peer_name" "$client_ip"
293}
294
295function rule::_get_identity_strict() {
296 local peer_name="${1:-}"
297 local identity_name
298 identity_name=$(identity::get_name "$peer_name")
299 [[ -z "$identity_name" ]] && echo "false" && return 0
300 identity::rule_flags "$identity_name" "strict_rule"
301}
302
303function rule::restore_all() {
304 while IFS= read -r peer_name; do
305 block::is_blocked "$peer_name" && continue
306
307 local rule_name
308 rule_name=$(peers::get_meta "$peer_name" "rule")
309 [[ -z "$rule_name" ]] && continue
310
311 if ! rule::exists "$rule_name"; then
312 log::wg_warning "Rule '${rule_name}' not found for peer '${peer_name}', skipping"
313 continue
314 fi
315
316 local client_ip
317 client_ip=$(peers::get_ip "$peer_name")
318 [[ -z "$client_ip" ]] && continue
319
320 # full_restore_peer ensures block rules are restored alongside rule rules
321 rule::full_restore_peer "$peer_name" "$client_ip"
322 done < <(peers::all)
323 log::wg "Rules restored for all peers"
324}
325
326# ============================================
327# Rendering
328# ============================================
329
330function rule::render_flat() {
331 local rule_name="${1:-}"
332
333 local allow_ports allow_ips block_ips block_ports dns
334 allow_ports=$(rule::get "$rule_name" "allow_ports")
335 allow_ips=$(rule::get "$rule_name" "allow_ips")
336 block_ips=$(rule::get "$rule_name" "block_ips")
337 block_ports=$(rule::get "$rule_name" "block_ports")
338 dns=$(rule::get_own "$rule_name" "dns_redirect")
339
340 local has_content=false
341 [[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && \
342 has_content=true
343
344 if ! $has_content; then
345 printf "\n full access (no restrictions)\n"
346 return 0
347 fi
348
349 if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
350 printf "\n"
351 while IFS= read -r e; do
352 [[ -z "$e" ]] && continue
353 net::print_entry "+" "$e" 2
354 done <<< "$allow_ports"$'\n'"$allow_ips"
355 fi
356
357 if [[ -n "$block_ips" || -n "$block_ports" ]]; then
358 printf "\n"
359 while IFS= read -r e; do
360 [[ -z "$e" ]] && continue
361 net::print_entry "-" "$e" 2
362 done <<< "$block_ips"$'\n'"$block_ports"
363 fi
364
365 [[ "${dns,,}" == "true" ]] && \
366 net::print_dns_redirect "$(config::dns)" 6 "DNS"
367
368 return 0
369}
370
371function rule::render_entries() {
372 local rule_name="${1:-}" indent="${2:-4}"
373
374 local allow_ports allow_ips block_ips block_ports dns
375 allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
376 allow_ips=$(rule::get "$rule_name" "allow_ips" 2>/dev/null || true)
377 block_ips=$(rule::get "$rule_name" "block_ips" 2>/dev/null || true)
378 block_ports=$(rule::get "$rule_name" "block_ports" 2>/dev/null || true)
379 dns=$(rule::get_own "$rule_name" "dns_redirect")
380
381 while IFS= read -r e; do
382 [[ -z "$e" ]] && continue
383 net::print_entry "+" "$e"
384 done <<< "$allow_ports"$'\n'"$allow_ips"
385
386 while IFS= read -r e; do
387 [[ -z "$e" ]] && continue
388 net::print_entry "-" "$e"
389 done <<< "$block_ips"$'\n'"$block_ports"
390
391 [[ "${dns,,}" == "true" ]] && \
392 net::print_dns_redirect "$(config::dns)" 6 "DNS"
393}
394
395function rule::render_own_entries() {
396 local rule_name="${1:-}"
397 local rule_file
398 rule_file="$(rule::path "$rule_name")"
399
400 local allow_ports allow_ips block_ips block_ports dns
401 allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
402 allow_ips=$(json::get "$rule_file" "allow_ips" 2>/dev/null || true)
403 block_ips=$(json::get "$rule_file" "block_ips" 2>/dev/null || true)
404 block_ports=$(json::get "$rule_file" "block_ports" 2>/dev/null || true)
405 dns=$(json::get "$rule_file" "dns_redirect" 2>/dev/null || true)
406
407 local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
408 [[ -z "${combined//[$'\n']/}" ]] && return 0
409
410 while IFS= read -r e; do
411 [[ -z "$e" ]] && continue
412 net::print_entry "+" "$e"
413 done <<< "$allow_ports"$'\n'"$allow_ips"
414
415 while IFS= read -r e; do
416 [[ -z "$e" ]] && continue
417 net::print_entry "-" "$e"
418 done <<< "$block_ips"$'\n'"$block_ports"
419
420 [[ "${dns,,}" == "true" ]] && \
421 net::print_dns_redirect "$(config::dns)" 6 "DNS"
422
423 return 0
424}
425
426function rule::render_extends_tree() {
427 local rule_name="${1:-}"
428 local rule_file
429 rule_file="$(rule::path "$rule_name")"
430
431 local extends_raw=()
432 mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
433
434 [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]:-}" ]] && return 1
435
436 for base_name in "${extends_raw[@]}"; do
437 [[ -z "$base_name" ]] && continue
438 printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
439 rule::render_entries "$base_name"
440 done
441
442 local own_output
443 own_output=$(rule::render_own_entries "$rule_name")
444 if [[ -n "$own_output" ]]; then
445 printf "\n \033[0;37mOwn:\033[0m\n"
446 printf "%s\n" "$own_output"
447 fi
448
449 return 0
450}
451
452# ============================================
453# DNS Redirect
454# ============================================
455
456function rule::apply_dns_redirect() {
457 local client_subnet="${1:-}"
458 fw::nat_add_dns_redirect "$client_subnet" "$(config::dns)" "$(config::interface)"
459}
460
461function rule::remove_dns_redirect() {
462 local client_subnet="${1:-}"
463 fw::nat_remove_dns_redirect "$client_subnet" "$(config::dns)" "$(config::interface)"
464}