illumio-pylo 0.3.12__tar.gz → 0.3.13__tar.gz

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.
Files changed (124) hide show
  1. {illumio_pylo-0.3.12/illumio_pylo.egg-info → illumio_pylo-0.3.13}/PKG-INFO +1 -1
  2. illumio_pylo-0.3.13/docs/ENV_CREDENTIALS.md +205 -0
  3. illumio_pylo-0.3.13/examples/example_env_credentials.py +77 -0
  4. illumio_pylo-0.3.13/examples/explorer_query.py +101 -0
  5. illumio_pylo-0.3.13/examples/filter_query_example.py +122 -0
  6. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/API/APIConnector.py +61 -14
  7. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/API/CredentialsManager.py +130 -3
  8. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/API/Explorer.py +619 -14
  9. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/API/JsonPayloadTypes.py +64 -4
  10. illumio_pylo-0.3.13/illumio_pylo/FilterQuery.py +892 -0
  11. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/LabelCommon.py +13 -3
  12. illumio_pylo-0.3.13/illumio_pylo/LabelDimension.py +109 -0
  13. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/LabelStore.py +97 -38
  14. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/WorkloadStore.py +58 -0
  15. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/__init__.py +9 -3
  16. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/__init__.py +5 -2
  17. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/__init__.py +1 -0
  18. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/credential_manager.py +176 -0
  19. illumio_pylo-0.3.13/illumio_pylo/cli/commands/traffic_export.py +358 -0
  20. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/ui/credential_manager_ui/app.js +191 -2
  21. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/ui/credential_manager_ui/index.html +50 -1
  22. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/ui/credential_manager_ui/styles.css +179 -28
  23. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/update_pce_objects_cache.py +1 -2
  24. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/workload_export.py +29 -0
  25. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13/illumio_pylo.egg-info}/PKG-INFO +1 -1
  26. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo.egg-info/SOURCES.txt +10 -2
  27. illumio_pylo-0.3.13/tests/test_env_credentials.py +198 -0
  28. illumio_pylo-0.3.13/tests/test_filter_query.py +344 -0
  29. illumio_pylo-0.3.13/tests/test_nested_queries.py +34 -0
  30. illumio_pylo-0.3.12/examples/explorer_query.py +0 -84
  31. illumio_pylo-0.3.12/illumio_pylo/Query.py +0 -331
  32. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/.devcontainer/Dockerfile +0 -0
  33. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/.devcontainer/devcontainer.json +0 -0
  34. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/.gitattributes +0 -0
  35. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/.github/workflows/doxygen-publish.yml +0 -0
  36. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/.github/workflows/make-binaries.yml +0 -0
  37. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/.github/workflows/python-publish.yml +0 -0
  38. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/.gitignore +0 -0
  39. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/LICENSE +0 -0
  40. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/README.md +0 -0
  41. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/check_unique_hostnames.py +0 -0
  42. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/check_unique_services.py +0 -0
  43. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/delete_all_workloads.py +0 -0
  44. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/delete_unused_services.py +0 -0
  45. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/explorer_report_exporter.py +0 -0
  46. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/export_rules_to_firewall.py +0 -0
  47. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/generate-random-workloads.py +0 -0
  48. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/healthcheck_log.py +0 -0
  49. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/import-labels.py +0 -0
  50. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/import_workloads_placeholders.py +0 -0
  51. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/iplists_stats_duplicates_unused_finder.py +0 -0
  52. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/recalculate_explorer_logs.py +0 -0
  53. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/recalculate_explorer_logs_multithreaded.py +0 -0
  54. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/rules_exporter.py +0 -0
  55. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/rules_exporter_special.py +0 -0
  56. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/test.py +0 -0
  57. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/test_change_workload_desc.py +0 -0
  58. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/test_query.py +0 -0
  59. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/test_query2.py +0 -0
  60. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/test_securityprincipals.py +0 -0
  61. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/ven_idle_to_illumination.py +0 -0
  62. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/dev_playground/ven_reassign_pce.py +0 -0
  63. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/examples/extend_cli.py +0 -0
  64. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/API/AuditLog.py +0 -0
  65. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/API/ClusterHealth.py +0 -0
  66. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/API/RuleSearchQuery.py +0 -0
  67. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/API/__init__.py +0 -0
  68. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/AgentStore.py +0 -0
  69. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Exception.py +0 -0
  70. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Helpers/__init__.py +0 -0
  71. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Helpers/exports.py +0 -0
  72. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Helpers/functions.py +0 -0
  73. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/IPList.py +0 -0
  74. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/IPMap.py +0 -0
  75. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Label.py +0 -0
  76. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/LabelGroup.py +0 -0
  77. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/LabeledObject.py +0 -0
  78. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Organization.py +0 -0
  79. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/ReferenceTracker.py +0 -0
  80. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Rule.py +0 -0
  81. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Ruleset.py +0 -0
  82. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/RulesetStore.py +0 -0
  83. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/SecurityPrincipal.py +0 -0
  84. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Service.py +0 -0
  85. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/SoftwareVersion.py +0 -0
  86. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/VirtualService.py +0 -0
  87. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/VirtualServiceStore.py +0 -0
  88. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/Workload.py +0 -0
  89. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/WorkloadStoreSubClasses.py +0 -0
  90. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/NativeParsers.py +0 -0
  91. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/__main__.py +0 -0
  92. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/iplist_analyzer.py +0 -0
  93. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/iplist_import_from_file.py +0 -0
  94. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/label_delete_unused.py +0 -0
  95. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/ruleset_export.py +0 -0
  96. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/utils/LabelCreation.py +0 -0
  97. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/utils/__init__.py +0 -0
  98. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/utils/misc.py +0 -0
  99. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/ven_compatibility_report_export.py +0 -0
  100. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/ven_duplicate_remover.py +0 -0
  101. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/ven_idle_to_visibility.py +0 -0
  102. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/ven_upgrader.py +0 -0
  103. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/workload_import.py +0 -0
  104. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/workload_reset_names_to_null.py +0 -0
  105. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/workload_update.py +0 -0
  106. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/cli/commands/workload_used_in_rule_finder.py +0 -0
  107. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/docs/Doxygen +0 -0
  108. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/tmp.py +0 -0
  109. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/__init__.py +0 -0
  110. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/cli.py +0 -0
  111. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/credentials.example.json +0 -0
  112. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/health_monitoring.py +0 -0
  113. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/resources/iplists-import-example.csv +0 -0
  114. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/resources/iplists-import-example.xlsx +0 -0
  115. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/resources/workload-exporter-filter-example.csv +0 -0
  116. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/resources/workloads-import-example.csv +0 -0
  117. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo/utilities/resources/workloads-import-example.xlsx +0 -0
  118. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo.egg-info/dependency_links.txt +0 -0
  119. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo.egg-info/requires.txt +0 -0
  120. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/illumio_pylo.egg-info/top_level.txt +0 -0
  121. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/pyproject.toml +0 -0
  122. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/requirements.txt +0 -0
  123. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/setup.cfg +0 -0
  124. {illumio_pylo-0.3.12 → illumio_pylo-0.3.13}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: illumio_pylo
