cloudx-proxy 0.3.3__py3-none-any.whl → 0.3.5__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
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.3.3'
16
- __version_tuple__ = version_tuple = (0, 3, 3)
15
+ __version__ = version = '0.3.5'
16
+ __version_tuple__ = version_tuple = (0, 3, 5)
cloudx_proxy/setup.py CHANGED
@@ -2,6 +2,7 @@ import os
2
2
  import time
3
3
  import json
4
4
  import subprocess
5
+ import platform
5
6
  from pathlib import Path
6
7
  from typing import Optional, Tuple
7
8
  import boto3
@@ -142,16 +143,17 @@ class CloudXSetup:
142
143
 
143
144
  if key_exists:
144
145
  self.print_status(f"SSH key '{self.ssh_key}' exists", True, 2)
145
- self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
146
- if self.using_1password:
147
- self.print_status("Using 1Password SSH agent", True, 2)
148
- else:
149
- store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
150
- if store_in_1password:
151
- if self._store_key_in_1password():
152
- self.print_status("Private key stored in 1Password", True, 2)
153
- else:
154
- self.print_status("Failed to store private key in 1Password", False, 2)
146
+ if platform.system() != 'Windows':
147
+ self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
148
+ if self.using_1password:
149
+ self.print_status("Using 1Password SSH agent", True, 2)
150
+ else:
151
+ store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
152
+ if store_in_1password:
153
+ if self._store_key_in_1password():
154
+ self.print_status("Private key stored in 1Password", True, 2)
155
+ else:
156
+ self.print_status("Failed to store private key in 1Password", False, 2)
155
157
  else:
156
158
  self.print_status(f"Generating new SSH key '{self.ssh_key}'...", None, 2)
157
159
  subprocess.run([
@@ -162,16 +164,17 @@ class CloudXSetup:
162
164
  ], check=True)
163
165
  self.print_status("SSH key generated", True, 2)
164
166
 
165
- self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
166
- if self.using_1password:
167
- self.print_status("Using 1Password SSH agent", True, 2)
168
- else:
169
- store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
170
- if store_in_1password:
171
- if self._store_key_in_1password():
172
- self.print_status("Private key stored in 1Password", True, 2)
173
- else:
174
- self.print_status("Failed to store private key in 1Password", False, 2)
167
+ if platform.system() != 'Windows':
168
+ self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
169
+ if self.using_1password:
170
+ self.print_status("Using 1Password SSH agent", True, 2)
171
+ else:
172
+ store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
173
+ if store_in_1password:
174
+ if self._store_key_in_1password():
175
+ self.print_status("Private key stored in 1Password", True, 2)
176
+ else:
177
+ self.print_status("Failed to store private key in 1Password", False, 2)
175
178
 
176
179
  return True
177
180
 
@@ -184,20 +187,51 @@ class CloudXSetup:
184
187
  return False
185
188
 
186
189
  def _store_key_in_1password(self) -> bool:
187
- """Store SSH private key in 1Password.
190
+ """Store SSH private key in 1Password and configure SSH agent.
188
191
 
189
192
  Returns:
190
193
  bool: True if key was stored successfully
