This repository was archived by the owner on May 16, 2026. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathemergency.py
More file actions
425 lines (354 loc) · 20.1 KB
/
Copy pathemergency.py
File metadata and controls
425 lines (354 loc) · 20.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
#!/usr/bin/env python3
"""
Emergency Operations Module
Provides emergency response and recovery operations for the Azazel TUI menu system.
"""
import subprocess
from azazel_pi.utils.cmd_runner import run as run_cmd
import os
from datetime import datetime
from pathlib import Path
from typing import Optional
from rich.console import Console
from rich.text import Text
from rich.prompt import Prompt, Confirm
from azctl.menu.types import MenuCategory, MenuAction
from azazel_pi.utils.network_utils import get_wlan_ap_status, get_wlan_link_info
from azazel_pi.utils.wan_state import get_active_wan_interface
class EmergencyModule:
"""Emergency operations and recovery functionality."""
def __init__(self, console: Console, lan_if: Optional[str] = None, wan_if: Optional[str] = None):
self.console = console
# LAN precedence: explicit arg -> AZAZEL_LAN_IF env -> default wlan0
self.lan_if = lan_if or os.environ.get("AZAZEL_LAN_IF") or "wlan0"
# WAN precedence: explicit arg -> AZAZEL_WAN_IF env -> helper -> fallback wlan1
try:
self.wan_if = wan_if or os.environ.get("AZAZEL_WAN_IF") or get_active_wan_interface()
except Exception:
self.wan_if = wan_if or os.environ.get("AZAZEL_WAN_IF") or "wlan1"
def get_category(self) -> MenuCategory:
"""Get the emergency operations menu category."""
return MenuCategory(
title="Emergency Operations",
description="Emergency response and recovery operations",
actions=[
MenuAction("Emergency Lockdown 🔒 ⚠️", "Immediately lock down all network access", self._emergency_lockdown, requires_root=True, dangerous=True),
MenuAction("Reset Network Configuration 🔒 ⚠️", "Reset all network settings to defaults", self._reset_network, requires_root=True, dangerous=True),
MenuAction("Generate System Report", "Create comprehensive system status report", self._system_report),
MenuAction("Factory Reset 🔒 ⚠️", "Reset system to factory defaults", self._factory_reset, requires_root=True, dangerous=True),
]
)
def _emergency_lockdown(self) -> None:
"""Immediate emergency lockdown of all network access."""
self.console.print("[bold red]EMERGENCY LOCKDOWN PROCEDURE[/bold red]")
title = Text("Emergency Network Lockdown", style="bold red")
self.console.print(title)
self.console.print(Text("─" * len("Emergency Network Lockdown"), style="dim"))
self.console.print("[yellow]This will immediately:")
self.console.print("• Block all incoming and outgoing traffic")
self.console.print("• Disconnect all wireless connections")
self.console.print("• Stop all network services")
self.console.print("• Switch to maximum security mode[/yellow]")
self.console.print()
if not Confirm.ask("[red]Proceed with emergency lockdown?[/red]", default=False):
self.console.print("[yellow]Emergency lockdown cancelled.[/yellow]")
self._pause()
return
self.console.print("[red]Initiating emergency lockdown...[/red]")
try:
# Step 1: Switch to lockdown mode
self.console.print("[blue]1. Switching to lockdown mode...[/blue]")
result = run_cmd(
["python3", "-m", "azctl", "events", "--mode", "lockdown"],
capture_output=True, text=True, timeout=30
)
if result.returncode == 0:
self.console.print("[green]✓ Lockdown mode activated[/green]")
else:
self.console.print(f"[red]✗ Mode switch failed: {result.stderr.strip()}[/red]")
# Step 2: Apply emergency firewall rules
self.console.print("[blue]2. Applying emergency firewall rules...[/blue]")
try:
run_cmd(["sudo", "nft", "flush", "ruleset"], timeout=10)
run_cmd([
"sudo", "nft", "add", "table", "inet", "emergency"
], timeout=5)
run_cmd([
"sudo", "nft", "add", "chain", "inet", "emergency", "input",
"{", "type", "filter", "hook", "input", "priority", "0", ";", "policy", "drop", ";", "}"
], timeout=5)
run_cmd([
"sudo", "nft", "add", "chain", "inet", "emergency", "forward",
"{", "type", "filter", "hook", "forward", "priority", "0", ";", "policy", "drop", ";", "}"
], timeout=5)
run_cmd([
"sudo", "nft", "add", "chain", "inet", "emergency", "output",
"{", "type", "filter", "hook", "output", "priority", "0", ";", "policy", "drop", ";", "}"
], timeout=5)
# Allow loopback
run_cmd([
"sudo", "nft", "add", "rule", "inet", "emergency", "input", "iif", "lo", "accept"
], timeout=5)
run_cmd([
"sudo", "nft", "add", "rule", "inet", "emergency", "output", "oif", "lo", "accept"
], timeout=5)
self.console.print("[green]✓ Emergency firewall rules applied[/green]")
except Exception as e:
self.console.print(f"[red]✗ Firewall rules failed: {e}[/red]")
# Step 3: Disconnect wireless
self.console.print("[blue]3. Disconnecting wireless connections...[/blue]")
try:
run_cmd(["sudo", "wpa_cli", "-i", self.wan_if, "disconnect"], timeout=10)
self.console.print("[green]✓ Wireless connections disconnected[/green]")
except Exception as e:
self.console.print(f"[red]✗ Wireless disconnect failed: {e}[/red]")
# Step 4: Stop services
self.console.print("[blue]4. Stopping non-essential services...[/blue]")
services_to_stop = ["vector"]
for service in services_to_stop:
try:
run_cmd(["sudo", "systemctl", "stop", f"{service}.service"], timeout=15)
self.console.print(f"[green]✓ {service} stopped[/green]")
except Exception:
self.console.print(f"[yellow]! {service} stop failed[/yellow]")
try:
run_cmd(["sudo", "docker", "stop", "azazel_opencanary"], timeout=30)
self.console.print("[green]✓ azazel_opencanary stopped[/green]")
except Exception:
self.console.print("[yellow]! azazel_opencanary stop failed[/yellow]")
self.console.print("\n[bold red]EMERGENCY LOCKDOWN COMPLETED[/bold red]")
self.console.print("[yellow]System is now in maximum security lockdown mode.[/yellow]")
self.console.print("[yellow]Manual intervention required to restore normal operations.[/yellow]")
except Exception as e:
self.console.print(f"[red]Emergency lockdown failed: {e}[/red]")
self._pause()
def _reset_network(self) -> None:
"""Reset network configuration to defaults."""
title = Text("Reset Network Configuration", style="bold red")
self.console.print(title)
self.console.print(Text("─" * len("Reset Network Configuration"), style="dim"))
self.console.print("[yellow]This will:")
self.console.print("• Remove all saved Wi-Fi networks")
self.console.print("• Reset network interfaces")
self.console.print("• Restore default network configuration")
self.console.print("• Restart network services[/yellow]")
self.console.print()
if not Confirm.ask("[red]Proceed with network reset?[/red]", default=False):
return
self.console.print("[blue]Resetting network configuration...[/blue]")
try:
# Reset wpa_supplicant configuration
self.console.print("[blue]1. Resetting Wi-Fi configuration...[/blue]")
try:
run_cmd(["sudo", "systemctl", "stop", "wpa_supplicant"], timeout=10)
# Backup and reset wpa_supplicant.conf
run_cmd([
"sudo", "cp", "/etc/wpa_supplicant/wpa_supplicant.conf",
f"/etc/wpa_supplicant/wpa_supplicant.conf.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
], timeout=5)
# Create minimal wpa_supplicant.conf
minimal_config = """ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=US
"""
with open("/tmp/wpa_supplicant_reset.conf", "w") as f:
f.write(minimal_config)
run_cmd([
"sudo", "cp", "/tmp/wpa_supplicant_reset.conf",
"/etc/wpa_supplicant/wpa_supplicant.conf"
], timeout=5)
run_cmd(["sudo", "systemctl", "start", "wpa_supplicant"], timeout=10)
self.console.print("[green]✓ Wi-Fi configuration reset[/green]")
except Exception as e:
self.console.print(f"[red]✗ Wi-Fi reset failed: {e}[/red]")
# Reset network interfaces
self.console.print("[blue]2. Resetting network interfaces...[/blue]")
try:
run_cmd(["sudo", "ip", "link", "set", self.wan_if, "down"], timeout=5)
run_cmd(["sudo", "ip", "link", "set", self.wan_if, "up"], timeout=5)
run_cmd(["sudo", "ip", "link", "set", self.lan_if, "down"], timeout=5)
run_cmd(["sudo", "ip", "link", "set", self.lan_if, "up"], timeout=5)
self.console.print("[green]✓ Network interfaces reset[/green]")
except Exception as e:
self.console.print(f"[red]✗ Interface reset failed: {e}[/red]")
# Restart network services
self.console.print("[blue]3. Restarting network services...[/blue]")
services = ["dhcpcd", "hostapd"]
for service in services:
try:
run_cmd(["sudo", "systemctl", "restart", service], timeout=15)
self.console.print(f"[green]✓ {service} restarted[/green]")
except Exception:
self.console.print(f"[yellow]! {service} restart failed[/yellow]")
self.console.print("\n[bold green]Network configuration reset completed[/bold green]")
except Exception as e:
self.console.print(f"[red]Network reset failed: {e}[/red]")
self._pause()
def _system_report(self) -> None:
"""Generate comprehensive system status report."""
title = Text("System Status Report Generator", style="bold")
self.console.print(title)
self.console.print(Text("─" * len("System Status Report Generator"), style="dim"))
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
report_file = f"/tmp/azazel_system_report_{timestamp}.txt"
self.console.print(f"[blue]Generating system report: {report_file}[/blue]")
try:
with open(report_file, 'w') as report:
report.write(f"AZAZEL-PI SYSTEM REPORT\n")
report.write(f"Generated: {datetime.now().isoformat()}\n")
report.write("=" * 50 + "\n\n")
# System information
report.write("SYSTEM INFORMATION\n")
report.write("-" * 20 + "\n")
try:
result = run_cmd(["uname", "-a"], capture_output=True, text=True, timeout=5)
report.write(f"Kernel: {result.stdout.strip()}\n")
except Exception:
report.write("Kernel: Unable to determine\n")
try:
result = run_cmd(["uptime"], capture_output=True, text=True, timeout=5)
report.write(f"Uptime: {result.stdout.strip()}\n")
except Exception:
report.write("Uptime: Unable to determine\n")
report.write("\n")
# Current mode
report.write("AZAZEL STATUS\n")
report.write("-" * 15 + "\n")
try:
from azctl.cli import _read_last_decision
decision_paths = [Path("/var/log/azazel/decisions.log")]
current = _read_last_decision(decision_paths)
if current:
report.write(f"Current Mode: {current.get('mode', 'unknown').upper()}\n")
report.write(f"Last Decision: {current.get('timestamp', 'unknown')}\n")
report.write(f"Reason: {current.get('reason', 'No reason provided')}\n")
else:
report.write("Current Mode: Unable to determine\n")
except Exception:
report.write("Current Mode: Error reading decision log\n")
report.write("\n")
# Network status
report.write("NETWORK STATUS\n")
report.write("-" * 15 + "\n")
try:
wlan0 = get_wlan_ap_status(self.lan_if)
wlan1 = get_wlan_link_info(self.wan_if)
report.write(f"LAN Interface ({self.lan_if}):\n")
report.write(f" AP Mode: {'Yes' if wlan0.get('is_ap') else 'No'}\n")
report.write(f" SSID: {wlan0.get('ssid', 'N/A')}\n")
report.write(f" Clients: {wlan0.get('stations', 'N/A')}\n")
report.write(f"WAN Interface ({self.wan_if}):\n")
report.write(f" Connected: {'Yes' if wlan1.get('connected') else 'No'}\n")
report.write(f" SSID: {wlan1.get('ssid', 'N/A')}\n")
report.write(f" IP: {wlan1.get('ip4', 'N/A')}\n")
except Exception as e:
report.write(f"Network Status: Error - {e}\n")
report.write("\n")
# Service status
report.write("SERVICE STATUS\n")
report.write("-" * 15 + "\n")
services = ["azctl", "azctl-serve", "suricata", "vector"]
for service in services:
try:
result = run_cmd(
["systemctl", "is-active", f"{service}.service"],
capture_output=True, text=True, timeout=5
)
status = "ACTIVE" if result.returncode == 0 else "INACTIVE"
report.write(f"{service}: {status}\n")
except Exception:
report.write(f"{service}: UNKNOWN\n")
report.write(f"azazel_opencanary (Docker): {'ACTIVE' if self._is_container_running('azazel_opencanary') else 'INACTIVE'}\n")
report.write("\n")
# System resources
report.write("SYSTEM RESOURCES\n")
report.write("-" * 17 + "\n")
try:
result = run_cmd(["free", "-h"], capture_output=True, text=True, timeout=5)
report.write("Memory Usage:\n")
report.write(result.stdout)
except Exception:
report.write("Memory Usage: Unable to determine\n")
try:
result = run_cmd(["df", "-h", "/"], capture_output=True, text=True, timeout=5)
report.write("\nDisk Usage:\n")
report.write(result.stdout)
except Exception:
report.write("Disk Usage: Unable to determine\n")
report.write("\n")
# Recent logs
report.write("RECENT SYSTEM LOGS\n")
report.write("-" * 19 + "\n")
try:
result = run_cmd(
["journalctl", "-n", "20", "--no-pager"],
capture_output=True, text=True, timeout=10
)
report.write(result.stdout)
except Exception:
report.write("Recent logs: Unable to retrieve\n")
self.console.print(f"[green]✓ System report generated: {report_file}[/green]")
self.console.print(f"[blue]Report can be viewed with: cat {report_file}[/blue]")
except Exception as e:
self.console.print(f"[red]Failed to generate report: {e}[/red]")
self._pause()
def _factory_reset(self) -> None:
"""Reset system to factory defaults."""
title = Text("Factory Reset", style="bold red")
self.console.print(title)
self.console.print(Text("─" * len("Factory Reset"), style="dim"))
self.console.print("[red]WARNING: This will reset ALL system settings to factory defaults![/red]")
self.console.print("[yellow]This includes:")
self.console.print("• All Wi-Fi networks and passwords")
self.console.print("• Custom configurations")
self.console.print("• Log files and history")
self.console.print("• User settings and profiles")
self.console.print("• Decision history[/yellow]")
self.console.print()
# Multiple confirmations for factory reset
if not Confirm.ask("[red]Are you sure you want to perform a factory reset?[/red]", default=False):
return
if not Confirm.ask("[red]This cannot be undone. Really proceed with factory reset?[/red]", default=False):
return
# Require typing "FACTORY RESET" to confirm
confirmation = Prompt.ask("[red]Type 'FACTORY RESET' to confirm")
if confirmation != "FACTORY RESET":
self.console.print("[yellow]Factory reset cancelled.[/yellow]")
self._pause()
return
self.console.print("[red]Performing factory reset...[/red]")
try:
# This would implement actual factory reset logic
# For safety, we'll just simulate the process
self.console.print("[blue]1. Stopping all services...[/blue]")
self.console.print("[blue]2. Clearing configurations...[/blue]")
self.console.print("[blue]3. Resetting to defaults...[/blue]")
self.console.print("[blue]4. Restarting services...[/blue]")
# In a real implementation, you would:
# - Stop all Azazel services
# - Remove configuration files
# - Restore default configurations
# - Clear logs and decision history
# - Reset network settings
# - Restart all services
self.console.print("[yellow]Factory reset simulation completed.[/yellow]")
self.console.print("[red]In production, this would reset all system settings.[/red]")
except Exception as e:
self.console.print(f"[red]Factory reset failed: {e}[/red]")
self._pause()
def _pause(self) -> None:
"""Pause for user input."""
Prompt.ask("\n[dim]Press Enter to continue[/dim]", default="", show_default=False)
def _is_container_running(self, container_name: str) -> bool:
"""Check whether a Docker container is running."""
try:
result = run_cmd(
["docker", "inspect", "-f", "{{.State.Running}}", container_name],
capture_output=True,
text=True,
timeout=5,
check=False,
)
return result.returncode == 0 and (result.stdout or "").strip().lower() == "true"
except Exception:
return False