3
- Version: 0.3.12
3
+ Version: 0.3.13
4
4
  Summary: A set of tools and library for working with Illumio PCE
5
5
  Home-page: https://github.com/cpainchaud/pylo
6
6
  Author: Christophe Painchaud
@@ -0,0 +1,205 @@
1
+ # Environment Variable Credentials
2
+
3
+ ## Overview
4
+
5
+ The `CredentialProfile.from_environment_variables()` feature allows you to provide Illumio PCE credentials via environment variables instead of storing them in credential files on disk. This is particularly useful for:
6
+
7
+ - CI/CD pipelines
8
+ - Docker containers
9
+ - Security-conscious environments where credentials shouldn't be written to disk
10
+ - Temporary credential usage
11
+
12
+ ## Usage
13
+
14
+ To use environment variable credentials, simply specify the profile name `'ENV'` (case-insensitive) when loading credentials:
15
+
16
+ ```python
17
+ import illumio_pylo as pylo
18
+
19
+ # Load credentials from environment variables
20
+ credentials = pylo.get_credentials_from_file('ENV')
21
+
22
+ # Or use with APIConnector
23
+ connector = pylo.APIConnector.create_from_credentials_in_file('ENV')
24
+
25
+ # Or use with Organization
26
+ org = pylo.Organization.get_from_api_using_credential_file('ENV')
27
+ ```
28
+
29
+ ## Required Environment Variables
30
+
31
+ - **`PYLO_FQDN`**: Fully qualified domain name of the PCE (e.g., `pce.example.com`)
32
+ - **`PYLO_API_USER`**: API username
33
+ - **`PYLO_API_KEY`**: API key (can be encrypted with `$encrypted$:` prefix)
34
+
35
+ ## Optional Environment Variables
36
+
37
+ - **`PYLO_PORT`**: Port number
38
+ - Default: `8443` for standard PCE
39
+ - Default: `443` for illum.io hosted domains (SaaS)
40
+ - Valid range: 1-65535
41
+
42
+ - **`PYLO_ORG_ID`**: Organization ID
43
+ - Default: `1` for standard PCE
44
+ - **Required** for illum.io hosted domains (no default)
45
+ - Must be a positive integer
46
+
47
+ - **`PYLO_VERIFY_SSL`**: Verify SSL certificate
48
+ - Default: `true`
49
+ - Accepts: `true`, `false`, `1`, `0`, `yes`, `no`, `y`, `n` (case-insensitive)
50
+
51
+ ## Examples
52
+
53
+ ### Basic Example (Standard PCE)
54
+
55
+ ```bash
56
+ # Set environment variables
57
+ export PYLO_FQDN="pce.example.com"
58
+ export PYLO_API_USER="api_12345"
59
+ export PYLO_API_KEY="your_api_key_here"
60
+
61
+ # Run your Python script
62
+ python your_script.py
63
+ ```
64
+
65
+ ```python
66
+ import illumio_pylo as pylo
67
+
68
+ # Load from environment
69
+ org = pylo.Organization.get_from_api_using_credential_file('ENV')
70
+ print(f"Connected to {org.connector.fqdn}")
71
+ ```
72
+
73
+ ### Illumio SaaS Example
74
+
75
+ ```bash
76
+ # For illum.io domains, ORG_ID is required
77
+ export PYLO_FQDN="mycompany.illum.io"
78
+ export PYLO_API_USER="api_12345"
79
+ export PYLO_API_KEY="your_api_key_here"
80
+ export PYLO_ORG_ID="5"
81
+
82
+ # Port defaults to 443 for illum.io domains
83
+ python your_script.py
84
+ ```
85
+
86
+ ### Custom Configuration
87
+
88
+ ```bash
89
+ # Custom port and disable SSL verification
90
+ export PYLO_FQDN="pce.internal.local"
91
+ export PYLO_API_USER="api_12345"
92
+ export PYLO_API_KEY="your_api_key_here"
93
+ export PYLO_PORT="9443"
94
+ export PYLO_ORG_ID="2"
95
+ export PYLO_VERIFY_SSL="false"
96
+
97
+ python your_script.py
98
+ ```
99
+
100
+ ### Docker Container Example
101
+
102
+ ```bash
103
+ docker run -e PYLO_FQDN="pce.example.com" \
104
+ -e PYLO_API_USER="api_12345" \
105
+ -e PYLO_API_KEY="your_api_key" \
106
+ your-container:latest
107
+ ```
108
+
109
+ ### Encrypted API Key
110
+
111
+ If you have an encrypted API key (generated with the `cred-manager` tool), you can use it directly:
112
+
113
+ ```bash
114
+ export PYLO_FQDN="pce.example.com"
115
+ export PYLO_API_USER="api_12345"
116
+ export PYLO_API_KEY='$encrypted$:ssh-ChaCha20Poly1305:...'
117
+
118
+ python your_script.py
119
+ ```
120
+
121
+ The API key will be automatically decrypted using your SSH agent.
122
+
123
+ ## Checking Availability
124
+
125
+ You can check if required environment variables are set before attempting to load:
126
+
127
+ ```python
128
+ from illumio_pylo.API.CredentialsManager import is_env_credentials_available
129
+
130
+ if is_env_credentials_available():
131
+ print("Environment credentials are available")
132
+ credentials = pylo.get_credentials_from_file('ENV')
133
+ else:
134
+ print("Missing required environment variables")
135
+ # Fall back to file-based credentials
136
+ credentials = pylo.get_credentials_from_file('default')
137
+ ```
138
+
139
+ ## Validation and Error Handling
140
+
141
+ The implementation includes comprehensive validation:
142
+
143
+ - **Missing required variables**: Clear error message listing which variables are missing
144
+ - **Invalid port**: Must be a valid integer between 1-65535
145
+ - **Invalid org_id**: Must be a positive integer
146
+ - **Invalid verify_ssl**: Must be a boolean-like value
147
+ - **illum.io domains**: Automatically defaults port to 443 and requires ORG_ID
148
+
149
+ Example error messages:
150
+
151
+ ```
152
+ Missing required environment variables for ENV profile: PYLO_FQDN, PYLO_API_KEY.
153
+ Required: PYLO_FQDN, PYLO_API_USER, PYLO_API_KEY.
154
+ Optional: PYLO_PORT, PYLO_ORG_ID, PYLO_VERIFY_SSL
155
+
156
+ Invalid PYLO_PORT value 'abc': must be a valid port number (1-65535)
157
+
158
+ PYLO_ORG_ID is required for illum.io domains (no default available)
159
+
160
+ Invalid PYLO_VERIFY_SSL value 'maybe': must be true/false/1/0/yes/no/y/n (case-insensitive)
161
+ ```
162
+
163
+ ## Security Considerations
164
+
165
+ ### Advantages
166
+ - Credentials never written to disk
167
+ - Easier to rotate in CI/CD environments
168
+ - No risk of accidentally committing credentials to version control
169
+ - Better integration with secrets management tools (AWS Secrets Manager, HashiCorp Vault, etc.)
170
+
171
+ ### Best Practices
172
+ 1. **Never log environment variables** - Be careful not to print or log the actual API key
173
+ 2. **Use encrypted keys when possible** - Leverage SSH agent encryption for additional security
174
+ 3. **Rotate credentials regularly** - Environment-based approach makes rotation easier
175
+ 4. **Use secrets management** - In production, use proper secrets management tools to inject environment variables
176
+ 5. **Limit scope** - Set environment variables only for the specific process that needs them
177
+
178
+ ### Example with AWS Secrets Manager
179
+
180
+ ```python
181
+ import boto3
182
+ import json
183
+ import os
184
+ import illumio_pylo as pylo
185
+
186
+ # Fetch credentials from AWS Secrets Manager
187
+ client = boto3.client('secretsmanager')
188
+ secret = client.get_secret_value(SecretId='pylo/pce/credentials')
189
+ creds = json.loads(secret['SecretString'])
190
+
191
+ # Set environment variables
192
+ os.environ['PYLO_FQDN'] = creds['fqdn']
193
+ os.environ['PYLO_API_USER'] = creds['api_user']
194
+ os.environ['PYLO_API_KEY'] = creds['api_key']
195
+
196
+ # Use ENV profile
197
+ org = pylo.Organization.get_from_api_using_credential_file('ENV')
198
+ ```
199
+
200
+ ## Notes
201
+
202
+ - The profile name is always `'ENV'` (case-insensitive: `'env'`, `'Env'`, `'ENV'` all work)
203
+ - The `originating_file` attribute is set to `'environment'` for env-based profiles
204
+ - Environment credentials are **never** included in `get_all_credentials()` - they must be explicitly requested
205
+ - Environment credentials don't require any credential files to exist on disk
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Practical example of using environment variable credentials with Illumio Pylo
4
+ This demonstrates how to connect to a PCE using only environment variables
5
+ """
6
+ import os
7
+ import illumio_pylo as pylo
8
+
9
+ def main():
10
+ # Check if environment credentials are available
11
+ from illumio_pylo.API.CredentialsManager import is_env_credentials_available
12
+
13
+ if not is_env_credentials_available():
14
+ print("ERROR: Required environment variables are not set!")
15
+ print("Please set: PYLO_FQDN, PYLO_API_USER, PYLO_API_KEY")
16
+ print("\nExample:")
17
+ print(" export PYLO_FQDN='pce.example.com'")
18
+ print(" export PYLO_API_USER='api_12345'")
19
+ print(" export PYLO_API_KEY='your_api_key_here'")
20
+ return 1
21
+
22
+ print("Environment credentials detected!")
23
+ print("-" * 60)
24
+
25
+ # Load credentials from environment
26
+ try:
27
+ credentials = pylo.get_credentials_from_file('ENV')
28
+ print(f"Credentials loaded:")
29
+ print(f" FQDN: {credentials.fqdn}")
30
+ print(f" Port: {credentials.port}")
31
+ print(f" Org ID: {credentials.org_id}")
32
+ print(f" API User: {credentials.api_user}")
33
+ print(f" Verify SSL: {credentials.verify_ssl}")
34
+ print(f" Source: {credentials.originating_file}")
35
+ print("-" * 60)
36
+ except pylo.PyloEx as e:
37
+ print(f"ERROR loading credentials: {e}")
38
+ return 1
39
+
40
+ # Example 1: Create an APIConnector
41
+ print("\nExample 1: Creating APIConnector from ENV profile")
42
+ try:
43
+ connector = pylo.APIConnector.create_from_credentials_in_file('ENV')
44
+ print(f"✓ APIConnector created for {connector.fqdn}:{connector.port}")
45
+
46
+ # Test connection by getting software version
47
+ version = connector.get_software_version()
48
+ print(f"✓ Connected successfully! PCE Version: {version}")
49
+
50
+ except Exception as e:
51
+ print(f"✗ Connection failed: {e}")
52
+ print(" (This is expected if the PCE is not accessible)")
53
+
54
+ # Example 2: Load Organization data
55
+ print("\nExample 2: Loading Organization from ENV profile")
56
+ try:
57
+ org = pylo.Organization.get_from_api_using_credential_file(
58
+ 'ENV',
59
+ list_of_objects_to_load=[
60
+ pylo.ObjectTypes.LABEL,
61
+ pylo.ObjectTypes.WORKLOAD
62
+ ]
63
+ )
64
+ print(f"✓ Organization loaded successfully!")
65
+ print(f" Labels: {len(org.LabelStore.itemsByHref)}")
66
+ print(f" Workloads: {len(org.WorkloadStore.itemsByHref)}")
67
+
68
+ except Exception as e:
69
+ print(f"✗ Failed to load organization: {e}")
70
+ print(" (This is expected if the PCE is not accessible)")
71
+
72
+ print("\n" + "=" * 60)
73
+ print("Example complete!")
74
+ return 0
75
+
76
+ if __name__ == '__main__':
77
+ exit(main())
@@ -0,0 +1,101 @@
1
+ """
2
+ In this example we will show how to query traffic logs from the PCE using the Explorer V2 APIs.
3
+ We will make a query for all traffic logs matching the following conditions:
4
+ - Source (consumer) has a label 'E-PRODUCTION' or 'E-PREPROD' or is part of IPList 'I-Prod-Networks'
5
+ - Destination (provider) can be any workload or IP
6
+ - Service is TCP port 80 or 443 or ICMP protocol
7
+ - Policy decision is 'allowed'
8
+ - Traffic was detected within the last 5 days
9
+ """
10
+
11
+ import os
12
+ import sys
13
+
14
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) # only required if you run this script from the examples folder without installing pylo
15
+ import illumio_pylo as pylo
16
+
17
+ # PCE connection parameters
18
+ pce_hostname = 'pce1.company.com'
19
+ pce_port = 9443
20
+ pce_api_user = 'api_xxxxxxxxx'
21
+ pce_api_key = 'xxxxxxxxxxxxxxxxxxxx'
22
+ pce_org_id = 1
23
+ pce_verify_ssl = True
24
+
25
+ print("Loading organization from PCE '{}'... ".format(pce_hostname), end='', flush=True)
26
+ organization = pylo.get_organization(pce_hostname, pce_port, pce_api_user, pce_api_key, pce_org_id, pce_verify_ssl)
27
+ print("OK!")
28
+
29
+ # Create a new V2 Explorer query with a max of 1500 results
30
+ explorer_query = organization.connector.new_explorer_query_v2(max_results=1500)
31
+
32
+ # Define source filter criteria
33
+ source_labels_names = ['E-PRODUCTION', 'E-PREPROD']
34
+ source_ip_list_name = 'I-Prod-Networks'
35
+
36
+ # Create a source filter that combines labels and IPList (treated with OR logic)
37
+ source_filter = explorer_query.filters.new_source_filter()
38
+
39
+ # Lookup and add Label objects to the source filter
40
+ for label_name in source_labels_names:
41
+ label_search_result = organization.LabelStore.find_label_by_name(label_name, raise_exception_if_not_found=False, case_sensitive=False)
42
+ if len(label_search_result) == 0:
43
+ raise pylo.PyloEx("Label '{}' not found in PCE!".format(label_name))
44
+ elif len(label_search_result) > 1:
45
+ raise pylo.PyloEx("Multiple labels found for name '{}', please use a more specific name!".format(label_name))
46
+ source_filter.add_label(label_search_result[0])
47
+
48
+ # Lookup and add IPList object to the source filter
49
+ source_iplist = organization.IPListStore.find_by_name(source_ip_list_name)
50
+ if source_iplist is None:
51
+ raise pylo.PyloEx("IPList '{}' not found in PCE!".format(source_ip_list_name))
52
+ source_filter.add_iplist(source_iplist)
53
+
54
+ # Filter by services (ICMP, HTTP, HTTPS)
55
+ explorer_query.filters.service_include_add_protocol(1) # ICMP
56
+ explorer_query.filters.service_include_add('tcp/80') # HTTP
57
+ explorer_query.filters.service_include_add('tcp/443') # HTTPS
58
+
59
+ # Filter by policy decision (only allowed traffic)
60
+ explorer_query.filters.filter_on_policy_decision_allowed()
61
+
62
+ # Filter by time range (last 5 days)
63
+ explorer_query.filters.set_time_from_x_days_ago(5)
64
+
65
+ # Execute the query and retrieve traffic logs
66
+ print("Querying PCE for traffic logs matching the filter... ", end='', flush=True)
67
+ traffic_logs = explorer_query.execute()
68
+ records = traffic_logs.get_all_records()
69
+ print("OK! Found {} traffic log(s)".format(len(records)))
70
+
71
+ # Print the results
72
+ for record in records:
73
+ # Format source information
74
+ if record.source_is_workload():
75
+ workload = record.get_source_workload(organization)
76
+ # Get labels as a formatted string
77
+ label_values = [record.source_workload_labels_by_type.get(lt) for lt in organization.LabelStore.label_types]
78
+ label_values = [lv for lv in label_values if lv is not None]
79
+ labels_str = '|'.join(label_values) if label_values else 'unlabeled'
80
+ consumer_text = "Workload '{}' ({})".format(workload.name if workload else record.source_workload_hostname, labels_str)
81
+ else:
82
+ consumer_text = "IP '{}'".format(record.source_ip)
83
+
84
+ # Format destination information
85
+ if record.destination_is_workload():
86
+ workload = record.get_destination_workload(organization)
87
+ # Get labels as a formatted string
88
+ label_values = [record.destination_workload_labels_by_type.get(lt) for lt in organization.LabelStore.label_types]
89
+ label_values = [lv for lv in label_values if lv is not None]
90
+ labels_str = '|'.join(label_values) if label_values else 'unlabeled'
91
+ provider_text = "Workload '{}' ({})".format(workload.name if workload else record.destination_workload_hostname, labels_str)
92
+ else:
93
+ provider_text = "IP '{}'".format(record.destination_ip)
94
+
95
+ # Format service information
96
+ service_text = record.service_to_str()
97
+
98
+ # Print the traffic log entry
99
+ print("Traffic log: {} -> {} via {} (policy decision: {})".format(
100
+ consumer_text, provider_text, service_text, record.policy_decision_string))
101
+
@@ -0,0 +1,122 @@
1
+ """
2
+ Example: Using the Filter Query feature to search workloads
3
+
4
+ This example demonstrates how to use the find_workloads_matching_query() method
5
+ to filter workloads using a SQL-like query syntax.
6
+ """
7
+
8
+ import sys
9
+ import os
10
+
11
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
12
+
13
+ import illumio_pylo as pylo
14
+
15
+
16
+ def main():
17
+ # Load organization from PCE (using credentials file or cached data)
18
+ # Replace 'your-pce-hostname' with your actual PCE hostname
19
+ pce_hostname = 'your-pce-hostname'
20
+
21
+ print(f"Loading PCE configuration from {pce_hostname}...")
22
+ org = pylo.Organization(1)
23
+
24
+ # Option 1: Load from API using saved credentials
25
+ # org.load_from_api_using_credential_file(pce_hostname)
26
+
27
+ # Option 2: Load from cache (if you've previously cached the data)
28
+ try:
29
+ org.load_from_cache_or_saved_credentials(pce_hostname)
30
+ except Exception as e:
31
+ print(f"Failed to load PCE data: {e}")
32
+ print("\nTo use this example, you need to either:")
33
+ print(" 1. Configure credentials in ~/.pylo/credentials.json")
34
+ print(" 2. Have cached PCE data available")
35
+ return
36
+
37
+ print(f"Loaded {len(org.WorkloadStore.workloads)} workloads from PCE\n")
38
+
39
+ # Example queries
40
+ example_queries = [
41
+ # Find workloads by name
42
+ "name == 'web-server-01'",
43
+
44
+ # Find workloads by partial name match
45
+ "name contains 'web'",
46
+
47
+ # Find workloads by name using regex
48
+ "name matches 'web-.*-[0-9]+'",
49
+
50
+ # Find workloads by IP address
51
+ "ip_address == '192.168.1.100'",
52
+
53
+ # Find online workloads
54
+ "online == true",
55
+
56
+ # Find workloads by label
57
+ "env == 'Production'",
58
+ "label.app == 'WebApp' and label.env == 'Production'",
59
+
60
+ # Complex query with OR
61
+ "(name == 'server-01' or name == 'server-02') and online == true",
62
+
63
+ # Find workloads with old heartbeat
64
+ "last_heartbeat <= '2024-01-01'",
65
+
66
+ # Find workloads in a specific mode
67
+ "mode == 'enforced'",
68
+
69
+ # Find deleted workloads (need to pass include_deleted=True)
70
+ "deleted == true",
71
+
72
+ # Combine multiple conditions
73
+ "hostname contains 'prod' and env == 'Production' and not deleted == true",
74
+ ]
75
+
76
+ print("=" * 60)
77
+ print("Filter Query Examples")
78
+ print("=" * 60)
79
+
80
+ for query in example_queries:
81
+ print(f"\nQuery: {query}")
82
+ try:
83
+ results = org.WorkloadStore.find_workloads_matching_query(query)
84
+ print(f"Found {len(results)} workload(s)")
85
+ for wkl in results[:5]: # Show first 5 results
86
+ print(f" - {wkl.get_name()} ({wkl.href})")
87
+ if len(results) > 5:
88
+ print(f" ... and {len(results) - 5} more")
89
+ except pylo.PyloEx as e:
90
+ print(f"Error: {e}")
91
+
92
+ # Interactive query mode
93
+ print("\n" + "=" * 60)
94
+ print("Interactive Query Mode")
95
+ print("Enter a query to search workloads, or 'quit' to exit")
96
+ print("=" * 60)
97
+
98
+ while True:
99
+ try:
100
+ query = input("\nQuery> ").strip()
101
+ if query.lower() in ('quit', 'exit', 'q'):
102
+ break
103
+ if not query:
104
+ continue
105
+
106
+ results = org.WorkloadStore.find_workloads_matching_query(query)
107
+ print(f"Found {len(results)} workload(s)")
108
+ for wkl in results[:10]:
109
+ labels = wkl.get_labels_str()
110
+ print(f" - {wkl.get_name()} | {labels} | online={wkl.online}")
111
+ if len(results) > 10:
112
+ print(f" ... and {len(results) - 10} more")
113
+ except pylo.PyloEx as e:
114
+ print(f"Query error: {e}")
115
+ except KeyboardInterrupt:
116
+ break
117
+
118
+ print("\nGoodbye!")
119
+
120
+
121
+ if __name__ == '__main__':
122
+ main()
@@ -28,10 +28,6 @@ from typing import Union, Dict, Any, List, Optional, Literal
28
28
 
29
29
  requests.packages.urllib3.disable_warnings()
30
30
 
31
- objects_types_strings = Literal[
32
- 'workloads', 'virtual_services', 'labels', 'labelgroups', 'iplists', 'services',
33
- 'rulesets', 'security_principals', 'label_dimensions']
34
-
35
31
  default_retry_count_if_api_call_limit_reached = 3
36
32
  default_retry_wait_time_if_api_call_limit_reached = 10
37
33
  default_max_objects_for_sync_calls = 200000
@@ -49,7 +45,7 @@ def get_field_or_die(field_name: str, data):
49
45
 
50
46
 
51
47
  ObjectTypes = Literal['iplists', 'workloads', 'virtual_services', 'labels', 'labelgroups', 'services', 'rulesets',
52
- 'security_principals', 'label_dimensions']
48
+ 'security_principals', 'label_dimensions']
53
49
 
54
50
  all_object_types: Dict[ObjectTypes, ObjectTypes] = {
55
51
  'iplists': 'iplists',
@@ -316,7 +312,7 @@ class APIConnector:
316
312
  or\
317
313
  method == 'DELETE' and req.status_code != 204 \
318
314
  or \
319
- method == 'PUT' and req.status_code != 204 and req.status_code != 200:
315
+ method == 'PUT' and req.status_code != 204 and req.status_code != 202 and req.status_code != 200:
320
316
 
321
317
  if req.status_code == 429:
322
318
  # too many requests sent in short amount of time? [{"token":"too_many_requests_error", ....}]
@@ -371,7 +367,7 @@ class APIConnector:
371
367
  self.collect_pce_infos()
372
368
  return self.version_string
373
369
 
374
- def get_objects_count_by_type(self, object_type: objects_types_strings) -> int:
370
+ def get_objects_count_by_type(self, object_type: ObjectTypes) -> int:
375
371
 
376
372
  def extract_count(headers):
377
373
  count = headers.get('x-total-count')
@@ -1323,12 +1319,23 @@ class APIConnector:
1323
1319
 
1324
1320
  raise pylo.PyloObjectNotFound("Request with ID {} not found".format(request_href))
1325
1321
 
1326
- def explorer_search(self, filters: Union[Dict, 'pylo.ExplorerFilterSetV1'],
1327
- max_running_time_seconds=1800,
1328
- check_for_update_interval_seconds=10) -> 'pylo.ExplorerResultSetV1':
1322
+ def explorer_search(self, filters: Union[Dict, 'pylo.ExplorerFilterSetV1', 'pylo.ExplorerFilterSetV2'],
1323
+ max_running_time_seconds=1800,check_for_update_interval_seconds=10, draft_mode_enabled=False)\
1324
+ -> Union['pylo.ExplorerResultSetV1', 'pylo.ExplorerResultSetV2']:
1325
+ """
1326
+
1327
+ :param filters:
1328
+ :param max_running_time_seconds:
1329
+ :param check_for_update_interval_seconds:
1330
+ :param draft_mode_enabled: only for V2 filters
1331
+ :return:
1332
+ """
1333
+
1329
1334
  path = "/traffic_flows/async_queries"
1330
1335
  if isinstance(filters, pylo.ExplorerFilterSetV1):
1331
1336
  data = filters.generate_json_query()
1337
+ elif isinstance(filters, pylo.ExplorerFilterSetV2):
1338
+ data = filters.generate_json_query()
1332
1339
  else:
1333
1340
  data = filters
1334
1341
 
@@ -1373,11 +1380,45 @@ class APIConnector:
1373
1380
  if query_status is None:
1374
1381
  raise pylo.PyloEx("Unexpected logic where query_status is None", query_queued_json_response)
1375
1382
 
1376
- query_json_response = self.do_get_call(query_href + "/download", json_output_expected=True, include_org_id=False)
1383
+ # if draft mode is not enabled we can download the results and pass them over
1384
+ if not draft_mode_enabled or isinstance(filters, pylo.ExplorerFilterSetV1):
1385
+ query_json_response = self.do_get_call(query_href + "/download", json_output_expected=True, include_org_id=False)
1386
+
1387
+ if isinstance(filters, pylo.ExplorerFilterSetV1):
1388
+ result = pylo.ExplorerResultSetV1(query_json_response,
1389
+ owner=self,
1390
+ emulated_process_exclusion=filters.exclude_processes_emulate)
1391
+ else:
1392
+ result = pylo.ExplorerResultSetV2(query_json_response)
1393
+
1394
+ return result
1395
+
1396
+ # from here we are in draft mode with V2 filters so we must request API to calculate the draft results
1397
+ draft_mode_trigger_url = query_href + "/update_rules?label_based_rules=false&offset=0&limit=250000"
1398
+ draft_mode_trigger_response = self.do_put_call(draft_mode_trigger_url, json_output_expected=False, include_org_id=False)
1399
+
1400
+ time.sleep(5) # wait a bit before checking for results
1377
1401
 
1378
- result = pylo.ExplorerResultSetV1(query_json_response,
1379
- owner=self,
1380
- emulated_process_exclusion=filters.exclude_processes_emulate)
1402
+ while True:
1403
+ # check that we don't wait too long
1404
+ if time.time() - start_time > max_running_time_seconds:
1405
+ raise pylo.PyloApiEx("Timeout while waiting for draft mode results to be calculated", draft_mode_trigger_response)
1406
+
1407
+ draft_mode_status_response = self.explorer_async_query_get_specific_request_status(query_href)
1408
+ if draft_mode_status_response['rules'] == "completed":
1409
+ query_status = draft_mode_status_response
1410
+ break
1411
+
1412
+ if draft_mode_status_response['rules'] not in ["queued", "working"]:
1413
+ raise pylo.PyloApiEx("Draft mode results calculation failed with status {}".format(draft_mode_status_response['status']),
1414
+ draft_mode_status_response)
1415
+
1416
+ time.sleep(check_for_update_interval_seconds)
1417
+
1418
+ if query_status is None:
1419
+ raise pylo.PyloEx("Unexpected logic where query_status is None", query_queued_json_response)
1420
+ query_json_response = self.do_get_call(query_href + "/download", json_output_expected=True, include_org_id=False)
1421
+ result = pylo.ExplorerResultSetV2(query_json_response)
1381
1422
 
1382
1423
  return result
1383
1424
 
@@ -1407,6 +1448,12 @@ class APIConnector:
1407
1448
  check_for_update_interval_seconds: int = 10) -> 'pylo.ExplorerQuery':
1408
1449
  return pylo.ExplorerQuery(self, max_results, max_running_time_seconds, check_for_update_interval_seconds)
1409
1450
 
1451
+ def new_explorer_query_v2(self, max_results: int = 2500, draft_mode_enabled=False, max_running_time_seconds: int = 1800,
1452
+ check_for_update_interval_seconds: int = 10) -> 'pylo.ExplorerQueryV2':
1453
+ return pylo.ExplorerQueryV2(self, max_results=max_results, draft_mode_enabled=draft_mode_enabled,
1454
+ max_running_time_seconds=max_running_time_seconds,
1455
+ check_for_update_interval_seconds=check_for_update_interval_seconds)
1456
+
1410
1457
  def new_audit_log_query(self, max_results: int = 10000, max_running_time_seconds: int = 1800,
1411
1458
  check_for_update_interval_seconds: int = 10) -> 'pylo.AuditLogQuery':
1412
1459
  return pylo.AuditLogQuery(self, max_results, max_running_time_seconds)