aws-ssh-utils 0.1.1__tar.gz → 0.2.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 mvanderlee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-ssh-utils
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Easy AWS SSHing
5
- Project-URL: Homepage, https://github.com/mvanderlee/aws_ssh_utils
5
+ Project-URL: Homepage, https://github.com/mvanderlee/aws-ssh-utils
6
6
  Author-email: Michiel Vanderlee <jmt.vanderlee@gmail.com>
7
7
  License-Expression: MIT
8
+ License-File: LICENSE
8
9
  Classifier: Intended Audience :: Developers
9
10
  Classifier: Intended Audience :: Information Technology
10
11
  Classifier: Intended Audience :: System Administrators
@@ -37,6 +38,10 @@ Description-Content-Type: text/markdown
37
38
 
38
39
  # AWS SSH Utils
39
40
 
41
+ [![Version](https://img.shields.io/pypi/v/aws-ssh-utils.svg)](https://pypi.org/project/aws-ssh-utils/)
42
+ [![License](https://img.shields.io/pypi/l/aws-ssh-utils.svg)](#)
43
+ [![Supported Python Versions](https://img.shields.io/pypi/pyversions/aws-ssh-utils.svg)](https://pypi.org/project/aws-ssh-utils/)
44
+
40
45
  ```shell
41
46
  pip install aws-ssh-utils
42
47
 
@@ -1,5 +1,9 @@
1
1
  # AWS SSH Utils
2
2
 
3
+ [![Version](https://img.shields.io/pypi/v/aws-ssh-utils.svg)](https://pypi.org/project/aws-ssh-utils/)
4
+ [![License](https://img.shields.io/pypi/l/aws-ssh-utils.svg)](#)
5
+ [![Supported Python Versions](https://img.shields.io/pypi/pyversions/aws-ssh-utils.svg)](https://pypi.org/project/aws-ssh-utils/)
6
+
3
7
  ```shell
4
8
  pip install aws-ssh-utils
5
9
 
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -161,13 +161,3 @@ def get_emr_instance_ips(
161
161
  ]
162
162
  for k, v in get_emr_instances(emr, cluster_id).items()
163
163
  }
164
-
165
-
166
- def get_instance_key_name(
167
- emr: "EMRClient",
168
- cluster_id: str,
169
- ) -> str:
170
- cluster = emr.describe_cluster(ClusterId=cluster_id)
171
-
172
- key_name = cluster["Cluster"]["Ec2InstanceAttributes"]["Ec2KeyName"]
173
- return key_name
@@ -1,14 +1,18 @@
1
+ import datetime as dt
1
2
  import logging
2
3
  import os
4
+ import subprocess
3
5
  import sys
4
6
  import textwrap
5
- from dataclasses import dataclass
7
+ from dataclasses import dataclass, field
8
+ from hashlib import sha1
6
9
  from typing import TYPE_CHECKING
7
10
 
8
11
  import boto3
9
12
  import click
10
13
  import click_spinner
11
14
  import paramiko
15
+ import paramiko.pkey
12
16
  import questionary
13
17
  from botocore.exceptions import ClientError
14
18
  from environs import Env
@@ -17,7 +21,6 @@ from loguru import logger
17
21
  from .emr_utils import (
18
22
  IP,
19
23
  get_emr_instance_ips,
20
- get_instance_key_name,
21
24
  prompt_for_emr_cluster,
22
25
  prompt_for_emr_instance_group,
23
26
  )
@@ -28,6 +31,7 @@ if TYPE_CHECKING:
28
31
  from mypy_boto3_emr import EMRClient
29
32
 
30
33
  Env().read_env() # Load .env file
34
+ OPKSSH_PROVIDER_TAG = 'opkssh_provider'
31
35
 
32
36
 
33
37
  class ShellError(Exception):
@@ -248,13 +252,6 @@ def emr_ssh_all(
248
252
  exit(1)
249
253
 
250
254
 
251
- @cli.command('test')
252
- def test(**kwargs):
253
- tmux('new-session -d -s "test" -n "window 1" "echo hello && $SHELL -i"')
254
- tmux('new-window -n "window 2" -t "test:" "echo hello && $SHELL -i"')
255
- tmux('switch-client -t "test:window 1"')
256
-
257
-
258
255
  @dataclass
259
256
  class SSHShell:
260
257
  hostname: str
@@ -262,6 +259,17 @@ class SSHShell:
262
259
  key_filename: str
263
260
  terminal_title: str = None
264
261
 
262
+ private_key: paramiko.PKey = field(init=False)
263
+
264
+ def __post_init__(self):
265
+ if not os.path.isfile(self.key_filename):
266
+ raise ValueError(f'File {self.key_filename} does not exist')
267
+
268
+ self.private_key = paramiko.PKey.from_path(self.key_filename)
269
+ public_key_path = f'{self.key_filename}.pub'
270
+ if os.path.isfile(public_key_path):
271
+ self.private_key.load_certificate(public_key_path)
272
+
265
273
  def connect(self):
266
274
  self._open()
267
275
  self._launch()
@@ -278,7 +286,11 @@ class SSHShell:
278
286
  ssh_client.load_host_keys(host_key_path)
279
287
  ssh_client.set_missing_host_key_policy(ConfirmAddPolicy())
280
288
  try:
281
- ssh_client.connect(hostname=self.hostname, username=self.username, key_filename=self.key_filename)
289
+ ssh_client.connect(
290
+ hostname=self.hostname,
291
+ username=self.username,
292
+ pkey=self.private_key,
293
+ )
282
294
  except paramiko.BadHostKeyException as e:
283
295
  error_message = f'''
284
296
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@ -359,8 +371,18 @@ def get_ec2_ssh_options(
359
371
  key_file: str = None
360
372
  ) -> tuple[str, str, str, str]:
361
373
  instance = prompt_for_ec2_instance(b3s)
374
+ instance_tags = {
375
+ tag['Key'].lower(): tag['Value']
376
+ for tag in instance.tags
377
+ }
378
+
362
379
  if key_file is None:
363
- key_file = try_to_find_ssh_key_file(instance.key_name)
380
+ # Support https://github.com/openpubkey/opkssh via tags
381
+ if OPKSSH_PROVIDER_TAG in instance_tags:
382
+ key_file = get_opkssh_key_file(instance_tags[OPKSSH_PROVIDER_TAG])
383
+
384
+ else:
385
+ key_file = try_to_find_ssh_key_file(instance.key_name)
364
386
 
365
387
  if user is None:
366
388
  logger.info('No user specified, attempting to detect required user...')
@@ -406,8 +428,17 @@ def get_emr_ssh_key_file_for_cluster(
406
428
  cluster_id: str,
407
429
  ) -> str:
408
430
  with click_spinner.spinner():
409
- key_name = get_instance_key_name(emr, cluster_id)
410
- key_file = try_to_find_ssh_key_file(key_name)
431
+ cluster = emr.describe_cluster(ClusterId=cluster_id)
432
+ cluster_tags = {
433
+ tag['Key'].lower(): tag['Value']
434
+ for tag in cluster['Cluster']['Tags']
435
+ }
436
+ # Support https://github.com/openpubkey/opkssh via tags
437
+ if OPKSSH_PROVIDER_TAG in cluster_tags:
438
+ key_file = get_opkssh_key_file(cluster_tags[OPKSSH_PROVIDER_TAG])
439
+ else:
440
+ key_name = cluster["Cluster"]["Ec2InstanceAttributes"]["Ec2KeyName"]
441
+ key_file = try_to_find_ssh_key_file(key_name)
411
442
 
412
443
  if key_file is None:
413
444
  should_continue = questionary.confirm(f'Could not find the ssh key {key_name}, would you like to continue?').unsafe_ask()
@@ -472,6 +503,23 @@ def get_ec2_name(ec2_instance: "Instance") -> str:
472
503
  return ec2_instance.instance_id
473
504
 
474
505
 
506
+ def get_opkssh_key_file(provider: str) -> str:
507
+ opkssh_key_file = os.path.join(os.path.expanduser('~/.ssh/'), f'opkssh_{sha1(provider.encode()).hexdigest()}')
508
+
509
+ if os.path.exists(opkssh_key_file) and os.path.getmtime(opkssh_key_file) > dt.datetime.now().timestamp() - 86400:
510
+ logger.info('Found existing opkssh key')
511
+ else:
512
+ logger.info('Detected opkssh, logging in')
513
+ cmd = f"opkssh login --provider '{provider}' -i '{opkssh_key_file}'"
514
+ process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
515
+ process.wait()
516
+ if process.returncode != 0:
517
+ logger.error('Failed to authenticate using opkssh')
518
+ sys.exit(process.returncode)
519
+
520
+ return opkssh_key_file
521
+
522
+
475
523
  class ConfirmAddPolicy(paramiko.client.MissingHostKeyPolicy):
476
524
  """
477
525
  Policy for automatically adding the hostname and new host key to the
@@ -41,7 +41,7 @@ dependencies = [
41
41
  dynamic = ["version"]
42
42
 
43
43
  [project.urls]
44
- Homepage = "https://github.com/mvanderlee/aws_ssh_utils"
44
+ Homepage = "https://github.com/mvanderlee/aws-ssh-utils"
45
45
 
46
46
  [project.scripts]
47
47
  aws_ssh = "aws_ssh_utils.ssh:cli"
@@ -1 +0,0 @@
1
- __version__ = "0.1.1"
File without changes