illumio-pylo 0.3.11__py3-none-any.whl → 0.3.12__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.
- illumio_pylo/API/APIConnector.py +82 -97
- illumio_pylo/API/CredentialsManager.py +38 -0
- illumio_pylo/Helpers/exports.py +1 -1
- illumio_pylo/__init__.py +1 -1
- illumio_pylo/cli/commands/credential_manager.py +379 -4
- illumio_pylo/cli/commands/label_delete_unused.py +0 -3
- illumio_pylo/cli/commands/ui/credential_manager_ui/app.js +449 -0
- illumio_pylo/cli/commands/ui/credential_manager_ui/index.html +168 -0
- illumio_pylo/cli/commands/ui/credential_manager_ui/styles.css +430 -0
- illumio_pylo/cli/commands/ven_duplicate_remover.py +79 -59
- illumio_pylo/utilities/cli.py +4 -1
- illumio_pylo/utilities/health_monitoring.py +5 -1
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/METADATA +2 -1
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/RECORD +17 -14
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/WHEEL +1 -1
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/licenses/LICENSE +0 -0
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/top_level.txt +0 -0
|
@@ -9,7 +9,7 @@ import paramiko
|
|
|
9
9
|
import illumio_pylo as pylo
|
|
10
10
|
import click
|
|
11
11
|
from illumio_pylo.API.CredentialsManager import get_all_credentials, create_credential_in_file, CredentialFileEntry, \
|
|
12
|
-
create_credential_in_default_file, \
|
|
12
|
+
create_credential_in_default_file, delete_credential_from_file, \
|
|
13
13
|
get_credentials_from_file, encrypt_api_key_with_paramiko_ssh_key_chacha20poly1305, \
|
|
14
14
|
decrypt_api_key_with_paramiko_ssh_key_chacha20poly1305, get_supported_keys_from_ssh_agent, is_encryption_available
|
|
15
15
|
|
|
@@ -42,15 +42,42 @@ def fill_parser(parser: argparse.ArgumentParser):
|
|
|
42
42
|
create_parser.add_argument('--verify-ssl', required=False, type=bool, default=None,
|
|
43
43
|
help='Verify SSL')
|
|
44
44
|
|
|
45
|
+
update_parser = sub_parser.add_parser('update', help='Update a credential')
|
|
46
|
+
update_parser.add_argument('--name', required=False, type=str, default=None,
|
|
47
|
+
help='Name of the credential to update')
|
|
48
|
+
|
|
49
|
+
update_parser.add_argument('--fqdn', required=False, type=str, default=None,
|
|
50
|
+
help='FQDN of the PCE')
|
|
51
|
+
update_parser.add_argument('--port', required=False, type=int, default=None,
|
|
52
|
+
help='Port of the PCE')
|
|
53
|
+
update_parser.add_argument('--org', required=False, type=int, default=None,
|
|
54
|
+
help='Organization ID')
|
|
55
|
+
update_parser.add_argument('--api-user', required=False, type=str, default=None,
|
|
56
|
+
help='API user')
|
|
57
|
+
update_parser.add_argument('--verify-ssl', required=False, type=bool, default=None,
|
|
58
|
+
help='Verify SSL')
|
|
59
|
+
|
|
60
|
+
# Delete sub-command
|
|
61
|
+
delete_parser = sub_parser.add_parser('delete', help='Delete a credential')
|
|
62
|
+
delete_parser.add_argument('--name', required=False, type=str, default=None,
|
|
63
|
+
help='Name of the credential to delete')
|
|
64
|
+
delete_parser.add_argument('--yes', '-y', action='store_true', default=False,
|
|
65
|
+
help='Skip confirmation prompt')
|
|
66
|
+
|
|
67
|
+
# Web editor sub-command
|
|
68
|
+
web_editor_parser = sub_parser.add_parser('web-editor', help='Start web-based credential editor')
|
|
69
|
+
web_editor_parser.add_argument('--host', required=False, type=str, default='127.0.0.1',
|
|
70
|
+
help='Host to bind the web server to')
|
|
71
|
+
web_editor_parser.add_argument('--port', required=False, type=int, default=5000,
|
|
72
|
+
help='Port to bind the web server to')
|
|
73
|
+
|
|
45
74
|
|
|
46
75
|
def __main(args, **kwargs):
|
|
47
76
|
if args['sub_command'] == 'list':
|
|
48
77
|
table = PrettyTable(field_names=["Name", "URL", "API User", "Originating File"])
|
|
49
|
-
# all should be left justified
|
|
50
78
|
table.align = "l"
|
|
51
79
|
|
|
52
80
|
credentials = get_all_credentials()
|
|
53
|
-
# sort credentials by name
|
|
54
81
|
credentials.sort(key=lambda x: x.name)
|
|
55
82
|
|
|
56
83
|
for credential in credentials:
|
|
@@ -153,6 +180,116 @@ def __main(args, **kwargs):
|
|
|
153
180
|
|
|
154
181
|
print("OK! ({})".format(file_path))
|
|
155
182
|
|
|
183
|
+
elif args['sub_command'] == 'update':
|
|
184
|
+
# if name is not provided, prompt for it
|
|
185
|
+
if args['name'] is None:
|
|
186
|
+
wanted_name = click.prompt('> Input a Profile Name to update (ie: prod-pce)', type=str)
|
|
187
|
+
args['name'] = wanted_name
|
|
188
|
+
|
|
189
|
+
# find the credential by name
|
|
190
|
+
wanted_name = args['name']
|
|
191
|
+
found_profile = get_credentials_from_file(wanted_name, fail_with_an_exception=False)
|
|
192
|
+
if found_profile is None:
|
|
193
|
+
print("Cannot find a profile named '{}'".format(wanted_name))
|
|
194
|
+
print("Available profiles:")
|
|
195
|
+
credentials = get_all_credentials()
|
|
196
|
+
for credential in credentials:
|
|
197
|
+
print(" - {}".format(credential.name))
|
|
198
|
+
sys.exit(1)
|
|
199
|
+
|
|
200
|
+
print("Found profile '{}' to update in file '{}'".format(found_profile.name, found_profile.originating_file))
|
|
201
|
+
|
|
202
|
+
if args['fqdn'] is not None:
|
|
203
|
+
found_profile.fqdn = args['fqdn']
|
|
204
|
+
if args['port'] is not None:
|
|
205
|
+
found_profile.port = args['port']
|
|
206
|
+
if args['org'] is not None:
|
|
207
|
+
found_profile.org_id = args['org']
|
|
208
|
+
if args['api_user'] is not None:
|
|
209
|
+
found_profile.api_user = args['api_user']
|
|
210
|
+
if args['verify_ssl'] is not None:
|
|
211
|
+
found_profile.verify_ssl = args['verify_ssl']
|
|
212
|
+
|
|
213
|
+
# ask if user wants to update API key
|
|
214
|
+
update_api_key = click.prompt('> Do you want to update the API key? Y/N', type=bool)
|
|
215
|
+
if update_api_key:
|
|
216
|
+
api_key = click.prompt('> New API Key', hide_input=True)
|
|
217
|
+
found_profile.api_key = api_key
|
|
218
|
+
print()
|
|
219
|
+
|
|
220
|
+
if is_encryption_available():
|
|
221
|
+
encrypt_api_key = click.prompt('> Encrypt API (requires an SSH agent running and an RSA or Ed25519 key added to them) ? Y/N', type=bool)
|
|
222
|
+
if encrypt_api_key:
|
|
223
|
+
print("Available keys (ECDSA NISTPXXX keys and a few others are not supported and will be filtered out):")
|
|
224
|
+
ssh_keys = get_supported_keys_from_ssh_agent()
|
|
225
|
+
|
|
226
|
+
# display a table of keys
|
|
227
|
+
print_keys(keys=ssh_keys, display_index=True)
|
|
228
|
+
print()
|
|
229
|
+
|
|
230
|
+
index_of_selected_key = click.prompt('> Select key by ID#', type=click.IntRange(0, len(ssh_keys)-1))
|
|
231
|
+
selected_ssh_key = ssh_keys[index_of_selected_key]
|
|
232
|
+
print("Selected key: {} | {} | {}".format(selected_ssh_key.get_name(),
|
|
233
|
+
selected_ssh_key.get_fingerprint().hex(),
|
|
234
|
+
selected_ssh_key.comment))
|
|
235
|
+
print(" * encrypting API key with selected key (you may be prompted by your SSH agent for confirmation or PIN code) ...", flush=True, end="")
|
|
236
|
+
# encrypted_api_key = encrypt_api_key_with_paramiko_ssh_key_fernet(ssh_key=selected_ssh_key, api_key=found_profile.api_key)
|
|
237
|
+
encrypted_api_key = encrypt_api_key_with_paramiko_ssh_key_chacha20poly1305(ssh_key=selected_ssh_key, api_key=found_profile.api_key)
|
|
238
|
+
print("OK!")
|
|
239
|
+
print(" * trying to decrypt the encrypted API key...", flush=True, end="")
|
|
240
|
+
decrypted_api_key = decrypt_api_key_with_paramiko_ssh_key_chacha20poly1305(encrypted_api_key_payload=encrypted_api_key)
|
|
241
|
+
if decrypted_api_key != found_profile.api_key:
|
|
242
|
+
raise pylo.PyloEx("Decrypted API key does not match original API key")
|
|
243
|
+
print("OK!")
|
|
244
|
+
found_profile.api_key = encrypted_api_key
|
|
245
|
+
|
|
246
|
+
credentials_data: CredentialFileEntry = {
|
|
247
|
+
"name": found_profile.name,
|
|
248
|
+
"fqdn": found_profile.fqdn,
|
|
249
|
+
"port": found_profile.port,
|
|
250
|
+
"org_id": found_profile.org_id,
|
|
251
|
+
"api_user": found_profile.api_user,
|
|
252
|
+
"verify_ssl": found_profile.verify_ssl,
|
|
253
|
+
"api_key": found_profile.api_key
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
print("* Updating credential in file '{}'...".format(found_profile.originating_file), flush=True, end="")
|
|
257
|
+
create_credential_in_file(file_full_path=found_profile.originating_file, data=credentials_data, overwrite_existing_profile=True)
|
|
258
|
+
print("OK!")
|
|
259
|
+
|
|
260
|
+
elif args['sub_command'] == 'delete':
|
|
261
|
+
# if name is not provided, prompt for it
|
|
262
|
+
wanted_name = args['name']
|
|
263
|
+
if wanted_name is None:
|
|
264
|
+
wanted_name = click.prompt('> Input a Profile Name to delete (ie: prod-pce)', type=str)
|
|
265
|
+
|
|
266
|
+
# find the credential by name
|
|
267
|
+
found_profile = get_credentials_from_file(wanted_name, fail_with_an_exception=False)
|
|
268
|
+
if found_profile is None:
|
|
269
|
+
print("Cannot find a profile named '{}'".format(wanted_name))
|
|
270
|
+
print("Available profiles:")
|
|
271
|
+
credentials = get_all_credentials()
|
|
272
|
+
for credential in credentials:
|
|
273
|
+
print(" - {}".format(credential.name))
|
|
274
|
+
sys.exit(1)
|
|
275
|
+
|
|
276
|
+
print("Found profile '{}' in file '{}'".format(found_profile.name, found_profile.originating_file))
|
|
277
|
+
print(" - FQDN: {}".format(found_profile.fqdn))
|
|
278
|
+
print(" - Port: {}".format(found_profile.port))
|
|
279
|
+
print(" - Org ID: {}".format(found_profile.org_id))
|
|
280
|
+
print(" - API User: {}".format(found_profile.api_user))
|
|
281
|
+
|
|
282
|
+
# Confirm deletion unless --yes flag is provided
|
|
283
|
+
if not args['yes']:
|
|
284
|
+
confirm = click.prompt('> Are you sure you want to delete this credential? Y/N', type=bool)
|
|
285
|
+
if not confirm:
|
|
286
|
+
print("Deletion cancelled.")
|
|
287
|
+
sys.exit(0)
|
|
288
|
+
|
|
289
|
+
print("* Deleting credential...", flush=True, end="")
|
|
290
|
+
delete_credential_from_file(profile_name=found_profile.name, file_path=found_profile.originating_file)
|
|
291
|
+
print("OK!")
|
|
292
|
+
|
|
156
293
|
elif args['sub_command'] == 'test':
|
|
157
294
|
print("* Profile Tester command")
|
|
158
295
|
wanted_name = args['name']
|
|
@@ -180,6 +317,9 @@ def __main(args, **kwargs):
|
|
|
180
317
|
connector.objects_label_dimension_get()
|
|
181
318
|
print("OK!")
|
|
182
319
|
|
|
320
|
+
elif args['sub_command'] == 'web-editor':
|
|
321
|
+
run_web_editor(host=args['host'], port=args['port'])
|
|
322
|
+
|
|
183
323
|
else:
|
|
184
324
|
raise pylo.PyloEx("Unknown sub-command '{}'".format(args['sub_command']))
|
|
185
325
|
|
|
@@ -189,7 +329,7 @@ command_object = Command(command_name, __main, fill_parser, credentials_manager_
|
|
|
189
329
|
|
|
190
330
|
def print_keys(keys: list[paramiko.AgentKey], display_index=True) -> None:
|
|
191
331
|
|
|
192
|
-
column_properties = [
|
|
332
|
+
column_properties = [
|
|
193
333
|
("ID#", 4),
|
|
194
334
|
("Type", 20),
|
|
195
335
|
("Fingerprint", 40),
|
|
@@ -214,3 +354,238 @@ def print_keys(keys: list[paramiko.AgentKey], display_index=True) -> None:
|
|
|
214
354
|
table.add_row(display_values)
|
|
215
355
|
|
|
216
356
|
print(table)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def run_web_editor(host: str = '127.0.0.1', port: int = 5000) -> None:
|
|
360
|
+
"""Start the Flask web server for credential management."""
|
|
361
|
+
try:
|
|
362
|
+
from flask import Flask, jsonify, request, send_from_directory
|
|
363
|
+
except ImportError:
|
|
364
|
+
print("Flask is not installed. Please install it with: pip install flask")
|
|
365
|
+
sys.exit(1)
|
|
366
|
+
|
|
367
|
+
# Determine paths for static files
|
|
368
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
369
|
+
web_editor_dir = os.path.join(current_dir, 'ui/credential_manager_ui')
|
|
370
|
+
# That directory should contain index.html, error if not
|
|
371
|
+
if not os.path.exists(os.path.join(web_editor_dir, 'index.html')):
|
|
372
|
+
print("Cannot find web editor static files in expected location: {}".format(web_editor_dir))
|
|
373
|
+
sys.exit(1)
|
|
374
|
+
|
|
375
|
+
app = Flask(__name__, static_folder=web_editor_dir)
|
|
376
|
+
|
|
377
|
+
# Serve static files
|
|
378
|
+
@app.route('/')
|
|
379
|
+
def index():
|
|
380
|
+
return send_from_directory(web_editor_dir, 'index.html')
|
|
381
|
+
|
|
382
|
+
@app.route('/static/<path:filename>')
|
|
383
|
+
def serve_static(filename):
|
|
384
|
+
return send_from_directory(web_editor_dir, filename)
|
|
385
|
+
|
|
386
|
+
# API: List all credentials
|
|
387
|
+
@app.route('/api/credentials', methods=['GET'])
|
|
388
|
+
def api_list_credentials():
|
|
389
|
+
credentials = get_all_credentials()
|
|
390
|
+
credentials.sort(key=lambda x: x.name)
|
|
391
|
+
result = []
|
|
392
|
+
for cred in credentials:
|
|
393
|
+
result.append({
|
|
394
|
+
'name': cred.name,
|
|
395
|
+
'fqdn': cred.fqdn,
|
|
396
|
+
'port': cred.port,
|
|
397
|
+
'org_id': cred.org_id,
|
|
398
|
+
'api_user': cred.api_user,
|
|
399
|
+
'verify_ssl': cred.verify_ssl,
|
|
400
|
+
'originating_file': cred.originating_file
|
|
401
|
+
})
|
|
402
|
+
return jsonify(result)
|
|
403
|
+
|
|
404
|
+
# API: Get a single credential
|
|
405
|
+
@app.route('/api/credentials/<name>', methods=['GET'])
|
|
406
|
+
def api_get_credential(name):
|
|
407
|
+
found_profile = get_credentials_from_file(name, fail_with_an_exception=False)
|
|
408
|
+
if found_profile is None:
|
|
409
|
+
return jsonify({'error': 'Credential not found'}), 404
|
|
410
|
+
return jsonify({
|
|
411
|
+
'name': found_profile.name,
|
|
412
|
+
'fqdn': found_profile.fqdn,
|
|
413
|
+
'port': found_profile.port,
|
|
414
|
+
'org_id': found_profile.org_id,
|
|
415
|
+
'api_user': found_profile.api_user,
|
|
416
|
+
'verify_ssl': found_profile.verify_ssl,
|
|
417
|
+
'originating_file': found_profile.originating_file
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
# API: Create a new credential
|
|
421
|
+
@app.route('/api/credentials', methods=['POST'])
|
|
422
|
+
def api_create_credential():
|
|
423
|
+
data = request.get_json()
|
|
424
|
+
if not data:
|
|
425
|
+
return jsonify({'error': 'No data provided'}), 400
|
|
426
|
+
|
|
427
|
+
required_fields = ['name', 'fqdn', 'port', 'org_id', 'api_user', 'api_key']
|
|
428
|
+
for field in required_fields:
|
|
429
|
+
if field not in data:
|
|
430
|
+
return jsonify({'error': f'Missing required field: {field}'}), 400
|
|
431
|
+
|
|
432
|
+
# Check if credential already exists
|
|
433
|
+
credentials = get_all_credentials()
|
|
434
|
+
for credential in credentials:
|
|
435
|
+
if credential.name == data['name']:
|
|
436
|
+
return jsonify({'error': f"A credential named '{data['name']}' already exists"}), 400
|
|
437
|
+
|
|
438
|
+
credentials_data: CredentialFileEntry = {
|
|
439
|
+
"name": data['name'],
|
|
440
|
+
"fqdn": data['fqdn'],
|
|
441
|
+
"port": int(data['port']),
|
|
442
|
+
"org_id": int(data['org_id']),
|
|
443
|
+
"api_user": data['api_user'],
|
|
444
|
+
"verify_ssl": data.get('verify_ssl', True),
|
|
445
|
+
"api_key": data['api_key']
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
# Handle encryption if requested
|
|
449
|
+
if data.get('encrypt') and data.get('ssh_key_index') is not None:
|
|
450
|
+
if is_encryption_available():
|
|
451
|
+
try:
|
|
452
|
+
ssh_keys = get_supported_keys_from_ssh_agent()
|
|
453
|
+
key_index = int(data['ssh_key_index'])
|
|
454
|
+
if 0 <= key_index < len(ssh_keys):
|
|
455
|
+
selected_ssh_key = ssh_keys[key_index]
|
|
456
|
+
encrypted_api_key = encrypt_api_key_with_paramiko_ssh_key_chacha20poly1305(
|
|
457
|
+
ssh_key=selected_ssh_key, api_key=data['api_key'])
|
|
458
|
+
# Verify encryption
|
|
459
|
+
decrypted_api_key = decrypt_api_key_with_paramiko_ssh_key_chacha20poly1305(
|
|
460
|
+
encrypted_api_key_payload=encrypted_api_key)
|
|
461
|
+
if decrypted_api_key != data['api_key']:
|
|
462
|
+
return jsonify({'error': 'Encryption verification failed'}), 500
|
|
463
|
+
credentials_data["api_key"] = encrypted_api_key
|
|
464
|
+
except Exception as e:
|
|
465
|
+
return jsonify({'error': f'Encryption failed: {str(e)}'}), 500
|
|
466
|
+
|
|
467
|
+
try:
|
|
468
|
+
if data.get('use_current_workdir'):
|
|
469
|
+
file_path = create_credential_in_file(file_full_path=os.getcwd(), data=credentials_data)
|
|
470
|
+
else:
|
|
471
|
+
file_path = create_credential_in_default_file(data=credentials_data)
|
|
472
|
+
return jsonify({'success': True, 'file_path': file_path})
|
|
473
|
+
except Exception as e:
|
|
474
|
+
return jsonify({'error': str(e)}), 500
|
|
475
|
+
|
|
476
|
+
# API: Update a credential
|
|
477
|
+
@app.route('/api/credentials/<name>', methods=['PUT'])
|
|
478
|
+
def api_update_credential(name):
|
|
479
|
+
data = request.get_json()
|
|
480
|
+
if not data:
|
|
481
|
+
return jsonify({'error': 'No data provided'}), 400
|
|
482
|
+
|
|
483
|
+
found_profile = get_credentials_from_file(name, fail_with_an_exception=False)
|
|
484
|
+
if found_profile is None:
|
|
485
|
+
return jsonify({'error': 'Credential not found'}), 404
|
|
486
|
+
|
|
487
|
+
# Update fields if provided
|
|
488
|
+
if 'fqdn' in data:
|
|
489
|
+
found_profile.fqdn = data['fqdn']
|
|
490
|
+
if 'port' in data:
|
|
491
|
+
found_profile.port = int(data['port'])
|
|
492
|
+
if 'org_id' in data:
|
|
493
|
+
found_profile.org_id = int(data['org_id'])
|
|
494
|
+
if 'api_user' in data:
|
|
495
|
+
found_profile.api_user = data['api_user']
|
|
496
|
+
if 'verify_ssl' in data:
|
|
497
|
+
found_profile.verify_ssl = data['verify_ssl']
|
|
498
|
+
if 'api_key' in data and data['api_key']:
|
|
499
|
+
found_profile.api_key = data['api_key']
|
|
500
|
+
|
|
501
|
+
# Handle encryption if requested
|
|
502
|
+
if data.get('encrypt') and data.get('ssh_key_index') is not None:
|
|
503
|
+
if is_encryption_available():
|
|
504
|
+
try:
|
|
505
|
+
ssh_keys = get_supported_keys_from_ssh_agent()
|
|
506
|
+
key_index = int(data['ssh_key_index'])
|
|
507
|
+
if 0 <= key_index < len(ssh_keys):
|
|
508
|
+
selected_ssh_key = ssh_keys[key_index]
|
|
509
|
+
encrypted_api_key = encrypt_api_key_with_paramiko_ssh_key_chacha20poly1305(
|
|
510
|
+
ssh_key=selected_ssh_key, api_key=found_profile.api_key)
|
|
511
|
+
decrypted_api_key = decrypt_api_key_with_paramiko_ssh_key_chacha20poly1305(
|
|
512
|
+
encrypted_api_key_payload=encrypted_api_key)
|
|
513
|
+
if decrypted_api_key != found_profile.api_key:
|
|
514
|
+
return jsonify({'error': 'Encryption verification failed'}), 500
|
|
515
|
+
found_profile.api_key = encrypted_api_key
|
|
516
|
+
except Exception as e:
|
|
517
|
+
return jsonify({'error': f'Encryption failed: {str(e)}'}), 500
|
|
518
|
+
|
|
519
|
+
credentials_data: CredentialFileEntry = {
|
|
520
|
+
"name": found_profile.name,
|
|
521
|
+
"fqdn": found_profile.fqdn,
|
|
522
|
+
"port": found_profile.port,
|
|
523
|
+
"org_id": found_profile.org_id,
|
|
524
|
+
"api_user": found_profile.api_user,
|
|
525
|
+
"verify_ssl": found_profile.verify_ssl,
|
|
526
|
+
"api_key": found_profile.api_key
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
create_credential_in_file(file_full_path=found_profile.originating_file,
|
|
531
|
+
data=credentials_data, overwrite_existing_profile=True)
|
|
532
|
+
return jsonify({'success': True})
|
|
533
|
+
except Exception as e:
|
|
534
|
+
return jsonify({'error': str(e)}), 500
|
|
535
|
+
|
|
536
|
+
# API: Test a credential
|
|
537
|
+
@app.route('/api/credentials/<name>/test', methods=['POST'])
|
|
538
|
+
def api_test_credential(name):
|
|
539
|
+
found_profile = get_credentials_from_file(name, fail_with_an_exception=False)
|
|
540
|
+
if found_profile is None:
|
|
541
|
+
return jsonify({'error': 'Credential not found'}), 404
|
|
542
|
+
|
|
543
|
+
try:
|
|
544
|
+
connector = pylo.APIConnector.create_from_credentials_object(found_profile)
|
|
545
|
+
connector.objects_label_dimension_get()
|
|
546
|
+
return jsonify({'success': True, 'message': 'Connection successful'})
|
|
547
|
+
except Exception as e:
|
|
548
|
+
return jsonify({'error': str(e)}), 500
|
|
549
|
+
|
|
550
|
+
# API: Delete a credential
|
|
551
|
+
@app.route('/api/credentials/<name>', methods=['DELETE'])
|
|
552
|
+
def api_delete_credential(name):
|
|
553
|
+
found_profile = get_credentials_from_file(name, fail_with_an_exception=False)
|
|
554
|
+
if found_profile is None:
|
|
555
|
+
return jsonify({'error': 'Credential not found'}), 404
|
|
556
|
+
|
|
557
|
+
try:
|
|
558
|
+
delete_credential_from_file(profile_name=found_profile.name, file_path=found_profile.originating_file)
|
|
559
|
+
return jsonify({'success': True, 'message': f"Credential '{name}' deleted successfully"})
|
|
560
|
+
except Exception as e:
|
|
561
|
+
return jsonify({'error': str(e)}), 500
|
|
562
|
+
|
|
563
|
+
# API: Get SSH keys for encryption
|
|
564
|
+
@app.route('/api/ssh-keys', methods=['GET'])
|
|
565
|
+
def api_get_ssh_keys():
|
|
566
|
+
if not is_encryption_available():
|
|
567
|
+
return jsonify({'available': False, 'keys': []})
|
|
568
|
+
|
|
569
|
+
try:
|
|
570
|
+
ssh_keys = get_supported_keys_from_ssh_agent()
|
|
571
|
+
keys_list = []
|
|
572
|
+
for i, key in enumerate(ssh_keys):
|
|
573
|
+
keys_list.append({
|
|
574
|
+
'index': i,
|
|
575
|
+
'type': key.get_name(),
|
|
576
|
+
'fingerprint': key.get_fingerprint().hex(),
|
|
577
|
+
'comment': key.comment
|
|
578
|
+
})
|
|
579
|
+
return jsonify({'available': True, 'keys': keys_list})
|
|
580
|
+
except Exception as e:
|
|
581
|
+
return jsonify({'available': False, 'error': str(e), 'keys': []})
|
|
582
|
+
|
|
583
|
+
# API: Check encryption availability
|
|
584
|
+
@app.route('/api/encryption-status', methods=['GET'])
|
|
585
|
+
def api_encryption_status():
|
|
586
|
+
return jsonify({'available': is_encryption_available()})
|
|
587
|
+
|
|
588
|
+
print(f"Starting web editor at http://{host}:{port}")
|
|
589
|
+
print("Press Ctrl+C to stop the server")
|
|
590
|
+
app.run(host=host, port=port, debug=False)
|
|
591
|
+
|