191
194
  """
192
195
  try:
196
+ # Check if 1Password CLI is available
193
197
  subprocess.run(['op', '--version'], check=True, capture_output=True)
194
- print("Storing private key in 1Password...")
195
- subprocess.run([
196
- 'op', 'document', 'create',
197
- str(self.ssh_key_file),
198
- '--title', f'cloudx-proxy SSH Key - {self.ssh_key}'
199
- ], check=True)
200
- return True
198
+
199
+ # Check if 1Password SSH agent is running
200
+ agent_sock = Path.home() / ".1password" / "agent.sock"
201
+ if platform.system() == 'Darwin':
202
+ agent_sock = Path.home() / "Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
203
+
204
+ if not agent_sock.exists():
205
+ self.print_status("1Password SSH agent not running. Please enable it in 1Password settings.", False, 2)
206
+ return False
207
+
208
+ print("Adding SSH key to 1Password SSH agent...")
209
+ try:
210
+ # First try to add to SSH agent
211
+ subprocess.run([
212
+ 'op', 'ssh-add',
213
+ '--name', f'cloudx-proxy-{self.ssh_key}',
214
+ str(self.ssh_key_file)
215
+ ], check=True)
216
+
217
+ # Read private key content before removing
218
+ with open(self.ssh_key_file, 'r') as f:
219
+ private_key = f.read()
220
+
221
+ # Store private key in 1Password as document for backup
222
+ subprocess.run([
223
+ 'op', 'document', 'create',
224
+ '--title', f'cloudx-proxy SSH Key - {self.ssh_key}',
225
+ ], input=private_key.encode(), check=True)
226
+
227
+ # Remove private key file but keep public key
228
+ os.remove(self.ssh_key_file)
229
+ self.print_status("Private key added to 1Password SSH agent and removed from disk", True, 2)
230
+ self.print_status("Backup copy stored in 1Password documents", True, 2)
231
+ return True
232
+ except subprocess.CalledProcessError as e:
233
+ self.print_status(f"Failed to add key to SSH agent: {e}", False, 2)
234
+ return False
201
235
  except subprocess.CalledProcessError:
202
236
  print("Error: 1Password CLI not installed or not signed in.")
203
237
  return False
@@ -229,13 +263,19 @@ Host cloudx-{cloudx_env}-{hostname}
229
263
  HostName {instance_id}
230
264
  User ec2-user
231
265
  """
232
- if self.using_1password:
233
- host_entry += f""" IdentityAgent ~/.1password/agent.sock
266
+ if self.using_1password and platform.system() != 'Windows':
267
+ # Use platform-specific agent socket path
268
+ if platform.system() == 'Darwin': # macOS
269
+ agent_sock = "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
270
+ else: # Linux
271
+ agent_sock = "~/.1password/agent.sock"
272
+ host_entry += f""" IdentityAgent {agent_sock}
234
273
  IdentityFile {self.ssh_key_file}.pub
235
274
  IdentitiesOnly yes
236
275
  """
237
276
  else:
238
277
  host_entry += f""" IdentityFile {self.ssh_key_file}
278
+ IdentitiesOnly yes
239
279
  """
240
280
  host_entry += f""" ProxyCommand {proxy_command}
241
281
  """
@@ -343,14 +383,22 @@ Host cloudx-{cloudx_env}-{hostname}
343
383
  Host cloudx-{cloudx_env}-*
344
384
  User ec2-user
345
385
  """
346
- # Add 1Password or standard key configuration
347
- if self.using_1password:
348
- base_config += f""" IdentityAgent ~/.1password/agent.sock
386
+ # Add key configuration
387
+ if self.using_1password and platform.system() != 'Windows':
388
+ # Use platform-specific agent socket path
389
+ if platform.system() == 'Darwin': # macOS
390
+ agent_sock = "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
391
+ else: # Linux
392
+ agent_sock = "~/.1password/agent.sock"
393
+ base_config += f""" IdentityAgent {agent_sock}
349
394
  IdentityFile {self.ssh_key_file}.pub
350
395
  IdentitiesOnly yes
396
+
351
397
  """
352
398
  else:
353
399
  base_config += f""" IdentityFile {self.ssh_key_file}
400
+ IdentitiesOnly yes
401
+
354
402
  """
355
403
  # Add ProxyCommand
356
404
  base_config += f""" ProxyCommand {proxy_command}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudx-proxy
3
- Version: 0.3.3
3
+ Version: 0.3.5
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
@@ -95,14 +95,9 @@ cloudX-proxy enables seamless SSH connections from VSCode to EC2 instances using
95
95
  - Uses the SSH configuration to connect to instances
96
96
  - Handles file synchronization and terminal sessions
97
97
 
98
- ## AWS Credentials Setup
98
+ ## Installation
99
99
 
