cloudx-proxy 0.3.13__py3-none-any.whl → 0.3.15__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.
cloudx_proxy/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.3.13'
21
- __version_tuple__ = version_tuple = (0, 3, 13)
20
+ __version__ = version = '0.3.15'
21
+ __version_tuple__ = version_tuple = (0, 3, 15)
cloudx_proxy/cli.py CHANGED
@@ -17,8 +17,9 @@ def cli():
17
17
  @click.option('--profile', default='vscode', help='AWS profile to use (default: vscode)')
18
18
  @click.option('--region', help='AWS region (default: from profile, or eu-west-1 if not set)')
19
19
  @click.option('--ssh-key', default='vscode', help='SSH key name to use (default: vscode)')
20
+ @click.option('--ssh-config', help='SSH config file to use (default: ~/.ssh/vscode/config)')
20
21
  @click.option('--aws-env', help='AWS environment directory (default: ~/.aws, use name of directory in ~/.aws/aws-envs/)')
21
- def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str, aws_env: str):
22
+ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str, ssh_config: str, aws_env: str):
22
23
  """Connect to an EC2 instance via SSM.
23
24
 
24
25
  INSTANCE_ID is the EC2 instance ID to connect to (e.g., i-0123456789abcdef0)
@@ -35,6 +36,7 @@ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str
35
36
  profile=profile,
36
37
  region=region,
37
38
  ssh_key=ssh_key,
39
+ ssh_config=ssh_config,
38
40
  aws_env=aws_env
39
41
  )
40
42
 
@@ -48,8 +50,9 @@ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str
48
50
  @cli.command()
49
51
  @click.option('--profile', default='vscode', help='AWS profile to use (default: vscode)')
50
52
  @click.option('--ssh-key', default='vscode', help='SSH key name to use (default: vscode)')
53
+ @click.option('--ssh-config', help='SSH config file to use (default: ~/.ssh/vscode/config)')
51
54
  @click.option('--aws-env', help='AWS environment directory (default: ~/.aws, use name of directory in ~/.aws/aws-envs/)')
52
- def setup(profile: str, ssh_key: str, aws_env: str):
55
+ def setup(profile: str, ssh_key: str, ssh_config: str, aws_env: str):
53
56
  """Set up AWS profile, SSH keys, and configuration for CloudX.
54
57
 
55
58
  This command will:
@@ -61,9 +64,10 @@ def setup(profile: str, ssh_key: str, aws_env: str):
61
64
  Example usage:
62
65
  cloudx-proxy setup
63
66
  cloudx-proxy setup --profile myprofile --ssh-key mykey
67
+ cloudx-proxy setup --ssh-config ~/.ssh/cloudx/config
64
68
  """
65
69
  try:
66
- setup = CloudXSetup(profile=profile, ssh_key=ssh_key, aws_env=aws_env)
70
+ setup = CloudXSetup(profile=profile, ssh_key=ssh_key, ssh_config=ssh_config, aws_env=aws_env)
67
71
 
68
72
  print("\n\033[1;95m=== cloudx-proxy Setup ===\033[0m\n")
69
73
 
cloudx_proxy/core.py CHANGED
@@ -7,7 +7,7 @@ from botocore.exceptions import ClientError
7
7
 
8
8
  class CloudXProxy:
9
9
  def __init__(self, instance_id: str, port: int = 22, profile: str = "vscode",
10
- region: str = None, ssh_key: str = "vscode", aws_env: str = None):
10
+ region: str = None, ssh_key: str = "vscode", ssh_config: str = None, aws_env: str = None):
11
11
  """Initialize CloudX client for SSH tunneling via AWS SSM.
12
12
 
13
13
  Args:
@@ -39,8 +39,14 @@ class CloudXProxy:
39
39
  self.ec2 = self.session.client('ec2')
40
40
  self.ec2_connect = self.session.client('ec2-instance-connect')
41
41
 
42
- # Set up SSH key path
43
- self.ssh_dir = os.path.expanduser("~/.ssh/vscode")
42
+ # Set up SSH configuration and key paths
43
+ if ssh_config:
44
+ self.ssh_config_file = os.path.expanduser(ssh_config)
45
+ self.ssh_dir = os.path.dirname(self.ssh_config_file)
46
+ else:
47
+ self.ssh_dir = os.path.expanduser("~/.ssh/vscode")
48
+ self.ssh_config_file = os.path.join(self.ssh_dir, "config")
49
+
44
50
  self.ssh_key = os.path.join(self.ssh_dir, f"{ssh_key}.pub")
45
51
 
46
52
  def log(self, message: str) -> None:
