cloudx-proxy 0.4.2__py3-none-any.whl → 0.4.4__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 +2 -2
- cloudx_proxy/cli.py +2 -0
- cloudx_proxy/setup.py +282 -132
- {cloudx_proxy-0.4.2.dist-info → cloudx_proxy-0.4.4.dist-info}/METADATA +70 -24
- cloudx_proxy-0.4.4.dist-info/RECORD +12 -0
- cloudx_proxy-0.4.2.dist-info/RECORD +0 -12
- {cloudx_proxy-0.4.2.dist-info → cloudx_proxy-0.4.4.dist-info}/LICENSE +0 -0
- {cloudx_proxy-0.4.2.dist-info → cloudx_proxy-0.4.4.dist-info}/WHEEL +0 -0
- {cloudx_proxy-0.4.2.dist-info → cloudx_proxy-0.4.4.dist-info}/entry_points.txt +0 -0
- {cloudx_proxy-0.4.2.dist-info → cloudx_proxy-0.4.4.dist-info}/top_level.txt +0 -0
cloudx_proxy/_version.py
CHANGED
cloudx_proxy/cli.py
CHANGED
cloudx_proxy/setup.py
CHANGED
@@ -346,6 +346,18 @@ class CloudXSetup:
|
|
346
346
|
return True
|
347
347
|
return False
|
348
348
|
|
349
|
+
def _get_version(self) -> str:
|
350
|
+
"""Get the current version of the cloudx-proxy package.
|
351
|
+
|
352
|
+
Returns:
|
353
|
+
str: Version string
|
354
|
+
"""
|
355
|
+
try:
|
356
|
+
from . import __version__
|
357
|
+
return __version__
|
358
|
+
except (ImportError, AttributeError):
|
359
|
+
return "unknown"
|
360
|
+
|
349
361
|
def _build_proxy_command(self) -> str:
|
350
362
|
"""Build the ProxyCommand with appropriate parameters.
|
351
363
|
|
@@ -383,38 +395,138 @@ class CloudXSetup:
|
|
383
395
|
IdentitiesOnly yes
|
384
396
|
"""
|
385
397
|
|
386
|
-
def
|
387
|
-
"""
|
398
|
+
def _get_timestamp(self) -> str:
|
399
|
+
"""Get a formatted timestamp for configuration comments.
|
400
|
+
|
401
|
+
Returns:
|
402
|
+
str: Formatted timestamp
|
403
|
+
"""
|
404
|
+
from datetime import datetime
|
405
|
+
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
406
|
+
|
407
|
+
def _build_generic_config(self) -> str:
|
408
|
+
"""Build a generic configuration block with common settings for all environments.
|
409
|
+
|
410
|
+
Returns:
|
411
|
+
str: Generic configuration block
|
412
|
+
"""
|
413
|
+
version = self._get_version()
|
414
|
+
timestamp = self._get_timestamp()
|
415
|
+
|
416
|
+
# Start with metadata comment
|
417
|
+
config = f"""
|
418
|
+
# Created by cloudx-proxy v{version} on {timestamp}
|
419
|
+
# Configuration type: generic
|
420
|
+
Host cloudx-*
|
421
|
+
User ec2-user
|
422
|
+
TCPKeepAlive yes
|
423
|
+
"""
|
424
|
+
|
425
|
+
# Add SSH multiplexing configuration
|
426
|
+
control_path = "~/.ssh/control/%r@%h:%p"
|
427
|
+
if platform.system() == 'Windows':
|
428
|
+
# Use forward slashes for Windows as well, SSH client will handle conversion
|
429
|
+
control_path = "~/.ssh/control/%r@%h:%p"
|
430
|
+
|
431
|
+
config += f""" ControlMaster auto
|
432
|
+
ControlPath {control_path}
|
433
|
+
ControlPersist 4h
|
434
|
+
"""
|
435
|
+
|
436
|
+
return config
|
437
|
+
|
438
|
+
def _build_environment_config(self, cloudx_env: str) -> str:
|
439
|
+
"""Build an environment-specific configuration block.
|
388
440
|
|
389
441
|
Args:
|
390
442
|
cloudx_env: CloudX environment
|
391
|
-
hostname: Hostname for the instance
|
392
|
-
instance_id: EC2 instance ID (None for wildcard entries)
|
393
|
-
include_proxy: Whether to include the ProxyCommand (default: True)
|
394
443
|
|
395
444
|
Returns:
|
396
|
-
str:
|
445
|
+
str: Environment configuration block
|
397
446
|
"""
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
host_entry += """ User ec2-user
|
447
|
+
version = self._get_version()
|
448
|
+
timestamp = self._get_timestamp()
|
449
|
+
|
450
|
+
# Start with metadata comment
|
451
|
+
config = f"""
|
452
|
+
# Created by cloudx-proxy v{version} on {timestamp}
|
453
|
+
# Configuration type: environment
|
454
|
+
Host cloudx-{cloudx_env}-*
|
407
455
|
"""
|
408
456
|
# Add authentication configuration
|
409
|
-
|
457
|
+
config += self._build_auth_config()
|
410
458
|
|
411
|
-
# Add
|
412
|
-
|
413
|
-
host_entry += f""" ProxyCommand {self._build_proxy_command()}
|
459
|
+
# Add ProxyCommand
|
460
|
+
config += f""" ProxyCommand {self._build_proxy_command()}
|
414
461
|
"""
|
415
462
|
|
416
|
-
return
|
463
|
+
return config
|
417
464
|
|
465
|
+
def _build_host_config(self, cloudx_env: str, hostname: str, instance_id: str) -> str:
|
466
|
+
"""Build a host-specific configuration block.
|
467
|
+
|
468
|
+
Args:
|
469
|
+
cloudx_env: CloudX environment
|
470
|
+
hostname: Hostname for the instance
|
471
|
+
instance_id: EC2 instance ID
|
472
|
+
|
473
|
+
Returns:
|
474
|
+
str: Host configuration block
|
475
|
+
"""
|
476
|
+
version = self._get_version()
|
477
|
+
timestamp = self._get_timestamp()
|
478
|
+
|
479
|
+
# Start with metadata comment
|
480
|
+
config = f"""
|
481
|
+
# Created by cloudx-proxy v{version} on {timestamp}
|
482
|
+
# Configuration type: host
|
483
|
+
Host cloudx-{cloudx_env}-{hostname}
|
484
|
+
HostName {instance_id}
|
485
|
+
"""
|
486
|
+
|
487
|
+
return config
|
488
|
+
|
489
|
+
def _check_config_exists(self, pattern: str, current_config: str) -> bool:
|
490
|
+
"""Check if a configuration pattern exists in the current config.
|
491
|
+
|
492
|
+
Args:
|
493
|
+
pattern: Host pattern to look for (e.g., 'cloudx-*', 'cloudx-dev-*')
|
494
|
+
current_config: Current SSH config content
|
495
|
+
|
496
|
+
Returns:
|
497
|
+
bool: True if pattern exists in configuration
|
498
|
+
"""
|
499
|
+
return f"Host {pattern}" in current_config
|
500
|
+
|
501
|
+
def _extract_host_config(self, pattern: str, current_config: str) -> Tuple[str, str]:
|
502
|
+
"""Extract a host configuration block from the current config.
|
503
|
+
|
504
|
+
Args:
|
505
|
+
pattern: Host pattern to extract (e.g., 'cloudx-*', 'cloudx-dev-*')
|
506
|
+
current_config: Current SSH config content
|
507
|
+
|
508
|
+
Returns:
|
509
|
+
Tuple[str, str]: Extracted host configuration, remaining configuration
|
510
|
+
"""
|
511
|
+
lines = current_config.splitlines()
|
512
|
+
host_config_lines = []
|
513
|
+
remaining_lines = []
|
514
|
+
in_host_block = False
|
515
|
+
|
516
|
+
for line in lines:
|
517
|
+
if line.strip() == f"Host {pattern}":
|
518
|
+
in_host_block = True
|
519
|
+
host_config_lines.append(line)
|
520
|
+
elif in_host_block and line.strip().startswith("Host "):
|
521
|
+
in_host_block = False
|
522
|
+
remaining_lines.append(line)
|
523
|
+
elif in_host_block:
|
524
|
+
host_config_lines.append(line)
|
525
|
+
else:
|
526
|
+
remaining_lines.append(line)
|
527
|
+
|
528
|
+
return "\n".join(host_config_lines), "\n".join(remaining_lines)
|
529
|
+
|
418
530
|
def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
|
419
531
|
"""Add settings to a specific host entry.
|
420
532
|
|
@@ -428,13 +540,29 @@ Host cloudx-{cloudx_env}-{host_pattern}
|
|
428
540
|
bool: True if settings were added successfully
|
429
541
|
"""
|
430
542
|
try:
|
431
|
-
#
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
543
|
+
# Check if host entry already exists
|
544
|
+
host_pattern = f"cloudx-{cloudx_env}-{hostname}"
|
545
|
+
if self._check_config_exists(host_pattern, current_config):
|
546
|
+
# Extract existing host configuration
|
547
|
+
host_config, remaining_config = self._extract_host_config(host_pattern, current_config)
|
548
|
+
|
549
|
+
# Update host configuration
|
550
|
+
host_config = self._build_host_config(cloudx_env, hostname, instance_id)
|
551
|
+
|
552
|
+
# Write updated config
|
553
|
+
with open(self.ssh_config_file, 'w') as f:
|
554
|
+
f.write(remaining_config)
|
555
|
+
f.write(host_config)
|
556
|
+
|
557
|
+
self.print_status(f"Updated existing host entry for {host_pattern}", True, 2)
|
558
|
+
else:
|
559
|
+
# Generate new host entry
|
560
|
+
host_entry = self._build_host_config(cloudx_env, hostname, instance_id)
|
561
|
+
|
562
|
+
# Append host entry
|
563
|
+
with open(self.ssh_config_file, 'a') as f:
|
564
|
+
f.write(host_entry)
|
565
|
+
self.print_status(f"Added new host entry for {host_pattern}", True, 2)
|
438
566
|
|
439
567
|
# Set proper permissions on the config file
|
440
568
|
if platform.system() != 'Windows':
|
@@ -451,6 +579,81 @@ Host cloudx-{cloudx_env}-{host_pattern}
|
|
451
579
|
self.print_status("Continuing setup despite SSH config issues", None, 2)
|
452
580
|
return True
|
453
581
|
return False
|
582
|
+
|
583
|
+
def _check_and_create_generic_config(self, current_config: str) -> Tuple[bool, str]:
|
584
|
+
"""Check if generic configuration exists and create it if needed.
|
585
|
+
|
586
|
+
Args:
|
587
|
+
current_config: Current SSH config content
|
588
|
+
|
589
|
+
Returns:
|
590
|
+
Tuple[bool, str]: Success flag, Updated configuration
|
591
|
+
"""
|
592
|
+
if self._check_config_exists("cloudx-*", current_config):
|
593
|
+
self.print_status("Found existing generic config for cloudx-*", True, 2)
|
594
|
+
return True, current_config
|
595
|
+
|
596
|
+
self.print_status("Creating generic config for cloudx-*", None, 2)
|
597
|
+
generic_config = self._build_generic_config()
|
598
|
+
|
599
|
+
# Append generic config to current config
|
600
|
+
updated_config = current_config
|
601
|
+
if updated_config and not updated_config.endswith('\n'):
|
602
|
+
updated_config += '\n'
|
603
|
+
updated_config += generic_config
|
604
|
+
|
605
|
+
return True, updated_config
|
606
|
+
|
607
|
+
def _check_and_create_environment_config(self, cloudx_env: str, current_config: str) -> Tuple[bool, str]:
|
608
|
+
"""Check if environment configuration exists and create it if needed.
|
609
|
+
|
610
|
+
Args:
|
611
|
+
cloudx_env: CloudX environment
|
612
|
+
current_config: Current SSH config content
|
613
|
+
|
614
|
+
Returns:
|
615
|
+
Tuple[bool, str]: Success flag, Updated configuration
|
616
|
+
"""
|
617
|
+
if self._check_config_exists(f"cloudx-{cloudx_env}-*", current_config):
|
618
|
+
self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
|
619
|
+
|
620
|
+
# Option to override if needed
|
621
|
+
choice = self.prompt(
|
622
|
+
"Would you like to \n"
|
623
|
+
" 1: override the existing environment config\n"
|
624
|
+
" 2: keep existing environment config?\n"
|
625
|
+
"Select an option",
|
626
|
+
"2"
|
627
|
+
)
|
628
|
+
|
629
|
+
if choice == "1":
|
630
|
+
# Remove existing config for this environment
|
631
|
+
self.print_status("Removing existing environment configuration", None, 2)
|
632
|
+
env_config, remaining_config = self._extract_host_config(f"cloudx-{cloudx_env}-*", current_config)
|
633
|
+
|
634
|
+
# Create new environment config
|
635
|
+
env_config = self._build_environment_config(cloudx_env)
|
636
|
+
|
637
|
+
# Append new environment config to remaining config
|
638
|
+
updated_config = remaining_config
|
639
|
+
if updated_config and not updated_config.endswith('\n'):
|
640
|
+
updated_config += '\n'
|
641
|
+
updated_config += env_config
|
642
|
+
|
643
|
+
return True, updated_config
|
644
|
+
|
645
|
+
return True, current_config
|
646
|
+
|
647
|
+
self.print_status(f"Creating environment config for cloudx-{cloudx_env}-*", None, 2)
|
648
|
+
env_config = self._build_environment_config(cloudx_env)
|
649
|
+
|
650
|
+
# Append environment config to current config
|
651
|
+
updated_config = current_config
|
652
|
+
if updated_config and not updated_config.endswith('\n'):
|
653
|
+
updated_config += '\n'
|
654
|
+
updated_config += env_config
|
655
|
+
|
656
|
+
return True, updated_config
|
454
657
|
|
455
658
|
def _ensure_control_dir(self) -> bool:
|
456
659
|
"""Create SSH control directory with proper permissions.
|
@@ -478,38 +681,21 @@ Host cloudx-{cloudx_env}-{host_pattern}
|
|
478
681
|
return False
|
479
682
|
|
480
683
|
def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
|
481
|
-
"""Set up SSH config for the instance.
|
482
|
-
|
483
|
-
This method
|
484
|
-
1.
|
485
|
-
|
486
|
-
-
|
487
|
-
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
```
|
497
|
-
# Base environment config (created only once per environment)
|
498
|
-
Host cloudx-{env}-*
|
499
|
-
User ec2-user
|
500
|
-
IdentityAgent ~/.1password/agent.sock # If using 1Password
|
501
|
-
IdentityFile ~/.ssh/vscode/key.pub # .pub for 1Password, no .pub otherwise
|
502
|
-
IdentitiesOnly yes # If using 1Password
|
503
|
-
TCPKeepAlive yes
|
504
|
-
ControlMaster auto
|
505
|
-
ControlPath ~/.ssh/control/%r@%h:%p
|
506
|
-
ControlPersist 4h
|
507
|
-
ProxyCommand uvx cloudx-proxy connect %h %p --profile profile --aws-env env
|
508
|
-
|
509
|
-
# Host entries (added for each instance)
|
510
|
-
Host cloudx-{env}-hostname
|
511
|
-
HostName i-1234567890
|
512
|
-
```
|
684
|
+
"""Set up SSH config for the instance using a three-tier configuration approach.
|
685
|
+
|
686
|
+
This method implements a hierarchical SSH configuration with three levels:
|
687
|
+
1. Generic (cloudx-*): Common settings for all environments
|
688
|
+
- User settings
|
689
|
+
- TCP keepalive
|
690
|
+
- SSH multiplexing configuration
|
691
|
+
|
692
|
+
2. Environment (cloudx-{env}-*): Environment-specific settings
|
693
|
+
- Authentication configuration (identity settings)
|
694
|
+
- ProxyCommand with environment-specific parameters
|
695
|
+
|
696
|
+
3. Host (cloudx-{env}-hostname): Instance-specific settings
|
697
|
+
- HostName (instance ID)
|
698
|
+
- Optional overrides for incompatible settings
|
513
699
|
|
514
700
|
Args:
|
515
701
|
cloudx_env: CloudX environment (e.g., dev, prod)
|
@@ -520,91 +706,46 @@ Host cloudx-{cloudx_env}-{host_pattern}
|
|
520
706
|
bool: True if config was set up successfully
|
521
707
|
"""
|
522
708
|
self.print_header("SSH Configuration")
|
523
|
-
self.print_status("Setting up SSH configuration...")
|
709
|
+
self.print_status("Setting up SSH configuration with three-tier approach...")
|
524
710
|
|
525
711
|
try:
|
526
|
-
# Check existing configuration
|
527
|
-
if self.ssh_config_file.exists():
|
528
|
-
current_config = self.ssh_config_file.read_text()
|
529
|
-
# Check if configuration for this environment already exists
|
530
|
-
if f"Host cloudx-{cloudx_env}-*" in current_config:
|
531
|
-
self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
|
532
|
-
choice = self.prompt(
|
533
|
-
"Would you like to \n"
|
534
|
-
" 1: override the existing config\n"
|
535
|
-
" 2: add settings to the specific host entry?\n"
|
536
|
-
"Select an option",
|
537
|
-
"1"
|
538
|
-
)
|
539
|
-
if choice == "2":
|
540
|
-
# Add settings to specific host entry
|
541
|
-
self.print_status("Adding settings to specific host entry", None, 2)
|
542
|
-
return self._add_host_entry(cloudx_env, instance_id, hostname, current_config)
|
543
|
-
else:
|
544
|
-
# Remove existing config for this environment
|
545
|
-
self.print_status("Removing existing configuration", None, 2)
|
546
|
-
lines = current_config.splitlines()
|
547
|
-
new_lines = []
|
548
|
-
skip = False
|
549
|
-
for line in lines:
|
550
|
-
if line.strip() == f"Host cloudx-{cloudx_env}-*":
|
551
|
-
skip = True
|
552
|
-
elif skip and line.startswith("Host "):
|
553
|
-
skip = False
|
554
|
-
if not skip:
|
555
|
-
new_lines.append(line)
|
556
|
-
current_config = "\n".join(new_lines)
|
557
|
-
with open(self.ssh_config_file, 'w') as f:
|
558
|
-
f.write(current_config)
|
559
|
-
|
560
|
-
# Create base config
|
561
|
-
self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
|
562
|
-
|
563
712
|
# Ensure control directory exists with proper permissions
|
564
713
|
if not self._ensure_control_dir():
|
565
714
|
return False
|
566
715
|
|
567
|
-
#
|
568
|
-
|
569
|
-
|
716
|
+
# Initialize or read current configuration
|
717
|
+
current_config = ""
|
718
|
+
if self.ssh_config_file.exists():
|
719
|
+
current_config = self.ssh_config_file.read_text()
|
570
720
|
|
571
|
-
#
|
572
|
-
|
721
|
+
# 1. Check and create generic config (highest level)
|
722
|
+
self.print_status("Checking generic configuration...", None, 2)
|
723
|
+
success, current_config = self._check_and_create_generic_config(current_config)
|
724
|
+
if not success:
|
725
|
+
return False
|
573
726
|
|
574
|
-
#
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
727
|
+
# 2. Check and create environment config
|
728
|
+
self.print_status("Checking environment configuration...", None, 2)
|
729
|
+
success, current_config = self._check_and_create_environment_config(cloudx_env, current_config)
|
730
|
+
if not success:
|
731
|
+
return False
|
579
732
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
"""
|
586
|
-
|
587
|
-
# If file exists, append the new config, otherwise create it
|
588
|
-
if self.ssh_config_file.exists():
|
589
|
-
with open(self.ssh_config_file, 'a') as f:
|
590
|
-
f.write("\n" + base_config)
|
591
|
-
else:
|
592
|
-
self.ssh_config_file.write_text(base_config)
|
593
|
-
self.print_status("Base configuration created", True, 2)
|
733
|
+
# Write the updated config with generic and environment tiers
|
734
|
+
self.ssh_config_file.parent.mkdir(parents=True, exist_ok=True)
|
735
|
+
self.ssh_config_file.write_text(current_config)
|
736
|
+
self.print_status("Generic and environment configurations created", True, 2)
|
594
737
|
|
595
738
|
# Set proper permissions on the config file
|
596
739
|
if platform.system() != 'Windows':
|
597
740
|
import stat
|
598
|
-
self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
|
741
|
+
self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
|
599
742
|
self.print_status("Set config file permissions to 600", True, 2)
|
600
|
-
|
601
|
-
# Add
|
602
|
-
self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
self.print_status("Host entry added", True, 2)
|
607
|
-
|
743
|
+
|
744
|
+
# 3. Add or update host entry (lowest level)
|
745
|
+
self.print_status(f"Adding/updating host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
746
|
+
if not self._add_host_entry(cloudx_env, instance_id, hostname, current_config):
|
747
|
+
return False
|
748
|
+
|
608
749
|
# Handle system SSH config integration
|
609
750
|
system_config_path = Path(self.home_dir) / ".ssh" / "config"
|
610
751
|
|
@@ -615,8 +756,17 @@ Host cloudx-{cloudx_env}-{host_pattern}
|
|
615
756
|
self.print_status(f"Created SSH directory: {ssh_parent_dir}", True, 2)
|
616
757
|
self._set_directory_permissions(ssh_parent_dir)
|
617
758
|
|
618
|
-
#
|
619
|
-
|
759
|
+
# Handle system config integration
|
760
|
+
same_file = False
|
761
|
+
if self.ssh_config_file.exists() and system_config_path.exists():
|
762
|
+
try:
|
763
|
+
same_file = self.ssh_config_file.samefile(system_config_path)
|
764
|
+
except:
|
765
|
+
same_file = str(self.ssh_config_file) == str(system_config_path)
|
766
|
+
else:
|
767
|
+
same_file = str(self.ssh_config_file) == str(system_config_path)
|
768
|
+
|
769
|
+
if same_file:
|
620
770
|
self.print_status("Using system SSH config directly, no Include needed", True, 2)
|
621
771
|
else:
|
622
772
|
# Otherwise, make sure the system config includes our config file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: cloudx-proxy
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.4
|
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
|
@@ -112,6 +112,15 @@ uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
|
|
112
112
|
|
113
113
|
# Setup with AWS environment
|
114
114
|
uvx cloudx-proxy setup --aws-env prod
|
115
|
+
|
116
|
+
# Setup with custom SSH config location
|
117
|
+
uvx cloudx-proxy setup --ssh-config ~/.ssh/cloudx/config
|
118
|
+
|
119
|
+
# Setup with 1Password integration
|
120
|
+
uvx cloudx-proxy setup --1password
|
121
|
+
|
122
|
+
# Combine options
|
123
|
+
uvx cloudx-proxy setup --profile myprofile --ssh-key mykey --ssh-config ~/.ssh/cloudx/config --1password --aws-env prod
|
115
124
|
```
|
116
125
|
|
117
126
|
The setup command will:
|
@@ -123,13 +132,16 @@ The setup command will:
|
|
123
132
|
|
124
133
|
2. Manage SSH Keys:
|
125
134
|
- Creates new SSH key pair if needed
|
126
|
-
-
|
127
|
-
* Using 1Password SSH agent
|
128
|
-
*
|
135
|
+
- Fully supports 1Password integration:
|
136
|
+
* Using 1Password SSH agent via `--1password` flag
|
137
|
+
* Creates keys directly in 1Password's secure vault
|
138
|
+
* Only public keys are exported to the filesystem
|
139
|
+
* Follows SSH best practices using public keys to limit authentication attempts
|
129
140
|
|
130
141
|
3. Configure SSH:
|
131
|
-
- Creates ~/.ssh/vscode/config
|
132
|
-
-
|
142
|
+
- Creates SSH configs with proper settings (default: ~/.ssh/vscode/config)
|
143
|
+
- Custom config location can be specified with `--ssh-config`
|
144
|
+
- Sets up optimized environment-specific configurations
|
133
145
|
- Configures ProxyCommand with all necessary parameters
|
134
146
|
- Ensures main ~/.ssh/config includes the configuration
|
135
147
|
|
@@ -146,16 +158,30 @@ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling
|
|
146
158
|
uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
|
147
159
|
```
|
148
160
|
|
149
|
-
Will create a configuration like this:
|
161
|
+
Will create a three-tier configuration structure like this:
|
150
162
|
|
151
163
|
```
|
152
|
-
#
|
153
|
-
|
164
|
+
# Generic configuration (shared by all environments)
|
165
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
166
|
+
# Configuration type: generic
|
167
|
+
Host cloudx-*
|
154
168
|
User ec2-user
|
169
|
+
TCPKeepAlive yes
|
170
|
+
ControlMaster auto
|
171
|
+
ControlPath ~/.ssh/control/%r@%h:%p
|
172
|
+
ControlPersist 4h
|
173
|
+
|
174
|
+
# Environment configuration (specific to a single environment)
|
175
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
176
|
+
# Configuration type: environment
|
177
|
+
Host cloudx-dev-*
|
155
178
|
IdentityFile ~/.ssh/vscode/mykey
|
179
|
+
IdentitiesOnly yes
|
156
180
|
ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
|
157
181
|
|
158
|
-
# Host
|
182
|
+
# Host configuration (specific to a single instance)
|
183
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
184
|
+
# Configuration type: host
|
159
185
|
Host cloudx-dev-myserver
|
160
186
|
HostName i-0123456789abcdef0
|
161
187
|
```
|
@@ -173,21 +199,30 @@ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofi
|
|
173
199
|
VSCode will be able to connect to the instance using the same SSH configuration.
|
174
200
|
|
175
201
|
### SSH Configuration Details
|
176
|
-
The setup command creates:
|
202
|
+
The setup command creates a hierarchical three-tier SSH configuration structure:
|
177
203
|
|
178
|
-
1.
|
179
|
-
- User
|
180
|
-
-
|
181
|
-
-
|
204
|
+
1. Generic configuration (cloudx-*) containing common settings shared across all environments:
|
205
|
+
- User settings (ec2-user)
|
206
|
+
- TCP keepalive for connection stability
|
207
|
+
- SSH multiplexing for better performance (ControlMaster, ControlPath, ControlPersist)
|
182
208
|
|
183
|
-
2.
|
184
|
-
-
|
185
|
-
-
|
186
|
-
- Inherits
|
209
|
+
2. Environment-specific configuration (cloudx-{env}-*) with:
|
210
|
+
- Authentication settings (IdentityFile, IdentityAgent for 1Password)
|
211
|
+
- ProxyCommand with environment-specific parameters
|
212
|
+
- Inherits all settings from the generic configuration
|
213
|
+
|
214
|
+
3. Host-specific entries (cloudx-{env}-hostname) with:
|
215
|
+
- Instance ID (HostName directive)
|
216
|
+
- Inherits all settings from both generic and environment configurations
|
217
|
+
|
218
|
+
Each configuration tier is clearly marked with a timestamp and version information comment, making it easy to track when and how configurations were created.
|
187
219
|
|
188
220
|
When adding new instances to an existing environment, you can choose to:
|
221
|
+
- Keep the existing environment configuration if it's compatible
|
189
222
|
- Override the environment configuration with new settings
|
190
|
-
- Add
|
223
|
+
- Add host-specific settings only
|
224
|
+
|
225
|
+
This three-tier structure offers better maintainability by reducing duplication and making it clear which settings apply broadly and which are specific to an environment or host.
|
191
226
|
|
192
227
|
### VSCode Configuration
|
193
228
|
|
@@ -211,7 +246,9 @@ uvx cloudx-proxy setup [OPTIONS]
|
|
211
246
|
|
212
247
|
Options:
|
213
248
|
- `--profile` (default: vscode): AWS profile to use. The profile's IAM user should follow the format cloudX-{env}-{user}. The environment part will be used as the default environment during setup.
|
214
|
-
- `--ssh-key` (default: vscode): Name of the SSH key to create/use. The key will be stored in
|
249
|
+
- `--ssh-key` (default: vscode): Name of the SSH key to create/use. The key will be stored in the SSH config directory. This same name can be used in the connect command.
|
250
|
+
- `--ssh-config` (optional): Path to the SSH config file to use. If specified, configuration and keys will be stored in this location. Default is ~/.ssh/vscode/config.
|
251
|
+
- `--1password` (flag): Enable 1Password SSH agent integration. Creates keys directly in 1Password and configures SSH to use the 1Password SSH agent.
|
215
252
|
- `--aws-env` (optional): AWS environment directory to use. If specified, AWS configuration and credentials will be read from ~/.aws/aws-envs/{env}/.
|
216
253
|
|
217
254
|
Example usage:
|
@@ -222,8 +259,11 @@ uvx cloudx-proxy setup
|
|
222
259
|
# Setup with custom profile and key
|
223
260
|
uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
|
224
261
|
|
225
|
-
# Setup with
|
226
|
-
uvx cloudx-proxy setup --
|
262
|
+
# Setup with custom SSH config and 1Password integration
|
263
|
+
uvx cloudx-proxy setup --ssh-config ~/.ssh/cloudx/config --1password
|
264
|
+
|
265
|
+
# Complete setup with all options
|
266
|
+
uvx cloudx-proxy setup --profile myprofile --ssh-key mykey --ssh-config ~/.ssh/cloudx/config --1password --aws-env prod
|
227
267
|
```
|
228
268
|
|
229
269
|
#### Connect Command
|
@@ -238,6 +278,7 @@ Arguments:
|
|
238
278
|
Options:
|
239
279
|
- `--profile` (default: vscode): AWS profile to use. Should match the profile used in setup.
|
240
280
|
- `--ssh-key` (default: vscode): Name of the SSH key to use. Should match the key name used in setup.
|
281
|
+
- `--ssh-config` (optional): Path to the SSH config file to use. If provided during setup, should match here.
|
241
282
|
- `--region` (optional): AWS region to use. If not specified, uses the region from the AWS profile.
|
242
283
|
- `--aws-env` (optional): AWS environment directory to use. Should match the environment used in setup.
|
243
284
|
|
@@ -303,12 +344,17 @@ These permissions are required to bootstrap the instance, so that after creation
|
|
303
344
|
- Check that your AWS credentials have the required permissions
|
304
345
|
- Verify the instance ID is correct
|
305
346
|
- Increase the VSCode SSH timeout if needed
|
347
|
+
- Check if the instance is starting up (can take several minutes)
|
306
348
|
|
307
349
|
3. **SSH Key Issues**
|
308
350
|
- If using 1Password SSH agent, verify agent is running (~/.1password/agent.sock exists)
|
309
351
|
- Check file permissions (600 for private key, 644 for public key)
|
310
352
|
- Verify the public key is being successfully pushed to the instance
|
311
|
-
- For
|
353
|
+
- For 1Password-managed keys, make sure:
|
354
|
+
* 1Password CLI is installed and authenticated (`op account list` works)
|
355
|
+
* SSH agent is enabled in 1Password settings
|
356
|
+
* Keys are added to the SSH agent in 1Password
|
357
|
+
* The key is visible with `op item list --categories "SSH Key"`
|
312
358
|
|
313
359
|
4. **AWS Configuration**
|
314
360
|
- Confirm AWS CLI is configured with valid credentials
|
@@ -0,0 +1,12 @@
|
|
1
|
+
cloudx_proxy/_1password.py,sha256=uxyCfVvO1eQrOfYRojst_LN2DV4fIwxM5moaQTn3wQY,5853
|
2
|
+
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
3
|
+
cloudx_proxy/_version.py,sha256=rQpExgwkkSMYhPbtDdfhSejoe7mM9tgzyWoNno0mgIw,511
|
4
|
+
cloudx_proxy/cli.py,sha256=5IcfYFACUOa4pqSKuHucqZionI9P8n5ZLvtzyXYeTvw,4218
|
5
|
+
cloudx_proxy/core.py,sha256=RF3bX5MQiokRKjYEPnfWdKywGdtoVUvV2xZqm9uOl1g,8135
|
6
|
+
cloudx_proxy/setup.py,sha256=11DsAVt6L4d3VmuJNpU07-QdF3FwahOUpJyLDksMeaE,38216
|
7
|
+
cloudx_proxy-0.4.4.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
8
|
+
cloudx_proxy-0.4.4.dist-info/METADATA,sha256=22Yrnn7514Dd8eYte1c03xXpSjMhgATvwIftn1A95iQ,16757
|
9
|
+
cloudx_proxy-0.4.4.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
10
|
+
cloudx_proxy-0.4.4.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
11
|
+
cloudx_proxy-0.4.4.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
12
|
+
cloudx_proxy-0.4.4.dist-info/RECORD,,
|
@@ -1,12 +0,0 @@
|
|
1
|
-
cloudx_proxy/_1password.py,sha256=uxyCfVvO1eQrOfYRojst_LN2DV4fIwxM5moaQTn3wQY,5853
|
2
|
-
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
3
|
-
cloudx_proxy/_version.py,sha256=_F8vLxUxrAtC2alXNPGVa9l3P6_vLpQAzemS6QlnPGQ,511
|
4
|
-
cloudx_proxy/cli.py,sha256=kdrZydxL94BJrv6NnjIcceRqhoonBzMIx4vfm1Wl7qc,4104
|
5
|
-
cloudx_proxy/core.py,sha256=RF3bX5MQiokRKjYEPnfWdKywGdtoVUvV2xZqm9uOl1g,8135
|
6
|
-
cloudx_proxy/setup.py,sha256=jvv7ibJQ8svyjYYeVKwGa70L7RV2W7yS7JXEvKed3wI,33339
|
7
|
-
cloudx_proxy-0.4.2.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
8
|
-
cloudx_proxy-0.4.2.dist-info/METADATA,sha256=YAHtMfsqZ1aDk1FryZhgV-Q_vr4GcdBO_mzs-gbEpK8,14037
|
9
|
-
cloudx_proxy-0.4.2.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
10
|
-
cloudx_proxy-0.4.2.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
11
|
-
cloudx_proxy-0.4.2.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
12
|
-
cloudx_proxy-0.4.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|