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.
- {ssm_cli-1.1.0.dev3/ssm_cli.egg-info → ssm_cli-1.1.0.dev5}/PKG-INFO +2 -1
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/pyproject.toml +2 -1
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/app.py +6 -2
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/instances.py +2 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/forwarding.py +86 -5
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/server.py +2 -1
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5/ssm_cli.egg-info}/PKG-INFO +2 -1
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli.egg-info/requires.txt +1 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/LICENCE +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/README.md +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/setup.cfg +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/__init__.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/__main__.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/aws.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/cli.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/click.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/config.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/logging.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/selectors/__init__.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/selectors/first.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/selectors/tui.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/__init__.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/channels.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/shell.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ssh_proxy/socket.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/ui.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli/xdg.py +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli.egg-info/SOURCES.txt +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli.egg-info/dependency_links.txt +0 -0
- {ssm_cli-1.1.0.dev3 → ssm_cli-1.1.0.dev5}/ssm_cli.egg-info/entry_points.txt +0 -0
- {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.
|
|
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
|
|
@@ -109,8 +109,12 @@ def sshproxy(group):
|
|
|
109
109
|
|
|
110
110
|
logger.info(f"connecting to {repr(instance)}")
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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.
|
|
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
|
|
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
|