wnm 0.0.9__py3-none-any.whl → 0.0.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wnm might be problematic. Click here for more details.
- wnm/__init__.py +1 -1
- wnm/__main__.py +184 -1133
- wnm/actions.py +45 -0
- wnm/common.py +21 -0
- wnm/config.py +653 -1
- wnm/decision_engine.py +388 -0
- wnm/executor.py +1292 -0
- wnm/firewall/__init__.py +13 -0
- wnm/firewall/base.py +71 -0
- wnm/firewall/factory.py +95 -0
- wnm/firewall/null_firewall.py +71 -0
- wnm/firewall/ufw_manager.py +118 -0
- wnm/migration.py +42 -0
- wnm/models.py +305 -126
- wnm/process_managers/__init__.py +23 -0
- wnm/process_managers/base.py +203 -0
- wnm/process_managers/docker_manager.py +371 -0
- wnm/process_managers/factory.py +83 -0
- wnm/process_managers/launchd_manager.py +592 -0
- wnm/process_managers/setsid_manager.py +340 -0
- wnm/process_managers/systemd_manager.py +529 -0
- wnm/reports.py +286 -0
- wnm/utils.py +403 -0
- wnm-0.0.11.dist-info/METADATA +316 -0
- wnm-0.0.11.dist-info/RECORD +28 -0
- {wnm-0.0.9.dist-info → wnm-0.0.11.dist-info}/WHEEL +1 -1
- wnm-0.0.9.dist-info/METADATA +0 -95
- wnm-0.0.9.dist-info/RECORD +0 -9
- {wnm-0.0.9.dist-info → wnm-0.0.11.dist-info}/entry_points.txt +0 -0
- {wnm-0.0.9.dist-info → wnm-0.0.11.dist-info}/top_level.txt +0 -0
wnm/firewall/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Firewall management abstraction for WNM.
|
|
3
|
+
|
|
4
|
+
Provides a pluggable interface for firewall operations across different
|
|
5
|
+
firewall implementations (ufw, firewalld, iptables, or no-op).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from wnm.firewall.base import FirewallManager
|
|
9
|
+
from wnm.firewall.factory import get_firewall_manager
|
|
10
|
+
from wnm.firewall.null_firewall import NullFirewall
|
|
11
|
+
from wnm.firewall.ufw_manager import UFWManager
|
|
12
|
+
|
|
13
|
+
__all__ = ["FirewallManager", "get_firewall_manager", "NullFirewall", "UFWManager"]
|
wnm/firewall/base.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Abstract base class for firewall managers.
|
|
3
|
+
|
|
4
|
+
Firewall managers handle opening/closing ports for node communication
|
|
5
|
+
across different firewall implementations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FirewallManager(ABC):
|
|
12
|
+
"""
|
|
13
|
+
Abstract interface for firewall management.
|
|
14
|
+
|
|
15
|
+
Each implementation handles a specific firewall backend:
|
|
16
|
+
- UFWManager: Ubuntu's Uncomplicated Firewall (ufw)
|
|
17
|
+
- FirewalldManager: Firewalld (RHEL/Fedora/CentOS)
|
|
18
|
+
- IptablesManager: Direct iptables manipulation
|
|
19
|
+
- NullFirewall: No-op implementation (firewall disabled)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def enable_port(
|
|
24
|
+
self, port: int, protocol: str = "udp", comment: str = None
|
|
25
|
+
) -> bool:
|
|
26
|
+
"""
|
|
27
|
+
Open a firewall port for incoming traffic.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
port: Port number to open
|
|
31
|
+
protocol: Protocol type (udp/tcp)
|
|
32
|
+
comment: Optional comment/description for the rule
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
True if port was opened successfully, False otherwise
|
|
36
|
+
"""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def disable_port(self, port: int, protocol: str = "udp") -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Close a firewall port, blocking incoming traffic.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
port: Port number to close
|
|
46
|
+
protocol: Protocol type (udp/tcp)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
True if port was closed successfully, False otherwise
|
|
50
|
+
"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def is_enabled(self) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Check if the firewall is enabled and active.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
True if firewall is active, False otherwise
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def is_available(self) -> bool:
|
|
65
|
+
"""
|
|
66
|
+
Check if this firewall implementation is available on the system.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if firewall tools are installed, False otherwise
|
|
70
|
+
"""
|
|
71
|
+
pass
|
wnm/firewall/factory.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Factory for creating firewall manager instances.
|
|
3
|
+
|
|
4
|
+
Provides auto-detection of available firewall implementations
|
|
5
|
+
and a factory function for creating the appropriate manager.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from wnm.firewall.base import FirewallManager
|
|
12
|
+
from wnm.firewall.null_firewall import NullFirewall
|
|
13
|
+
from wnm.firewall.ufw_manager import UFWManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_default_firewall_type() -> str:
|
|
17
|
+
"""
|
|
18
|
+
Auto-detect the best firewall manager for this system.
|
|
19
|
+
|
|
20
|
+
Checks for available firewall tools in priority order:
|
|
21
|
+
1. UFW (Ubuntu/Debian)
|
|
22
|
+
2. Firewalld (RHEL/Fedora) - not yet implemented
|
|
23
|
+
3. Iptables (Linux fallback) - not yet implemented
|
|
24
|
+
4. NullFirewall (disabled/unavailable)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Firewall type name: "ufw", "firewalld", "iptables", or "null"
|
|
28
|
+
"""
|
|
29
|
+
# Check if firewall is disabled via environment variable
|
|
30
|
+
if os.environ.get("WNM_FIREWALL_DISABLED", "").lower() in ("1", "true", "yes"):
|
|
31
|
+
logging.info("Firewall disabled via WNM_FIREWALL_DISABLED environment variable")
|
|
32
|
+
return "null"
|
|
33
|
+
|
|
34
|
+
# Try UFW first (most common on Ubuntu/Debian)
|
|
35
|
+
ufw = UFWManager()
|
|
36
|
+
if ufw.is_available():
|
|
37
|
+
logging.debug("Auto-detected UFW firewall")
|
|
38
|
+
return "ufw"
|
|
39
|
+
|
|
40
|
+
# TODO: Add firewalld detection
|
|
41
|
+
# firewalld = FirewalldManager()
|
|
42
|
+
# if firewalld.is_available():
|
|
43
|
+
# logging.debug("Auto-detected firewalld")
|
|
44
|
+
# return "firewalld"
|
|
45
|
+
|
|
46
|
+
# TODO: Add iptables detection
|
|
47
|
+
# iptables = IptablesManager()
|
|
48
|
+
# if iptables.is_available():
|
|
49
|
+
# logging.debug("Auto-detected iptables")
|
|
50
|
+
# return "iptables"
|
|
51
|
+
|
|
52
|
+
# Default to null firewall if nothing available
|
|
53
|
+
logging.warning("No firewall implementation available, using NullFirewall")
|
|
54
|
+
return "null"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_firewall_manager(firewall_type: str = None) -> FirewallManager:
|
|
58
|
+
"""
|
|
59
|
+
Create a firewall manager instance.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
firewall_type: Type of firewall ("ufw", "firewalld", "iptables", "null")
|
|
63
|
+
If None, auto-detects best available option
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
FirewallManager instance
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If firewall_type is not recognized
|
|
70
|
+
"""
|
|
71
|
+
if firewall_type is None:
|
|
72
|
+
firewall_type = get_default_firewall_type()
|
|
73
|
+
|
|
74
|
+
firewall_type = firewall_type.lower()
|
|
75
|
+
|
|
76
|
+
managers = {
|
|
77
|
+
"ufw": UFWManager,
|
|
78
|
+
"null": NullFirewall,
|
|
79
|
+
"disabled": NullFirewall, # Alias for null
|
|
80
|
+
# TODO: Add when implemented
|
|
81
|
+
# "firewalld": FirewalldManager,
|
|
82
|
+
# "iptables": IptablesManager,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if firewall_type not in managers:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Unknown firewall type: {firewall_type}. "
|
|
88
|
+
f"Available: {', '.join(managers.keys())}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
manager_class = managers[firewall_type]
|
|
92
|
+
manager = manager_class()
|
|
93
|
+
|
|
94
|
+
logging.debug(f"Created firewall manager: {firewall_type}")
|
|
95
|
+
return manager
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Null firewall manager implementation.
|
|
3
|
+
|
|
4
|
+
A no-op firewall manager for systems where firewall management is
|
|
5
|
+
disabled, not needed, or handled externally (e.g., Docker networking).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from wnm.firewall.base import FirewallManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NullFirewall(FirewallManager):
|
|
14
|
+
"""
|
|
15
|
+
No-op firewall manager that performs no actual firewall operations.
|
|
16
|
+
|
|
17
|
+
This is used when:
|
|
18
|
+
- Firewall management is disabled in configuration
|
|
19
|
+
- Running in Docker where port mapping handles exposure
|
|
20
|
+
- Firewall is managed externally
|
|
21
|
+
- Development/testing environments without firewall
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def enable_port(
|
|
25
|
+
self, port: int, protocol: str = "udp", comment: str = None
|
|
26
|
+
) -> bool:
|
|
27
|
+
"""
|
|
28
|
+
No-op port enable operation.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
port: Port number (ignored)
|
|
32
|
+
protocol: Protocol type (ignored)
|
|
33
|
+
comment: Comment (ignored)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Always True
|
|
37
|
+
"""
|
|
38
|
+
logging.debug(f"NullFirewall: Skipping enable for port {port}/{protocol}")
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
def disable_port(self, port: int, protocol: str = "udp") -> bool:
|
|
42
|
+
"""
|
|
43
|
+
No-op port disable operation.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
port: Port number (ignored)
|
|
47
|
+
protocol: Protocol type (ignored)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Always True
|
|
51
|
+
"""
|
|
52
|
+
logging.debug(f"NullFirewall: Skipping disable for port {port}/{protocol}")
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
def is_enabled(self) -> bool:
|
|
56
|
+
"""
|
|
57
|
+
Check if firewall is enabled.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Always False (null firewall is never "enabled")
|
|
61
|
+
"""
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
def is_available(self) -> bool:
|
|
65
|
+
"""
|
|
66
|
+
Check if firewall is available.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Always True (null firewall is always "available")
|
|
70
|
+
"""
|
|
71
|
+
return True
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
UFW (Uncomplicated Firewall) manager implementation.
|
|
3
|
+
|
|
4
|
+
Manages firewall ports using Ubuntu's ufw command-line tool.
|
|
5
|
+
Requires sudo privileges for port operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
|
|
12
|
+
from wnm.firewall.base import FirewallManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UFWManager(FirewallManager):
|
|
16
|
+
"""
|
|
17
|
+
Firewall manager for Ubuntu's Uncomplicated Firewall (ufw).
|
|
18
|
+
|
|
19
|
+
Uses 'sudo ufw' commands to manage port rules.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def enable_port(
|
|
23
|
+
self, port: int, protocol: str = "udp", comment: str = None
|
|
24
|
+
) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Open a firewall port using ufw.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
port: Port number to open
|
|
30
|
+
protocol: Protocol type (udp/tcp)
|
|
31
|
+
comment: Optional comment for the rule
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
True if port was opened successfully
|
|
35
|
+
"""
|
|
36
|
+
if not self.is_available():
|
|
37
|
+
logging.warning("UFW is not available on this system")
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
logging.info(f"Opening firewall port {port}/{protocol}")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
cmd = ["sudo", "ufw", "allow", f"{port}/{protocol}"]
|
|
44
|
+
if comment:
|
|
45
|
+
cmd.extend(["comment", comment])
|
|
46
|
+
|
|
47
|
+
subprocess.run(
|
|
48
|
+
cmd,
|
|
49
|
+
check=True,
|
|
50
|
+
capture_output=True,
|
|
51
|
+
text=True,
|
|
52
|
+
)
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
except (subprocess.CalledProcessError, Exception) as err:
|
|
56
|
+
logging.error(f"Failed to open firewall port: {err}")
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def disable_port(self, port: int, protocol: str = "udp") -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Close a firewall port using ufw.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
port: Port number to close
|
|
65
|
+
protocol: Protocol type (udp/tcp)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
True if port was closed successfully
|
|
69
|
+
"""
|
|
70
|
+
if not self.is_available():
|
|
71
|
+
logging.warning("UFW is not available on this system")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
logging.info(f"Closing firewall port {port}/{protocol}")
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
subprocess.run(
|
|
78
|
+
["sudo", "ufw", "delete", "allow", f"{port}/{protocol}"],
|
|
79
|
+
check=True,
|
|
80
|
+
capture_output=True,
|
|
81
|
+
text=True,
|
|
82
|
+
)
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
except (subprocess.CalledProcessError, Exception) as err:
|
|
86
|
+
logging.error(f"Failed to close firewall port: {err}")
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
def is_enabled(self) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Check if UFW is active.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True if ufw is active, False otherwise
|
|
95
|
+
"""
|
|
96
|
+
if not self.is_available():
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
["sudo", "ufw", "status"],
|
|
102
|
+
check=True,
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
)
|
|
106
|
+
return "Status: active" in result.stdout
|
|
107
|
+
|
|
108
|
+
except subprocess.CalledProcessError:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
def is_available(self) -> bool:
|
|
112
|
+
"""
|
|
113
|
+
Check if ufw is installed on the system.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
True if ufw command is available
|
|
117
|
+
"""
|
|
118
|
+
return shutil.which("ufw") is not None
|
wnm/migration.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration and node discovery utilities.
|
|
3
|
+
|
|
4
|
+
This module handles surveying existing nodes from process managers for
|
|
5
|
+
database initialization and migration from anm (Autonomi Node Manager).
|
|
6
|
+
|
|
7
|
+
These functions are only used during initialization, not regular operation.
|
|
8
|
+
The database is the source of truth for node management.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
from wnm.process_managers.factory import get_default_manager_type, get_process_manager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def survey_machine(machine_config, manager_type: str = None) -> list:
|
|
17
|
+
"""
|
|
18
|
+
Survey all nodes managed by the process manager.
|
|
19
|
+
|
|
20
|
+
This is used during database initialization/rebuild to discover
|
|
21
|
+
existing nodes. The specific process manager handles its own path
|
|
22
|
+
logic internally.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
machine_config: Machine configuration object
|
|
26
|
+
manager_type: Type of process manager ("systemd", "launchctl", etc.)
|
|
27
|
+
If None, auto-detects from platform
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of node dictionaries ready for database insertion
|
|
31
|
+
"""
|
|
32
|
+
if manager_type is None:
|
|
33
|
+
# Auto-detect manager type from platform
|
|
34
|
+
manager_type = get_default_manager_type()
|
|
35
|
+
|
|
36
|
+
logging.info(f"Surveying machine with {manager_type} manager")
|
|
37
|
+
|
|
38
|
+
# Get the appropriate process manager
|
|
39
|
+
manager = get_process_manager(manager_type)
|
|
40
|
+
|
|
41
|
+
# Use the manager's survey_nodes() method
|
|
42
|
+
return manager.survey_nodes(machine_config)
|