cloudx_proxy/setup.py CHANGED
@@ -9,20 +9,28 @@ import boto3
9
9
  from botocore.exceptions import ClientError
10
10
 
11
11
  class CloudXSetup:
12
- def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", aws_env: str = None):
12
+ def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", ssh_config: str = None, aws_env: str = None):
13
13
  """Initialize cloudx-proxy setup.
14
14
 
15
15
  Args:
16
16
  profile: AWS profile name (default: "vscode")
17
17
  ssh_key: SSH key name (default: "vscode")
18
+ ssh_config: SSH config file path (default: None, uses ~/.ssh/vscode/config)
18
19
  aws_env: AWS environment directory (default: None)
19
20
  """
20
21
  self.profile = profile
21
22
  self.ssh_key = ssh_key
22
23
  self.aws_env = aws_env
23
24
  self.home_dir = str(Path.home())
24
- self.ssh_dir = Path(self.home_dir) / ".ssh" / "vscode"
25
- self.ssh_config_file = self.ssh_dir / "config"
25
+
26
+ # Set up ssh config paths based on provided config or default
27
+ if ssh_config:
28
+ self.ssh_config_file = Path(os.path.expanduser(ssh_config))
29
+ self.ssh_dir = self.ssh_config_file.parent
30
+ else:
31
+ self.ssh_dir = Path(self.home_dir) / ".ssh" / "vscode"
32
+ self.ssh_config_file = self.ssh_dir / "config"
33
+
26
34
  self.ssh_key_file = self.ssh_dir / f"{ssh_key}"
27
35
  self.default_env = None
28
36
 
@@ -68,6 +76,25 @@ class CloudXSetup:
68
76
  response = input(prompt_text)
69
77
  return response if response else default
70
78
 
79
+ def _set_directory_permissions(self, directory: Path) -> bool:
80
+ """Set proper permissions (700) on a directory for Unix-like systems.
81
+
82
+ Args:
83
+ directory: Path to the directory
84
+
85
+ Returns:
86
+ bool: True if permissions were set successfully
87
+ """
88
+ try:
89
+ if platform.system() != 'Windows':
90
+ import stat
91
+ directory.chmod(stat.S_IRWXU) # 700 permissions (owner read/write/execute)
92
+ self.print_status(f"Set {directory} permissions to 700", True, 2)
93
+ return True
94
+ except Exception as e:
95
+ self.print_status(f"Error setting permissions: {str(e)}", False, 2)
96
+ return False
97
+
71
98
  def setup_aws_profile(self) -> bool:
72
99
  """Set up AWS profile using aws configure command.
73
100
 
@@ -138,10 +165,20 @@ class CloudXSetup:
138
165
  self.ssh_dir.mkdir(parents=True, exist_ok=True)
139
166
  self.print_status("SSH directory exists", True, 2)
140
167
 
168
+ # Set proper permissions on the vscode directory
169
+ if not self._set_directory_permissions(self.ssh_dir):
170
+ return False
171
+
141
172
  key_exists = self.ssh_key_file.exists() and (self.ssh_key_file.with_suffix('.pub')).exists()
142
173
 
143
174
  if key_exists:
144
175
  self.print_status(f"SSH key '{self.ssh_key}' exists", True, 2)
176
+ # Set proper permissions on existing key files
177
+ if platform.system() != 'Windows':
178
+ import stat
179
+ self.ssh_key_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
180
+ self.ssh_key_file.with_suffix('.pub').chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IROTH | stat.S_IRGRP) # 644 permissions
181
+ self.print_status("Set key file permissions", True, 2)
145
182
  else:
146
183
  self.print_status(f"Generating new SSH key '{self.ssh_key}'...", None, 2)
147
184
  subprocess.run([
@@ -152,6 +189,12 @@ class CloudXSetup:
152
189
  ], check=True)
153
190
  self.print_status("SSH key generated", True, 2)
154
191
 
192
+ # Set proper permissions on newly generated key files
193
+ if platform.system() != 'Windows':
194
+ import stat
195
+ self.ssh_key_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
196
+ self.ssh_key_file.with_suffix('.pub').chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IROTH | stat.S_IRGRP) # 644 permissions
197
+ self.print_status("Set key file permissions", True, 2)
155
198
 
156
199
  return True
157
200
 
@@ -200,6 +243,13 @@ Host cloudx-{cloudx_env}-{hostname}
200
243
  with open(self.ssh_config_file, 'a') as f:
201
244
  f.write(host_entry)
202
245
  self.print_status("Host entry added with settings", True, 2)
246
+
247
+ # Set proper permissions on the config file
248
+ if platform.system() != 'Windows':
249
+ import stat
250
+ self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
251
+ self.print_status("Set config file permissions to 600", True, 2)
252
+
203
253
  return True
204
254
 
205
255
  except Exception as e:
@@ -220,24 +270,16 @@ Host cloudx-{cloudx_env}-{hostname}
220
270
  bool: True if directory was created or exists with proper permissions
221
271
  """