100
- The proxy expects to find AWS credentials in a profile named 'vscode' by default. These credentials should be the Access Key and Secret Key that were created by deploying the cloudX-user stack in your AWS account. The cloudX-user stack creates an IAM user with the minimal permissions required for:
101
- - Starting/stopping EC2 instances
102
- - Establishing SSM sessions
103
- - Pushing SSH keys via EC2 Instance Connect
104
-
105
- The proxy supports easytocloud's AWS profile organizer for managing multiple AWS environments. You can store your AWS configuration and credentials in `~/.aws/aws-envs/<environment>` directories and use the `--aws-env` option to specify which environment to use.
100
+ The cloudX-proxy package is available on PyPI and can run using uvx without explicit installation.
106
101
 
107
102
  ## Setup
108
103
 
@@ -122,7 +117,7 @@ uvx cloudx-proxy setup --aws-env prod
122
117
  The setup command will:
123
118
 
124
119
  1. Configure AWS Profile:
125
- - Creates/validates AWS profile with cloudX-{env}-{user} format
120
+ - Creates/validates AWS profile for IAM user in cloudX-{env}-{user} format
126
121
  - Supports AWS environment directories via --aws-env
127
122
  - Uses aws configure for credential input
128
123
 
@@ -145,7 +140,40 @@ The setup command will:
145
140
 
146
141
  ### SSH Configuration
147
142
 
148
- The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling seamless connections through AWS Systems Manager. It creates:
143
+ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling seamless connections through AWS Systems Manager. For example, running:
144
+
145
+ ```bash
146
+ uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
147
+ ```
148
+
149
+ Will create a configuration like this:
150
+
151
+ ```
152
+ # Base environment config (created once per environment)
153
+ Host cloudx-dev-*
154
+ User ec2-user
155
+ IdentityFile ~/.ssh/vscode/mykey
156
+ ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
157
+
158
+ # Host entry (added for specific instance)
159
+ Host cloudx-dev-myserver
160
+ HostName i-0123456789abcdef0
161
+ ```
162
+
163
+ Allowing the user to:
164
+
165
+ ```bash
166
+ ssh cloudx-dev-myserver
167
+ scp cloudx-dev-myserver:/path/to/file /local/path/to/file
168
+ ```
169
+ without the need to provide any further credentials.
170
+
171
+ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofile` credentials, allowing it to check the instance state and start the instance if it's stopped. Next cloudx-proxy will use `myprofile` to push the public part of the key `mykey` to the instance using SSM. Finally a tunnel is created between the local machine and the instance, using the SSM plugin, allowing SSH to connect to the instance using the private part of the `mykey` key.
172
+
173
+ VSCode will be able to connect to the instance using the same SSH configuration.
174
+
175
+ ### SSH Configuration Details
176
+ The setup command creates:
149
177
 
150
178
  1. A base configuration for each environment (cloudx-{env}-*) with:
151
179
  - User and key settings
@@ -171,7 +199,7 @@ When adding new instances to an existing environment, you can choose to:
171
199
  "remote.SSH.connectTimeout": 90
172
200
  }
173
201
  ```
174
-
202
+ This extra long timeout is necessary to account for the time it takes to start the instance and establish the connection.
175
203
  ## Usage
176
204
 
177
205
  ### Command Line Options
@@ -239,28 +267,28 @@ Note: The connect command is typically used through the SSH ProxyCommand configu
239
267
  5. VSCode will handle the rest, using cloudX-proxy to establish the connection
240
268
 
241
269
  ## AWS Permissions
270
+ ### IAM User Permissions
242
271
 
