illumio-pylo 0.3.10__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.
@@ -0,0 +1,79 @@
1
+ import argparse
2
+ from typing import Optional, List
3
+
4
+ import illumio_pylo as pylo
5
+
6
+ from . import Command
7
+ from illumio_pylo.API.JsonPayloadTypes import LabelObjectJsonStructure
8
+
9
+ command_name = "label-delete-unused"
10
+ objects_load_filter = [] # No need to load any objects from PCE
11
+
12
+
13
+ def fill_parser(parser: argparse.ArgumentParser):
14
+ parser.add_argument('--confirm', action='store_true',
15
+ help='No change will be implemented in the PCE until you use this function to confirm you\'re good with them after review')
16
+ parser.add_argument('--limit', type=int, required=False, default=None,
17
+ help='Maximum number of unused labels to delete (default: all found unused labels)')
18
+
19
+
20
+ def __main(args, org: pylo.Organization = None, connector: pylo.APIConnector = None, config_data=None, **kwargs):
21
+
22
+ settings_confirmed_changes: bool = args['confirm']
23
+ settings_limit_deletions: Optional[int] = args['limit']
24
+
25
+ print("Fetching all Labels from the PCE... ", end='', flush=True)
26
+ # pylo.log_set_debug()
27
+ labels_json = connector.objects_label_get(max_results=199000, get_usage=True, async_mode=False)
28
+ print("OK!")
29
+
30
+ print(f"Analyzing {len(labels_json)} labels to find unused ones... ")
31
+ unused_labels: List[LabelObjectJsonStructure] = []
32
+
33
+ for label_json in labels_json:
34
+ usage_json = label_json.get('usage', {})
35
+ label_is_used = False
36
+
37
+ for usage_type, usage_confirmed in usage_json.items():
38
+ if usage_confirmed:
39
+ label_is_used = True
40
+ print(f"Label '{label_json.get('value')}' is used in '{usage_type}', skipping deletion.")
41
+ break
42
+
43
+ if not label_is_used:
44
+ print(f"Label '{label_json.get('value')}' is unused, marking for deletion.")
45
+ unused_labels.append(label_json)
46
+
47
+ print()
48
+ print(f"Found {len(unused_labels)} unused labels vs total of {len(labels_json)} labels.")
49
+
50
+ if len(unused_labels) > 0:
51
+ if not settings_confirmed_changes:
52
+ print("No change will be implemented in the PCE until you use the '--confirm' flag to confirm you're good with them after review.")
53
+ else:
54
+ print()
55
+ print(f"Proceeding to delete unused labels up to the limit of '{settings_limit_deletions if settings_limit_deletions is not None else 'all'}'...")
56
+ tracker = connector.new_tracker_for_label_multi_deletion()
57
+
58
+ if settings_limit_deletions is not None:
59
+ unused_labels = unused_labels[:settings_limit_deletions]
60
+
61
+ for label_json in unused_labels:
62
+ tracker.add_label(label_json['href'])
63
+
64
+ tracker.execute_deletion()
65
+ errors_count = tracker.get_errors_count()
66
+ success_count = len(unused_labels) - errors_count
67
+
68
+ for label_json in unused_labels:
69
+ error = tracker.get_error(label_json['href'])
70
+ if error is not None:
71
+ print(f" - ERROR deleting label '{label_json.get('value')}': {error}")
72
+ else:
73
+ print(f" - SUCCESS deleting label '{label_json.get('value')}'")
74
+
75
+ print()
76
+ print(f"Deletion completed: {success_count} labels deleted successfully, {errors_count} errors encountered.")
77
+
78
+
79
+ command_object = Command(command_name, __main, fill_parser, skip_pce_config_loading=True, load_specific_objects_only=objects_load_filter)
@@ -0,0 +1,449 @@
1
+ // Credential Manager Web Editor - JavaScript Application
2
+
3
+ (function() {
4
+ 'use strict';
5
+
6
+ // DOM Elements
7
+ const elements = {
8
+ // Credentials table
9
+ credentialsTable: document.getElementById('credentials-table'),
10
+ credentialsBody: document.getElementById('credentials-body'),
11
+ credentialsLoading: document.getElementById('credentials-loading'),
12
+ noCredentials: document.getElementById('no-credentials'),
13
+
14
+ // Notification
15
+ notification: document.getElementById('notification'),
16
+
17
+ // Buttons
18
+ btnNewCredential: document.getElementById('btn-new-credential'),
19
+ btnCloseModal: document.getElementById('btn-close-modal'),
20
+ btnCancel: document.getElementById('btn-cancel'),
21
+ btnSubmit: document.getElementById('btn-submit'),
22
+ btnCloseTestModal: document.getElementById('btn-close-test-modal'),
23
+ btnCloseTest: document.getElementById('btn-close-test'),
24
+
25
+ // Modal
26
+ credentialModal: document.getElementById('credential-modal'),
27
+ modalTitle: document.getElementById('modal-title'),
28
+ credentialForm: document.getElementById('credential-form'),
29
+
30
+ // Form fields
31
+ formMode: document.getElementById('form-mode'),
32
+ formOriginalName: document.getElementById('form-original-name'),
33
+ formName: document.getElementById('form-name'),
34
+ formFqdn: document.getElementById('form-fqdn'),
35
+ formPort: document.getElementById('form-port'),
36
+ formOrgId: document.getElementById('form-org-id'),
37
+ formApiUser: document.getElementById('form-api-user'),
38
+ formApiKey: document.getElementById('form-api-key'),
39
+ formVerifySsl: document.getElementById('form-verify-ssl'),
40
+ formEncrypt: document.getElementById('form-encrypt'),
41
+ formSshKey: document.getElementById('form-ssh-key'),
42
+ formUseWorkdir: document.getElementById('form-use-workdir'),
43
+
44
+ // Form sections
45
+ encryptionSection: document.getElementById('encryption-section'),
46
+ sshKeysSection: document.getElementById('ssh-keys-section'),
47
+ storageSection: document.getElementById('storage-section'),
48
+ apiKeyRequired: document.getElementById('api-key-required'),
49
+ apiKeyHint: document.getElementById('api-key-hint'),
50
+
51
+ // Test modal
52
+ testModal: document.getElementById('test-modal'),
53
+ testResult: document.getElementById('test-result'),
54
+
55
+ // Delete modal
56
+ deleteModal: document.getElementById('delete-modal'),
57
+ deleteCredentialName: document.getElementById('delete-credential-name'),
58
+ btnCloseDeleteModal: document.getElementById('btn-close-delete-modal'),
59
+ btnCancelDelete: document.getElementById('btn-cancel-delete'),
60
+ btnConfirmDelete: document.getElementById('btn-confirm-delete')
61
+ };
62
+
63
+ // State
64
+ let sshKeys = [];
65
+ let encryptionAvailable = false;
66
+ let credentialToDelete = null;
67
+
68
+ // Initialize the application
69
+ async function init() {
70
+ await loadCredentials();
71
+ await checkEncryptionStatus();
72
+ setupEventListeners();
73
+ }
74
+
75
+ // Setup event listeners
76
+ function setupEventListeners() {
77
+ // New credential button
78
+ elements.btnNewCredential.addEventListener('click', () => openCreateModal());
79
+
80
+ // Modal close buttons
81
+ elements.btnCloseModal.addEventListener('click', closeModal);
82
+ elements.btnCancel.addEventListener('click', closeModal);
83
+
84
+ // Form submission
85
+ elements.credentialForm.addEventListener('submit', handleFormSubmit);
86
+
87
+ // Encryption checkbox
88
+ elements.formEncrypt.addEventListener('change', toggleSshKeySelection);
89
+
90
+ // Test modal close
91
+ elements.btnCloseTestModal.addEventListener('click', closeTestModal);
92
+ elements.btnCloseTest.addEventListener('click', closeTestModal);
93
+
94
+ // Delete modal buttons
95
+ elements.btnCloseDeleteModal.addEventListener('click', closeDeleteModal);
96
+ elements.btnCancelDelete.addEventListener('click', closeDeleteModal);
97
+ elements.btnConfirmDelete.addEventListener('click', confirmDelete);
98
+
99
+ // Close modals on background click
100
+ elements.credentialModal.addEventListener('click', (e) => {
101
+ if (e.target === elements.credentialModal) closeModal();
102
+ });
103
+ elements.testModal.addEventListener('click', (e) => {
104
+ if (e.target === elements.testModal) closeTestModal();
105
+ });
106
+ elements.deleteModal.addEventListener('click', (e) => {
107
+ if (e.target === elements.deleteModal) closeDeleteModal();
108
+ });
109
+ }
110
+
111
+ // API Functions
112
+ async function apiCall(url, options = {}) {
113
+ try {
114
+ const response = await fetch(url, {
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ ...options.headers
118
+ },
119
+ ...options
120
+ });
121
+ const data = await response.json();
122
+ if (!response.ok) {
123
+ throw new Error(data.error || 'An error occurred');
124
+ }
125
+ return data;
126
+ } catch (error) {
127
+ throw error;
128
+ }
129
+ }
130
+
131
+ // Load all credentials
132
+ async function loadCredentials() {
133
+ elements.credentialsLoading.classList.remove('hidden');
134
+ elements.credentialsTable.classList.add('hidden');
135
+ elements.noCredentials.classList.add('hidden');
136
+
137
+ try {
138
+ const credentials = await apiCall('/api/credentials');
139
+ renderCredentials(credentials);
140
+ } catch (error) {
141
+ showNotification('Failed to load credentials: ' + error.message, 'error');
142
+ elements.credentialsLoading.classList.add('hidden');
143
+ }
144
+ }
145
+
146
+ // Render credentials table
147
+ function renderCredentials(credentials) {
148
+ elements.credentialsLoading.classList.add('hidden');
149
+
150
+ if (credentials.length === 0) {
151
+ elements.noCredentials.classList.remove('hidden');
152
+ elements.credentialsTable.classList.add('hidden');
153
+ return;
154
+ }
155
+
156
+ elements.credentialsTable.classList.remove('hidden');
157
+ elements.credentialsBody.innerHTML = '';
158
+
159
+ for (const cred of credentials) {
160
+ const row = document.createElement('tr');
161
+ row.innerHTML = `
162
+ <td><strong>${escapeHtml(cred.name)}</strong></td>
163
+ <td>${escapeHtml(cred.fqdn)}</td>
164
+ <td>${cred.port}</td>
165
+ <td>${cred.org_id}</td>
166
+ <td>${escapeHtml(cred.api_user)}</td>
167
+ <td class="${cred.verify_ssl ? 'status-yes' : 'status-no'}">${cred.verify_ssl ? 'Yes' : 'No'}</td>
168
+ <td title="${escapeHtml(cred.originating_file)}">${truncatePath(cred.originating_file)}</td>
169
+ <td class="actions-cell">
170
+ <button class="btn btn-small btn-success btn-test" data-name="${escapeHtml(cred.name)}">Test</button>
171
+ <button class="btn btn-small btn-secondary btn-edit" data-name="${escapeHtml(cred.name)}">Edit</button>
172
+ <button class="btn btn-small btn-danger btn-delete" data-name="${escapeHtml(cred.name)}">Delete</button>
173
+ </td>
174
+ `;
175
+ elements.credentialsBody.appendChild(row);
176
+ }
177
+
178
+ // Add event listeners to buttons
179
+ document.querySelectorAll('.btn-test').forEach(btn => {
180
+ btn.addEventListener('click', () => testCredential(btn.dataset.name));
181
+ });
182
+ document.querySelectorAll('.btn-edit').forEach(btn => {
183
+ btn.addEventListener('click', () => openEditModal(btn.dataset.name));
184
+ });
185
+ document.querySelectorAll('.btn-delete').forEach(btn => {
186
+ btn.addEventListener('click', () => openDeleteModal(btn.dataset.name));
187
+ });
188
+ }
189
+
190
+ // Check encryption availability and load SSH keys
191
+ async function checkEncryptionStatus() {
192
+ try {
193
+ const status = await apiCall('/api/encryption-status');
194
+ encryptionAvailable = status.available;
195
+
196
+ if (encryptionAvailable) {
197
+ const keysData = await apiCall('/api/ssh-keys');
198
+ sshKeys = keysData.keys || [];
199
+
200
+ if (sshKeys.length > 0) {
201
+ elements.encryptionSection.classList.remove('hidden');
202
+ populateSshKeySelect();
203
+ }
204
+ }
205
+ } catch (error) {
206
+ console.log('Encryption not available:', error.message);
207
+ }
208
+ }
209
+
210
+ // Populate SSH key select dropdown
211
+ function populateSshKeySelect() {
212
+ elements.formSshKey.innerHTML = '';
213
+ for (const key of sshKeys) {
214
+ const option = document.createElement('option');
215
+ option.value = key.index;
216
+ option.textContent = `${key.type} | ${key.fingerprint.substring(0, 16)}... | ${key.comment || 'No comment'}`;
217
+ elements.formSshKey.appendChild(option);
218
+ }
219
+ }
220
+
221
+ // Toggle SSH key selection visibility
222
+ function toggleSshKeySelection() {
223
+ if (elements.formEncrypt.checked && sshKeys.length > 0) {
224
+ elements.sshKeysSection.classList.remove('hidden');
225
+ } else {
226
+ elements.sshKeysSection.classList.add('hidden');
227
+ }
228
+ }
229
+
230
+ // Open modal for creating new credential
231
+ function openCreateModal() {
232
+ resetForm();
233
+ elements.formMode.value = 'create';
234
+ elements.modalTitle.textContent = 'New Credential';
235
+ elements.btnSubmit.textContent = 'Create';
236
+ elements.formName.removeAttribute('readonly');
237
+ elements.formApiKey.setAttribute('required', 'required');
238
+ elements.apiKeyRequired.classList.remove('hidden');
239
+ elements.apiKeyHint.classList.add('hidden');
240
+ elements.storageSection.classList.remove('hidden');
241
+ elements.credentialModal.classList.remove('hidden');
242
+ elements.formName.focus();
243
+ }
244
+
245
+ // Open modal for editing existing credential
246
+ async function openEditModal(name) {
247
+ resetForm();
248
+
249
+ try {
250
+ const credential = await apiCall(`/api/credentials/${encodeURIComponent(name)}`);
251
+
252
+ elements.formMode.value = 'edit';
253
+ elements.formOriginalName.value = credential.name;
254
+ elements.modalTitle.textContent = 'Edit Credential';
255
+ elements.btnSubmit.textContent = 'Update';
256
+
257
+ // Populate form
258
+ elements.formName.value = credential.name;
259
+ elements.formName.setAttribute('readonly', 'readonly');
260
+ elements.formFqdn.value = credential.fqdn;
261
+ elements.formPort.value = credential.port;
262
+ elements.formOrgId.value = credential.org_id;
263
+ elements.formApiUser.value = credential.api_user;
264
+ elements.formVerifySsl.checked = credential.verify_ssl;
265
+
266
+ // API key is optional for updates
267
+ elements.formApiKey.removeAttribute('required');
268
+ elements.apiKeyRequired.classList.add('hidden');
269
+ elements.apiKeyHint.classList.remove('hidden');
270
+
271
+ // Hide storage section for edits
272
+ elements.storageSection.classList.add('hidden');
273
+
274
+ elements.credentialModal.classList.remove('hidden');
275
+ elements.formFqdn.focus();
276
+ } catch (error) {
277
+ showNotification('Failed to load credential: ' + error.message, 'error');
278
+ }
279
+ }
280
+
281
+ // Open modal for deleting credential
282
+ function openDeleteModal(name) {
283
+ credentialToDelete = name;
284
+ elements.deleteCredentialName.textContent = name;
285
+ elements.deleteModal.classList.remove('hidden');
286
+ }
287
+
288
+ // Close modal
289
+ function closeModal() {
290
+ elements.credentialModal.classList.add('hidden');
291
+ resetForm();
292
+ }
293
+
294
+ // Close delete modal
295
+ function closeDeleteModal() {
296
+ elements.deleteModal.classList.add('hidden');
297
+ credentialToDelete = null;
298
+ }
299
+
300
+ // Reset form
301
+ function resetForm() {
302
+ elements.credentialForm.reset();
303
+ elements.formMode.value = 'create';
304
+ elements.formOriginalName.value = '';
305
+ elements.formPort.value = '8443';
306
+ elements.formOrgId.value = '1';
307
+ elements.formVerifySsl.checked = true;
308
+ elements.formEncrypt.checked = false;
309
+ elements.sshKeysSection.classList.add('hidden');
310
+ }
311
+
312
+ // Handle form submission
313
+ async function handleFormSubmit(e) {
314
+ e.preventDefault();
315
+
316
+ const mode = elements.formMode.value;
317
+ const data = {
318
+ name: elements.formName.value.trim(),
319
+ fqdn: elements.formFqdn.value.trim(),
320
+ port: parseInt(elements.formPort.value),
321
+ org_id: parseInt(elements.formOrgId.value),
322
+ api_user: elements.formApiUser.value.trim(),
323
+ verify_ssl: elements.formVerifySsl.checked
324
+ };
325
+
326
+ // Add API key if provided
327
+ const apiKey = elements.formApiKey.value;
328
+ if (apiKey) {
329
+ data.api_key = apiKey;
330
+ } else if (mode === 'create') {
331
+ showNotification('API key is required', 'error');
332
+ return;
333
+ }
334
+
335
+ // Add encryption settings
336
+ if (elements.formEncrypt.checked && sshKeys.length > 0) {
337
+ data.encrypt = true;
338
+ data.ssh_key_index = parseInt(elements.formSshKey.value);
339
+ }
340
+
341
+ // Add storage location for create
342
+ if (mode === 'create') {
343
+ data.use_current_workdir = elements.formUseWorkdir.checked;
344
+ }
345
+
346
+ elements.btnSubmit.disabled = true;
347
+ elements.btnSubmit.textContent = mode === 'create' ? 'Creating...' : 'Updating...';
348
+
349
+ try {
350
+ if (mode === 'create') {
351
+ await apiCall('/api/credentials', {
352
+ method: 'POST',
353
+ body: JSON.stringify(data)
354
+ });
355
+ showNotification('Credential created successfully!', 'success');
356
+ } else {
357
+ const originalName = elements.formOriginalName.value;
358
+ await apiCall(`/api/credentials/${encodeURIComponent(originalName)}`, {
359
+ method: 'PUT',
360
+ body: JSON.stringify(data)
361
+ });
362
+ showNotification('Credential updated successfully!', 'success');
363
+ }
364
+
365
+ closeModal();
366
+ await loadCredentials();
367
+ } catch (error) {
368
+ showNotification('Failed to save credential: ' + error.message, 'error');
369
+ } finally {
370
+ elements.btnSubmit.disabled = false;
371
+ elements.btnSubmit.textContent = mode === 'create' ? 'Create' : 'Update';
372
+ }
373
+ }
374
+
375
+ // Confirm credential deletion
376
+ async function confirmDelete() {
377
+ if (!credentialToDelete) return;
378
+
379
+ elements.btnConfirmDelete.disabled = true;
380
+ elements.btnConfirmDelete.textContent = 'Deleting...';
381
+
382
+ try {
383
+ await apiCall(`/api/credentials/${encodeURIComponent(credentialToDelete)}`, {
384
+ method: 'DELETE'
385
+ });
386
+ showNotification('Credential deleted successfully!', 'success');
387
+ closeDeleteModal();
388
+ await loadCredentials();
389
+ } catch (error) {
390
+ showNotification('Failed to delete credential: ' + error.message, 'error');
391
+ } finally {
392
+ elements.btnConfirmDelete.disabled = false;
393
+ elements.btnConfirmDelete.textContent = 'Delete';
394
+ }
395
+ }
396
+
397
+ // Test credential connection
398
+ async function testCredential(name) {
399
+ elements.testResult.className = 'test-result loading';
400
+ elements.testResult.textContent = 'Testing connection...';
401
+ elements.testModal.classList.remove('hidden');
402
+
403
+ try {
404
+ const result = await apiCall(`/api/credentials/${encodeURIComponent(name)}/test`, {
405
+ method: 'POST'
406
+ });
407
+ elements.testResult.className = 'test-result success';
408
+ elements.testResult.textContent = `Connection to "${name}" successful!`;
409
+ } catch (error) {
410
+ elements.testResult.className = 'test-result error';
411
+ elements.testResult.textContent = `Connection failed: ${error.message}`;
412
+ }
413
+ }
414
+
415
+ // Close test modal
416
+ function closeTestModal() {
417
+ elements.testModal.classList.add('hidden');
418
+ }
419
+
420
+ // Show notification
421
+ function showNotification(message, type = 'info') {
422
+ elements.notification.textContent = message;
423
+ elements.notification.className = `notification ${type}`;
424
+ elements.notification.classList.remove('hidden');
425
+
426
+ // Auto-hide after 5 seconds
427
+ setTimeout(() => {
428
+ elements.notification.classList.add('hidden');
429
+ }, 5000);
430
+ }
431
+
432
+ // Utility: Escape HTML
433
+ function escapeHtml(text) {
434
+ const div = document.createElement('div');
435
+ div.textContent = text;
436
+ return div.innerHTML;
437
+ }
438
+
439
+ // Utility: Truncate file path for display
440
+ function truncatePath(path) {
441
+ if (path.length > 30) {
442
+ return '...' + path.slice(-27);
443
+ }
444
+ return path;
445
+ }
446
+
447
+ // Start the application
448
+ document.addEventListener('DOMContentLoaded', init);
449
+ })();