ssm-cli 1.1.0.dev3__tar.gz → 1.1.0.dev5__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.dev3/ssm_cli.egg-info → ssm_cli-1.1.0.dev5}/PKG-INFO +2 -1
  2. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/pyproject.toml +2 -1
  3. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/app.py +6 -2
  4. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/instances.py +2 -0
  5. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/forwarding.py +86 -5
  6. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/server.py +2 -1
  7. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5/ssm_cli.egg-info}/PKG-INFO +2 -1
  8. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli.egg-info/requires.txt +1 -0
  9. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/LICENCE +0 -0
  10. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/README.md +0 -0
  11. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/setup.cfg +0 -0
  12. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/__init__.py +0 -0
  13. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/__main__.py +0 -0
  14. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/aws.py +0 -0
  15. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/cli.py +0 -0
  16. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/click.py +0 -0
  17. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/config.py +0 -0
  18. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/logging.py +0 -0
  19. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/selectors/__init__.py +0 -0
  20. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/selectors/first.py +0 -0
  21. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/selectors/tui.py +0 -0
  22. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/__init__.py +0 -0
  23. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/channels.py +0 -0
  24. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/shell.py +0 -0
  25. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/socket.py +0 -0
  26. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ui.py +0 -0
  27. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/xdg.py +0 -0
  28. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli.egg-info/SOURCES.txt +0 -0
  29. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli.egg-info/dependency_links.txt +0 -0
  30. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli.egg-info/entry_points.txt +0 -0
  31. {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/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.dev3
3
+ Version: 1.1.0.dev5
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
 
@@ -109,8 +109,12 @@ def sshproxy(group):
109
109
 
110
110
  logger.info(f"connecting to {repr(instance)}")
111
111
 
112
- server = ssm_cli.ssh_proxy.server.SshServer(instance)
113
- server.start()
112
+ try:
113
+ server = ssm_cli.ssh_proxy.server.SshServer(instance)
114
+ server.start()
115
+ except Exception as e:
116
+ logger.info(f"hiding exceptions in sshproxy {e}")
117
+ server.event.set()
114
118
 
115
119
  @app.command(help="setups up ssm-cli, can be rerun safely")
116
120
  @click.option("--replace-config", is_flag=True, help="if we should replace existing config file")
@@ -153,8 +153,10 @@ class Instances:
153
153
  instances = sorted(self.list_instances(group_tag_value), key=lambda x: ip_as_int(x.ip))
154
154
  count = len(instances)
155
155
  if count == 1:
156
+ logger.info("only one instance found, selecting it automatically")
156
157
  return instances[0]
157
158
  if count < 1:
159
+ logger.error("no instances found")
158
160
  return
159
161
 
160
162
  if selector not in SELECTORS:
@@ -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,6 +210,7 @@ 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)
@@ -230,12 +232,52 @@ class PortForwardingManagerProcess(multiprocessing.Process):
230
232
  for logger_name, level in CONFIG.log.loggers.items():
231
233
  set_log_level(level, name=logger_name)
232
234
 
235
+
233
236
  logger.info("manager process started")
234
-
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_parent()
248
+ except Exception as e:
249
+ logger.error(f"failed to detatch from parent: {e}")
250
+
251
+ # cleanup handlers
252
+ import atexit
253
+ self.cleanup_called = False
254
+ atexit.register(self.cleanup)
255
+
235
256
  self.sessions = []
236
257
  self.pipe.send("ready")
237
258
  while True:
238
- 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.cleanup()
267
+
268
+ def cleanup(self):
269
+ """ Any cleanup tasks go here """
270
+ # ensure clean up only called once
271
+ if self.cleanup_called:
272
+ return
273
+ self.cleanup_called = True
274
+
275
+ logger.info("cleaning up")
276
+ self.close_all_sessions()
277
+
278
+ def process_messages(self):
279
+ try:
280
+ if self.pipe.poll(timeout=1.0):
239
281
  msg, data = self.pipe.recv()
240
282
  logger.debug(f"received {msg} {data} from proxy")
241
283
  reply = None
@@ -249,9 +291,12 @@ class PortForwardingManagerProcess(multiprocessing.Process):
249
291
  if reply is not None:
250
292
  logger.debug(f"sending {reply} to proxy")
251
293
  self.pipe.send(reply)
252
- except EOFError:
253
- break
254
-
294
+
295
+ return True
296
+ except (EOFError, OSError) as e:
297
+ logger.warning(f"pipe to proxy closed: {e}")
298
+ return False
299
+
255
300
  def open_session(self, host: str, remote_port: int):
256
301
  session = None
257
302
  for s in self.sessions:
@@ -274,6 +319,12 @@ class PortForwardingManagerProcess(multiprocessing.Process):
274
319
  if session.session_id == session_id:
275
320
  session.close()
276
321
  self.sessions.remove(session)
322
+ break
323
+
324
+ def close_all_sessions(self):
325
+ for session in self.sessions:
326
+ session.close()
327
+ self.sessions = []
277
328
 
278
329
  def is_open(self, session_id: str) -> bool:
279
330
  for session in self.sessions:
@@ -284,6 +335,36 @@ class PortForwardingManagerProcess(multiprocessing.Process):
284
335
  self.sessions.remove(session)
285
336
  break
286
337
  return False
338
+
339
+ def detatch_from_parent(self):
340
+ """Detach from the parent process to avoid zombies on unix like systems, this will need refining after more testing"""
341
+ if os.name != 'nt':
342
+ os.setsid()
343
+ else:
344
+ import ctypes
345
+ kernel32 = ctypes.windll.kernel32
346
+
347
+ # Set up independent console control handler
348
+ kernel32.SetConsoleCtrlHandler(None, True)
349
+
350
+ # Set independent process priority
351
+ current_process = kernel32.GetCurrentProcess()
352
+ NORMAL_PRIORITY_CLASS = 0x00000020
353
+ kernel32.SetPriorityClass(current_process, NORMAL_PRIORITY_CLASS)
354
+
355
+ # Detach from console if present
356
+ console_window = kernel32.GetConsoleWindow()
357
+ if console_window:
358
+ kernel32.FreeConsole()
359
+
360
+ # Break out of job object (most important)
361
+ ntdll = ctypes.windll.ntdll
362
+ ntdll.NtSetInformationProcess(
363
+ kernel32.GetCurrentProcess(),
364
+ 0x11,
365
+ ctypes.byref(ctypes.c_ulong(0)),
366
+ ctypes.sizeof(ctypes.c_ulong)
367
+ )
287
368
 
288
369
  def get_free_port(bind_host="127.0.0.1"):
289
370
  """
@@ -44,7 +44,8 @@ class SshServer(paramiko.ServerInterface):
44
44
 
45
45
  self.port_forwarding_manager.start()
46
46
  self.transport.start_server(server=self)
47
-
47
+
48
+ logger.info("Handshake complete, entering main loop")
48
49
  self.event.wait()
49
50
 
50
51
  # Auth handlers, just allow anything. The only use of this code is ProxyCommand and auth is not needed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssm-cli
3
- Version: 1.1.0.dev3
3
+ Version: 1.1.0.dev5
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