222
272
  try:
223
- # Create path based on platform
224
- if platform.system() == 'Windows':
225
- control_dir = Path(self.home_dir) / ".ssh" / "control"
226
- else:
227
- control_dir = Path(self.home_dir) / ".ssh" / "control"
273
+ # Create control directory path
274
+ control_dir = Path(self.home_dir) / ".ssh" / "control"
228
275
 
229
276
  # Create directory if it doesn't exist
230
277
  if not control_dir.exists():
231
278
  control_dir.mkdir(parents=True, exist_ok=True)
232
279
  self.print_status(f"Created control directory: {control_dir}", True, 2)
233
280
 
234
- # Set proper permissions on Unix-like systems
235
- if platform.system() != 'Windows':
236
- import stat
237
- control_dir.chmod(stat.S_IRWXU) # 700 permissions (owner read/write/execute)
238
- self.print_status("Set directory permissions to 700", True, 2)
239
-
240
- return True
281
+ # Set proper permissions
282
+ return self._set_directory_permissions(control_dir)
241
283
 
242
284
  except Exception as e:
243
285
  self.print_status(f"Error creating control directory: {str(e)}", False, 2)
@@ -296,8 +338,10 @@ Host cloudx-{cloudx_env}-{hostname}
296
338
  if f"Host cloudx-{cloudx_env}-*" in current_config:
297
339
  self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
298
340
  choice = self.prompt(
299
- "Would you like to \n(1) override the existing config\n "
300
- "(2) add settings to the specific host entry?",
341
+ "Would you like to \n"
342
+ " 1: override the existing config\n"
343
+ " 2: add settings to the specific host entry?\n"
344
+ "Select an option",
301
345
  "1"
302
346
  )
303
347
  if choice == "2":
@@ -324,6 +368,7 @@ Host cloudx-{cloudx_env}-{hostname}
324
368
  # Create base config
325
369
  self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
326
370
  # Build ProxyCommand with only non-default parameters
371
+ # We don't need to include ssh_config here as SSH will handle that through the config
327
372
  proxy_command = "uvx cloudx-proxy connect %h %p"
328
373
  if self.profile != "vscode":
329
374
  proxy_command += f" --profile {self.profile}"
@@ -369,6 +414,12 @@ Host cloudx-{cloudx_env}-*
369
414
  else:
370
415
  self.ssh_config_file.write_text(base_config)
371
416
  self.print_status("Base configuration created", True, 2)
417
+
418
+ # Set proper permissions on the config file
419
+ if platform.system() != 'Windows':
420
+ import stat
421
+ self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
422
+ self.print_status("Set config file permissions to 600", True, 2)
372
423
 
373
424
  # Add specific host entry
374
425
  self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
@@ -380,25 +431,51 @@ Host cloudx-{cloudx_env}-{hostname}
380
431
  f.write(host_entry)
381
432
  self.print_status("Host entry added", True, 2)
382
433
 
383
- # Ensure main SSH config includes our config
384
- main_config = Path(self.home_dir) / ".ssh" / "config"
385
- include_line = f"Include {self.ssh_config_file}\n"
434
+ # Handle system SSH config integration
435
+ system_config_path = Path(self.home_dir) / ".ssh" / "config"
386
436
 
387
- if main_config.exists():
388
- content = main_config.read_text()
389
- if include_line not in content:
390
- with open(main_config, 'a') as f:
391
- f.write(f"\n{include_line}")
392
- self.print_status("Added include line to main SSH config", True, 2)
393
- else:
394
- self.print_status("Main SSH config already includes our config", True, 2)
437
+ # Ensure ~/.ssh directory has proper permissions
438
+ ssh_parent_dir = Path(self.home_dir) / ".ssh"
439
+ if not ssh_parent_dir.exists():
440
+ ssh_parent_dir.mkdir(parents=True, exist_ok=True)
441
+ self.print_status(f"Created SSH directory: {ssh_parent_dir}", True, 2)
442
+ self._set_directory_permissions(ssh_parent_dir)
443
+
444
+ # If our config file is the system config, we're done
445
+ if self.ssh_config_file.samefile(system_config_path) if self.ssh_config_file.exists() and system_config_path.exists() else str(self.ssh_config_file) == str(system_config_path):
446
+ self.print_status("Using system SSH config directly, no Include needed", True, 2)
395
447
  else:
