mcp-ssh-vps 0.4.1__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.
Files changed (47) hide show
  1. mcp_ssh_vps-0.4.1.dist-info/METADATA +482 -0
  2. mcp_ssh_vps-0.4.1.dist-info/RECORD +47 -0
  3. mcp_ssh_vps-0.4.1.dist-info/WHEEL +5 -0
  4. mcp_ssh_vps-0.4.1.dist-info/entry_points.txt +4 -0
  5. mcp_ssh_vps-0.4.1.dist-info/licenses/LICENSE +21 -0
  6. mcp_ssh_vps-0.4.1.dist-info/top_level.txt +1 -0
  7. sshmcp/__init__.py +3 -0
  8. sshmcp/cli.py +473 -0
  9. sshmcp/config.py +155 -0
  10. sshmcp/core/__init__.py +5 -0
  11. sshmcp/core/container.py +291 -0
  12. sshmcp/models/__init__.py +15 -0
  13. sshmcp/models/command.py +69 -0
  14. sshmcp/models/file.py +102 -0
  15. sshmcp/models/machine.py +139 -0
  16. sshmcp/monitoring/__init__.py +0 -0
  17. sshmcp/monitoring/alerts.py +464 -0
  18. sshmcp/prompts/__init__.py +7 -0
  19. sshmcp/prompts/backup.py +151 -0
  20. sshmcp/prompts/deploy.py +115 -0
  21. sshmcp/prompts/monitor.py +146 -0
  22. sshmcp/resources/__init__.py +7 -0
  23. sshmcp/resources/logs.py +99 -0
  24. sshmcp/resources/metrics.py +204 -0
  25. sshmcp/resources/status.py +160 -0
  26. sshmcp/security/__init__.py +7 -0
  27. sshmcp/security/audit.py +314 -0
  28. sshmcp/security/rate_limiter.py +221 -0
  29. sshmcp/security/totp.py +392 -0
  30. sshmcp/security/validator.py +234 -0
  31. sshmcp/security/whitelist.py +169 -0
  32. sshmcp/server.py +632 -0
  33. sshmcp/ssh/__init__.py +6 -0
  34. sshmcp/ssh/async_client.py +247 -0
  35. sshmcp/ssh/client.py +464 -0
  36. sshmcp/ssh/executor.py +79 -0
  37. sshmcp/ssh/forwarding.py +368 -0
  38. sshmcp/ssh/pool.py +343 -0
  39. sshmcp/ssh/shell.py +518 -0
  40. sshmcp/ssh/transfer.py +461 -0
  41. sshmcp/tools/__init__.py +13 -0
  42. sshmcp/tools/commands.py +226 -0
  43. sshmcp/tools/files.py +220 -0
  44. sshmcp/tools/helpers.py +321 -0
  45. sshmcp/tools/history.py +372 -0
  46. sshmcp/tools/processes.py +214 -0
  47. sshmcp/tools/servers.py +484 -0
