ssm-cli 1.1.0.dev2__tar.gz → 1.1.0.dev4__tar.gz
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.
- {ssm_cli-1.1.0.dev2/ssm_cli.egg-info → ssm_cli-1.1.0.dev4}/PKG-INFO +2 -1
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/pyproject.toml +2 -1
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/logging.py +1 -2
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/forwarding.py +94 -7
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4/ssm_cli.egg-info}/PKG-INFO +2 -1
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/requires.txt +1 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/LICENCE +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/README.md +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/setup.cfg +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/__init__.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/__main__.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/app.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/aws.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/cli.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/click.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/config.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/instances.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/selectors/__init__.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/selectors/first.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/selectors/tui.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/__init__.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/channels.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/server.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/shell.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/socket.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ui.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/xdg.py +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/SOURCES.txt +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/dependency_links.txt +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/entry_points.txt +0 -0
- {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssm-cli
|
|
3
|
-
Version: 1.1.0.
|
|
3
|
+
Version: 1.1.0.dev4
|
|
4
4
|
Summary: CLI tool to help with SSM functionality, aimed at adminstrators
|
|
5
5
|
Author-email: Simon Fletcher <simon.fletcher@lexisnexisrisk.com>
|
|
6
6
|
License: MIT License
|
|
@@ -39,6 +39,7 @@ Requires-Dist: rich
|
|
|
39
39
|
Requires-Dist: confclasses>=0.3.2
|
|
40
40
|
Requires-Dist: xdg_base_dirs
|
|
41
41
|
Requires-Dist: rich-click
|
|
42
|
+
Requires-Dist: psutil
|
|
42
43
|
Dynamic: license-file
|
|
43
44
|
|
|
44
45
|
# SSM CLI
|
|
@@ -20,9 +20,8 @@ def setup_logging(name:str = "cli"):
|
|
|
20
20
|
|
|
21
21
|
def set_log_level(level: str, name: Optional[str] = None):
|
|
22
22
|
"""Configure logger level."""
|
|
23
|
-
logger.debug(f"setting logger {name} to {level}")
|
|
24
23
|
logging.getLogger(name).setLevel(level.upper())
|
|
25
|
-
|
|
24
|
+
logger.debug(f"setting logger {name} to {level}")
|
|
26
25
|
|
|
27
26
|
def cleanup_old_logs(days_to_keep: int = 7):
|
|
28
27
|
"""Clean up log files older than specified days. Runs with no error handling."""
|
|
@@ -15,6 +15,7 @@ The manager side is the code that runs in a separate process and is responsible
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import io
|
|
18
|
+
import os
|
|
18
19
|
import socket
|
|
19
20
|
from typing import Dict, Any, List
|
|
20
21
|
import time
|
|
@@ -209,11 +210,12 @@ class PortForwardingManagerProcess(multiprocessing.Process):
|
|
|
209
210
|
def __init__(self, pipe: Connection, instance: Instance):
|
|
210
211
|
self.pipe = pipe
|
|
211
212
|
self.instance = instance
|
|
213
|
+
self.parent_pid = os.getppid()
|
|
212
214
|
|
|
213
215
|
with io.StringIO() as f:
|
|
214
216
|
save(CONFIG, f)
|
|
215
217
|
self.config_yaml = f.getvalue()
|
|
216
|
-
|
|
218
|
+
|
|
217
219
|
super().__init__()
|
|
218
220
|
|
|
219
221
|
def run(self):
|
|
@@ -222,16 +224,62 @@ class PortForwardingManagerProcess(multiprocessing.Process):
|
|
|
222
224
|
# when config/args are refactored, this should be taken into account.
|
|
223
225
|
from ssm_cli.logging import setup_logging, set_log_level
|
|
224
226
|
setup_logging("manager")
|
|
227
|
+
|
|
228
|
+
with io.StringIO(self.config_yaml) as f:
|
|
229
|
+
load(CONFIG, f)
|
|
230
|
+
|
|
225
231
|
set_log_level(CONFIG.log.level)
|
|
226
232
|
for logger_name, level in CONFIG.log.loggers.items():
|
|
227
233
|
set_log_level(level, name=logger_name)
|
|
228
234
|
|
|
229
|
-
|
|
230
|
-
|
|
235
|
+
|
|
236
|
+
logger.info("manager process started")
|
|
237
|
+
|
|
238
|
+
# get parent process object
|
|
239
|
+
import psutil
|
|
240
|
+
try:
|
|
241
|
+
self.parent = psutil.Process(self.parent_pid)
|
|
242
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
|
|
243
|
+
logger.warning(f"Could not get parent process object: {e}")
|
|
244
|
+
|
|
245
|
+
# detaching from parent
|
|
246
|
+
try:
|
|
247
|
+
self.detatch_from_proxy()
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.error(f"failed to detatch from proxy: {e}")
|
|
250
|
+
|
|
251
|
+
# cleanup handlers
|
|
252
|
+
import atexit
|
|
253
|
+
self.cleanup_called = False
|
|
254
|
+
atexit.register(self.safe_cleanup)
|
|
255
|
+
|
|
231
256
|
self.sessions = []
|
|
232
257
|
self.pipe.send("ready")
|
|
233
258
|
while True:
|
|
234
|
-
|
|
259
|
+
if not self.parent.is_running():
|
|
260
|
+
logger.info("proxy process is no longer running, exiting manager process")
|
|
261
|
+
break
|
|
262
|
+
if not self.process_messages():
|
|
263
|
+
logger.info("proxy pipe closed, exiting manager process")
|
|
264
|
+
break
|
|
265
|
+
|
|
266
|
+
self.safe_cleanup()
|
|
267
|
+
|
|
268
|
+
def safe_cleanup(self):
|
|
269
|
+
""" Used to ensure cleanup only called once """
|
|
270
|
+
if self.cleanup_called:
|
|
271
|
+
return
|
|
272
|
+
self.cleanup_called = True
|
|
273
|
+
self.cleanup()
|
|
274
|
+
|
|
275
|
+
def cleanup(self):
|
|
276
|
+
""" Any cleanup tasks go here """
|
|
277
|
+
logger.info("cleaning up")
|
|
278
|
+
self.close_all_sessions()
|
|
279
|
+
|
|
280
|
+
def process_messages(self):
|
|
281
|
+
try:
|
|
282
|
+
if self.pipe.poll(timeout=1.0):
|
|
235
283
|
msg, data = self.pipe.recv()
|
|
236
284
|
logger.debug(f"received {msg} {data} from proxy")
|
|
237
285
|
reply = None
|
|
@@ -245,9 +293,12 @@ class PortForwardingManagerProcess(multiprocessing.Process):
|
|
|
245
293
|
if reply is not None:
|
|
246
294
|
logger.debug(f"sending {reply} to proxy")
|
|
247
295
|
self.pipe.send(reply)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
296
|
+
|
|
297
|
+
return True
|
|
298
|
+
except (EOFError, OSError) as e:
|
|
299
|
+
logger.warning(f"pipe to proxy closed: {e}")
|
|
300
|
+
return False
|
|
301
|
+
|
|
251
302
|
def open_session(self, host: str, remote_port: int):
|
|
252
303
|
session = None
|
|
253
304
|
for s in self.sessions:
|
|
@@ -270,6 +321,12 @@ class PortForwardingManagerProcess(multiprocessing.Process):
|
|
|
270
321
|
if session.session_id == session_id:
|
|
271
322
|
session.close()
|
|
272
323
|
self.sessions.remove(session)
|
|
324
|
+
break
|
|
325
|
+
|
|
326
|
+
def close_all_sessions(self):
|
|
327
|
+
for session in self.sessions:
|
|
328
|
+
session.close()
|
|
329
|
+
self.sessions = []
|
|
273
330
|
|
|
274
331
|
def is_open(self, session_id: str) -> bool:
|
|
275
332
|
for session in self.sessions:
|
|
@@ -280,6 +337,36 @@ class PortForwardingManagerProcess(multiprocessing.Process):
|
|
|
280
337
|
self.sessions.remove(session)
|
|
281
338
|
break
|
|
282
339
|
return False
|
|
340
|
+
|
|
341
|
+
def detatch_from_proxy(self):
|
|
342
|
+
"""Detach from the parent process to avoid zombies on unix like systems, this will need refining after more testing"""
|
|
343
|
+
if os.name != 'nt':
|
|
344
|
+
os.setsid()
|
|
345
|
+
else:
|
|
346
|
+
import ctypes
|
|
347
|
+
kernel32 = ctypes.windll.kernel32
|
|
348
|
+
|
|
349
|
+
# Set up independent console control handler
|
|
350
|
+
kernel32.SetConsoleCtrlHandler(None, True)
|
|
351
|
+
|
|
352
|
+
# Set independent process priority
|
|
353
|
+
current_process = kernel32.GetCurrentProcess()
|
|
354
|
+
NORMAL_PRIORITY_CLASS = 0x00000020
|
|
355
|
+
kernel32.SetPriorityClass(current_process, NORMAL_PRIORITY_CLASS)
|
|
356
|
+
|
|
357
|
+
# Detach from console if present
|
|
358
|
+
console_window = kernel32.GetConsoleWindow()
|
|
359
|
+
if console_window:
|
|
360
|
+
kernel32.FreeConsole()
|
|
361
|
+
|
|
362
|
+
# Break out of job object (most important)
|
|
363
|
+
ntdll = ctypes.windll.ntdll
|
|
364
|
+
ntdll.NtSetInformationProcess(
|
|
365
|
+
kernel32.GetCurrentProcess(),
|
|
366
|
+
0x11,
|
|
367
|
+
ctypes.byref(ctypes.c_ulong(0)),
|
|
368
|
+
ctypes.sizeof(ctypes.c_ulong)
|
|
369
|
+
)
|
|
283
370
|
|
|
284
371
|
def get_free_port(bind_host="127.0.0.1"):
|
|
285
372
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssm-cli
|
|
3
|
-
Version: 1.1.0.
|
|
3
|
+
Version: 1.1.0.dev4
|
|
4
4
|
Summary: CLI tool to help with SSM functionality, aimed at adminstrators
|
|
5
5
|
Author-email: Simon Fletcher <simon.fletcher@lexisnexisrisk.com>
|
|
6
6
|
License: MIT License
|
|
@@ -39,6 +39,7 @@ Requires-Dist: rich
|
|
|
39
39
|
Requires-Dist: confclasses>=0.3.2
|
|
40
40
|
Requires-Dist: xdg_base_dirs
|
|
41
41
|
Requires-Dist: rich-click
|
|
42
|
+
Requires-Dist: psutil
|
|
42
43
|
Dynamic: license-file
|
|
43
44
|
|
|
44
45
|
# SSM CLI
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|