243
- The AWS user needs these permissions:
244
-
245
- ```json
246
- {
247
- "Version": "2012-10-17",
248
- "Statement": [
249
- {
250
- "Effect": "Allow",
251
- "Action": [
252
- "ec2:StartInstances",
253
- "ec2:DescribeInstances",
254
- "ssm:StartSession",
255
- "ssm:DescribeInstanceInformation",
256
- "ec2-instance-connect:SendSSHPublicKey"
257
- ],
258
- "Resource": "*"
259
- }
260
- ]
261
- }
262
- ```
263
- Note: This user should be created using the cloudX-user product from Service Catalog in the AWS Console. This assures proper permissions and naming conventions.
272
+ The AWS IAM user has to be member of the AWS IAM Group that is created as part of the cloudX environment.
273
+ The group uses ABAC (Attribute Based Access Control) to allow access to the instances based on the tags.
274
+ The ABAC tag defaults to `cloudxuser` and should have the value of the username of the user that owns the instance.
275
+
276
+ Example:
277
+ - AWS IAM User `cloudx-dev-user1` is connecting to an instance with the tag `cloudxuser=cloudx-dev-user1`
278
+
279
+ Note: This user should be created using the cloudX-user product from Service Catalog in the AWS Console. This assures proper permissions and naming conventions. The user in the example is member of the `dev` group, part as part of the `cloudx-dev` environment.
280
+
281
+ The EC2 instance should have the tag `cloudxuser` with the value of the username of the user that is connecting to the instance. This is automatically set when the instance is created using the cloudX-instance product from Service Catalog in the AWS Console.
282
+
283
+ ### EC2 Instance Permissions
284
+
285
+ The EC2 instance has a profile/role that provides enough permissions to allow the AWS SSM agent to connect to the instance, as well as
286
+ - CodeArtifact read only access, to use as a source for pip
287
+ - CodeCommit read only access, to pull code from the repository for installation
288
+ - Organizations read only access, to create aws sso configuration
289
+ - EC2 basic access, to allow the instance to introspect for tags and other metadata
290
+
291
+ These permissions are required to bootstrap the instance, so that after creation the instance can perform software installation and configuration without a user being present.
264
292
 
265
293
  ## Troubleshooting
266
294
 
@@ -0,0 +1,11 @@
1
+ cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
2
+ cloudx_proxy/_version.py,sha256=3eLsZPTwWh0zKBhR6n3c4iAL9geCCdJGMojIL6dF0IA,411
3
+ cloudx_proxy/cli.py,sha256=Ph-m8lDsdU2zZab9Y6YgBBzd_UDouBnfNrYFFx0bI_E,3426
4
+ cloudx_proxy/core.py,sha256=WjKoqMmmnt6e_4JMeq4gTka75JAvQcMUs9r9XUBLmFE,7289
5
+ cloudx_proxy/setup.py,sha256=9uwUsuWjeqpSK5FJd8ltICkbqkhFjBmprhaaVOi14Yc,24987
6
+ cloudx_proxy-0.3.5.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
7
+ cloudx_proxy-0.3.5.dist-info/METADATA,sha256=5lbxvj5-e33akvzp9Vdiq18G-qgVhJDFLFrMAq6grGs,14037
8
+ cloudx_proxy-0.3.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
+ cloudx_proxy-0.3.5.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
10
+ cloudx_proxy-0.3.5.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
11
+ cloudx_proxy-0.3.5.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
2
- cloudx_proxy/_version.py,sha256=FKnJIExgNrZG2xJ0y_dGNBpxGbGBYylvfat-jHhLUuM,411
3
- cloudx_proxy/cli.py,sha256=Ph-m8lDsdU2zZab9Y6YgBBzd_UDouBnfNrYFFx0bI_E,3426
4
- cloudx_proxy/core.py,sha256=WjKoqMmmnt6e_4JMeq4gTka75JAvQcMUs9r9XUBLmFE,7289
5
- cloudx_proxy/setup.py,sha256=Y8YYMJ47fb57FAr6llQaFGuVOQ-fstYEg_Pdv5uCd-A,22486
6
- cloudx_proxy-0.3.3.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
7
- cloudx_proxy-0.3.3.dist-info/METADATA,sha256=AgKxYfcB9ld5fuvmXyuxPqffxQoV2MoeCOf6LhjKJ44,12247
8
- cloudx_proxy-0.3.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
- cloudx_proxy-0.3.3.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
10
- cloudx_proxy-0.3.3.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
11
- cloudx_proxy-0.3.3.dist-info/RECORD,,