396
- main_config.write_text(include_line)
397
- self.print_status("Created main SSH config with include line", True, 2)
448
+ # Otherwise, make sure the system config includes our config file
449
+ include_line = f"Include {self.ssh_config_file}\n"
450
+
451
+ if system_config_path.exists():
452
+ content = system_config_path.read_text()
453
+ if include_line not in content:
454
+ with open(system_config_path, 'a') as f:
455
+ f.write(f"\n{include_line}")
456
+ self.print_status("Added include line to system SSH config", True, 2)
457
+ else:
458
+ self.print_status("System SSH config already includes our config", True, 2)
459
+
460
+ # Set correct permissions on system config file
461
+ if platform.system() != 'Windows':
462
+ import stat
463
+ system_config_path.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
464
+ self.print_status("Set system config file permissions to 600", True, 2)
465
+ else:
466
+ system_config_path.write_text(include_line)
467
+ self.print_status("Created system SSH config with include line", True, 2)
468
+
469
+ # Set correct permissions on newly created system config file
470
+ if platform.system() != 'Windows':
471
+ import stat
472
+ system_config_path.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
473
+ self.print_status("Set system config file permissions to 600", True, 2)
398
474
 
399
- self.print_status("\nSSH configuration summary:", None)
400
- self.print_status(f"Main config: {main_config}", None, 2)
475
+ self.print_status("SSH configuration summary:", None)
476
+ self.print_status(f"System config: {system_config_path}", None, 2)
401
477
  self.print_status(f"cloudx-proxy config: {self.ssh_config_file}", None, 2)
478
+ self.print_status(f"SSH key directory: {self.ssh_dir}", None, 2)
402
479
  self.print_status(f"Connect using: ssh cloudx-{cloudx_env}-{hostname}", None, 2)
403
480
 
404
481
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudx-proxy
3
- Version: 0.3.13
3
+ Version: 0.3.15
4
4
  Summary: SSH proxy command to connect VSCode with Cloud9/CloudX instance using AWS Systems Manager
5
5
  Author-email: easytocloud <info@easytocloud.com>
6
6
  License: MIT License
@@ -0,0 +1,11 @@
1
+ cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
2
+ cloudx_proxy/_version.py,sha256=HSn5cGZLA_vnXREa9sRtSYCA5Ii9CJlQbi1YMzsfUGM,513
3
+ cloudx_proxy/cli.py,sha256=tP-Ftf2VNKtaiu26x5nDKCMZOPKoa4XCNDd5-okoE4s,3827
4
+ cloudx_proxy/core.py,sha256=iHloywyiDcWRXzFxgX0TdcOPHujW2u83WUMllk4m9Es,7588
5
+ cloudx_proxy/setup.py,sha256=zZMMGAAQMqXlfVJ8_M4f5-SEcYqsFQ82kQv18ikdta0,25409
6
+ cloudx_proxy-0.3.15.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
7
+ cloudx_proxy-0.3.15.dist-info/METADATA,sha256=qOT0s-RuqhXnU69PHxWELyeBKNgluZyHpO-R6O5d2q8,14038
8
+ cloudx_proxy-0.3.15.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
9
+ cloudx_proxy-0.3.15.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
10
+ cloudx_proxy-0.3.15.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
11
+ cloudx_proxy-0.3.15.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
2
- cloudx_proxy/_version.py,sha256=EwvT0-VckFIV9s9wWIrrcCCLDLz10Bn0_zRsokqxmZs,513
3
- cloudx_proxy/cli.py,sha256=7wi00p5CUl0Dt8huMEkP85SWgA_vzcoCzch0wctvqHk,3488
4
- cloudx_proxy/core.py,sha256=XQbVlPaqQQ352Ao_JJlN-PpqIdQtSBec0lNRB1s0JSk,7288
5
- cloudx_proxy/setup.py,sha256=93elRbfBUCUJ9Yj8uyMki8FUyt7YZCNODICKqYDA1j0,20917
6
- cloudx_proxy-0.3.13.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
7
- cloudx_proxy-0.3.13.dist-info/METADATA,sha256=Pr1JQOxTtIhjqHitm9ekmQjeBorMwKkMdAbpe2jehrY,14038
8
- cloudx_proxy-0.3.13.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
9
- cloudx_proxy-0.3.13.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
10
- cloudx_proxy-0.3.13.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
11
- cloudx_proxy-0.3.13.dist-info/RECORD,,