@@ -0,0 +1,169 @@
1
+ """Command whitelist management."""
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING, Dict, List, Pattern
5
+
6
+ import structlog
7
+
8
+ if TYPE_CHECKING:
9
+ from sshmcp.models.machine import MachinesConfig
10
+
11
+ logger = structlog.get_logger()
12
+
13
+
14
+ class CommandWhitelist:
15
+ """
16
+ Manages command whitelist patterns with compiled regex caching.
17
+ """
18
+
19
+ def __init__(self) -> None:
20
+ """Initialize whitelist manager."""
21
+ self._patterns: Dict[str, List[Pattern[str]]] = {}
22
+ self._forbidden: Dict[str, List[Pattern[str]]] = {}
23
+
24
+ def load_patterns(
25
+ self,
26
+ machine_name: str,
27
+ allowed: List[str],
28
+ forbidden: List[str],
29
+ ) -> None:
30
+ """
31
+ Load and compile patterns for a machine.
32
+
33
+ Args:
34
+ machine_name: Name of the machine.
35
+ allowed: List of allowed command patterns.
36
+ forbidden: List of forbidden command patterns.
37
+ """
38
+ self._patterns[machine_name] = []
39
+ self._forbidden[machine_name] = []
40
+
41
+ for pattern in allowed:
42
+ try:
43
+ compiled = re.compile(pattern, re.IGNORECASE)
44
+ self._patterns[machine_name].append(compiled)
45
+ except re.error as e:
46
+ logger.error(
47
+ "invalid_allowed_pattern",
48
+ machine=machine_name,
49
+ pattern=pattern,
50
+ error=str(e),
51
+ )
52
+
53
+ for pattern in forbidden:
54
+ try:
55
+ compiled = re.compile(pattern, re.IGNORECASE)
56
+ self._forbidden[machine_name].append(compiled)
57
+ except re.error as e:
58
+ logger.error(
59
+ "invalid_forbidden_pattern",
60
+ machine=machine_name,
61
+ pattern=pattern,
62
+ error=str(e),
63
+ )
64
+
65
+ logger.info(
66
+ "whitelist_loaded",
67
+ machine=machine_name,
68
+ allowed_count=len(self._patterns[machine_name]),
69
+ forbidden_count=len(self._forbidden[machine_name]),
70
+ )
71
+
72
+ def is_allowed(self, machine_name: str, command: str) -> bool:
73
+ """
74
+ Check if command is allowed for machine.
75
+
76
+ Args:
77
+ machine_name: Name of the machine.
78
+ command: Command to check.
79
+
80
+ Returns:
81
+ True if command is allowed.
82
+ """
83
+ command = command.strip()
84
+
85
+ # Check forbidden first
86
+ if machine_name in self._forbidden:
87
+ for pattern in self._forbidden[machine_name]:
88
+ if pattern.match(command):
89
+ return False
90
+
91
+ # If no allowed patterns, allow all (except forbidden)
92
+ if machine_name not in self._patterns or not self._patterns[machine_name]:
93
+ return True
94
+
95
+ # Check allowed patterns
96
+ for pattern in self._patterns[machine_name]:
97
+ if pattern.match(command):
98
+ return True
99
+
100
+ return False
101
+
102
+ def is_forbidden(self, machine_name: str, command: str) -> bool:
103
+ """
104
+ Check if command is explicitly forbidden.
105
+
106
+ Args:
107
+ machine_name: Name of the machine.
108
+ command: Command to check.
109
+
110
+ Returns:
111
+ True if command is forbidden.
112
+ """
113
+ if machine_name not in self._forbidden:
114
+ return False
115
+
116
+ command = command.strip()
117
+ for pattern in self._forbidden[machine_name]:
118
+ if pattern.match(command):
119
+ return True
120
+
121
+ return False
122
+
123
+ def get_patterns(self, machine_name: str) -> Dict[str, int]:
124
+ """
125
+ Get pattern counts for a machine.
126
+
127
+ Args:
128
+ machine_name: Name of the machine.
129
+
130
+ Returns:
131
+ Dictionary with allowed and forbidden pattern counts.
132
+ """
133
+ return {
134
+ "allowed": len(self._patterns.get(machine_name, [])),
135
+ "forbidden": len(self._forbidden.get(machine_name, [])),
136
+ }
137
+
138
+
139
+ # Global whitelist instance
140
+ _whitelist: CommandWhitelist | None = None
141
+
142
+
143
+ def get_whitelist() -> CommandWhitelist:
144
+ """Get or create the global whitelist."""
145
+ global _whitelist
146
+ if _whitelist is None:
147
+ _whitelist = CommandWhitelist()
148
+ return _whitelist
149
+
150
+
151
+ def init_whitelist(config: "MachinesConfig") -> CommandWhitelist: # type: ignore
152
+ """
153
+ Initialize whitelist from configuration.
154
+
155
+ Args:
156
+ config: MachinesConfig with machine definitions.
157
+
158
+ Returns:
159
+ Initialized CommandWhitelist.
160
+ """
161
+
162
+ whitelist = get_whitelist()
163
+ for machine in config.machines:
164
+ whitelist.load_patterns(
165
+ machine.name,
166
+ machine.security.allowed_commands,
167
+ machine.security.forbidden_commands,
168
+ )
169
+ return whitelist