tunnel-manager 1.0.0__py3-none-any.whl → 1.0.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.
Potentially problematic release.
This version of tunnel-manager might be problematic. Click here for more details.
- tests/test_tunnel.py +3 -4
- tunnel_manager/tunnel_manager.py +138 -39
- tunnel_manager/tunnel_manager_mcp.py +680 -181
- {tunnel_manager-1.0.0.dist-info → tunnel_manager-1.0.1.dist-info}/METADATA +106 -57
- tunnel_manager-1.0.1.dist-info/RECORD +11 -0
- tunnel_manager-1.0.0.dist-info/RECORD +0 -11
- {tunnel_manager-1.0.0.dist-info → tunnel_manager-1.0.1.dist-info}/WHEEL +0 -0
- {tunnel_manager-1.0.0.dist-info → tunnel_manager-1.0.1.dist-info}/entry_points.txt +0 -0
- {tunnel_manager-1.0.0.dist-info → tunnel_manager-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {tunnel_manager-1.0.0.dist-info → tunnel_manager-1.0.1.dist-info}/top_level.txt +0 -0
tests/test_tunnel.py
CHANGED
|
@@ -14,7 +14,6 @@ def test_password_authentication():
|
|
|
14
14
|
remote_host="10.0.0.11",
|
|
15
15
|
username=username,
|
|
16
16
|
password=password,
|
|
17
|
-
log_file="tunnel_password.log",
|
|
18
17
|
)
|
|
19
18
|
|
|
20
19
|
# Connect to the remote host
|
|
@@ -29,10 +28,11 @@ def test_password_authentication():
|
|
|
29
28
|
print(f"Command output: {out}")
|
|
30
29
|
# Example file transfer (uncomment to test, ensure files exist)
|
|
31
30
|
tunnel.send_file(
|
|
32
|
-
"
|
|
31
|
+
"/home/genius/Development/inventory/inventory.yml",
|
|
32
|
+
"/home/genius/Downloads/remote_test.txt",
|
|
33
33
|
)
|
|
34
34
|
tunnel.receive_file(
|
|
35
|
-
"/home/genius/Downloads/remote_test.txt", "./tests/
|
|
35
|
+
"/home/genius/Downloads/remote_test.txt", "./tests/downloaded_inventory.txt"
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
tunnel.close()
|
|
@@ -49,7 +49,6 @@ def test_key_authentication():
|
|
|
49
49
|
remote_host="10.0.0.11",
|
|
50
50
|
username=username,
|
|
51
51
|
identity_file=os.path.expanduser("~/.ssh/id_rsa"),
|
|
52
|
-
log_file="tunnel_key.log",
|
|
53
52
|
)
|
|
54
53
|
|
|
55
54
|
# Connect to the remote host
|
tunnel_manager/tunnel_manager.py
CHANGED
|
@@ -68,9 +68,6 @@ class Tunnel:
|
|
|
68
68
|
raise ValueError("Either identity_file or password must be provided.")
|
|
69
69
|
|
|
70
70
|
def connect(self):
|
|
71
|
-
"""
|
|
72
|
-
Establish the SSH connection if not already connected.
|
|
73
|
-
"""
|
|
74
71
|
if (
|
|
75
72
|
self.ssh_client
|
|
76
73
|
and self.ssh_client.get_transport()
|
|
@@ -88,7 +85,18 @@ class Tunnel:
|
|
|
88
85
|
|
|
89
86
|
try:
|
|
90
87
|
if self.identity_file:
|
|
91
|
-
|
|
88
|
+
# Try loading as ED25519 key first
|
|
89
|
+
try:
|
|
90
|
+
private_key = paramiko.Ed25519Key.from_private_key_file(
|
|
91
|
+
self.identity_file
|
|
92
|
+
)
|
|
93
|
+
self.logger.info(f"Loaded ED25519 key from: {self.identity_file}")
|
|
94
|
+
except paramiko.ssh_exception.SSHException:
|
|
95
|
+
# Fallback to RSA key
|
|
96
|
+
private_key = paramiko.RSAKey.from_private_key_file(
|
|
97
|
+
self.identity_file
|
|
98
|
+
)
|
|
99
|
+
self.logger.info(f"Loaded RSA key from: {self.identity_file}")
|
|
92
100
|
if self.certificate_file:
|
|
93
101
|
private_key.load_certificate(self.certificate_file)
|
|
94
102
|
self.logger.info(f"Loaded certificate: {self.certificate_file}")
|
|
@@ -141,18 +149,60 @@ class Tunnel:
|
|
|
141
149
|
def send_file(self, local_path, remote_path):
|
|
142
150
|
"""
|
|
143
151
|
Send (upload) a file to the remote host.
|
|
144
|
-
|
|
145
152
|
:param local_path: Path to the local file.
|
|
146
153
|
:param remote_path: Path on the remote host.
|
|
147
154
|
"""
|
|
148
155
|
self.connect()
|
|
149
156
|
try:
|
|
157
|
+
# Normalize paths for consistency
|
|
158
|
+
local_path = os.path.abspath(os.path.expanduser(local_path))
|
|
159
|
+
remote_path = os.path.expanduser(
|
|
160
|
+
remote_path
|
|
161
|
+
) # ~ expansion for remote, but paramiko handles it
|
|
162
|
+
|
|
163
|
+
self.logger.debug(
|
|
164
|
+
f"send_file: local_path='{local_path}', remote_path='{remote_path}'"
|
|
165
|
+
)
|
|
166
|
+
self.logger.debug(f"send_file: CWD={os.getcwd()}")
|
|
167
|
+
|
|
168
|
+
# Explicit checks before SFTP
|
|
169
|
+
if not os.path.exists(local_path):
|
|
170
|
+
err_msg = f"Local file does not exist: {local_path}"
|
|
171
|
+
self.logger.error(err_msg)
|
|
172
|
+
raise IOError(err_msg)
|
|
173
|
+
if not os.path.isfile(local_path):
|
|
174
|
+
err_msg = (
|
|
175
|
+
f"Local path is not a regular file (dir/symlink?): {local_path}"
|
|
176
|
+
)
|
|
177
|
+
self.logger.error(err_msg)
|
|
178
|
+
raise IOError(err_msg)
|
|
179
|
+
if not os.access(local_path, os.R_OK):
|
|
180
|
+
err_msg = f"No read permission for local file: {local_path}"
|
|
181
|
+
self.logger.error(err_msg)
|
|
182
|
+
raise PermissionError(err_msg)
|
|
183
|
+
|
|
184
|
+
# Test binary open (mimics what sftp.put does)
|
|
185
|
+
try:
|
|
186
|
+
with open(local_path, "rb") as f:
|
|
187
|
+
sample = f.read(1024) # Read a chunk to simulate transfer
|
|
188
|
+
self.logger.debug(
|
|
189
|
+
f"Binary open successful for {local_path}, sample size: {len(sample)} bytes"
|
|
190
|
+
)
|
|
191
|
+
except Exception as open_err:
|
|
192
|
+
err_msg = f"Failed to open {local_path} in binary mode: {str(open_err)}"
|
|
193
|
+
self.logger.error(err_msg)
|
|
194
|
+
raise IOError(err_msg)
|
|
195
|
+
|
|
150
196
|
if not self.sftp:
|
|
151
197
|
self.sftp = self.ssh_client.open_sftp()
|
|
198
|
+
self.logger.debug(f"Opening SFTP for put: {local_path} -> {remote_path}")
|
|
152
199
|
self.sftp.put(local_path, remote_path)
|
|
153
200
|
self.logger.info(f"File sent: {local_path} -> {remote_path}")
|
|
154
201
|
except Exception as e:
|
|
155
|
-
self.logger.error(f"File send failed: {str(e)}")
|
|
202
|
+
self.logger.error(f"File send failed: {str(e)} (type: {type(e).__name__})")
|
|
203
|
+
import traceback
|
|
204
|
+
|
|
205
|
+
self.logger.error(traceback.format_exc())
|
|
156
206
|
raise
|
|
157
207
|
finally:
|
|
158
208
|
if self.sftp:
|
|
@@ -234,13 +284,14 @@ class Tunnel:
|
|
|
234
284
|
self.ssh_client = None
|
|
235
285
|
|
|
236
286
|
def setup_passwordless_ssh(
|
|
237
|
-
self, local_key_path=os.path.expanduser("~/.ssh/id_rsa")
|
|
287
|
+
self, local_key_path=os.path.expanduser("~/.ssh/id_rsa"), key_type="ed25519"
|
|
238
288
|
):
|
|
239
289
|
"""
|
|
240
290
|
Set up passwordless SSH by copying a public key to the remote host.
|
|
241
291
|
Requires password-based authentication to be configured.
|
|
242
292
|
|
|
243
293
|
:param local_key_path: Path to the local private key (public key is assumed to be .pub).
|
|
294
|
+
:param key_type: Type of key to generate ('rsa' or 'ed25519', default: 'rsa').
|
|
244
295
|
"""
|
|
245
296
|
if not self.password:
|
|
246
297
|
raise ValueError("Password-based authentication required for setup.")
|
|
@@ -248,9 +299,17 @@ class Tunnel:
|
|
|
248
299
|
local_key_path = os.path.expanduser(local_key_path)
|
|
249
300
|
pub_key_path = local_key_path + ".pub"
|
|
250
301
|
|
|
302
|
+
if key_type not in ["rsa", "ed25519"]:
|
|
303
|
+
raise ValueError("key_type must be 'rsa' or 'ed25519'")
|
|
304
|
+
|
|
251
305
|
if not os.path.exists(pub_key_path):
|
|
252
|
-
|
|
253
|
-
|
|
306
|
+
if key_type == "rsa":
|
|
307
|
+
os.system(f"ssh-keygen -t rsa -b 4096 -f {local_key_path} -N ''")
|
|
308
|
+
else: # ed25519
|
|
309
|
+
os.system(f"ssh-keygen -t ed25519 -f {local_key_path} -N ''")
|
|
310
|
+
self.logger.info(
|
|
311
|
+
f"Generated {key_type} key pair: {local_key_path}, {pub_key_path}"
|
|
312
|
+
)
|
|
254
313
|
|
|
255
314
|
with open(pub_key_path, "r") as f:
|
|
256
315
|
pub_key = f.read().strip()
|
|
@@ -261,7 +320,7 @@ class Tunnel:
|
|
|
261
320
|
self.run_command(f"echo '{pub_key}' >> ~/.ssh/authorized_keys")
|
|
262
321
|
self.run_command("chmod 600 ~/.ssh/authorized_keys")
|
|
263
322
|
self.logger.info(
|
|
264
|
-
f"Set up passwordless SSH for {self.username}@{self.remote_host}"
|
|
323
|
+
f"Set up passwordless SSH for {self.username}@{self.remote_host} with {key_type} key"
|
|
265
324
|
)
|
|
266
325
|
except Exception as e:
|
|
267
326
|
self.logger.error(f"Failed to set up passwordless SSH: {str(e)}")
|
|
@@ -270,7 +329,9 @@ class Tunnel:
|
|
|
270
329
|
self.close()
|
|
271
330
|
|
|
272
331
|
@staticmethod
|
|
273
|
-
def
|
|
332
|
+
def execute_on_inventory(
|
|
333
|
+
inventory, func, group="all", parallel=False, max_threads=5
|
|
334
|
+
):
|
|
274
335
|
"""
|
|
275
336
|
Execute a function on all hosts in the specified group of the YAML inventory, sequentially or in parallel.
|
|
276
337
|
:param inventory: Path to the YAML inventory file.
|
|
@@ -400,16 +461,23 @@ class Tunnel:
|
|
|
400
461
|
f"Copied SSH config to {remote_config_path} on {self.remote_host}"
|
|
401
462
|
)
|
|
402
463
|
|
|
403
|
-
def rotate_ssh_key(self, new_key_path):
|
|
464
|
+
def rotate_ssh_key(self, new_key_path, key_type="ed25519"):
|
|
404
465
|
"""
|
|
405
466
|
Rotate the SSH key by generating a new pair and updating authorized_keys.
|
|
406
467
|
:param new_key_path: Path for the new private key.
|
|
468
|
+
:param key_type: Type of key to generate ('rsa' or 'ed25519', default: 'rsa').
|
|
407
469
|
"""
|
|
408
470
|
new_key_path = os.path.expanduser(new_key_path)
|
|
409
471
|
new_pub_path = new_key_path + ".pub"
|
|
472
|
+
if key_type not in ["rsa", "ed25519"]:
|
|
473
|
+
raise ValueError("key_type must be 'rsa' or 'ed25519'")
|
|
474
|
+
|
|
410
475
|
if not os.path.exists(new_key_path):
|
|
411
|
-
|
|
412
|
-
|
|
476
|
+
if key_type == "rsa":
|
|
477
|
+
os.system(f"ssh-keygen -t rsa -b 4096 -f {new_key_path} -N ''")
|
|
478
|
+
else: # ed25519
|
|
479
|
+
os.system(f"ssh-keygen -t ed25519 -f {new_key_path} -N ''")
|
|
480
|
+
self.logger.info(f"Generated new {key_type} key pair: {new_key_path}")
|
|
413
481
|
|
|
414
482
|
with open(new_pub_path, "r") as f:
|
|
415
483
|
new_pub = f.read().strip()
|
|
@@ -433,7 +501,6 @@ class Tunnel:
|
|
|
433
501
|
new_auth.append(new_pub)
|
|
434
502
|
|
|
435
503
|
temp_file = "/tmp/authorized_keys.new"
|
|
436
|
-
# Construct the command string without escape sequences in f-string
|
|
437
504
|
new_auth_joined = "\n".join(new_auth)
|
|
438
505
|
self.run_command(f"echo '{new_auth_joined}' > {temp_file}")
|
|
439
506
|
self.run_command(f"mv {temp_file} ~/.ssh/authorized_keys")
|
|
@@ -441,7 +508,9 @@ class Tunnel:
|
|
|
441
508
|
|
|
442
509
|
self.identity_file = new_key_path
|
|
443
510
|
self.password = None
|
|
444
|
-
self.logger.info(
|
|
511
|
+
self.logger.info(
|
|
512
|
+
f"Rotated {key_type} key to {new_key_path} on {self.remote_host}"
|
|
513
|
+
)
|
|
445
514
|
logging.info(
|
|
446
515
|
f"Please update SSH config for {self.remote_host} IdentityFile to {new_key_path}"
|
|
447
516
|
)
|
|
@@ -450,6 +519,7 @@ class Tunnel:
|
|
|
450
519
|
def setup_all_passwordless_ssh(
|
|
451
520
|
inventory,
|
|
452
521
|
shared_key_path=os.path.expanduser("~/.ssh/id_shared"),
|
|
522
|
+
key_type="ed25519",
|
|
453
523
|
group="all",
|
|
454
524
|
parallel=False,
|
|
455
525
|
max_threads=5,
|
|
@@ -458,16 +528,23 @@ class Tunnel:
|
|
|
458
528
|
Set up passwordless SSH for all hosts in the specified group of the YAML inventory.
|
|
459
529
|
:param inventory: Path to the YAML inventory file.
|
|
460
530
|
:param shared_key_path: Path to a shared private key (optional, generates if missing).
|
|
531
|
+
:param key_type: Type of key to generate ('rsa' or 'ed25519', default: 'rsa').
|
|
461
532
|
:param group: Inventory group to target (default: 'all').
|
|
462
533
|
:param parallel: Run in parallel.
|
|
463
534
|
:param max_threads: Max threads for parallel.
|
|
464
535
|
"""
|
|
465
536
|
shared_key_path = os.path.expanduser(shared_key_path)
|
|
466
537
|
shared_pub_key_path = shared_key_path + ".pub"
|
|
538
|
+
if key_type not in ["rsa", "ed25519"]:
|
|
539
|
+
raise ValueError("key_type must be 'rsa' or 'ed25519'")
|
|
540
|
+
|
|
467
541
|
if not os.path.exists(shared_key_path):
|
|
468
|
-
|
|
542
|
+
if key_type == "rsa":
|
|
543
|
+
os.system(f"ssh-keygen -t rsa -b 4096 -f {shared_key_path} -N ''")
|
|
544
|
+
else: # ed25519
|
|
545
|
+
os.system(f"ssh-keygen -t ed25519 -f {shared_key_path} -N ''")
|
|
469
546
|
logging.info(
|
|
470
|
-
f"Generated shared key pair: {shared_key_path}, {shared_pub_key_path}"
|
|
547
|
+
f"Generated shared {key_type} key pair: {shared_key_path}, {shared_pub_key_path}"
|
|
471
548
|
)
|
|
472
549
|
|
|
473
550
|
with open(shared_pub_key_path, "r") as f:
|
|
@@ -487,13 +564,13 @@ class Tunnel:
|
|
|
487
564
|
password=password,
|
|
488
565
|
)
|
|
489
566
|
tunnel.remove_host_key()
|
|
490
|
-
tunnel.setup_passwordless_ssh(local_key_path=key_path)
|
|
567
|
+
tunnel.setup_passwordless_ssh(local_key_path=key_path, key_type=key_type)
|
|
491
568
|
|
|
492
569
|
try:
|
|
493
570
|
tunnel.connect()
|
|
494
571
|
tunnel.run_command(f"echo '{shared_pub_key}' >> ~/.ssh/authorized_keys")
|
|
495
572
|
tunnel.run_command("chmod 600 ~/.ssh/authorized_keys")
|
|
496
|
-
logging.info(f"Added shared key to {username}@{hostname}")
|
|
573
|
+
logging.info(f"Added shared {key_type} key to {username}@{hostname}")
|
|
497
574
|
except Exception as e:
|
|
498
575
|
logging.error(
|
|
499
576
|
f"Failed to add shared key to {username}@{hostname}: {str(e)}"
|
|
@@ -501,13 +578,13 @@ class Tunnel:
|
|
|
501
578
|
finally:
|
|
502
579
|
tunnel.close()
|
|
503
580
|
|
|
504
|
-
result, msg = tunnel.test_key_auth(
|
|
581
|
+
result, msg = tunnel.test_key_auth(key_path)
|
|
505
582
|
logging.info(f"Key auth test for {username}@{hostname}: {msg}")
|
|
506
583
|
|
|
507
|
-
Tunnel.
|
|
584
|
+
Tunnel.execute_on_inventory(inventory, setup_host, group, parallel, max_threads)
|
|
508
585
|
|
|
509
586
|
@staticmethod
|
|
510
|
-
def
|
|
587
|
+
def run_command_on_inventory(
|
|
511
588
|
inventory, command, group="all", parallel=False, max_threads=5
|
|
512
589
|
):
|
|
513
590
|
"""
|
|
@@ -543,7 +620,9 @@ class Tunnel:
|
|
|
543
620
|
print(f"Error on {host['hostname']}: {str(e)}", file=sys.stderr)
|
|
544
621
|
|
|
545
622
|
try:
|
|
546
|
-
Tunnel.
|
|
623
|
+
Tunnel.execute_on_inventory(
|
|
624
|
+
inventory, run_host, group, parallel, max_threads
|
|
625
|
+
)
|
|
547
626
|
print(f"Completed command execution on group '{group}'")
|
|
548
627
|
except Exception as e:
|
|
549
628
|
logger.error(f"Failed to execute command on group '{group}': {str(e)}")
|
|
@@ -553,7 +632,7 @@ class Tunnel:
|
|
|
553
632
|
raise
|
|
554
633
|
|
|
555
634
|
@staticmethod
|
|
556
|
-
def
|
|
635
|
+
def copy_ssh_config_on_inventory(
|
|
557
636
|
inventory,
|
|
558
637
|
local_config_path,
|
|
559
638
|
remote_config_path=os.path.expanduser("~/.ssh/config"),
|
|
@@ -581,12 +660,13 @@ class Tunnel:
|
|
|
581
660
|
tunnel.copy_ssh_config(local_config_path, remote_config_path)
|
|
582
661
|
tunnel.close()
|
|
583
662
|
|
|
584
|
-
Tunnel.
|
|
663
|
+
Tunnel.execute_on_inventory(inventory, copy_host, group, parallel, max_threads)
|
|
585
664
|
|
|
586
665
|
@staticmethod
|
|
587
|
-
def
|
|
666
|
+
def rotate_ssh_key_on_inventory(
|
|
588
667
|
inventory,
|
|
589
668
|
key_prefix=os.path.expanduser("~/.ssh/id_"),
|
|
669
|
+
key_type="ed25519",
|
|
590
670
|
group="all",
|
|
591
671
|
parallel=False,
|
|
592
672
|
max_threads=5,
|
|
@@ -595,6 +675,7 @@ class Tunnel:
|
|
|
595
675
|
Rotate SSH keys for all hosts in the specified group of the YAML inventory.
|
|
596
676
|
:param inventory: Path to the YAML inventory file.
|
|
597
677
|
:param key_prefix: Prefix for new key paths (appends hostname).
|
|
678
|
+
:param key_type: Type of key to generate ('rsa' or 'ed25519', default: 'rsa').
|
|
598
679
|
:param group: Inventory group to target (default: 'all').
|
|
599
680
|
:param parallel: Run in parallel.
|
|
600
681
|
:param max_threads: Max threads for parallel.
|
|
@@ -608,16 +689,18 @@ class Tunnel:
|
|
|
608
689
|
password=host.get("password"),
|
|
609
690
|
identity_file=host.get("key_path"),
|
|
610
691
|
)
|
|
611
|
-
tunnel.rotate_ssh_key(new_key_path)
|
|
692
|
+
tunnel.rotate_ssh_key(new_key_path, key_type=key_type)
|
|
612
693
|
logging.info(
|
|
613
|
-
f"Rotated key for {host['hostname']}. Update inventory key_path to {new_key_path} if needed."
|
|
694
|
+
f"Rotated {key_type} key for {host['hostname']}. Update inventory key_path to {new_key_path} if needed."
|
|
614
695
|
)
|
|
615
696
|
tunnel.close()
|
|
616
697
|
|
|
617
|
-
Tunnel.
|
|
698
|
+
Tunnel.execute_on_inventory(
|
|
699
|
+
inventory, rotate_host, group, parallel, max_threads
|
|
700
|
+
)
|
|
618
701
|
|
|
619
702
|
@staticmethod
|
|
620
|
-
def
|
|
703
|
+
def send_file_on_inventory(
|
|
621
704
|
inventory,
|
|
622
705
|
local_path,
|
|
623
706
|
remote_path,
|
|
@@ -649,10 +732,10 @@ class Tunnel:
|
|
|
649
732
|
if not os.path.exists(local_path):
|
|
650
733
|
raise ValueError(f"Local file does not exist: {local_path}")
|
|
651
734
|
|
|
652
|
-
Tunnel.
|
|
735
|
+
Tunnel.execute_on_inventory(inventory, send_host, group, parallel, max_threads)
|
|
653
736
|
|
|
654
737
|
@staticmethod
|
|
655
|
-
def
|
|
738
|
+
def receive_file_on_inventory(
|
|
656
739
|
inventory,
|
|
657
740
|
remote_path: str,
|
|
658
741
|
local_path_prefix,
|
|
@@ -685,7 +768,9 @@ class Tunnel:
|
|
|
685
768
|
tunnel.close()
|
|
686
769
|
|
|
687
770
|
os.makedirs(local_path_prefix, exist_ok=True)
|
|
688
|
-
Tunnel.
|
|
771
|
+
Tunnel.execute_on_inventory(
|
|
772
|
+
inventory, receive_host, group, parallel, max_threads
|
|
773
|
+
)
|
|
689
774
|
|
|
690
775
|
|
|
691
776
|
def tunnel_manager():
|
|
@@ -702,6 +787,12 @@ def tunnel_manager():
|
|
|
702
787
|
default="~/.ssh/id_shared",
|
|
703
788
|
help="Path to shared private key",
|
|
704
789
|
)
|
|
790
|
+
setup_parser.add_argument(
|
|
791
|
+
"--key-type",
|
|
792
|
+
choices=["rsa", "ed25519"],
|
|
793
|
+
default="ed25519",
|
|
794
|
+
help="Key type to generate (rsa or ed25519, default: ed25519)",
|
|
795
|
+
)
|
|
705
796
|
setup_parser.add_argument(
|
|
706
797
|
"--group", default="all", help="Inventory group to target (default: all)"
|
|
707
798
|
)
|
|
@@ -749,6 +840,12 @@ def tunnel_manager():
|
|
|
749
840
|
default="~/.ssh/id_",
|
|
750
841
|
help="Prefix for new key paths (appends hostname)",
|
|
751
842
|
)
|
|
843
|
+
rotate_parser.add_argument(
|
|
844
|
+
"--key-type",
|
|
845
|
+
choices=["rsa", "ed25519"],
|
|
846
|
+
default="ed25519",
|
|
847
|
+
help="Key type to generate (rsa or ed25519, default: ed25519)",
|
|
848
|
+
)
|
|
752
849
|
rotate_parser.add_argument(
|
|
753
850
|
"--group", default="all", help="Inventory group to target (default: all)"
|
|
754
851
|
)
|
|
@@ -832,12 +929,13 @@ def tunnel_manager():
|
|
|
832
929
|
Tunnel.setup_all_passwordless_ssh(
|
|
833
930
|
args.inventory,
|
|
834
931
|
args.shared_key_path,
|
|
932
|
+
args.key_type,
|
|
835
933
|
args.group,
|
|
836
934
|
args.parallel,
|
|
837
935
|
args.max_threads,
|
|
838
936
|
)
|
|
839
937
|
elif args.command == "run-command":
|
|
840
|
-
Tunnel.
|
|
938
|
+
Tunnel.run_command_on_inventory(
|
|
841
939
|
args.inventory,
|
|
842
940
|
args.remote_command,
|
|
843
941
|
args.group,
|
|
@@ -845,7 +943,7 @@ def tunnel_manager():
|
|
|
845
943
|
args.max_threads,
|
|
846
944
|
)
|
|
847
945
|
elif args.command == "copy-config":
|
|
848
|
-
Tunnel.
|
|
946
|
+
Tunnel.copy_ssh_config_on_inventory(
|
|
849
947
|
args.inventory,
|
|
850
948
|
args.local_config_path,
|
|
851
949
|
args.remote_config_path,
|
|
@@ -854,15 +952,16 @@ def tunnel_manager():
|
|
|
854
952
|
args.max_threads,
|
|
855
953
|
)
|
|
856
954
|
elif args.command == "rotate-key":
|
|
857
|
-
Tunnel.
|
|
955
|
+
Tunnel.rotate_ssh_key_on_inventory(
|
|
858
956
|
args.inventory,
|
|
859
957
|
args.key_prefix,
|
|
958
|
+
args.key_type,
|
|
860
959
|
args.group,
|
|
861
960
|
args.parallel,
|
|
862
961
|
args.max_threads,
|
|
863
962
|
)
|
|
864
963
|
elif args.command == "send-file":
|
|
865
|
-
Tunnel.
|
|
964
|
+
Tunnel.send_file_on_inventory(
|
|
866
965
|
args.inventory,
|
|
867
966
|
args.local_path,
|
|
868
967
|
args.remote_path,
|
|
@@ -871,7 +970,7 @@ def tunnel_manager():
|
|
|
871
970
|
args.max_threads,
|
|
872
971
|
)
|
|
873
972
|
elif args.command == "receive-file":
|
|
874
|
-
Tunnel.
|
|
973
|
+
Tunnel.receive_file_on_inventory(
|
|
875
974
|
args.inventory,
|
|
876
975
|
args.remote_path,
|
|
877
976
|
args.local_path_prefix,
|