cloudx-proxy 0.3.12__py3-none-any.whl → 0.3.14__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 +9 -4
- cloudx_proxy/setup.py +116 -3
- {cloudx_proxy-0.3.12.dist-info → cloudx_proxy-0.3.14.dist-info}/METADATA +1 -1
- cloudx_proxy-0.3.14.dist-info/RECORD +11 -0
- {cloudx_proxy-0.3.12.dist-info → cloudx_proxy-0.3.14.dist-info}/WHEEL +1 -1
- cloudx_proxy-0.3.12.dist-info/RECORD +0 -11
- {cloudx_proxy-0.3.12.dist-info → cloudx_proxy-0.3.14.dist-info}/LICENSE +0 -0
- {cloudx_proxy-0.3.12.dist-info → cloudx_proxy-0.3.14.dist-info}/entry_points.txt +0 -0
- {cloudx_proxy-0.3.12.dist-info → cloudx_proxy-0.3.14.dist-info}/top_level.txt +0 -0
cloudx_proxy/_version.py
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
# file generated by
|
1
|
+
# file generated by setuptools-scm
|
2
2
|
# don't change, don't track in version control
|
3
|
+
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
5
|
+
|
3
6
|
TYPE_CHECKING = False
|
4
7
|
if TYPE_CHECKING:
|
5
|
-
from typing import Tuple
|
8
|
+
from typing import Tuple
|
9
|
+
from typing import Union
|
10
|
+
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
7
12
|
else:
|
8
13
|
VERSION_TUPLE = object
|
@@ -12,5 +17,5 @@ __version__: str
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
13
18
|
version_tuple: VERSION_TUPLE
|
14
19
|
|
15
|
-
__version__ = version = '0.3.
|
16
|
-
__version_tuple__ = version_tuple = (0, 3,
|
20
|
+
__version__ = version = '0.3.14'
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 14)
|
cloudx_proxy/setup.py
CHANGED
@@ -68,6 +68,25 @@ class CloudXSetup:
|
|
68
68
|
response = input(prompt_text)
|
69
69
|
return response if response else default
|
70
70
|
|
71
|
+
def _set_directory_permissions(self, directory: Path) -> bool:
|
72
|
+
"""Set proper permissions (700) on a directory for Unix-like systems.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
directory: Path to the directory
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
bool: True if permissions were set successfully
|
79
|
+
"""
|
80
|
+
try:
|
81
|
+
if platform.system() != 'Windows':
|
82
|
+
import stat
|
83
|
+
directory.chmod(stat.S_IRWXU) # 700 permissions (owner read/write/execute)
|
84
|
+
self.print_status(f"Set {directory} permissions to 700", True, 2)
|
85
|
+
return True
|
86
|
+
except Exception as e:
|
87
|
+
self.print_status(f"Error setting permissions: {str(e)}", False, 2)
|
88
|
+
return False
|
89
|
+
|
71
90
|
def setup_aws_profile(self) -> bool:
|
72
91
|
"""Set up AWS profile using aws configure command.
|
73
92
|
|
@@ -138,10 +157,20 @@ class CloudXSetup:
|
|
138
157
|
self.ssh_dir.mkdir(parents=True, exist_ok=True)
|
139
158
|
self.print_status("SSH directory exists", True, 2)
|
140
159
|
|
160
|
+
# Set proper permissions on the vscode directory
|
161
|
+
if not self._set_directory_permissions(self.ssh_dir):
|
162
|
+
return False
|
163
|
+
|
141
164
|
key_exists = self.ssh_key_file.exists() and (self.ssh_key_file.with_suffix('.pub')).exists()
|
142
165
|
|
143
166
|
if key_exists:
|
144
167
|
self.print_status(f"SSH key '{self.ssh_key}' exists", True, 2)
|
168
|
+
# Set proper permissions on existing key files
|
169
|
+
if platform.system() != 'Windows':
|
170
|
+
import stat
|
171
|
+
self.ssh_key_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
|
172
|
+
self.ssh_key_file.with_suffix('.pub').chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IROTH | stat.S_IRGRP) # 644 permissions
|
173
|
+
self.print_status("Set key file permissions", True, 2)
|
145
174
|
else:
|
146
175
|
self.print_status(f"Generating new SSH key '{self.ssh_key}'...", None, 2)
|
147
176
|
subprocess.run([
|
@@ -152,6 +181,12 @@ class CloudXSetup:
|
|
152
181
|
], check=True)
|
153
182
|
self.print_status("SSH key generated", True, 2)
|
154
183
|
|
184
|
+
# Set proper permissions on newly generated key files
|
185
|
+
if platform.system() != 'Windows':
|
186
|
+
import stat
|
187
|
+
self.ssh_key_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
|
188
|
+
self.ssh_key_file.with_suffix('.pub').chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IROTH | stat.S_IRGRP) # 644 permissions
|
189
|
+
self.print_status("Set key file permissions", True, 2)
|
155
190
|
|
156
191
|
return True
|
157
192
|
|
@@ -200,6 +235,13 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
200
235
|
with open(self.ssh_config_file, 'a') as f:
|
201
236
|
f.write(host_entry)
|
202
237
|
self.print_status("Host entry added with settings", True, 2)
|
238
|
+
|
239
|
+
# Set proper permissions on the config file
|
240
|
+
if platform.system() != 'Windows':
|
241
|
+
import stat
|
242
|
+
self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
|
243
|
+
self.print_status("Set config file permissions to 600", True, 2)
|
244
|
+
|
203
245
|
return True
|
204
246
|
|
205
247
|
except Exception as e:
|
@@ -210,6 +252,31 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
210
252
|
return True
|
211
253
|
return False
|
212
254
|
|
255
|
+
def _ensure_control_dir(self) -> bool:
|
256
|
+
"""Create SSH control directory with proper permissions.
|
257
|
+
|
258
|
+
Creates ~/.ssh/control directory with 700 permissions on Unix-like systems,
|
259
|
+
or appropriate permissions on Windows.
|
260
|
+
|
261
|
+
Returns:
|
262
|
+
bool: True if directory was created or exists with proper permissions
|
263
|
+
"""
|
264
|
+
try:
|
265
|
+
# Create control directory path
|
266
|
+
control_dir = Path(self.home_dir) / ".ssh" / "control"
|
267
|
+
|
268
|
+
# Create directory if it doesn't exist
|
269
|
+
if not control_dir.exists():
|
270
|
+
control_dir.mkdir(parents=True, exist_ok=True)
|
271
|
+
self.print_status(f"Created control directory: {control_dir}", True, 2)
|
272
|
+
|
273
|
+
# Set proper permissions
|
274
|
+
return self._set_directory_permissions(control_dir)
|
275
|
+
|
276
|
+
except Exception as e:
|
277
|
+
self.print_status(f"Error creating control directory: {str(e)}", False, 2)
|
278
|
+
return False
|
279
|
+
|
213
280
|
def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
|
214
281
|
"""Set up SSH config for the instance.
|
215
282
|
|
@@ -219,6 +286,7 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
219
286
|
- User and key configuration
|
220
287
|
- 1Password SSH agent integration if selected
|
221
288
|
- ProxyCommand using uvx cloudx-proxy with proper parameters
|
289
|
+
- SSH multiplexing configuration (ControlMaster, ControlPath, ControlPersist)
|
222
290
|
|
223
291
|
2. For an existing environment:
|
224
292
|
- Skips creating duplicate environment config
|
@@ -232,6 +300,10 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
232
300
|
IdentityAgent ~/.1password/agent.sock # If using 1Password
|
233
301
|
IdentityFile ~/.ssh/vscode/key.pub # .pub for 1Password, no .pub otherwise
|
234
302
|
IdentitiesOnly yes # If using 1Password
|
303
|
+
TCPKeepAlive yes
|
304
|
+
ControlMaster auto
|
305
|
+
ControlPath ~/.ssh/control/%r@%h:%p
|
306
|
+
ControlPersist 4h
|
235
307
|
ProxyCommand uvx cloudx-proxy connect %h %p --profile profile --aws-env env
|
236
308
|
|
237
309
|
# Host entries (added for each instance)
|
@@ -258,8 +330,8 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
258
330
|
if f"Host cloudx-{cloudx_env}-*" in current_config:
|
259
331
|
self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
|
260
332
|
choice = self.prompt(
|
261
|
-
"Would you like to
|
262
|
-
"
|
333
|
+
"Would you like to \n 1: override the existing config\n "
|
334
|
+
" 2: add settings to the specific host entry?\nSelect an option ",
|
263
335
|
"1"
|
264
336
|
)
|
265
337
|
if choice == "2":
|
@@ -294,6 +366,10 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
294
366
|
if self.ssh_key != "vscode":
|
295
367
|
proxy_command += f" --ssh-key {self.ssh_key}"
|
296
368
|
|
369
|
+
# Ensure control directory exists with proper permissions
|
370
|
+
if not self._ensure_control_dir():
|
371
|
+
return False
|
372
|
+
|
297
373
|
# Build base configuration
|
298
374
|
base_config = f"""# cloudx-proxy SSH Configuration
|
299
375
|
Host cloudx-{cloudx_env}-*
|
@@ -303,6 +379,18 @@ Host cloudx-{cloudx_env}-*
|
|
303
379
|
base_config += f""" IdentityFile {self.ssh_key_file}
|
304
380
|
IdentitiesOnly yes
|
305
381
|
|
382
|
+
"""
|
383
|
+
# Add SSH multiplexing configuration
|
384
|
+
control_path = "~/.ssh/control/%r@%h:%p"
|
385
|
+
if platform.system() == 'Windows':
|
386
|
+
# Use forward slashes for Windows as well, SSH client will handle conversion
|
387
|
+
control_path = "~/.ssh/control/%r@%h:%p"
|
388
|
+
|
389
|
+
base_config += f""" TCPKeepAlive yes
|
390
|
+
ControlMaster auto
|
391
|
+
ControlPath {control_path}
|
392
|
+
ControlPersist 4h
|
393
|
+
|
306
394
|
"""
|
307
395
|
# Add ProxyCommand
|
308
396
|
base_config += f""" ProxyCommand {proxy_command}
|
@@ -315,6 +403,12 @@ Host cloudx-{cloudx_env}-*
|
|
315
403
|
else:
|
316
404
|
self.ssh_config_file.write_text(base_config)
|
317
405
|
self.print_status("Base configuration created", True, 2)
|
406
|
+
|
407
|
+
# Set proper permissions on the config file
|
408
|
+
if platform.system() != 'Windows':
|
409
|
+
import stat
|
410
|
+
self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
|
411
|
+
self.print_status("Set config file permissions to 600", True, 2)
|
318
412
|
|
319
413
|
# Add specific host entry
|
320
414
|
self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
@@ -327,6 +421,13 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
327
421
|
self.print_status("Host entry added", True, 2)
|
328
422
|
|
329
423
|
# Ensure main SSH config includes our config
|
424
|
+
# Ensure ~/.ssh directory has proper permissions
|
425
|
+
ssh_parent_dir = Path(self.home_dir) / ".ssh"
|
426
|
+
if not ssh_parent_dir.exists():
|
427
|
+
ssh_parent_dir.mkdir(parents=True, exist_ok=True)
|
428
|
+
self.print_status(f"Created SSH directory: {ssh_parent_dir}", True, 2)
|
429
|
+
self._set_directory_permissions(ssh_parent_dir)
|
430
|
+
|
330
431
|
main_config = Path(self.home_dir) / ".ssh" / "config"
|
331
432
|
include_line = f"Include {self.ssh_config_file}\n"
|
332
433
|
|
@@ -338,11 +439,23 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
338
439
|
self.print_status("Added include line to main SSH config", True, 2)
|
339
440
|
else:
|
340
441
|
self.print_status("Main SSH config already includes our config", True, 2)
|
442
|
+
|
443
|
+
# Set correct permissions on main config file
|
444
|
+
if platform.system() != 'Windows':
|
445
|
+
import stat
|
446
|
+
main_config.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
|
447
|
+
self.print_status("Set main config file permissions to 600", True, 2)
|
341
448
|
else:
|
342
449
|
main_config.write_text(include_line)
|
343
450
|
self.print_status("Created main SSH config with include line", True, 2)
|
451
|
+
|
452
|
+
# Set correct permissions on newly created main config file
|
453
|
+
if platform.system() != 'Windows':
|
454
|
+
import stat
|
455
|
+
main_config.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
|
456
|
+
self.print_status("Set main config file permissions to 600", True, 2)
|
344
457
|
|
345
|
-
self.print_status("
|
458
|
+
self.print_status("SSH configuration summary:", None)
|
346
459
|
self.print_status(f"Main config: {main_config}", None, 2)
|
347
460
|
self.print_status(f"cloudx-proxy config: {self.ssh_config_file}", None, 2)
|
348
461
|
self.print_status(f"Connect using: ssh cloudx-{cloudx_env}-{hostname}", None, 2)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
+
cloudx_proxy/_version.py,sha256=dAYECGus8vEags4_X66QYJmiBFCY-yJJXDyexsFv72w,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=Ing3UnTG_zeMZAcNI3SQbmTJzrY1UWUHUbu6UT14lao,24178
|
6
|
+
cloudx_proxy-0.3.14.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
+
cloudx_proxy-0.3.14.dist-info/METADATA,sha256=qvR4_o1puGZ2WNjb0Y5GLW8Ff2aX_IH7k8YPZx9-ORM,14038
|
8
|
+
cloudx_proxy-0.3.14.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
9
|
+
cloudx_proxy-0.3.14.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
+
cloudx_proxy-0.3.14.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
+
cloudx_proxy-0.3.14.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
-
cloudx_proxy/_version.py,sha256=KKAn9DIKrkf9v6mAp59Mf9qI6ZEjCmBhMjuc_1u5SFI,413
|
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=GNgZcUnnJG7q7npUl4QW_4mWfJT0gKZ_i1fwmxj3pmI,18672
|
6
|
-
cloudx_proxy-0.3.12.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
-
cloudx_proxy-0.3.12.dist-info/METADATA,sha256=_RhoHyOJKYPCixp2of61kt5qaYRCebFaQB89L2hlZ00,14038
|
8
|
-
cloudx_proxy-0.3.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
-
cloudx_proxy-0.3.12.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
-
cloudx_proxy-0.3.12.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
-
cloudx_proxy-0.3.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|