cloudx-proxy 0.4.0__py3-none-any.whl → 0.4.1__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/setup.py +73 -138
- {cloudx_proxy-0.4.0.dist-info → cloudx_proxy-0.4.1.dist-info}/METADATA +1 -1
- cloudx_proxy-0.4.1.dist-info/RECORD +11 -0
- cloudx_proxy-0.4.0.dist-info/RECORD +0 -11
- {cloudx_proxy-0.4.0.dist-info → cloudx_proxy-0.4.1.dist-info}/LICENSE +0 -0
- {cloudx_proxy-0.4.0.dist-info → cloudx_proxy-0.4.1.dist-info}/WHEEL +0 -0
- {cloudx_proxy-0.4.0.dist-info → cloudx_proxy-0.4.1.dist-info}/entry_points.txt +0 -0
- {cloudx_proxy-0.4.0.dist-info → cloudx_proxy-0.4.1.dist-info}/top_level.txt +0 -0
cloudx_proxy/_version.py
CHANGED
cloudx_proxy/setup.py
CHANGED
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
7
7
|
from typing import Optional, Tuple
|
8
8
|
import boto3
|
9
9
|
from botocore.exceptions import ClientError
|
10
|
+
from ._1password import check_1password_cli, check_ssh_agent, list_ssh_keys, create_ssh_key, get_vaults, save_public_key
|
10
11
|
|
11
12
|
class CloudXSetup:
|
12
13
|
def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", ssh_config: str = None,
|
@@ -166,107 +167,51 @@ class CloudXSetup:
|
|
166
167
|
|
167
168
|
self.print_status("Checking 1Password availability...")
|
168
169
|
|
169
|
-
#
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
capture_output=True,
|
174
|
-
text=True,
|
175
|
-
check=False
|
176
|
-
)
|
177
|
-
if result.returncode != 0:
|
178
|
-
self.print_status("1Password CLI not found. Please install it from https://1password.com/downloads/command-line/", False, 2)
|
179
|
-
return False
|
180
|
-
|
181
|
-
self.print_status(f"1Password CLI {result.stdout.strip()} installed", True, 2)
|
182
|
-
|
183
|
-
# Check if 1Password SSH agent is running
|
184
|
-
agent_sock_exists = self.onepassword_agent_sock.exists()
|
185
|
-
if not agent_sock_exists:
|
186
|
-
self.print_status("1Password SSH agent socket not found at ~/.1password/agent.sock", False, 2)
|
187
|
-
self.print_status("Please ensure 1Password SSH agent is enabled in 1Password settings", None, 2)
|
188
|
-
return False
|
189
|
-
|
190
|
-
self.print_status("1Password SSH agent socket found", True, 2)
|
191
|
-
|
192
|
-
# Check if agent is active by trying to list identities
|
193
|
-
result = subprocess.run(
|
194
|
-
['ssh-add', '-l'],
|
195
|
-
env={**os.environ, 'SSH_AUTH_SOCK': str(self.onepassword_agent_sock)},
|
196
|
-
capture_output=True,
|
197
|
-
text=True,
|
198
|
-
check=False
|
199
|
-
)
|
200
|
-
|
201
|
-
if "Could not open a connection to your authentication agent" in result.stderr:
|
202
|
-
self.print_status("1Password SSH agent is not running", False, 2)
|
203
|
-
return False
|
204
|
-
|
205
|
-
self.print_status("1Password SSH agent is running", True, 2)
|
206
|
-
|
207
|
-
# Check if 1Password CLI is authenticated
|
208
|
-
result = subprocess.run(
|
209
|
-
['op', 'account', 'list'],
|
210
|
-
capture_output=True,
|
211
|
-
text=True,
|
212
|
-
check=False
|
213
|
-
)
|
214
|
-
|
215
|
-
if result.returncode != 0:
|
216
|
-
self.print_status("1Password CLI is not authenticated. Run 'op signin' first.", False, 2)
|
217
|
-
return False
|
218
|
-
|
219
|
-
self.print_status("1Password CLI is authenticated", True, 2)
|
220
|
-
return True
|
221
|
-
|
222
|
-
except FileNotFoundError:
|
170
|
+
# Use our helper function to check 1Password CLI
|
171
|
+
installed, authenticated, version = check_1password_cli()
|
172
|
+
|
173
|
+
if not installed:
|
223
174
|
self.print_status("1Password CLI not found. Please install it from https://1password.com/downloads/command-line/", False, 2)
|
224
175
|
return False
|
225
|
-
|
226
|
-
|
176
|
+
|
177
|
+
self.print_status(f"1Password CLI {version} installed", True, 2)
|
178
|
+
|
179
|
+
if not authenticated:
|
180
|
+
self.print_status("1Password CLI is not authenticated. Run 'op signin' first.", False, 2)
|
227
181
|
return False
|
182
|
+
|
183
|
+
self.print_status("1Password CLI is authenticated", True, 2)
|
184
|
+
|
185
|
+
# Check if 1Password SSH agent is running
|
186
|
+
agent_running = check_ssh_agent(str(self.onepassword_agent_sock))
|
187
|
+
|
188
|
+
if not agent_running:
|
189
|
+
self.print_status("1Password SSH agent is not running", False, 2)
|
190
|
+
self.print_status("Please ensure 1Password SSH agent is enabled in 1Password settings", None, 2)
|
191
|
+
return False
|
192
|
+
|
193
|
+
self.print_status("1Password SSH agent is running", True, 2)
|
194
|
+
return True
|
228
195
|
|
229
|
-
def
|
230
|
-
"""
|
196
|
+
def _create_1password_key(self) -> bool:
|
197
|
+
"""Create a new SSH key in 1Password.
|
231
198
|
|
232
199
|
Returns:
|
233
200
|
bool: True if successful
|
234
201
|
"""
|
235
202
|
try:
|
236
|
-
#
|
237
|
-
|
238
|
-
|
239
|
-
if not key_exists:
|
240
|
-
self.print_status("No SSH key found to store in 1Password", False, 2)
|
241
|
-
return False
|
242
|
-
|
243
|
-
# Ask if user wants to store the key in 1Password
|
244
|
-
store_in_1password = self.prompt("Would you like to store this SSH key in 1Password?", "Y").lower() == "y"
|
245
|
-
if not store_in_1password:
|
246
|
-
return True
|
247
|
-
|
248
|
-
# Get vault to store the key in
|
249
|
-
result = subprocess.run(
|
250
|
-
['op', 'vault', 'list', '--format=json'],
|
251
|
-
capture_output=True,
|
252
|
-
text=True,
|
253
|
-
check=True
|
254
|
-
)
|
255
|
-
|
256
|
-
if result.returncode != 0:
|
257
|
-
self.print_status("Error listing 1Password vaults", False, 2)
|
258
|
-
return False
|
259
|
-
|
260
|
-
vaults = json.loads(result.stdout)
|
203
|
+
# Get vaults to determine where to store the key
|
204
|
+
vaults = get_vaults()
|
261
205
|
if not vaults:
|
262
206
|
self.print_status("No 1Password vaults found", False, 2)
|
263
207
|
return False
|
264
|
-
|
208
|
+
|
265
209
|
# Display available vaults
|
210
|
+
self.print_status("Creating a new SSH key in 1Password", None, 2)
|
266
211
|
print("\n\033[96mAvailable 1Password vaults:\033[0m")
|
267
212
|
for i, vault in enumerate(vaults):
|
268
213
|
print(f" {i+1}. {vault['name']}")
|
269
|
-
|
214
|
+
|
270
215
|
# Let user select vault
|
271
216
|
vault_num = self.prompt("Select vault number to store SSH key", "1")
|
272
217
|
try:
|
@@ -278,46 +223,55 @@ class CloudXSetup:
|
|
278
223
|
except ValueError:
|
279
224
|
self.print_status("Invalid input", False, 2)
|
280
225
|
return False
|
281
|
-
|
282
|
-
# Read private key
|
283
|
-
private_key = self.ssh_key_file.read_text()
|
284
226
|
|
285
227
|
# Create a title for the 1Password item
|
286
228
|
ssh_key_title = f"cloudX SSH Key - {self.ssh_key}"
|
287
229
|
|
288
|
-
#
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
230
|
+
# Check if a key with this title already exists in 1Password
|
231
|
+
ssh_keys = list_ssh_keys()
|
232
|
+
existing_key = next((key for key in ssh_keys if key['title'] == ssh_key_title), None)
|
233
|
+
|
234
|
+
if existing_key:
|
235
|
+
self.print_status(f"SSH key '{ssh_key_title}' already exists in 1Password", True, 2)
|
236
|
+
# Get the public key
|
237
|
+
result = subprocess.run(
|
238
|
+
['op', 'item', 'get', existing_key['id'], '--fields', 'public key'],
|
239
|
+
capture_output=True,
|
240
|
+
text=True,
|
241
|
+
check=False
|
242
|
+
)
|
243
|
+
|
244
|
+
if result.returncode == 0:
|
245
|
+
public_key = result.stdout.strip()
|
246
|
+
# Save it to the expected location
|
247
|
+
if save_public_key(public_key, f"{self.ssh_key_file}.pub"):
|
248
|
+
self.print_status(f"Saved existing public key to {self.ssh_key_file}.pub", True, 2)
|
249
|
+
return True
|
250
|
+
else:
|
251
|
+
# Create a new SSH key in 1Password
|
252
|
+
self.print_status(f"Creating new SSH key '{ssh_key_title}' in 1Password...", None, 2)
|
253
|
+
success, public_key, item_id = create_ssh_key(ssh_key_title, selected_vault)
|
306
254
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
remove_private_key = self.prompt("Would you like to remove the private key from the filesystem? (for security)", "Y").lower() == "y"
|
311
|
-
if remove_private_key:
|
312
|
-
# We should only remove the private key, keep the public key
|
313
|
-
self.ssh_key_file.unlink()
|
314
|
-
self.print_status("Private key removed from filesystem", True, 2)
|
315
|
-
self.print_status("\033[93mImportant: If you need to recover this key, it's now only available in 1Password\033[0m", None, 2)
|
255
|
+
if not success:
|
256
|
+
self.print_status("Failed to create SSH key in 1Password", False, 2)
|
257
|
+
return False
|
316
258
|
|
259
|
+
self.print_status("SSH key created successfully in 1Password", True, 2)
|
260
|
+
|
261
|
+
# Save the public key to the expected location
|
262
|
+
if save_public_key(public_key, f"{self.ssh_key_file}.pub"):
|
263
|
+
self.print_status(f"Saved public key to {self.ssh_key_file}.pub", True, 2)
|
264
|
+
return True
|
265
|
+
else:
|
266
|
+
self.print_status(f"Failed to save public key to {self.ssh_key_file}.pub", False, 2)
|
267
|
+
return False
|
268
|
+
|
269
|
+
# Remind user to enable the key in 1Password SSH agent
|
270
|
+
self.print_status("\033[93mImportant: Make sure the key is enabled in 1Password's SSH agent settings\033[0m", None, 2)
|
317
271
|
return True
|
318
272
|
|
319
273
|
except Exception as e:
|
320
|
-
self.print_status(f"Error
|
274
|
+
self.print_status(f"Error creating key in 1Password: {str(e)}", False, 2)
|
321
275
|
return False
|
322
276
|
|
323
277
|
def setup_ssh_key(self) -> bool:
|
@@ -334,21 +288,8 @@ class CloudXSetup:
|
|
334
288
|
if op_available:
|
335
289
|
self.print_status("Using 1Password SSH agent for authentication", True, 2)
|
336
290
|
|
337
|
-
#
|
338
|
-
|
339
|
-
if not key_exists:
|
340
|
-
self.print_status(f"No SSH key found for '{self.ssh_key}'", None, 2)
|
341
|
-
create_key = self.prompt("Would you like to create a new SSH key?", "Y").lower() == "y"
|
342
|
-
if create_key:
|
343
|
-
# We'll continue to the standard key creation flow
|
344
|
-
pass
|
345
|
-
else:
|
346
|
-
# User doesn't want a key, so we'll skip and just rely on 1Password
|
347
|
-
return True
|
348
|
-
else:
|
349
|
-
# Key exists, offer to store it in 1Password
|
350
|
-
self._store_key_in_1password()
|
351
|
-
return True
|
291
|
+
# Always prefer to create keys in 1Password
|
292
|
+
return self._create_1password_key()
|
352
293
|
else:
|
353
294
|
proceed = self.prompt("1Password integration not available. Continue with standard SSH key setup?", "Y").lower() != "n"
|
354
295
|
if not proceed:
|
@@ -393,14 +334,8 @@ class CloudXSetup:
|
|
393
334
|
self.ssh_key_file.with_suffix('.pub').chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IROTH | stat.S_IRGRP) # 644 permissions
|
394
335
|
self.print_status("Set key file permissions", True, 2)
|
395
336
|
|
396
|
-
#
|
397
|
-
# we'll use the same code to generate the key
|
337
|
+
# Standard key generation successful
|
398
338
|
self.print_status("Key generated successfully", True, 2)
|
399
|
-
|
400
|
-
# If using 1Password, offer to store the key there
|
401
|
-
if self.use_1password:
|
402
|
-
self._store_key_in_1password()
|
403
|
-
|
404
339
|
return True
|
405
340
|
|
406
341
|
except Exception as e:
|
@@ -0,0 +1,11 @@
|
|
1
|
+
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
+
cloudx_proxy/_version.py,sha256=yF2DwGUoQKNnLhAbpZX8kCQKjw77EZzhRk7_OTftets,511
|
3
|
+
cloudx_proxy/cli.py,sha256=kdrZydxL94BJrv6NnjIcceRqhoonBzMIx4vfm1Wl7qc,4104
|
4
|
+
cloudx_proxy/core.py,sha256=RF3bX5MQiokRKjYEPnfWdKywGdtoVUvV2xZqm9uOl1g,8135
|
5
|
+
cloudx_proxy/setup.py,sha256=jvv7ibJQ8svyjYYeVKwGa70L7RV2W7yS7JXEvKed3wI,33339
|
6
|
+
cloudx_proxy-0.4.1.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
+
cloudx_proxy-0.4.1.dist-info/METADATA,sha256=wFL6lV-shLLfI6cn8H5ZItIF944-TrX67LJC0Ml-muc,14037
|
8
|
+
cloudx_proxy-0.4.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
9
|
+
cloudx_proxy-0.4.1.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
+
cloudx_proxy-0.4.1.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
+
cloudx_proxy-0.4.1.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
-
cloudx_proxy/_version.py,sha256=l5eo51MdCumDFCp44TFT1JH8yCDo1krag-GJubLxnVo,511
|
3
|
-
cloudx_proxy/cli.py,sha256=kdrZydxL94BJrv6NnjIcceRqhoonBzMIx4vfm1Wl7qc,4104
|
4
|
-
cloudx_proxy/core.py,sha256=RF3bX5MQiokRKjYEPnfWdKywGdtoVUvV2xZqm9uOl1g,8135
|
5
|
-
cloudx_proxy/setup.py,sha256=OGJszBaTzw94pB5B61h2i3fWath44DBRshfKYvlLu6U,36024
|
6
|
-
cloudx_proxy-0.4.0.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
-
cloudx_proxy-0.4.0.dist-info/METADATA,sha256=XC0WQBkIzpUuMnb_sJkQgqT7RKPJnHHYq9XFwB1a2U8,14037
|
8
|
-
cloudx_proxy-0.4.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
9
|
-
cloudx_proxy-0.4.0.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
-
cloudx_proxy-0.4.0.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
-
cloudx_proxy-0.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|