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.
Files changed (31) hide show
  1. {ssm_cli-1.1.0.dev2/ssm_cli.egg-info → ssm_cli-1.1.0.dev4}/PKG-INFO +2 -1
  2. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/pyproject.toml +2 -1
  3. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/logging.py +1 -2
  4. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/forwarding.py +94 -7
  5. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4/ssm_cli.egg-info}/PKG-INFO +2 -1
  6. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/requires.txt +1 -0
  7. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/LICENCE +0 -0
  8. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/README.md +0 -0
  9. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/setup.cfg +0 -0
  10. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/__init__.py +0 -0
  11. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/__main__.py +0 -0
  12. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/app.py +0 -0
  13. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/aws.py +0 -0
  14. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/cli.py +0 -0
  15. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/click.py +0 -0
  16. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/config.py +0 -0
  17. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/instances.py +0 -0
  18. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/selectors/__init__.py +0 -0
  19. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/selectors/first.py +0 -0
  20. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/selectors/tui.py +0 -0
  21. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/__init__.py +0 -0
  22. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/channels.py +0 -0
  23. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/server.py +0 -0
  24. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/shell.py +0 -0
  25. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ssh_proxy/socket.py +0 -0
  26. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/ui.py +0 -0
  27. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli/xdg.py +0 -0
  28. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/SOURCES.txt +0 -0
  29. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/dependency_links.txt +0 -0
  30. {ssm_cli-1.1.0.dev2 → ssm_cli-1.1.0.dev4}/ssm_cli.egg-info/entry_points.txt +0 -0
  31. {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.dev2
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
@@ -22,7 +22,8 @@ dependencies = [
22
22
  "rich",
23
23
  "confclasses>=0.3.2",
24
24
  "xdg_base_dirs",
25
- "rich-click"
25
+ "rich-click",
26
+ "psutil"
26
27
  ]
27
28
  dynamic = ["version"]
28
29
 
@@ -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
- with io.StringIO(self.config_yaml) as f:
230
- load(CONFIG, f)
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
- try:
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
- except EOFError:
249
- break
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.dev2
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
@@ -4,3 +4,4 @@ rich
4
4
  confclasses>=0.3.2
5
5
  xdg_base_dirs
6
6
  rich-click
7
+ psutil
File without changes
File without changes
File without changes
File without changes