diff --git a/README.md b/README.md
index a391348..d3ef89d 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,32 @@
# Python SDK for the Prisma Cloud APIs
-This project includes a Python SDK for the Prisma Cloud APIs (CSPM, CWPP, and CCS) in the form of a Python package.
+This is a Python SDK for the Prisma Cloud APIs (CSPM, CWPP, and PCCS) in the form of a Python package.
+
+This package is a fork of [prismacloud-api-python](https://github.com/PaloAltoNetworks/prismacloud-api-python), package [prismacloud-api](https://pypi.org/project/prismacloud-api), forked at version 5.2.24.
+
+This package is not maintained by Prisma Cloud SEs.
+
It also includes reference scripts that utilize this SDK.
-Major changes with Version 5.0:
-* Command-line argument and configuration file changes.
## Table of Contents
-* [Setup](#Setup)
+* [Installation](#Installation)
* [Support](#Support)
+* [References](#References)
+* [Changelog](#Changelog)
-## Setup
+## Installation
-Install the SDK via `pip3`:
+Install the SDK via `pip`:
```
-pip3 install prismacloud-api
+pip3 install prismacloudapi
```
-Please refer to [PyPI](https://pypi.org/project/prismacloud-api) for details.
+Please refer to [PyPI](https://pypi.org/project/prismacloudapi) for details.
### Example Scripts
@@ -101,5 +106,29 @@ settings = {
## Support
-This project has been developed by members of the Prisma Cloud CS and SE teams, it is not Supported by Palo Alto Networks.
-Nevertheless, the maintainers will make a best-effort to address issues, and (of course) contributors are encouraged to submit issues and pull requests.
+This package is not maintained by Prisma Cloud SEs or any Palo Alto Networks employees.
+
+The maintainers will make a best-effort to address issues, and (of course) contributors are encouraged to submit issues and pull requests.
+
+
+## References
+
+Prisma Cloud APIs:
+
+https://prisma.pan.dev/api/cloud/
+
+Access Keys:
+
+https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/manage-prisma-cloud-administrators/create-access-keys.html
+
+Permissions:
+
+https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/manage-prisma-cloud-administrators/prisma-cloud-admin-permissions.html
+
+## Changelog
+
+2025-03 Major changes with Version 5.2.28:
+* Leverage iterator construct for large dataset
+
+2024-01 Major changes with Version 5.0:
+* Command-line argument and configuration file changes.
diff --git a/prismacloud/api/README.md b/prismacloud/api/README.md
deleted file mode 100644
index 671afde..0000000
--- a/prismacloud/api/README.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Python SDK for the Prisma Cloud APIs
-
-This is a Python SDK for the Prisma Cloud APIs (CSPM, CWPP, and PCCS) in the form of a Python package.
-
-
-## Table of Contents
-
-* [Setup](#Usage)
-* [Support](#Support)
-* [References](#References)
-
-
-## Setup
-
-Install the SDK via:
-
-```
-pip3 install prismacloud-api
-```
-
-
-## Support
-
-This project has been developed by Prisma Cloud SEs, it is not Supported by Palo Alto Networks.
-Nevertheless, the maintainers will make a best-effort to address issues, and (of course) contributors are encouraged to submit issues and pull requests.
-
-
-### References
-
-Prisma Cloud APIs:
-
-https://prisma.pan.dev/api/cloud/
-
-Access Keys:
-
-https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/manage-prisma-cloud-administrators/create-access-keys.html
-
-Permissions:
-
-https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/manage-prisma-cloud-administrators/prisma-cloud-admin-permissions.html
\ No newline at end of file
diff --git a/prismacloud/api/cspm/_endpoints.py b/prismacloud/api/cspm/_endpoints.py
deleted file mode 100644
index 8aa6402..0000000
--- a/prismacloud/api/cspm/_endpoints.py
+++ /dev/null
@@ -1,705 +0,0 @@
-""" Prisma Cloud API Endpoints Class """
-
-# TODO: Split into multiple files, one per endpoint ...
-
-# pylint: disable=too-many-public-methods
-
-
-class EndpointsPrismaCloudAPIMixin():
- """ Prisma Cloud API Endpoints Class """
-
- def current_user(self):
- return self.execute('GET', 'user/me')
-
- """
- Note: Eventually, all objects covered should have full CRUD capability, ie, to create, read, update, and delete (and list).
-
- Template
-
- [ ] LIST
- [ ] CREATE
- [ ] READ
- [ ] UPDATE
- [ ] DELETE
- """
-
- """
- Alerts
-
- [x] LIST
- [ ] CREATE
- [ ] READ
- [ ] UPDATE
- [ ] DELETE
- Additional:
- [x] LIST (v2)
- """
-
- def alert_list_read(self, query_params=None, body_params=None):
- return self.execute('POST', 'alert', query_params=query_params, body_params=body_params)
-
- def alert_v2_list_read(self, query_params=None, body_params=None):
- return self.execute('POST', 'v2/alert', query_params=query_params, body_params=body_params, paginated=True)
-
- def alert_csv_create(self, body_params=None):
- return self.execute('POST', 'alert/csv', body_params=body_params)
-
- def alert_csv_status(self, csv_report_id):
- return self.execute('GET', 'alert/csv/%s/status' % csv_report_id)
-
- def alert_csv_download(self, csv_report_id):
- return self.execute('GET', 'alert/csv/%s/download' % csv_report_id)
-
-
- """
- Policies
-
- [x] LIST
- [x] CREATE
- [x] READ
- [x] UPDATE
- [x] DELETE
- Additional:
- [x] LIST (v2)
- [x] LIST (v2) CUSTOM
- [x] UPDATE STATUS
- """
-
- def policy_list_read(self):
- return self.execute('GET', 'policy')
-
- def policy_v2_list_read(self):
- return self.execute('GET', 'v2/policy')
-
- def policy_custom_v2_list_read(self):
- filters = [('policy.policyMode', 'custom')]
- return self.execute('GET', 'v2/policy', query_params=filters)
-
- def policy_create(self, policy_to_add):
- return self.execute('POST', 'policy', body_params=policy_to_add)
-
- def policy_read(self, policy_id, message=None):
- self.progress(message)
- return self.execute('GET', 'policy/%s' % policy_id)
-
- def policy_update(self, policy_id, policy_update):
- return self.execute('PUT', 'policy/%s' % policy_id, body_params=policy_update)
-
- def policy_status_update(self, policy_id, policy_status_update):
- return self.execute('PATCH', 'policy/%s/status/%s' % (policy_id, policy_status_update))
-
- def policy_delete(self, policy_id):
- return self.execute('DELETE', 'policy/%s' % policy_id)
-
- """
- Saved Searches
-
- [x] LIST
- [x] CREATE
- [x] READ
- [ ] UPDATE
- [x] DELETE
- """
-
- def saved_search_list_read(self):
- return self.execute('GET', 'search/history?filter=saved')
-
- def saved_search_create(self, type_of_search, saved_search_to_add):
- if type_of_search == 'network':
- return self.execute('POST', 'search', body_params=saved_search_to_add)
- if type_of_search == 'audit_event':
- return self.execute('POST', 'search/event', body_params=saved_search_to_add)
- return self.execute('POST', 'search/%s' % type_of_search, body_params=saved_search_to_add)
-
- def saved_search_read(self, saved_search_id, message=None):
- self.progress(message)
- return self.execute('GET', 'search/history/%s' % saved_search_id)
-
- def saved_search_delete(self, saved_search_id):
- return self.execute('DELETE', 'search/history/%s' % saved_search_id)
-
- """
- Compliance Standards
-
- [x] LIST
- [x] CREATE
- [x] READ
- [ ] UPDATE
- [x] DELETE
- """
-
- def compliance_standard_list_read(self):
- return self.execute('GET', 'compliance')
-
- def compliance_standard_create(self, compliance_standard_to_add):
- return self.execute('POST', 'compliance', body_params=compliance_standard_to_add)
-
- def compliance_standard_read(self, compliance_standard_id):
- return self.execute('GET', 'compliance/%s' % compliance_standard_id)
-
- def compliance_standard_delete(self, compliance_standard_id):
- return self.execute('DELETE', 'compliance/%s' % compliance_standard_id)
-
- """
- Compliance Standard Requirements
-
- [x] LIST
- [x] CREATE
- [ ] READ
- [ ] UPDATE
- [ ] DELETE
- """
-
- def compliance_standard_requirement_list_read(self, compliance_standard_id):
- return self.execute('GET', 'compliance/%s/requirement' % compliance_standard_id)
-
- def compliance_standard_requirement_create(self, compliance_standard_id, compliance_requirement_to_add):
- return self.execute('POST', 'compliance/%s/requirement' % compliance_standard_id, body_params=compliance_requirement_to_add)
-
- """
- Compliance Standard Requirements Sections
-
- [x] LIST
- [x] CREATE
- [ ] READ
- [ ] UPDATE
- [ ] DELETE
- """
-
- def compliance_standard_requirement_section_list_read(self, compliance_requirement_id):
- return self.execute('GET', 'compliance/%s/section' % compliance_requirement_id)
-
- def compliance_standard_requirement_section_create(self, compliance_requirement_id, compliance_section_to_add):
- return self.execute('POST', 'compliance/%s/section' % compliance_requirement_id, body_params=compliance_section_to_add)
-
- """
- Compliance Standard Requirements Policies
-
- [x] LIST
- [ ] CREATE
- [ ] READ
- [ ] UPDATE
- [ ] DELETE
- Additional:
- [x] LIST (v2)
-
- """
-
- def compliance_standard_policy_list_read(self, compliance_standard_name):
- filters = [('policy.complianceStandard', compliance_standard_name)]
- return self.execute('GET', 'policy', query_params=filters)
-
- def compliance_standard_policy_v2_list_read(self, compliance_standard_name):
- filters = [('policy.complianceStandard', compliance_standard_name)]
- return self.execute('GET', 'v2/policy', query_params=filters)
-
- """
- Users
-
- [x] LIST
- [x] CREATE
- [x] READ
- [x] UPDATE
- [x] DELETE
- """
-
- def user_list_read(self):
- return self.execute('GET', 'v2/user')
-
- def user_create(self, user):
- return self.execute('POST', 'v2/user', body_params=user)
-
- def user_read(self, user_id):
- return self.execute('GET', 'v2/user/%s' % user_id)
-
- def user_update(self, user):
- return self.execute('PUT', 'v2/user/%s' % user['email'], body_params=user)
-
- def user_delete(self, user_id):
- return self.execute('DELETE', 'user/%s' % user_id)
-
- def user_bypass_sso(self, body_params):
- return self.execute('PUT', 'user/saml/bypass', body_params=body_params)
-
- """
- User Roles
-
- [x] LIST
- [ ] CREATE
- [x] READ
- [x] UPDATE
- [x] DELETE
- """
-
- def user_role_list_read(self):
- return self.execute('GET', 'user/role')
-
- def user_role_create(self, user_role_to_add):
- return self.execute('POST', 'user/role', body_params=user_role_to_add)
-
- def user_role_update(self, user_role_id, user_role_update):
- return self.execute('PUT', 'user/role/%s' % user_role_id, body_params=user_role_update)
-
- def user_role_read(self, user_role_id):
- return self.execute('GET', 'user/role/%s' % user_role_id)
-
- def user_role_delete(self, user_role_id):
- return self.execute('DELETE', 'user/role/%s' % user_role_id)
-
- """
- Access Keys
-
- [x] LIST
- [x] CREATE
- [x] READ
- [x] UPDATE
- [x] DELETE
- Additional:
- [x] UPDATE STATUS
- """
-
- def access_keys_list_read(self):
- return self.execute('GET', 'access_keys')
-
- def access_key_create(self, access_key_to_add):
- return self.execute('POST', 'access_keys', body_params=access_key_to_add)
-
- def access_key_read(self, access_key_id):
- return self.execute('GET', 'access_keys/%s' % access_key_id)
-
- def access_key_update(self, access_key_id, access_key_update):
- return self.execute('PUT', 'access_keys/%s' % access_key_id, body_params=access_key_update)
-
- # Note: Expired keys cannot be enabled.
- def access_key_status_update(self, access_key_id, access_key_status):
- return self.execute('PATCH', 'access_keys/%s/status/%s' % (access_key_id, access_key_status))
-
- def access_key_delete(self, access_key_id):
- return self.execute('DELETE', 'access_keys/%s' % access_key_id)
-
- """
- Cloud Accounts
-
- [x] LIST
- [x] CREATE
- [ ] READ
- [x] UPDATE
- [x] DELETE
- Additional:
- [x] LIST NAMES
- """
-
- def cloud_accounts_list_read(self, query_params=None):
- return self.execute('GET', 'cloud', query_params=query_params)
-
- def cloud_accounts_children_list_read(self, cloud_account_type, cloud_account_id):
- return self.execute('GET', 'cloud/%s/%s/project' % (cloud_account_type, cloud_account_id))
-
- def cloud_accounts_list_names_read(self, query_params=None):
- return self.execute('GET', 'cloud/name', query_params=query_params)
-
- def cloud_accounts_create(self, cloud_type, cloud_account_to_add):
- return self.execute('POST', 'cloud/%s' % cloud_type, body_params=cloud_account_to_add)
-
- def cloud_account_info_read(self, cloud_type, cloud_account_id):
- return self.execute('GET', 'cloud/%s/%s' % (cloud_type, cloud_account_id))
-
- def cloud_account_update(self, cloud_type, cloud_account_id, cloud_account_update):
- return self.execute('PUT', 'cloud/%s/%s' % (cloud_type, cloud_account_id), body_params=cloud_account_update)
-
- def cloud_account_delete(self, cloud_type, cloud_account_id):
- return self.execute('DELETE', 'cloud/%s/%s' % (cloud_type, cloud_account_id))
-
- def cloud_types_list_read(self, query_params=None):
- return self.execute('GET', 'cloud/type', query_params=query_params)
-
- """
- Cloud Account Groups
-
- [x] LIST
- [x] CREATE
- [x] READ
- [x] UPDATE
- [x] DELETE
- """
-
- def cloud_account_group_list_read(self):
- return self.execute('GET', 'cloud/group')
-
- def cloud_account_group_create(self, cloud_account_group_to_add):
- return self.execute('POST', 'cloud/group', body_params=cloud_account_group_to_add)
-
- def cloud_account_group_read(self, cloud_account_group_id):
- return self.execute('GET', 'cloud/group/%s' % cloud_account_group_id)
-
- def cloud_account_group_update(self, cloud_account_group_id, cloud_account_group_update):
- return self.execute('PUT', 'cloud/group/%s' % cloud_account_group_id, body_params=cloud_account_group_update)
-
- def cloud_account_group_delete(self, cloud_account_group_id):
- return self.execute('DELETE', 'cloud/group/%s' % cloud_account_group_id)
-
- """
- Asset (Resources) Inventory
-
- [x] LIST
- [ ] CREATE
- [ ] READ
- [ ] UPDATE
- [ ] DELETE
- Additional:
- [x] LIST (v2)
- [x] LIST WITH FILTERS(v2)
- """
-
- def asset_inventory_list_read(self, query_params=None):
- return self.execute('GET', 'v2/inventory', query_params=query_params)
-
- def asset_inventory_list_read_post(self, body_params=None):
- return self.execute('POST', 'v2/inventory', body_params=body_params)
-
- """
- Asset (Resources) Inventory V3
-
- [x] LIST
- [ ] CREATE
- [ ] READ
- [ ] UPDATE
- [ ] DELETE
- Additional:
- [x] LIST (v3)
- [x] LIST WITH FILTERS(v3)
- """
-
- def asset_inventory_list_read_v3(self, query_params=None):
- return self.execute('GET', 'v3/inventory', query_params=query_params)
-
- def asset_inventory_list_read_postv_3(self, body_params=None):
- return self.execute('POST', 'v3/inventory', body_params=body_params)
-
- """
- (Assets) Resources
-
- [ ] LIST
- [ ] CREATE
- [x] READ
- [ ] UPDATE
- [ ] DELETE
- """
-
- def resource_read(self, body_params=None, force=False, message=None):
- self.progress(message)
- return self.execute('POST', 'resource', body_params=body_params, force=force)
-
- def resource_network_read(self, body_params=None, force=False):
- return self.execute('POST', 'resource/network', body_params=body_params, force=force)
-
- def resource_scan_info_read(self, body_params=None):
- result = []
- page_number = 1
- while page_number == 1 or 'pageToken' in body_params:
- api_response = self.execute(
- 'POST', 'resource/scan_info', body_params=body_params)
- if 'resources' in api_response:
- result.extend(api_response['resources'])
- if 'nextPageToken' in api_response:
- body_params['pageToken'] = api_response['nextPageToken']
- else:
- body_params.pop('pageToken', None)
- # if 'totalMatchedCount' in api_response:
- # self.progress('Resources: %s, Page Size: %s, Page: %s' % (api_response['totalMatchedCount'], body_params['limit'], page_number))
- page_number += 1
- return result
-
- """
- Alert Rules
-
- [x] LIST
- [x] CREATE
- [x] READ
- [x] UPDATE
- [x] DELETE
- Additional:
- [x] LIST (v2)
- """
-
- def alert_rule_list_read(self):
- return self.execute('GET', 'v2/alert/rule')
-
- def alert_rule_create(self, alert_rule):
- return self.execute('POST', 'alert/rule', body_params=alert_rule)
-
- def alert_rule_read(self, alert_rule_id):
- return self.execute('GET', 'alert/rule/%s' % alert_rule_id)
-
- def alert_rule_delete(self, alert_rule_id):
- return self.execute('DELETE', 'alert/rule/%s' % alert_rule_id)
-
- def alert_rule_update(self, alert_rule_id, alert_rule_update):
- return self.execute('PUT', 'alert/rule/%s' % alert_rule_id, body_params=alert_rule_update)
-
- """
- Integrations
-
- [x] LIST
- [ ] CREATE
- [ ] READ
- [ ] UPDATE
- [x] DELETE
- Additional:
- [ ] LIST (v2)
- """
-
- def integration_list_read(self):
- return self.execute('GET', 'integration')
-
- def integration_delete(self, integration_id):
- return self.execute('DELETE', 'integration/%s' % integration_id)
-
- """
- Resource Lists
-
- [x] LIST
- [X] CREATE
- [ ] READ
- [ ] UPDATE
- [x] DELETE
- """
-
- def resource_list_read(self):
- return self.execute('GET', 'v1/resource_list')
-
- def resource_list_delete(self, resource_list_id):
- return self.execute('DELETE', 'v1/resource_list/%s' % resource_list_id)
-
- def resource_list_create(self, resource_list_to_add):
- return self.execute('POST', 'v1/resource_list', body_params=resource_list_to_add)
-
- """
- Adoption Advisor
-
- [x] LIST
- [X] CREATE
- [ ] READ
- [X] UPDATE
- [X] DELETE
- """
-
- def adoptionadvisor_report_read(self):
- return self.execute('GET', 'adoptionadvisor/report')
-
- def adoptionadvisor_report_delete(self, report_id):
- return self.execute('DELETE', 'adoptionadvisor/report/%s' % report_id)
-
- def adoptionadvisor_report_create(self, report_to_add):
- return self.execute('POST', 'adoptionadvisor/report', body_params=report_to_add)
-
- """
- Compliance Reports
-
- [x] LIST
- [x] CREATE
- [ ] READ
- [ ] UPDATE
- [x] DELETE
- Additional:
- [x] DOWNLOAD
- """
-
- def compliance_report_list_read(self):
- return self.execute('GET', 'report')
-
- def compliance_report_create(self, report_to_add):
- return self.execute('POST', 'report', body_params=report_to_add)
-
- def compliance_report_delete(self, report_id):
- return self.execute('DELETE', 'report/%s' % report_id)
-
- def compliance_report_download(self, report_id):
- return self.execute('GET', 'report/%s/download' % report_id)
- # TODO:
- # if response_status == 204:
- # # download pending
- # pass
- # elif response_status == 200:
- # # download ready
- # pass
- # else:
-
- """
- Search
-
- [ ] LIST
- [ ] CREATE
- [x] READ
- [ ] UPDATE
- [ ] DELETE
- """
-
- def search_config_read(self, search_params):
- result = []
- next_page_token = None
- api_response = self.execute(
- 'POST', 'search/config', body_params=search_params)
- if 'data' in api_response and 'items' in api_response['data']:
- result = api_response['data']['items']
- next_page_token = api_response['data'].pop('nextPageToken', None)
- while next_page_token:
- api_response = self.execute(
- 'POST', 'search/config/page', body_params={'limit': 1000, 'pageToken': next_page_token, 'withResourceJson': 'true'})
- if 'items' in api_response:
- result.extend(api_response['items'])
- next_page_token = api_response.pop('nextPageToken', None)
- return result
-
- def search_network_read(self, search_params, filtered=False):
- search_url = 'search'
- if filtered:
- search_url = 'search/filtered'
- return self.execute('POST', search_url, body_params=search_params)
-
- def search_event_read(self, search_params, subsearch=None):
- result = []
- next_page_token = None
- search_url = 'search/event'
- if subsearch and subsearch in ['aggregate', 'filtered']:
- search_url = 'search/event/%s' % subsearch
- api_response = self.execute(
- 'POST', search_url, body_params=search_params)
- if 'data' in api_response and 'items' in api_response['data']:
- result = api_response['data']['items']
- next_page_token = api_response['data'].pop('nextPageToken', None)
- while next_page_token:
- api_response = self.execute(
- 'POST', 'search/config/page', body_params={'limit': 1000, 'pageToken': next_page_token})
- if 'items' in api_response:
- result.extend(api_response['items'])
- next_page_token = api_response.pop('nextPageToken', None)
- return result
-
- def search_iam_read(self, search_params):
- result = []
- next_page_token = None
- api_response = self.execute(
- 'POST', 'api/v1/permission', body_params=search_params)
- if 'data' in api_response and 'items' in api_response['data']:
- result = api_response['data']['items']
- next_page_token = api_response['data'].pop('nextPageToken', None)
- while next_page_token:
- api_response = self.execute(
- 'POST', 'api/v1/permission/page', body_params={'limit': 1000, 'pageToken': next_page_token, 'withResourceJson': 'true'})
- if 'items' in api_response:
- result.extend(api_response['items'])
- next_page_token = api_response.pop('nextPageToken', None)
- return result
-
- def search_iam_source_to_granter(self, search_params):
- search_url = 'api/v1/permission/graph/source_to_granter'
- return self.execute('POST', search_url, body_params=search_params)
-
- def search_iam_granter_to_dest(self, search_params):
- search_url = 'api/v1/permission/graph/granter_to_dest'
- return self.execute('POST', search_url, body_params=search_params)
-
- def search_suggest_list_read(self, query_to_suggest):
- return self.execute('POST', 'search/suggest', body_params=query_to_suggest)
-
- """
- Configuration
-
- [ ] LIST
- [ ] CREATE
- [x] READ
- [ ] UPDATE
- [ ] DELETE
- """
-
- def compute_config(self):
- return self.execute('GET', 'compute/config')
-
- def meta_info(self):
- return self.execute('GET', 'meta_info')
-
- """
- Usage
-
- [ ] LIST
- [ ] CREATE
- [x] READ
- [ ] UPDATE
- [ ] DELETE
- """
-
- def resource_usage_by_cloud_type(self, body_params):
- return self.execute('POST', 'license/api/v1/usage', body_params=body_params)
-
- def resource_usage_over_time(self, body_params):
- return self.execute('POST', 'license/api/v1/usage/time_series', body_params=body_params)
-
- def resource_usage_by_cloud_type_v2(self, body_params):
- return self.execute('POST', 'license/api/v2/usage', body_params=body_params)
-
- def resource_usage_over_time_v2(self, body_params):
- return self.execute('POST', 'license/api/v2/time_series', body_params=body_params)
-
- """
- SSO SAML
-
- [X] LIST
- [X] CREATE
- [X] READ
- [X] UPDATE
- [ ] DELETE
- """
-
- def saml_config_read(self):
- return self.execute('GET', 'authn/v1/saml/config')
-
- def saml_config_create(self, body_params):
- return self.execute('POST', 'authn/v1/saml/config', body_params=body_params)
-
- def saml_config_update(self, body_params):
- return self.execute('PUT', 'authn/v1/saml/config', body_params=body_params)
-
-
- """
- Enterprise Settings
-
- [ ] LIST
- [ ] CREATE
- [X] READ
- [X] UPDATE
- [ ] DELETE
- """
-
- def enterprise_settings_config(self, body_params):
- return self.execute('POST', 'settings/enterprise', body_params=body_params)
-
- def enterprise_settings(self):
- return self.execute('GET', 'settings/enterprise')
-
- """
- Anomaly Settings
-
- [ ] LIST
- [ ] CREATE
- [ ] READ
- [X] UPDATE
- [ ] DELETE
- """
-
- def anomaly_settings_config(self, body_params, policy_id):
- anomaly_url = 'anomalies/settings/%s' % policy_id
- return self.execute('POST', anomaly_url, body_params=body_params)
-
- """
- Check the other side
-
- [ ] LIST
- [ ] CREATE
- [X] READ
- [X] UPDATE
- [ ] DELETE
- """
-
- def check(self):
- return self.execute('GET', 'check')
diff --git a/prismacloud/api/cspm/cspm.py b/prismacloud/api/cspm/cspm.py
deleted file mode 100644
index d25434b..0000000
--- a/prismacloud/api/cspm/cspm.py
+++ /dev/null
@@ -1,163 +0,0 @@
-""" Requests and Output """
-
-import json
-import time
-
-import requests
-
-class PrismaCloudAPIMixin():
- """ Requests and Output """
-
- def suppress_warnings_when_verify_false(self):
- if self.verify is False:
- # Pylint Issue #4584
- # pylint: disable=no-member
- requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
-
- def login(self, url=None):
- self.suppress_warnings_when_verify_false()
- if not url:
- url = 'https://%s/login' % self.api
- action = 'POST'
- request_headers = {'Content-Type': 'application/json'}
- # Add User-Agent to the headers
- request_headers['User-Agent'] = self.user_agent
- body_params_json = json.dumps({'username': self.identity, 'password': self.secret})
- # try:
- # api_response = requests.request(action, url, headers=request_headers, data=body_params_json, verify=self.verify, timeout=self.timeout)
- # except requests.exceptions.Timeout:
- # # continue in a retry loop
- # except requests.exceptions.RequestException as ex:
- # self.error_and_exit(api_response.status_code, 'API (%s) raised an exception\n%s' % (url, ex))
- api_response = requests.request(action, url, headers=request_headers, data=body_params_json, verify=self.verify, timeout=self.timeout)
- if api_response.status_code in self.retry_status_codes:
- for exponential_wait in self.retry_waits:
- time.sleep(exponential_wait)
- api_response = requests.request(action, url, headers=request_headers, data=body_params_json, verify=self.verify, timeout=self.timeout)
- if api_response.ok:
- break # retry loop
- if api_response.ok:
- api_response = json.loads(api_response.content)
- self.token = api_response.get('token')
- self.token_timer = time.time()
- else:
- self.error_and_exit(api_response.status_code, 'API (%s) responded with an error\n%s' % (url, api_response.text))
- if self.debug:
- print('New API Token: %s' % self.token)
-
- def extend_login(self):
- self.suppress_warnings_when_verify_false()
- url = 'https://%s/auth_token/extend' % self.api
- action = 'GET'
- request_headers = {'Content-Type': 'application/json', 'x-redlock-auth': self.token}
- # Add User-Agent to the headers
- request_headers['User-Agent'] = self.user_agent
- api_response = requests.request(action, url, headers=request_headers, verify=self.verify, timeout=self.timeout)
- if api_response.status_code in self.retry_status_codes:
- for exponential_wait in self.retry_waits:
- time.sleep(exponential_wait)
- api_response = requests.request(action, url, headers=request_headers, verify=self.verify, timeout=self.timeout)
- if api_response.ok:
- break # retry loop
- if api_response.ok:
- api_response = json.loads(api_response.content)
- self.token = api_response.get('token')
- self.token_timer = time.time()
- else:
- self.error_and_exit(api_response.status_code, 'API (%s) responded with an error\n%s' % (url, api_response.text))
- if self.debug:
- print('Extending API Token')
-
- # pylint: disable=too-many-arguments, too-many-branches, too-many-locals
- def execute(self, action, endpoint, query_params=None, body_params=None, request_headers=None, force=False, paginated=False):
- self.suppress_warnings_when_verify_false()
- if not self.token:
- self.login()
- if int(time.time() - self.token_timer) > self.token_limit:
- self.extend_login()
- # Endpoints that return large numbers of results use a 'nextPageToken' (and a 'totalRows') key.
- # Pagination appears to be specific to "List Alerts V2 - POST" and the limit has a maximum of 10000.
- more = True
- results = []
- while more is True:
- if int(time.time() - self.token_timer) > self.token_limit:
- self.extend_login()
- url = 'https://%s/%s' % (self.api, endpoint)
- if not request_headers:
- request_headers = {'Content-Type': 'application/json'}
- if self.token:
- request_headers['x-redlock-auth'] = self.token
- if body_params:
- body_params_json = json.dumps(body_params)
- else:
- body_params_json = None
- self.debug_print('API URL: %s' % url)
- self.debug_print('API Request Headers: (%s)' % request_headers)
- self.debug_print('API Query Params: %s' % query_params)
- self.debug_print('API Body Params: %s' % body_params_json)
- # Add User-Agent to the headers
- request_headers['User-Agent'] = self.user_agent
- api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
- self.debug_print('API Response Status Code: %s' % api_response.status_code)
- self.debug_print('API Response Headers: (%s)' % api_response.headers)
- if api_response.status_code in self.retry_status_codes:
- for exponential_wait in self.retry_waits:
- time.sleep(exponential_wait)
- api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
- if api_response.ok:
- break # retry loop
- if api_response.ok:
- if not api_response.content:
- return None
- if api_response.headers.get('Content-Type') == 'application/x-gzip':
- return api_response.content
- if api_response.headers.get('Content-Type') == 'text/csv':
- return api_response.content.decode('utf-8')
- try:
- result = json.loads(api_response.content)
- #if result is None:
- # self.logger.error('JSON returned None, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- # if force:
- # return results # or continue
- # self.error_and_exit(api_response.status_code, 'JSON returned None, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- except ValueError:
- self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- if force:
- return results # or continue
- self.error_and_exit(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- if paginated:
- results.extend(result['items'])
- if 'nextPageToken' in result and result['nextPageToken']:
- if self.debug:
- print('Retrieving Next Page of Results')
- body_params = {'pageToken': result['nextPageToken']}
- more = True
- else:
- more = False
- else:
- return result
- else:
- self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
- if force:
- return results
- self.error_and_exit(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
- return results
-
- # Exit handler (Error).
-
- @classmethod
- def error_and_exit(cls, error_code, error_message='', system_message=''):
- raise SystemExit('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message))
-
- # Output counted errors.
-
- def error_report(self):
- if self.logger.error.counter > 0:
- print('API responded with (%s) error(s): details logged to: (%s)' % (self.logger.error.counter, self.error_log))
-
- # Optionally output progress.
-
- @classmethod
- def progress(cls, txt=None):
- if txt:
- print(txt)
diff --git a/prismacloud/api/cwpp/_cloud.py b/prismacloud/api/cwpp/_cloud.py
deleted file mode 100644
index ab5d799..0000000
--- a/prismacloud/api/cwpp/_cloud.py
+++ /dev/null
@@ -1,26 +0,0 @@
-""" Prisma Compute API Cloud Endpoints Class """
-
-# Cloud
-
-class CloudPrismaCloudAPICWPPMixin:
- """ Prisma Cloud Compute API Cloud Endpoints Class """
-
- def cloud_discovery_read(self):
- return self.execute_compute('GET', 'api/v1/cloud/discovery')
-
- def cloud_discovery_download(self, query_params=None):
- # request_headers = {'Content-Type': 'text/csv'}
- # return self.execute_compute('GET', 'api/v1/cloud/discovery/download?', request_headers=request_headers, query_params=query_params)
- return self.execute_compute('GET', 'api/v1/cloud/discovery/download', query_params=query_params)
-
- def cloud_discovery_scan(self):
- return self.execute_compute('POST', 'api/v1/cloud/discovery/scan')
-
- def cloud_discovery_scan_stop(self):
- return self.execute_compute('POST', 'api/v1/cloud/discovery/stop')
-
- def cloud_discovery_vms(self, query_params=None):
- return self.execute_compute('GET', 'api/v1/cloud/discovery/vms', query_params=query_params, paginated=True)
-
- def cloud_discovery_entities(self, query_params=None):
- return self.execute_compute('GET', 'api/v1/cloud/discovery/entities', query_params=query_params)
diff --git a/prismacloud/api/cwpp/_containers.py b/prismacloud/api/cwpp/_containers.py
deleted file mode 100644
index 1346707..0000000
--- a/prismacloud/api/cwpp/_containers.py
+++ /dev/null
@@ -1,13 +0,0 @@
-""" Prisma Cloud Compute API Containers Endpoints Class """
-
-# Containers
-
-class ContainersPrismaCloudAPICWPPMixin:
- """ Prisma Cloud Compute API Containers Endpoints Class """
-
- def containers_list_read(self, image_id=None, query_params=None):
- if image_id:
- containers = self.execute_compute('GET', 'api/v1/containers?imageId=%s' % image_id, query_params=query_params, paginated=True)
- else:
- containers = self.execute_compute('GET', 'api/v1/containers?', query_params=query_params, paginated=True)
- return containers
diff --git a/prismacloud/api/cwpp/_scans.py b/prismacloud/api/cwpp/_scans.py
deleted file mode 100644
index c2687ec..0000000
--- a/prismacloud/api/cwpp/_scans.py
+++ /dev/null
@@ -1,13 +0,0 @@
-""" Prisma Cloud Compute API Scans Endpoints Class """
-
-# Scans (Monitor > Vulnerabilities/Compliance > Images > CI)
-
-class ScansPrismaCloudAPICWPPMixin:
- """ Prisma Cloud Compute API Scans Endpoints Class """
-
- def scans_list_read(self, image_id=None):
- if image_id:
- images = self.execute_compute('GET', 'api/v1/scans?imageID=%s&filterBaseImage=true' % image_id)
- else:
- images = self.execute_compute('GET', 'api/v1/scans?filterBaseImage=true', paginated=True)
- return images
diff --git a/prismacloud/api/cwpp/cwpp.py b/prismacloud/api/cwpp/cwpp.py
deleted file mode 100644
index cb0e750..0000000
--- a/prismacloud/api/cwpp/cwpp.py
+++ /dev/null
@@ -1,120 +0,0 @@
-""" Requests and Output """
-
-import json
-import time
-
-import requests
-
-class PrismaCloudAPICWPPMixin():
- """ Requests and Output """
-
- def login_compute(self):
- if self.api:
- # Login via CSPM.
- self.login()
- elif self.api_compute:
- # Login via CWP.
- self.login('https://%s/api/v1/authenticate' % self.api_compute)
- else:
- self.error_and_exit(418, "Specify a Prisma Cloud URL or Prisma Cloud Compute URL")
- self.debug_print('New API Token: %s' % self.token)
-
- def extend_login_compute(self):
- # There is no extend for CWP, just logon again.
- self.debug_print('Extending API Token')
- self.login_compute()
-
- # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements
- def execute_compute(self, action, endpoint, query_params=None, body_params=None, request_headers=None, force=False, paginated=False):
- self.suppress_warnings_when_verify_false()
- if not self.token:
- self.login_compute()
- if not request_headers:
- request_headers = {'Content-Type': 'application/json'}
- if body_params:
- body_params_json = json.dumps(body_params)
- else:
- body_params_json = None
- # Set User Agent
- request_headers['User-Agent'] = "W"
- # Endpoints that return large numbers of results use a 'Total-Count' response header.
- # Pagination is via query parameters for both GET and POST, and the limit has a maximum of 50.
- offset = 0
- limit = 50
- more = False
- results = []
- while offset == 0 or more is True:
- if int(time.time() - self.token_timer) > self.token_limit:
- self.extend_login_compute()
- if paginated:
- url = 'https://%s/%s?limit=%s&offset=%s' % (self.api_compute, endpoint, limit, offset)
- else:
- url = 'https://%s/%s' % (self.api_compute, endpoint)
- if self.token:
- if self.api:
- # Authenticate via CSPM
- request_headers['x-redlock-auth'] = self.token
- else:
- # Authenticate via CWP
- request_headers['Authorization'] = "Bearer %s" % self.token
- self.debug_print('API URL: %s' % url)
- self.debug_print('API Request Headers: (%s)' % request_headers)
- self.debug_print('API Query Params: %s' % query_params)
- self.debug_print('API Body Params: %s' % body_params_json)
- # Add User-Agent to the headers
- request_headers['User-Agent'] = self.user_agent
- api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
- self.debug_print('API Response Status Code: (%s)' % api_response.status_code)
- self.debug_print('API Response Headers: (%s)' % api_response.headers)
- if api_response.status_code in self.retry_status_codes:
- for exponential_wait in self.retry_waits:
- time.sleep(exponential_wait)
- api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
- if api_response.ok:
- break # retry loop
- if api_response.ok:
- if not api_response.content:
- return None
- if api_response.headers.get('Content-Type') == 'application/x-gzip':
- return api_response.content
- if api_response.headers.get('Content-Type') == 'text/csv':
- return api_response.content.decode('utf-8')
- try:
- result = json.loads(api_response.content)
- #if result is None:
- # self.logger.error('JSON returned None, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- # if force:
- # return results # or continue
- # self.error_and_exit(api_response.status_code, 'JSON returned None, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- except ValueError:
- self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- if force:
- return results # or continue
- self.error_and_exit(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- if 'Total-Count' in api_response.headers:
- self.debug_print('Retrieving Next Page of Results: Offset/Total Count: %s/%s' % (offset, api_response.headers['Total-Count']))
- total_count = int(api_response.headers['Total-Count'])
- if total_count > 0:
- results.extend(result)
- offset += limit
- more = bool(offset < total_count)
- else:
- return result
- else:
- self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
- if force:
- return results
- self.error_and_exit(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
- return results
-
- # The Compute API setting is optional.
-
- def validate_api_compute(self):
- if not self.api_compute:
- self.error_and_exit(500, 'Please specify a Prisma Cloud Compute API URL.')
-
- # Exit handler (Error).
-
- @classmethod
- def error_and_exit(cls, error_code, error_message='', system_message=''):
- raise SystemExit('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message))
diff --git a/prismacloud/api/pccs/pccs.py b/prismacloud/api/pccs/pccs.py
deleted file mode 100644
index 3d1dfc6..0000000
--- a/prismacloud/api/pccs/pccs.py
+++ /dev/null
@@ -1,94 +0,0 @@
-""" Requests and Output """
-
-import json
-import time
-
-import requests
-
-class PrismaCloudAPIPCCSMixin():
- """ Requests and Output """
-
- # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements
- def execute_code_security(self, action, endpoint, query_params=None, body_params=None, request_headers=None, force=False, paginated=False):
- self.suppress_warnings_when_verify_false()
- if not self.token:
- self.login()
- if int(time.time() - self.token_timer) > self.token_limit:
- self.extend_login()
- if not request_headers:
- request_headers = {'Content-Type': 'application/json'}
- if body_params:
- body_params_json = json.dumps(body_params)
- else:
- body_params_json = None
- # Endpoints that return large numbers of results use a 'hasNext' key.
- # Pagination is via query parameters for both GET and POST, and appears to be specific to "List File Errors - POST".
- offset = 0
- limit = 50
- more = False
- results = []
- while offset == 0 or more is True:
- if int(time.time() - self.token_timer) > self.token_limit:
- self.extend_login()
- if paginated:
- url = 'https://%s/%s?limit=%s&offset=%s' % (self.api, endpoint, limit, offset)
- else:
- url = 'https://%s/%s' % (self.api, endpoint)
- if self.token:
- request_headers['authorization'] = self.token
- self.debug_print('API URL: %s' % url)
- self.debug_print('API Headers: %s' % request_headers)
- self.debug_print('API Query Params: %s' % query_params)
- self.debug_print('API Body Params: %s' % body_params_json)
- # Add User-Agent to the headers
- request_headers['User-Agent'] = self.user_agent
- api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
- self.debug_print('API Response Status Code: %s' % api_response.status_code)
- self.debug_print('API Response Headers: (%s)' % api_response.headers)
- if api_response.status_code in self.retry_status_codes:
- for exponential_wait in self.retry_waits:
- time.sleep(exponential_wait)
- api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
- if api_response.ok:
- break # retry loop
- if api_response.ok:
- if not api_response.content:
- return None
- if api_response.headers.get('Content-Type') == 'application/x-gzip':
- return api_response.content
- if api_response.headers.get('Content-Type') == 'text/csv':
- return api_response.content.decode('utf-8')
- try:
- result = json.loads(api_response.content)
- #if result is None:
- # self.logger.error('JSON returned None, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- # if force:
- # return results # or continue
- # self.error_and_exit(api_response.status_code, 'JSON returned None, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- except ValueError:
- self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- if force:
- return results # or continue
- self.error_and_exit(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
- if paginated:
- results.extend(result['data'])
- if 'hasNext' in result:
- self.debug_print('Retrieving Next Page of Results')
- offset += limit
- more = result['hasNext']
- else:
- return results
- else:
- return result
- else:
- self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
- if force:
- return results
- self.error_and_exit(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
- return results
-
- # Exit handler (Error).
-
- @classmethod
- def error_and_exit(cls, error_code, error_message='', system_message=''):
- raise SystemExit('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message))
diff --git a/prismacloud/api/version.py b/prismacloud/api/version.py
deleted file mode 100644
index 369881d..0000000
--- a/prismacloud/api/version.py
+++ /dev/null
@@ -1 +0,0 @@
-version = "5.2.24"
diff --git a/prismacloud/api/__init__.py b/prismacloudapi/__init__.py
similarity index 54%
rename from prismacloud/api/__init__.py
rename to prismacloudapi/__init__.py
index 019495b..6592acb 100644
--- a/prismacloud/api/__init__.py
+++ b/prismacloudapi/__init__.py
@@ -2,12 +2,8 @@
import sys
-from .pc_lib_api import PrismaCloudAPI
-from .pc_lib_utility import PrismaCloudUtility
-from .version import version as api_version
-
-__author__ = 'Palo Alto Networks CSE/SE/SA Teams'
-__version__ = api_version
+from prismacloudapi.pc_lib_api import PrismaCloudAPI
+from prismacloudapi.pc_lib_utility import PrismaCloudUtility
MIN_PYTHON = (3, 6)
if sys.version_info < MIN_PYTHON:
diff --git a/prismacloud/api/cspm/README.md b/prismacloudapi/cspm/README.md
similarity index 100%
rename from prismacloud/api/cspm/README.md
rename to prismacloudapi/cspm/README.md
diff --git a/prismacloud/api/cspm/__init__.py b/prismacloudapi/cspm/__init__.py
similarity index 100%
rename from prismacloud/api/cspm/__init__.py
rename to prismacloudapi/cspm/__init__.py
diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py
new file mode 100644
index 0000000..2bd1db6
--- /dev/null
+++ b/prismacloudapi/cspm/_endpoints.py
@@ -0,0 +1,1254 @@
+""" Prisma Cloud API Endpoints Class """
+import logging
+import pprint
+
+# TODO: Split into multiple files, one per endpoint ...
+
+# pylint: disable=too-many-public-methods
+
+
+class EndpointsPrismaCloudAPIMixin():
+ """ Prisma Cloud API Endpoints Class """
+
+ def current_user(self):
+ return self.execute('GET', 'user/me')
+
+ """
+ Note: Eventually, all objects covered should have full CRUD capability, ie, to create, read, update, and delete (and list).
+
+ Template
+
+ [ ] LIST
+ [ ] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [ ] DELETE
+ """
+
+ """
+ Alerts
+
+ [x] LIST
+ [ ] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [ ] DELETE
+ Additional:
+ [x] LIST (v2)
+ """
+
+ def alert_filter_suggest(self, query_params):
+ return self.execute('GET', 'filter/alert/suggest', query_params=query_params)
+
+ def alert_filter_suggest_post(self, body_params):
+ return self.execute('POST', 'filter/alert/suggest', body_params=body_params)
+
+ def alert_list_read(self, query_params=None, body_params=None):
+ # returns items directly
+ return self.execute('POST', 'alert', query_params=query_params, body_params=body_params)
+
+ def alert_v2_list_read(self, query_params=None, body_params=None):
+ # returns items in results['items']. But really does not respect pagination request.
+ return self.execute_paginated('POST', 'v2/alert', query_params=query_params, body_params=body_params)
+
+ def alert_info(self, alert_id, detailed=False):
+ return self.execute('GET', f'alert/{alert_id}', query_params={'detailed': detailed})
+
+ def alert_csv_create(self, body_params=None):
+ return self.execute('POST', 'alert/csv', body_params=body_params)
+
+ def alert_csv_status(self, csv_report_id):
+ return self.execute('GET', 'alert/csv/%s/status' % csv_report_id)
+
+ def alert_csv_download(self, csv_report_id):
+ return self.execute('GET', 'alert/csv/%s/download' % csv_report_id)
+
+ def alert_count_by_policy(self, query_params):
+ """
+ `https://pan.dev/prisma-cloud/api/cspm/get-alerts-grouped/`
+ """
+ return self.execute('GET', 'alert/policy', query_params=query_params)
+
+ def alert_count_by_policy_post(self, detailed, body_params):
+ """
+ `https://pan.dev/prisma-cloud/api/cspm/post-alerts-grouped/`
+ """
+ body_params['detailed'] = detailed
+ return self.execute('POST', 'alert/policy', body_params=body_params)
+
+ def alert_count_by_status(self, status):
+ return self.execute('GET', f'alert/count/{status}')
+
+ """
+ Policies
+
+ [x] LIST
+ [x] CREATE
+ [x] READ
+ [x] UPDATE
+ [x] DELETE
+ Additional:
+ [x] LIST (v2)
+ [x] LIST (v2) CUSTOM
+ [x] UPDATE STATUS
+ """
+
+ def policy_list_read(self):
+ return self.execute('GET', 'policy')
+
+ def policy_v2_list_read(self):
+ return self.execute('GET', 'v2/policy')
+
+ def policy_custom_v2_list_read(self):
+ filters = [('policy.policyMode', 'custom')]
+ return self.execute('GET', 'v2/policy', query_params=filters)
+
+ def policy_create(self, policy_to_add):
+ return self.execute('POST', 'policy', body_params=policy_to_add)
+
+ def policy_read(self, policy_id, message=None):
+ self.progress(message)
+ return self.execute('GET', 'policy/%s' % policy_id)
+
+ def policy_update(self, policy_id, policy_update):
+ return self.execute('PUT', 'policy/%s' % policy_id, body_params=policy_update)
+
+ def policy_status_update(self, policy_id, policy_status_update):
+ return self.execute('PATCH', 'policy/%s/status/%s' % (policy_id, policy_status_update))
+
+ def policy_delete(self, policy_id):
+ return self.execute('DELETE', 'policy/%s' % policy_id)
+
+ """
+ Saved Searches
+
+ [x] LIST
+ [x] CREATE
+ [x] READ
+ [ ] UPDATE
+ [x] DELETE
+ """
+
+ def saved_search_list_read(self):
+ return self.execute('GET', 'search/history?filter=saved')
+
+ def saved_search_create(self, type_of_search, saved_search_to_add):
+ if type_of_search == 'network':
+ return self.execute('POST', 'search', body_params=saved_search_to_add)
+ if type_of_search == 'audit_event':
+ return self.execute('POST', 'search/event', body_params=saved_search_to_add)
+ return self.execute('POST', 'search/%s' % type_of_search, body_params=saved_search_to_add)
+
+ def saved_search_read(self, saved_search_id, message=None):
+ self.progress(message)
+ return self.execute('GET', 'search/history/%s' % saved_search_id)
+
+ def saved_search_delete(self, saved_search_id):
+ return self.execute('DELETE', 'search/history/%s' % saved_search_id)
+
+ """
+ Compliance Posture
+
+ [x] LIST
+ [ ] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [ ] DELETE
+ """
+
+ def compliance_posture_statistics(self, query_params):
+ """Get Compliance Statistics Breakdown V2
+ `PAN Api docs `_
+ """
+ return self.execute('GET', 'v2/compliance/posture', query_params=query_params)
+
+ def compliance_posture_statistics_post(self, body_params):
+ """Get Compliance Statistics Breakdown V2"""
+ return self.execute('POST', 'v2/compliance/posture', body_params=body_params)
+
+ def compliance_posture_statistics_for_standard(self, compliance_id, query_params):
+ """Get Compliance Statistics for Standard ID V2
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'v2/compliance/posture/{compliance_id}', query_params=query_params)
+
+ def compliance_posture_statistics_for_standard_post(self, compliance_id, body_params):
+ """Get Compliance Statistics for Standard ID V2"""
+ return self.execute('POST', f'v2/compliance/posture/{compliance_id}', body_params=body_params)
+
+ def compliance_posture_statistics_for_requirement(self, compliance_id, requirement_id, query_params):
+ """Get Compliance Statistics for Requirement ID V2
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'v2/compliance/posture/{compliance_id}/{requirement_id}', query_params=query_params)
+
+ def compliance_posture_statistics_for_requirement_post(self, compliance_id, requirement_id, body_params):
+ """Get Compliance Statistics for Requirement ID V2"""
+ return self.execute('POST', f'v2/compliance/posture/{compliance_id}/{requirement_id}', body_params=body_params)
+
+ def compliance_posture_trend(self, query_params):
+ """Get Compliance Trend V2
+ `PAN Api docs `_
+ """
+ return self.execute('GET', 'v2/compliance/posture/trend', query_params=query_params)
+
+ def compliance_posture_trend_post(self, body_params):
+ """Get Compliance Trend V2"""
+ return self.execute('POST', 'v2/compliance/posture/trend', body_params=body_params)
+
+ def compliance_posture_trend_for_standard(self, compliance_id):
+ """Get Compliance Trend for Standard ID V2
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'v2/compliance/posture/trend/{compliance_id}')
+
+ def compliance_posture_trend_for_standard_post(self, compliance_id, body_params):
+ """Get Compliance Trend for Standard ID V2"""
+ return self.execute('POST', f'v2/compliance/posture/trend/{compliance_id}', body_params=body_params)
+
+ def compliance_posture_trend_for_requirement(self, compliance_id, requirement_id, query_params):
+ """Get Compliance Trend for Requirement ID V2
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'v2/compliance/posture/trend/{compliance_id}/{requirement_id}', query_params=query_params)
+
+ def compliance_posture_trend_for_requirement_post(self, compliance_id, requirement_id, body_params):
+ """Get Compliance Trend for Requirement ID V2 """
+ return self.execute('POST', f'v2/compliance/posture/trend/{compliance_id}/{requirement_id}',
+ body_params=body_params)
+
+ """
+ Compliance Standards
+
+ [x] LIST
+ [x] CREATE
+ [x] READ
+ [ ] UPDATE
+ [x] DELETE
+ """
+
+ def compliance_standard_list_read(self):
+ return self.execute('GET', 'compliance')
+
+ def compliance_standard_create(self, compliance_standard_to_add):
+ return self.execute('POST', 'compliance', body_params=compliance_standard_to_add)
+
+ def compliance_standard_read(self, compliance_standard_id):
+ return self.execute('GET', 'compliance/%s' % compliance_standard_id)
+
+ def compliance_standard_delete(self, compliance_standard_id):
+ return self.execute('DELETE', 'compliance/%s' % compliance_standard_id)
+
+ """
+ Compliance Standard Requirements
+
+ [x] LIST
+ [x] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [ ] DELETE
+ """
+
+ def compliance_standard_requirement_list_read(self, compliance_standard_id):
+ return self.execute('GET', 'compliance/%s/requirement' % compliance_standard_id)
+
+ def compliance_standard_requirement_create(self, compliance_standard_id, compliance_requirement_to_add):
+ return self.execute('POST', 'compliance/%s/requirement' % compliance_standard_id,
+ body_params=compliance_requirement_to_add)
+
+ """
+ Compliance Standard Requirements Sections
+
+ [x] LIST
+ [x] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [ ] DELETE
+ """
+
+ def compliance_standard_requirement_section_list_read(self, compliance_requirement_id):
+ return self.execute('GET', 'compliance/%s/section' % compliance_requirement_id)
+
+ def compliance_standard_requirement_section_create(self, compliance_requirement_id, compliance_section_to_add):
+ return self.execute('POST', 'compliance/%s/section' % compliance_requirement_id,
+ body_params=compliance_section_to_add)
+
+ """
+ Compliance Standard Requirements Policies
+
+ [x] LIST
+ [ ] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [ ] DELETE
+ Additional:
+ [x] LIST (v2)
+
+ """
+
+ def compliance_standard_policy_list_read(self, compliance_standard_name):
+ filters = [('policy.complianceStandard', compliance_standard_name)]
+ return self.execute('GET', 'policy', query_params=filters)
+
+ def compliance_standard_policy_v2_list_read(self, compliance_standard_name):
+ filters = [('policy.complianceStandard', compliance_standard_name)]
+ return self.execute('GET', 'v2/policy', query_params=filters)
+
+ """
+ Users
+
+ [x] LIST
+ [x] CREATE
+ [x] READ
+ [x] UPDATE
+ [x] DELETE
+ """
+
+ def user_list_read_v3(self):
+ return self.execute('GET', 'v3/user')
+
+ def user_list_read_v2(self):
+ return self.execute('GET', 'v2/user')
+
+ def user_create(self, user):
+ return self.execute('POST', 'v2/user', body_params=user)
+
+ def user_read(self, user_id):
+ return self.execute('GET', 'v2/user/%s' % user_id)
+
+ def user_update(self, user):
+ return self.execute('PUT', 'v2/user/%s' % user['email'], body_params=user)
+
+ def user_delete(self, user_id):
+ return self.execute('DELETE', 'user/%s' % user_id)
+
+ def user_list_user_emails(self):
+ return self.execute('GET', 'user/name')
+
+ def user_list_email_domains(self):
+ return self.execute('GET', 'user/domain')
+
+ def user_list_bypass_sso(self):
+ return self.execute('GET', 'user/saml/bypass')
+
+ def user_update_bypass_sso(self, body_params):
+ return self.execute('PUT', 'user/saml/bypass', body_params=body_params)
+
+ """
+ User Roles
+
+ [x] LIST
+ [ ] CREATE
+ [x] READ
+ [x] UPDATE
+ [x] DELETE
+ """
+
+ def user_role_list_read(self):
+ return self.execute('GET', 'user/role')
+
+ def user_role_create(self, user_role_to_add):
+ return self.execute('POST', 'user/role', body_params=user_role_to_add)
+
+ def user_role_update(self, user_role_id, user_role_update):
+ return self.execute('PUT', 'user/role/%s' % user_role_id, body_params=user_role_update)
+
+ def user_role_read(self, user_role_id):
+ return self.execute('GET', 'user/role/%s' % user_role_id)
+
+ def user_role_delete(self, user_role_id):
+ return self.execute('DELETE', 'user/role/%s' % user_role_id)
+
+ """
+ Access Keys
+
+ [x] LIST
+ [x] CREATE
+ [x] READ
+ [x] UPDATE
+ [x] DELETE
+ Additional:
+ [x] UPDATE STATUS
+ """
+
+ def access_keys_list_read(self):
+ return self.execute('GET', 'access_keys')
+
+ def access_key_create(self, access_key_to_add):
+ return self.execute('POST', 'access_keys', body_params=access_key_to_add)
+
+ def access_key_read(self, access_key_id):
+ return self.execute('GET', 'access_keys/%s' % access_key_id)
+
+ def access_key_update(self, access_key_id, access_key_update):
+ return self.execute('PUT', 'access_keys/%s' % access_key_id, body_params=access_key_update)
+
+ # Note: Expired keys cannot be enabled.
+ def access_key_status_update(self, access_key_id, access_key_status):
+ return self.execute('PATCH', 'access_keys/%s/status/%s' % (access_key_id, access_key_status))
+
+ def access_key_delete(self, access_key_id):
+ return self.execute('DELETE', 'access_keys/%s' % access_key_id)
+
+ """
+ Cloud Accounts
+
+ [x] LIST
+ [x] CREATE
+ [ ] READ
+ [x] UPDATE
+ [x] DELETE
+ Additional:
+ [x] LIST NAMES
+ """
+
+ def cloud_accounts_list_read(self, query_params=None):
+ return self.execute('GET', 'cloud', query_params=query_params)
+
+ def cloud_accounts_children_list_read(self, cloud_account_type, cloud_account_id):
+ return self.execute('GET', 'cloud/%s/%s/project' % (cloud_account_type, cloud_account_id))
+
+ def cloud_accounts_list_names_read(self, query_params=None):
+ return self.execute('GET', 'cloud/name', query_params=query_params)
+
+ def cloud_accounts_create(self, cloud_type, cloud_account_to_add):
+ return self.execute('POST', 'cloud/%s' % cloud_type, body_params=cloud_account_to_add)
+
+ def cloud_account_info_read(self, cloud_type, cloud_account_id):
+ return self.execute('GET', 'cloud/%s/%s' % (cloud_type, cloud_account_id))
+
+ def cloud_account_update(self, cloud_type, cloud_account_id, cloud_account_update):
+ return self.execute('PUT', 'cloud/%s/%s' % (cloud_type, cloud_account_id), body_params=cloud_account_update)
+
+ def cloud_account_delete(self, cloud_type, cloud_account_id):
+ return self.execute('DELETE', 'cloud/%s/%s' % (cloud_type, cloud_account_id))
+
+ def cloud_account_status(self, cloud_account_id):
+ return self.execute('GET', f'account/{cloud_account_id}/config/status')
+
+ def cloud_types_list_read(self, query_params=None):
+ return self.execute('GET', 'cloud/type', query_params=query_params)
+
+ """
+ Cloud Account Groups
+
+ [x] LIST
+ [x] CREATE
+ [x] READ
+ [x] UPDATE
+ [x] DELETE
+ """
+
+ def cloud_account_group_list_read(self,exclude_details: bool=None, include_pending_accounts:bool=None):
+ """
+ `PAN Api docs `_
+ """
+ query_params = {}
+ if exclude_details is not None:
+ query_params['excludeAccountGroupDetails'] = exclude_details
+ if include_pending_accounts is not None:
+ query_params['includePendingAccounts'] = include_pending_accounts
+ return self.execute('GET', 'cloud/group', query_params=query_params)
+
+ def cloud_account_group_create(self, name, description, account_id_list: list):
+ """
+ Create an account group.
+
+ `PAN Api docs `_
+ """
+ body_params=dict(name=name, description=description, accountIds=account_id_list)
+ return self.execute('POST', 'cloud/group', body_params=body_params)
+
+ def cloud_account_group_read(self, cloud_account_group_id):
+ return self.execute('GET', 'cloud/group/%s' % cloud_account_group_id)
+
+ def cloud_account_group_update(self, cloud_account_group_id, name=None, description=None, account_id_list=None):
+ body_params=dict(id=cloud_account_group_id)
+ if name is not None:
+ body_params['name'] = name
+ if description is not None:
+ body_params['description'] = description
+ if account_id_list is not None:
+ body_params['accountIds'] = account_id_list
+ return self.execute('PUT', 'cloud/group/%s' % cloud_account_group_id, body_params=body_params)
+
+ def cloud_account_group_delete(self, cloud_account_group_id):
+ return self.execute('DELETE', 'cloud/group/%s' % cloud_account_group_id)
+
+ """
+ Asset (Resources) Inventory
+
+ [x] LIST
+ [ ] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [ ] DELETE
+ Additional:
+ [x] LIST (v2)
+ [x] LIST WITH FILTERS(v2)
+ """
+
+ def asset_inventory_list_read(self, query_params=None):
+ return self.execute('GET', 'v2/inventory', query_params=query_params)
+
+ def asset_inventory_list_read_post(self, body_params=None):
+ # timeRange just doesn't work here
+ return self.execute('POST', 'v2/inventory', body_params=body_params)
+
+ """
+ Asset (Resources) Inventory V3
+
+ [x] LIST
+ [ ] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [ ] DELETE
+ Additional:
+ [x] LIST (v3)
+ [x] LIST WITH FILTERS(v3)
+ """
+
+ def asset_inventory_list_read_v3(self, query_params=None):
+ return self.execute('GET', 'v3/inventory', query_params=query_params)
+
+ def asset_inventory_list_read_postv_3(self, body_params=None):
+ return self.execute('POST', 'v3/inventory', body_params=body_params)
+
+ def asset_inventory_trend_list_read_v3(self, query_params=None):
+ return self.execute('GET', 'v3/inventory/trend', query_params=query_params)
+
+ def asset_inventory_trend_list_read_postv_3(self, body_params=None):
+ return self.execute('POST', 'v3/inventory/trend', body_params=body_params)
+
+ def asset_inventory_v4(self, body_params=None):
+ # timeRange just doesn't work here
+ return self.execute('POST', 'api/v4/inventory', body_params=body_params)
+
+ def asset_inventory_count(self, body_params=None):
+ return self.execute('POST', 'api/v4/aggregation/count', body_params=body_params)
+
+ def asset_inventory_filters(self, query_params=None):
+ return self.execute('GET', 'filter/v2/inventory/suggest', query_params=query_params)
+
+ def asset_inventory_filters_post(self, body_params=None):
+ return self.execute('POST', 'filter/v2/inventory/suggest', body_params=body_params)
+
+
+ """
+ (Assets) Resources
+
+ [ ] LIST
+ [ ] CREATE
+ [x] READ
+ [ ] UPDATE
+ [ ] DELETE
+ """
+
+ def resource_read(self, body_params=None, force=False, message=None):
+ self.progress(message)
+ return self.execute('POST', 'resource', body_params=body_params, force=force)
+
+ def resource_network_read(self, body_params=None, force=False):
+ return self.execute('POST', 'resource/network', body_params=body_params, force=force)
+
+ def resource_scan_info_read(self, body_params=None):
+ result = []
+ page_number = 1
+ while page_number == 1 or 'pageToken' in body_params:
+ api_response = self.execute(
+ 'POST', 'resource/scan_info', body_params=body_params)
+ if 'resources' in api_response:
+ result.extend(api_response['resources'])
+ if 'nextPageToken' in api_response:
+ body_params['pageToken'] = api_response['nextPageToken']
+ else:
+ body_params.pop('pageToken', None)
+ # if 'totalMatchedCount' in api_response:
+ # self.progress('Resources: %s, Page Size: %s, Page: %s' % (api_response['totalMatchedCount'], body_params['limit'], page_number))
+ page_number += 1
+ return result
+
+ def resource_scan_info_read_v2(self, body_params=None):
+ page_number = 1
+ while page_number == 1 or 'pageToken' in body_params:
+ api_response = self.execute(
+ 'POST', 'api/v2/resource/scan_info', body_params=body_params)
+ if 'resources' in api_response:
+ yield from api_response['resources']
+ if 'nextPageToken' in api_response:
+ body_params['pageToken'] = api_response['nextPageToken']
+ else:
+ body_params.pop('pageToken', None)
+ # if 'totalMatchedCount' in api_response:
+ # self.progress('Resources: %s, Page Size: %s, Page: %s' % (api_response['totalMatchedCount'], body_params['limit'], page_number))
+ page_number += 1
+ return
+
+ def resource_scan_info_read_v4(self, body_params=None):
+ page_number = 1
+ while page_number == 1 or 'nextPageToken' in body_params:
+ api_response = self.execute(
+ 'POST', 'api/v4/resource/scan_info', body_params=body_params)
+ if 'resources' in api_response:
+ yield from api_response['resources']
+ if 'nextPageToken' in api_response:
+ body_params['nextPageToken'] = api_response['nextPageToken']
+ else:
+ body_params.pop('nextPageToken', None)
+ # if 'totalMatchedCount' in api_response:
+ # self.progress('Resources: %s, Page Size: %s, Page: %s' % (api_response['totalMatchedCount'], body_params['limit'], page_number))
+ page_number += 1
+ return
+
+
+ """
+ Alert Rules
+
+ [x] LIST
+ [x] CREATE
+ [x] READ
+ [x] UPDATE
+ [x] DELETE
+ Additional:
+ [x] LIST (v2)
+ """
+
+ def alert_rule_list_read(self):
+ return self.execute('GET', 'v2/alert/rule')
+
+ def alert_rule_create(self, alert_rule):
+ return self.execute('POST', 'alert/rule', body_params=alert_rule)
+
+ def alert_rule_read(self, alert_rule_id):
+ return self.execute('GET', 'alert/rule/%s' % alert_rule_id)
+
+ def alert_rule_delete(self, alert_rule_id):
+ return self.execute('DELETE', 'alert/rule/%s' % alert_rule_id)
+
+ def alert_rule_update(self, alert_rule_id, alert_rule_update):
+ return self.execute('PUT', 'alert/rule/%s' % alert_rule_id, body_params=alert_rule_update)
+
+ """
+ Integrations
+
+ [x] LIST
+ [ ] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [x] DELETE
+ Additional:
+ [ ] LIST (v2)
+ """
+
+ def integration_list_read(self):
+ return self.execute('GET', 'integration')
+
+ def integration_delete(self, integration_id):
+ return self.execute('DELETE', 'integration/%s' % integration_id)
+
+ def integration_list(self, tenant_id):
+ # use
+ return self.execute('GET', f'v1/tenant/{tenant_id}/integration')
+
+ """
+ Resource Lists
+
+ [x] LIST
+ [X] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [x] DELETE
+ """
+
+ def resource_list_read(self):
+ return self.execute('GET', 'v1/resource_list')
+
+ def resource_list_types_read(self):
+ return self.execute('GET', 'v1/resource_list/types')
+
+ def resource_list_delete(self, resource_list_id):
+ return self.execute('DELETE', 'v1/resource_list/%s' % resource_list_id)
+
+ def resource_list_create(self, resource_list_to_add):
+ return self.execute('POST', 'v1/resource_list', body_params=resource_list_to_add)
+
+ """
+ Adoption Advisor
+
+ [x] LIST
+ [X] CREATE
+ [ ] READ
+ [X] UPDATE
+ [X] DELETE
+ """
+
+ def adoptionadvisor_report_read(self):
+ return self.execute('GET', 'adoptionadvisor/report')
+
+ def adoptionadvisor_report_delete(self, report_id):
+ return self.execute('DELETE', 'adoptionadvisor/report/%s' % report_id)
+
+ def adoptionadvisor_report_create(self, report_to_add):
+ return self.execute('POST', 'adoptionadvisor/report', body_params=report_to_add)
+
+ """
+ Compliance Reports
+
+ [x] LIST
+ [x] CREATE
+ [ ] READ
+ [ ] UPDATE
+ [x] DELETE
+ Additional:
+ [x] DOWNLOAD
+ """
+
+ def compliance_report_list_read(self):
+ return self.execute('GET', 'report')
+
+ def compliance_report_create(self, report_to_add):
+ return self.execute('POST', 'report', body_params=report_to_add)
+
+ def compliance_report_create_v2(self, name, cloud_type, locale=None, target=None, type=None):
+ body_params = dict(name=name, cloudType=cloud_type)
+ if locale:
+ body_params['locale'] = locale
+ if target:
+ body_params['target'] = target
+ if type:
+ body_params['type'] = type
+ return self.execute('POST', 'v2/report', body_params=body_params)
+
+ def compliance_report_get_config(self, report_id):
+ return self.execute('GET', f'report/{report_id}')
+
+ def compliance_report_delete(self, report_id):
+ return self.execute('DELETE', f'report/{report_id}')
+
+ def compliance_report_download(self, report_id):
+ """
+ Download Report
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'report/{report_id}/download')
+
+ def compliance_report_history(self, report_id):
+ return self.execute('GET', f'report/{report_id}/history')
+
+ def compliance_report_type_list(self):
+ """
+ Get Report Types
+ `PAN Api docs `_
+ """
+ return self.execute('GET', 'report/type')
+
+ def compliance_report_type_get(self, report_id):
+ """
+ Get Report Config
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'report/type/{report_id}')
+
+ def compliance_report_filter_suggest(self):
+ """
+ Get Report filter suggest
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'filter/report/suggest')
+
+ def jobs_report_metadata(self, report_types=None):
+ """
+ This endpoint is available on the Prisma Cloud Darwin release only.
+
+ Get Reports Metadata
+ `PAN Api docs `_
+ """
+ query_params = {}
+ if report_types:
+ query_params['report_types'] = report_types
+ return self.execute('GET', 'report-service/api/v1/report', query_params=query_params)
+
+ def jobs_report_metadata_by_id(self, report_id):
+ """
+ This endpoint is available on the Prisma Cloud Darwin release only.
+
+ Background jobs - Get Reports - by report id
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'report-service/api/v1/report/{report_id}')
+
+ def jobs_report_status(self, report_id):
+ """
+ This endpoint is available on the Prisma Cloud Darwin release only.
+
+ Background jobs - Get Reports - get status
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'report-service/api/v1/report/{report_id}/status')
+
+ def jobs_report_download(self, report_id):
+ """
+ This endpoint is available on the Prisma Cloud Darwin release only.
+
+ Background jobs - Get Reports - download
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'report-service/api/v1/report/{report_id}/download')
+
+ """
+ Search
+
+ [ ] LIST
+ [ ] CREATE
+ [x] READ
+ [ ] UPDATE
+ [ ] DELETE
+ """
+
+ def search_config_read(self, query, search_id=None, search_name=None, search_description=None, limit=100,
+ with_resource_json=None, time_range=None, sort=None, heuristic_search=None,
+ paginate=True):
+ """
+ Perform Config Search
+ `PAN Api docs `_
+ """
+ # if time_range is None:
+ # time_range = dict(type="relative", value=dict(unit="hour", amount=24))
+ body_params = dict(query=query, id=search_id, searchName=search_name, searchDescription=search_description,
+ limit=limit, withResourceJson=with_resource_json, timeRange=time_range, sort=sort,
+ heuristicSearch=heuristic_search)
+ for k, v in dict(body_params).items():
+ if v is None:
+ del body_params[k]
+ next_page_token = None
+ # https://pan.dev/prisma-cloud/api/cspm/search-config/
+ api_response = self.execute('POST', 'search/config', body_params=body_params)
+ # logging.debug(pprint.pformat(api_response))
+ if 'data' in api_response and 'items' in api_response['data']:
+ yield from api_response['data']['items']
+ next_page_token = api_response['data'].pop('nextPageToken', None)
+ while paginate and next_page_token:
+ body_params['pageToken'] = next_page_token
+ # https://pan.dev/prisma-cloud/api/cspm/search-config-page/
+ # logging.debug("paging %s", pprint.pformat(body_params))
+ api_response = self.execute('POST', 'search/config/page', body_params=body_params)
+ # logging.debug(pprint.pformat(api_response))
+ if 'items' in api_response:
+ yield from api_response['items']
+ next_page_token = api_response.pop('nextPageToken', None)
+ return
+
+ def search_config_read_by_query(self, query, skip_search_creation=None, skip_results=None, limit=100,
+ with_resource_json=None, time_range=None,
+ sort=None, next_page_token=None, paginate=True):
+ """
+ Perform Config Search by Query
+ `PAN Api docs `_
+ """
+ # if time_range is None:
+ # time_range = dict(type="relative", value=dict(unit="hour", amount=24))
+ body_params = dict(query=query, skipSearchCreation=skip_search_creation, limit=limit,
+ withResourceJson=with_resource_json, timeRange=time_range,
+ skipResult=skip_results,
+ sort=sort,
+ nextPageToken=next_page_token)
+ for k, v in dict(body_params).items():
+ if v is None:
+ del body_params[k]
+ next_page_token = None
+ api_response = self.execute('POST', f'search/api/v1/config', body_params=body_params)
+ if 'items' in api_response:
+ yield from api_response['items']
+ next_page_token = api_response.pop('nextPageToken', None)
+ while paginate and next_page_token:
+ body_params['nextPageToken'] = next_page_token
+ api_response = self.execute('POST', f'search/api/v1/config', body_params=body_params)
+ if 'items' in api_response:
+ yield from api_response['items']
+ next_page_token = api_response.pop('nextPageToken', None)
+ return
+
+ def search_config_read_by_search_id(self, search_id, limit=100, with_resource_json=None, time_range=None,
+ sort=None, next_page_token=None, paginate=True):
+ """
+ Perform Config Search by Search Id
+ `PAN Api docs `_
+ """
+ # if time_range is None:
+ # time_range = dict(type="relative", value=dict(unit="hour", amount=24))
+ body_params = dict(limit=limit, withResourceJson=with_resource_json, timeRange=time_range, sort=sort,
+ nextPageToken=next_page_token)
+ for k, v in dict(body_params).items():
+ if v is None:
+ del body_params[k]
+ next_page_token = None
+ api_response = self.execute('POST', f'search/api/v1/config/{search_id}', body_params=body_params)
+ if 'items' in api_response:
+ yield from api_response['items']
+ next_page_token = api_response.pop('nextPageToken', None)
+ while paginate and next_page_token:
+ body_params['nextPageToken'] = next_page_token
+ api_response = self.execute('POST', f'search/api/v1/config/{search_id}', body_params=body_params)
+ if 'items' in api_response:
+ yield from api_response['items']
+ next_page_token = api_response.pop('nextPageToken', None)
+ return
+
+ def search_config_read_v2(self, query, start_time=None, skip_results=None, limit=100,
+ with_resource_json=None, # time_range=None,
+ sort=None, next_page_token=None, paginate=True):
+ """
+ Perform Config Search V2
+ `PAN Api docs `_
+ """
+ # if time_range is None:
+ # time_range = dict(type="relative", value=dict(unit="hour", amount=24))
+ body_params = dict(query=query, limit=limit, startTime=start_time,
+ withResourceJson=with_resource_json, # timeRange=time_range,
+ skipResult=skip_results,
+ sort=sort,
+ nextPageToken=next_page_token)
+ for k, v in dict(body_params).items():
+ if v is None:
+ del body_params[k]
+ next_page_token = None
+ api_response = self.execute('POST', f'search/api/v2/config', body_params=body_params)
+ if 'items' in api_response:
+ yield from api_response['items']
+ next_page_token = api_response.pop('nextPageToken', None)
+ while paginate and next_page_token:
+ body_params['nextPageToken'] = next_page_token
+ api_response = self.execute('POST', f'search/api/v2/config', body_params=body_params)
+ if 'items' in api_response:
+ yield from api_response['items']
+ next_page_token = api_response.pop('nextPageToken', None)
+ return
+
+ def search_network_read(self, search_params, filtered=False):
+ search_url = 'search'
+ if filtered:
+ search_url = 'search/filtered'
+ return self.execute('POST', search_url, body_params=search_params)
+
+ def search_event_read(self, search_params, subsearch=None):
+ result = []
+ next_page_token = None
+ search_url = 'search/event'
+ if subsearch and subsearch in ['aggregate', 'filtered']:
+ search_url = 'search/event/%s' % subsearch
+ api_response = self.execute_paginated(
+ 'POST', search_url, body_params=search_params)
+ if 'data' in api_response and 'items' in api_response['data']:
+ result = api_response['data']['items']
+ next_page_token = api_response['data'].pop('nextPageToken', None)
+ while next_page_token:
+ api_response = self.execute_paginated(
+ 'POST', 'search/config/page', body_params={'limit': 1000, 'pageToken': next_page_token})
+ if 'items' in api_response:
+ result.extend(api_response['items'])
+ next_page_token = api_response.pop('nextPageToken', None)
+ return result
+
+ def search_iam_read(self, search_params):
+ result = []
+ next_page_token = None
+ api_response = self.execute_paginated(
+ 'POST', 'api/v1/permission', body_params=search_params)
+ if 'data' in api_response and 'items' in api_response['data']:
+ result = api_response['data']['items']
+ next_page_token = api_response['data'].pop('nextPageToken', None)
+ while next_page_token:
+ api_response = self.execute_paginated(
+ 'POST', 'api/v1/permission/page',
+ body_params={'limit': 1000, 'pageToken': next_page_token, 'withResourceJson': 'true'})
+ if 'items' in api_response:
+ result.extend(api_response['items'])
+ next_page_token = api_response.pop('nextPageToken', None)
+ return result
+
+ def search_iam_source_to_granter(self, search_params):
+ search_url = 'api/v1/permission/graph/source_to_granter'
+ return self.execute('POST', search_url, body_params=search_params)
+
+ def search_iam_granter_to_dest(self, search_params):
+ search_url = 'api/v1/permission/graph/granter_to_dest'
+ return self.execute('POST', search_url, body_params=search_params)
+
+ def search_suggest_list_read(self, query_to_suggest):
+ return self.execute('POST', 'search/suggest', body_params=query_to_suggest)
+
+ def get_permissions_v4(self, query, limit=100, search_id=None, groupByFields=None, paginate=True):
+ """
+ Returns permissions grouped by requested fields and a page token for the next page if applicable.
+
+ `PAN Api docs `_
+ """
+ body_params = dict(query=query)
+ if search_id:
+ body_params['search_id'] = search_id
+ if groupByFields:
+ body_params['groupByFields'] = groupByFields
+ #
+ next_page_token = None
+ api_response = self.execute('POST', 'iam/api/v4/search/permission', query_params=dict(limit=limit), body_params=body_params)
+ if 'data' in api_response and 'items' in api_response['data']:
+ yield from api_response['data']['items']
+ next_page_token = api_response['data'].pop('nextPageToken', None)
+ while paginate and next_page_token:
+ body_params['nextPageToken'] = next_page_token
+ api_response = self.execute('POST', 'iam/api/v4/search/permission', query_params=dict(limit=limit), body_params=body_params)
+ if 'items' in api_response['data']:
+ yield from api_response['data']['items']
+ next_page_token = api_response.pop('nextPageToken', None)
+
+
+ """
+ Configuration
+
+ [ ] LIST
+ [ ] CREATE
+ [x] READ
+ [ ] UPDATE
+ [ ] DELETE
+ """
+
+ def compute_config(self):
+ return self.execute('GET', 'compute/config')
+
+ def meta_info(self):
+ return self.execute('GET', 'meta_info')
+
+ """
+ Usage
+
+ [ ] LIST
+ [ ] CREATE
+ [x] READ
+ [ ] UPDATE
+ [ ] DELETE
+ """
+
+ def resource_usage_by_cloud_type(self, body_params):
+ return self.execute('POST', 'license/api/v1/usage', body_params=body_params)
+
+ def resource_usage_over_time(self, body_params):
+ return self.execute('POST', 'license/api/v1/usage/time_series', body_params=body_params)
+
+ def resource_usage_by_cloud_type_v2(self, body_params):
+ return self.execute('POST', 'license/api/v2/usage', body_params=body_params)
+
+ def resource_usage_over_time_v2(self, body_params):
+ return self.execute('POST', 'license/api/v2/time_series', body_params=body_params)
+
+ """
+ SSO SAML
+
+ [X] LIST
+ [X] CREATE
+ [X] READ
+ [X] UPDATE
+ [ ] DELETE
+ """
+
+ def saml_config_read(self):
+ return self.execute('GET', 'authn/v1/saml/config')
+
+ def saml_config_create(self, body_params):
+ return self.execute('POST', 'authn/v1/saml/config', body_params=body_params)
+
+ def saml_config_update(self, body_params):
+ return self.execute('PUT', 'authn/v1/saml/config', body_params=body_params)
+
+ def oidc_config_read(self):
+ """
+ Get OIDC Configuration
+ `PAN Api docs `_
+ """
+ return self.execute('GET', 'authn/v1/oauth2/config')
+
+ """
+ Permission groups
+ """
+
+ def permission_group_list(self):
+ """
+ Get All Permission Groups
+ `PAN Api docs `_
+ """
+ return self.execute('GET', 'authz/v1/permission_group')
+
+ def permission_group_get(self, group_id, include_associated_roles: bool = None):
+ """
+ Get Permission Group by ID
+ `PAN Api docs `_
+ """
+ query_params = dict()
+ if include_associated_roles:
+ query_params = dict(includeAssociatedRoles=include_associated_roles)
+ return self.execute('GET', f'authz/v1/permission_group/{group_id}', query_params=query_params)
+
+ def permission_group_feature_list(self):
+ """
+ Get All Active Features
+ `PAN Api docs `_
+ """
+ return self.execute('GET', 'authz/v1/feature')
+
+ """
+ Enterprise Settings
+
+ [ ] LIST
+ [ ] CREATE
+ [X] READ
+ [X] UPDATE
+ [ ] DELETE
+ """
+
+ def enterprise_settings_config(self, body_params):
+ return self.execute('POST', 'settings/enterprise', body_params=body_params)
+
+ def enterprise_settings(self):
+ return self.execute('GET', 'settings/enterprise')
+
+ """
+ Anomaly Settings
+
+ [ ] LIST
+ [ ] CREATE
+ [ ] READ
+ [X] UPDATE
+ [ ] DELETE
+ """
+
+ def anomaly_settings_config(self, body_params, policy_id):
+ anomaly_url = 'anomalies/settings/%s' % policy_id
+ return self.execute('POST', anomaly_url, body_params=body_params)
+
+ """
+ Check the other side
+
+ [ ] LIST
+ [ ] CREATE
+ [X] READ
+ [X] UPDATE
+ [ ] DELETE
+ """
+
+ def check(self):
+ return self.execute('GET', 'check')
+
+ """
+ Notifications Templates
+ """
+
+ def templates_list(self):
+ """
+ List Templates
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'api/v1/tenant/{self.tenant_id}/template')
+
+ def templates_get(self, template_id):
+ """
+ Get Template
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'api/v1/tenant/{self.tenant_id}/template/{template_id}')
+
+ """
+ Cloud Ingested Logs
+ """
+
+ def aws_eventbridge_configuration_for_account(self, tenant_id, account_id):
+ """
+ Get AWS Eventbridge configuration details
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'audit_logs/v2/tenant/{tenant_id}/aws_accounts/{account_id}/eventbridge_config')
+
+ def aws_eventbridge_configuration_for_account(self, account_id):
+ """
+ Fetch AWS S3 Flow Log details
+ `PAN Api docs `_
+ """
+ return self.execute('GET',
+ f'cloud-accounts-manager/v1/cloud-accounts/aws/{account_id}/features/aws-flow-logs/s3')
+
+ """
+ CSPM collections
+ """
+
+ def cspm_collections_list_read(self):
+ """
+ Get all collections.
+
+ Note this is different from CWP Collections
+ `PAN Api docs `_
+ """
+ query_params = dict()
+ # can't use paginated, data in data['value'] instead of 'items'. nextPageToken if more than X
+ result = self.execute('GET', f'entitlement/api/v1/collection', query_params=query_params)
+ while True:
+ yield from result['value']
+ if result['nextPageToken']:
+ query_params = dict(nextPageToken=result['nextPageToken'])
+ result = self.execute('GET', f'entitlement/api/v1/collection', query_params=query_params)
+ else:
+ break
+ return
+
+ def cspm_collections_get(self, collection_id):
+ """
+ Get collection by id.
+
+ `PAN Api docs `_
+ """
+ return self.execute('GET', f'entitlement/api/v1/collection/{collection_id}')
+
+ def cspm_collections_create(self, name, description, account_id_list: list = None,
+ account_group_id_list: list = None, repository_id_list: list = None):
+ """
+ Create a Collection.
+
+ `PAN Api docs `_
+ """
+ body_params=dict(name=name, description=description)
+ if account_id_list or account_group_id_list or repository_id_list:
+ body_params['assetGroups'] = dict()
+ if account_id_list:
+ body_params['assetGroups']['accountIds'] = account_id_list
+ if account_group_id_list:
+ body_params['assetGroups']['accountGroupIds'] = account_group_id_list
+ if repository_id_list:
+ body_params['assetGroups']['repositoryIds'] = repository_id_list
+ return self.execute('POST', 'entitlement/api/v1/collection', body_params=body_params)
+
+ def cspm_collections_update(self, collection_id, name=None, description=None, account_id_list: list = None,
+ account_group_id_list: list = None, repository_id_list: list = None):
+ """
+ Update a Collection.
+
+ `PAN Api docs `_
+ """
+ body_params=dict()
+ if name:
+ body_params['name'] = name
+ if description:
+ body_params['description'] = description
+ if account_id_list or account_group_id_list or repository_id_list:
+ body_params['assetGroups'] = dict()
+ if account_id_list:
+ body_params['assetGroups']['accountIds'] = account_id_list
+ if account_group_id_list:
+ body_params['assetGroups']['accountGroupIds'] = account_group_id_list
+ if repository_id_list:
+ body_params['assetGroups']['repositoryIds'] = repository_id_list
+ return self.execute('PUT', f'entitlement/api/v1/collection/{collection_id}', body_params=body_params)
+
+ def cspm_collections_delete(self, collection_id):
+ """
+ Delete a Collection.
+
+ `PAN Api docs `_
+ """
+ return self.execute('DELETE', f'entitlement/api/v1/collection/{collection_id}')
\ No newline at end of file
diff --git a/prismacloud/api/cspm/_extended.py b/prismacloudapi/cspm/_extended.py
similarity index 100%
rename from prismacloud/api/cspm/_extended.py
rename to prismacloudapi/cspm/_extended.py
diff --git a/prismacloudapi/cspm/cspm.py b/prismacloudapi/cspm/cspm.py
new file mode 100644
index 0000000..fcf8c47
--- /dev/null
+++ b/prismacloudapi/cspm/cspm.py
@@ -0,0 +1,172 @@
+""" Requests and Output """
+
+import json
+import logging
+import time
+
+import requests
+
+class PrismaCloudAPIMixin():
+ """ Requests and Output """
+
+ def suppress_warnings_when_verify_false(self):
+ if self.verify is False:
+ # Pylint Issue #4584
+ # pylint: disable=no-member
+ requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
+
+ def login(self, url=None):
+ self.suppress_warnings_when_verify_false()
+ if not url:
+ # CSPM
+ url = f'https://{self.api}/login'
+ # remove previous tokens
+ if 'x-redlock-auth' in self.session.headers:
+ del self.session.headers['x-redlock-auth']
+ body_params_json = json.dumps({'username': self.identity, 'password': self.secret})
+ api_response = self.session.post(url, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ if api_response.ok:
+ api_response = api_response.json()
+ self.token = api_response.get('token')
+ self.token_timer = time.time()
+ self.session.headers['x-redlock-auth'] = self.token
+ # save tenant_id
+ self.tenant_id = api_response['customerNames'][0]['prismaId']
+ else:
+ self.error_and_raise(api_response.status_code, 'API (%s) responded with an error\n%s' % (url, api_response.text))
+ self.debug_print('New API Token: %s' % self.token)
+
+ def extend_login(self):
+ self.suppress_warnings_when_verify_false()
+ self.debug_print('Extending CSPM API Token')
+ url = f'https://{self.api}/auth_token/extend'
+ api_response = self.session.get(url, verify=self.verify, timeout=self.timeout)
+ if api_response.ok:
+ api_response = api_response.json()
+ self.token = api_response.get('token')
+ self.token_timer = time.time()
+ self.session.headers['x-redlock-auth'] = self.token
+ else:
+ logging.warning(f'HTTP error code {api_response.status_code} - API ({url}) responded with an error - lets try to login again\n {api_response.text}')
+ # try to login again
+ self.login()
+
+ # pylint: disable=too-many-arguments, too-many-branches, too-many-locals
+ def execute(self, action, endpoint, query_params=None, body_params=None, request_headers=None, force=False):
+ self.suppress_warnings_when_verify_false()
+ if not self.token:
+ self.login()
+ if int(time.time() - self.token_timer) > self.token_limit:
+ self.extend_login()
+ # Endpoints that return large numbers of results use a 'nextPageToken' (and a 'totalRows') key.
+ # Pagination appears to be specific to "List Alerts V2 - POST" and the limit has a maximum of 10000.
+ url = f'https://{self.api}/{endpoint}'
+ if body_params:
+ body_params_json = json.dumps(body_params)
+ else:
+ body_params_json = None
+ self.debug_print('API URL: %s' % url)
+ self.debug_print('API Request Headers: (%s)' % request_headers)
+ self.debug_print('API Query Params: %s' % query_params)
+ self.debug_print('API Body Params: %s' % body_params_json)
+ api_response = self.session.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ self.debug_print('API Response Status Code: %s' % api_response.status_code)
+ self.debug_print('API Response Headers: (%s)' % api_response.headers)
+ if api_response.ok:
+ if not api_response.content:
+ return None
+ if api_response.headers.get('Content-Type') == 'application/x-gzip':
+ return api_response.content
+ if api_response.headers.get('Content-Type') == 'text/csv':
+ return api_response.content.decode('utf-8')
+ try:
+ result = api_response.json()
+ except ValueError:
+ self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ if force:
+ return None
+ self.error_and_raise(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ return result
+ else:
+ self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
+ self.error_and_raise(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
+ return None
+
+ def execute_paginated(self, action, endpoint, query_params=None, body_params=None, request_headers=None):
+ self.suppress_warnings_when_verify_false()
+ if not self.token:
+ self.login()
+ if int(time.time() - self.token_timer) > self.token_limit:
+ self.extend_login()
+ # Endpoints that return large numbers of results use a 'nextPageToken' (and a 'totalRows') key.
+ # Pagination appears to be specific to "List Alerts V2 - POST" and the limit has a maximum of 10000.
+ returned_count = 0
+ more = True
+ while more is True:
+ if int(time.time() - self.token_timer) > self.token_limit:
+ self.extend_login()
+ url = f'https://{self.api}/{endpoint}'
+ if body_params:
+ body_params_json = json.dumps(body_params)
+ else:
+ body_params_json = None
+ self.debug_print('API URL: %s' % url)
+ self.debug_print('API Request Headers: (%s)' % request_headers)
+ self.debug_print('API Query Params: %s' % query_params)
+ self.debug_print('API Body Params: %s' % body_params_json)
+ api_response = self.session.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ self.debug_print('API Response Status Code: %s' % api_response.status_code)
+ self.debug_print('API Response Headers: (%s)' % api_response.headers)
+ if api_response.ok:
+ if not api_response.content:
+ return
+ if api_response.headers.get('Content-Type') == 'application/x-gzip':
+ # return api_response.content
+ raise RuntimeError("please use .execute instead of execute_paginated")
+ if api_response.headers.get('Content-Type') == 'text/csv':
+ #return api_response.content.decode('utf-8')
+ raise RuntimeError("please use .execute instead of execute_paginated")
+ try:
+ result = api_response.json()
+ except ValueError:
+ self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ self.error_and_raise(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ return
+ if 'totalRows' in result:
+ total_count = int(result['totalRows'])
+ self.debug_print(f'Retrieved Next Page of Results: Offset/Total Count: {returned_count}/{total_count}')
+ returned_count += len(result['items'])
+ #
+ yield from result['items']
+ if 'nextPageToken' in result and result['nextPageToken']:
+ self.debug_print('Retrieving Next Page of Results')
+ body_params = {'pageToken': result['nextPageToken']}
+ more = True
+ else:
+ more = False
+ else:
+ self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
+ self.error_and_raise(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
+ return
+ # Exit handler (Error).
+
+ @classmethod
+ def error_and_exit(cls, error_code, error_message='', system_message=''):
+ raise SystemExit('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message))
+
+ @classmethod
+ def error_and_raise(cls, error_code, error_message='', system_message=''):
+ raise RuntimeError('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message))
+
+ # Output counted errors.
+
+ def error_report(self):
+ if self.logger.error.counter > 0:
+ print('API responded with (%s) error(s): details logged to: (%s)' % (self.logger.error.counter, self.error_log))
+
+ # Optionally output progress.
+
+ @classmethod
+ def progress(cls, txt=None):
+ if txt:
+ print(txt)
diff --git a/prismacloud/api/cwpp/README.md b/prismacloudapi/cwpp/README.md
similarity index 100%
rename from prismacloud/api/cwpp/README.md
rename to prismacloudapi/cwpp/README.md
diff --git a/prismacloud/api/cwpp/__init__.py b/prismacloudapi/cwpp/__init__.py
similarity index 100%
rename from prismacloud/api/cwpp/__init__.py
rename to prismacloudapi/cwpp/__init__.py
diff --git a/prismacloud/api/cwpp/_audits.py b/prismacloudapi/cwpp/_audits.py
similarity index 86%
rename from prismacloud/api/cwpp/_audits.py
rename to prismacloudapi/cwpp/_audits.py
index 49af2ab..c7f2ed3 100644
--- a/prismacloud/api/cwpp/_audits.py
+++ b/prismacloudapi/cwpp/_audits.py
@@ -8,7 +8,7 @@ class AuditsPrismaCloudAPICWPPMixin:
# Reference: https://prisma.pan.dev/api/cloud/cwpp/audits
def audits_list_read(self, audit_type='incidents', query_params=None):
- audits = self.execute_compute('GET', 'api/v1/audits/%s' % audit_type, query_params=query_params, paginated=True)
+ audits = self.execute_compute_paginated('GET', 'api/v1/audits/%s' % audit_type, query_params=query_params)
return audits
# Other related and undocumented endpoints.
@@ -22,7 +22,7 @@ def forensic_read(self, workload_id, workload_type, defender_hostname):
elif workload_type == 'host':
response = self.execute_compute('GET', 'api/v1/profiles/%s/%s/forensic/download' % (workload_type, workload_id), query_params=query_params)
else:
- response = self.execute_compute('GET', 'api/v1/profiles/%s/%s/forensic' % (workload_type, workload_id), query_params=query_params, paginated=True)
+ response = self.execute_compute_paginated('GET', 'api/v1/profiles/%s/%s/forensic' % (workload_type, workload_id), query_params=query_params)
return response
# Monitor /Â Runtime > Incident Explorer
@@ -63,7 +63,7 @@ def compute_audit_types():
# Hosts > Host Activities
def host_forensic_activities_list_read(self, query_params=None):
- audits = self.execute_compute('GET', 'api/v1/forensic/activities', query_params=query_params, paginated=True)
+ audits = self.execute_compute_paginated('GET', 'api/v1/forensic/activities', query_params=query_params)
return audits
# Compute > Manage > History
diff --git a/prismacloudapi/cwpp/_cloud.py b/prismacloudapi/cwpp/_cloud.py
new file mode 100644
index 0000000..48dd3ab
--- /dev/null
+++ b/prismacloudapi/cwpp/_cloud.py
@@ -0,0 +1,64 @@
+""" Prisma Compute API Cloud Endpoints Class """
+
+
+# Cloud
+
+class CloudPrismaCloudAPICWPPMixin:
+ """ Prisma Cloud Compute API Cloud Endpoints Class """
+
+ def cloud_discovery_read(self, sort=None, reverse=None,
+ provider=None, credential_id=None, service_type=None, registry=None, account_name=None,
+ agentless=None, zone=None,
+ ):
+ """
+ Returns a list of all cloud discovery scan results in a paginated response.
+ `PAN Api docs `_
+ """
+ query_params = dict(sort=sort, reverse=reverse,
+ provider=provider, credentialID=credential_id, serviceType=service_type, registry=registry,
+ accountName=account_name, agentless=agentless,
+ zone=zone,
+ )
+ for k, v in dict(query_params).items():
+ if v is None:
+ del query_params[k]
+ elif isinstance(v, list):
+ query_params[k] = ','.join(v)
+ return self.execute_compute_paginated('GET', 'api/v1/cloud/discovery', query_params=query_params)
+
+ def cloud_discovery_download(self, query_params=None):
+ # request_headers = {'Content-Type': 'text/csv'}
+ # return self.execute_compute('GET', 'api/v1/cloud/discovery/download?', request_headers=request_headers, query_params=query_params)
+ return self.execute_compute('GET', 'api/v1/cloud/discovery/download', query_params=query_params)
+
+ def cloud_discovery_scan(self):
+ return self.execute_compute('POST', 'api/v1/cloud/discovery/scan')
+
+ def cloud_discovery_scan_stop(self):
+ return self.execute_compute('POST', 'api/v1/cloud/discovery/stop')
+
+ def cloud_discovery_vms(self, query_params=None):
+ return self.execute_compute_paginated('GET', 'api/v1/cloud/discovery/vms', query_params=query_params)
+
+ def cloud_discovery_entities(self, query_params=None):
+ return self.execute_compute_paginated('GET', 'api/v1/cloud/discovery/entities', query_params=query_params)
+
+ def cloud_discovery_entities2(self, sort=None, reverse=None, credential_id=None, service_type=None, registry=None,
+ zone=None, defended=None, images=None,
+ ):
+ """
+ Returns a list of discovered cloud entities.
+ `PAN Api docs `_
+ """
+ query_params = dict(
+ sort=sort, reverse=reverse,
+ credentialID=credential_id, serviceType=service_type, registry=registry,
+ zone=zone,
+ defended=defended, images=images
+ )
+ for k, v in dict(query_params).items():
+ if v is None:
+ del query_params[k]
+ elif isinstance(v, list):
+ query_params[k] = ','.join(v)
+ return self.execute_compute_paginated('GET', 'api/v1/cloud/discovery/entities', query_params=query_params)
diff --git a/prismacloud/api/cwpp/_collections.py b/prismacloudapi/cwpp/_collections.py
similarity index 81%
rename from prismacloud/api/cwpp/_collections.py
rename to prismacloudapi/cwpp/_collections.py
index 8847c87..a262a57 100644
--- a/prismacloud/api/cwpp/_collections.py
+++ b/prismacloudapi/cwpp/_collections.py
@@ -5,11 +5,12 @@
class CollectionsPrismaCloudAPICWPPMixin:
""" Prisma Cloud Compute API Collections Endpoints Class """
- def collections_list_read(self, query_params=None):
- return self.execute_compute('GET', 'api/v1/collections', query_params=query_params, paginated=True)
+ def collections_list_read(self, **kwargs):
+ query_params=kwargs
+ return self.execute_compute('GET', 'api/v1/collections', query_params=query_params)
def collection_usages(self, collection_id):
- return self.execute_compute('GET', 'api/v1/collections/%s/usages' % collection_id, paginated=True)
+ return self.execute_compute_paginated('GET', 'api/v1/collections/%s/usages' % collection_id)
# Note: No response is returned upon successful execution of POST, PUT, and DELETE.
# You must verify the collection via collections_list_read() or the Console.
diff --git a/prismacloudapi/cwpp/_containers.py b/prismacloudapi/cwpp/_containers.py
new file mode 100644
index 0000000..f46d1d0
--- /dev/null
+++ b/prismacloudapi/cwpp/_containers.py
@@ -0,0 +1,17 @@
+""" Prisma Cloud Compute API Containers Endpoints Class """
+
+# Containers
+
+class ContainersPrismaCloudAPICWPPMixin:
+ """ Prisma Cloud Compute API Containers Endpoints Class """
+
+ def containers_list_read(self, image_id=None, query_params=None):
+ if image_id:
+ containers = self.execute_compute_paginated('GET', 'api/v1/containers?imageId=%s' % image_id, query_params=query_params)
+ else:
+ containers = self.execute_compute_paginated('GET', 'api/v1/containers?', query_params=query_params)
+ return containers
+
+ def containers_download(self, query_params=None):
+ containers = self.execute_compute('GET', 'api/v1/containers/download?', query_params=query_params)
+ return containers
diff --git a/prismacloud/api/cwpp/_credentials.py b/prismacloudapi/cwpp/_credentials.py
similarity index 100%
rename from prismacloud/api/cwpp/_credentials.py
rename to prismacloudapi/cwpp/_credentials.py
diff --git a/prismacloud/api/cwpp/_defenders.py b/prismacloudapi/cwpp/_defenders.py
similarity index 54%
rename from prismacloud/api/cwpp/_defenders.py
rename to prismacloudapi/cwpp/_defenders.py
index 5d5f47e..cb465c5 100644
--- a/prismacloud/api/cwpp/_defenders.py
+++ b/prismacloudapi/cwpp/_defenders.py
@@ -6,9 +6,13 @@ class DefendersPrismaCloudAPICWPPMixin:
""" Prisma Cloud Compute API Defenders Endpoints Class """
def defenders_list_read(self, query_params=None):
- defenders = self.execute_compute('GET', 'api/v1/defenders', query_params=query_params, paginated=True)
+ defenders = self.execute_compute_paginated('GET', 'api/v1/defenders', query_params=query_params)
return defenders
def defenders_names_list_read(self, query_params=None):
- defenders = self.execute_compute('GET', 'api/v1/defenders/names', query_params=query_params, paginated=True)
+ defenders = self.execute_compute_paginated('GET', 'api/v1/defenders/names', query_params=query_params)
+ return defenders
+
+ def defenders_download(self, query_params=None):
+ defenders = self.execute_compute('GET', 'api/v1/defenders/download?', query_params=query_params)
return defenders
diff --git a/prismacloud/api/cwpp/_feeds.py b/prismacloudapi/cwpp/_feeds.py
similarity index 100%
rename from prismacloud/api/cwpp/_feeds.py
rename to prismacloudapi/cwpp/_feeds.py
diff --git a/prismacloud/api/cwpp/_hosts.py b/prismacloudapi/cwpp/_hosts.py
similarity index 76%
rename from prismacloud/api/cwpp/_hosts.py
rename to prismacloudapi/cwpp/_hosts.py
index 6893cfe..39439ff 100644
--- a/prismacloud/api/cwpp/_hosts.py
+++ b/prismacloudapi/cwpp/_hosts.py
@@ -7,11 +7,11 @@ class HostsPrismaCloudAPICWPPMixin:
# Running hosts table in Monitor > Vulnerabilities > Hosts > Running Hosts
def hosts_list_read(self, query_params=None):
- hosts = self.execute_compute('GET', 'api/v1/hosts', query_params=query_params, paginated=True)
+ hosts = self.execute_compute_paginated('GET', 'api/v1/hosts', query_params=query_params)
return hosts
def hosts_info_list_read(self, query_params=None):
- hosts = self.execute_compute('GET', 'api/v1/hosts/info', query_params=query_params, paginated=True)
+ hosts = self.execute_compute_paginated('GET', 'api/v1/hosts/info', query_params=query_params)
return hosts
def hosts_download(self, query_params=None):
diff --git a/prismacloud/api/cwpp/_images.py b/prismacloudapi/cwpp/_images.py
similarity index 56%
rename from prismacloud/api/cwpp/_images.py
rename to prismacloudapi/cwpp/_images.py
index d5a612c..8ea731c 100644
--- a/prismacloud/api/cwpp/_images.py
+++ b/prismacloudapi/cwpp/_images.py
@@ -5,11 +5,11 @@
class ImagesPrismaCloudAPICWPPMixin:
""" Prisma Cloud Compute API Images Endpoints Class """
- def images_list_read(self, image_id=None, query_params=None):
- if image_id:
- images = self.execute_compute('GET', 'api/v1/images?id=%s' % image_id, query_params=query_params)
- else:
- images = self.execute_compute('GET', 'api/v1/images?', query_params=query_params, paginated=True)
+ def images_get_read(self, image_id, query_params=None):
+ return self.execute_compute('GET', 'api/v1/images?id=%s' % image_id, query_params=query_params)
+
+ def images_list_read(self, query_params=None):
+ images = self.execute_compute_paginated('GET', 'api/v1/images?', query_params=query_params)
return images
def images_download(self, query_params=None):
diff --git a/prismacloud/api/cwpp/_logs.py b/prismacloudapi/cwpp/_logs.py
similarity index 100%
rename from prismacloud/api/cwpp/_logs.py
rename to prismacloudapi/cwpp/_logs.py
diff --git a/prismacloud/api/cwpp/_policies.py b/prismacloudapi/cwpp/_policies.py
similarity index 100%
rename from prismacloud/api/cwpp/_policies.py
rename to prismacloudapi/cwpp/_policies.py
diff --git a/prismacloud/api/cwpp/_registry.py b/prismacloudapi/cwpp/_registry.py
similarity index 62%
rename from prismacloud/api/cwpp/_registry.py
rename to prismacloudapi/cwpp/_registry.py
index a9563aa..3ac8591 100644
--- a/prismacloud/api/cwpp/_registry.py
+++ b/prismacloudapi/cwpp/_registry.py
@@ -5,11 +5,15 @@
class RegistryPrismaCloudAPICWPPMixin:
""" Prisma Cloud Compute API Images Endpoints Class """
- def registry_list_read(self, image_id=None):
- if image_id:
- images = self.execute_compute('GET', 'api/v1/registry?id=%s&filterBaseImage=true' % image_id)
- else:
- images = self.execute_compute('GET', 'api/v1/registry?filterBaseImage=true', paginated=True)
+ def registry_download(self, query_params=None):
+ registries = self.execute_compute('GET', 'api/v1/registry/download?', query_params=query_params)
+ return registries
+
+ def registry_get_read(self, image_id):
+ return self.execute_compute('GET', 'api/v1/registry?id=%s&filterBaseImage=true' % image_id)
+
+ def registry_list_read(self):
+ images = self.execute_compute_paginated('GET', 'api/v1/registry?filterBaseImage=true')
return images
def registry_list_image_names(self, query_params=None):
diff --git a/prismacloudapi/cwpp/_scans.py b/prismacloudapi/cwpp/_scans.py
new file mode 100644
index 0000000..b3a1d0c
--- /dev/null
+++ b/prismacloudapi/cwpp/_scans.py
@@ -0,0 +1,17 @@
+""" Prisma Cloud Compute API Scans Endpoints Class """
+
+# Scans (Monitor > Vulnerabilities/Compliance > Images > CI)
+
+class ScansPrismaCloudAPICWPPMixin:
+ """ Prisma Cloud Compute API Scans Endpoints Class """
+
+ def scans_get(self, image_id):
+ return self.execute_compute('GET', 'api/v1/scans?imageID=%s&filterBaseImage=true' % image_id)
+
+ def scans_list_read(self):
+ images = self.execute_compute_paginated('GET', 'api/v1/scans?filterBaseImage=true')
+ return images
+
+ def scans_download(self, query_params=None):
+ scans = self.execute_compute('GET', 'api/v1/scans/download?', query_params=query_params)
+ return scans
diff --git a/prismacloud/api/cwpp/_serverless.py b/prismacloudapi/cwpp/_serverless.py
similarity index 59%
rename from prismacloud/api/cwpp/_serverless.py
rename to prismacloudapi/cwpp/_serverless.py
index eb4acb3..a1c60e1 100644
--- a/prismacloud/api/cwpp/_serverless.py
+++ b/prismacloudapi/cwpp/_serverless.py
@@ -3,14 +3,18 @@ class ServerlessPrismaCloudAPICWPPMixin:
# Get serverless function scan results
def serverless_list_read(self, query_params=None):
- result = self.execute_compute('GET', 'api/v1/serverless', query_params=query_params, paginated=True)
- return result
-
+ return self.execute_compute_paginated('GET', 'api/v1/serverless', query_params=query_params)
+
# Download serverless function scan results
def serverless_download(self, query_params=None):
- result = self.execute_compute('GET', 'api/v1/serverless/download?', query_params=query_params)
- return result
-
+ return self.execute_compute('GET', 'api/v1/serverless/download', query_params=query_params)
+
+ def serverless_get_function_names(self, query_params=None):
+ """
+ Get Serverless Function Names
+ """
+ return self.execute_compute_paginated('GET', 'api/v1/serverless/names', query_params=query_params)
+
# Start serverless function scan
def serverless_start_scan(self):
result = self.execute_compute('POST', 'api/v1/serverless/scan')
diff --git a/prismacloud/api/cwpp/_settings.py b/prismacloudapi/cwpp/_settings.py
similarity index 100%
rename from prismacloud/api/cwpp/_settings.py
rename to prismacloudapi/cwpp/_settings.py
diff --git a/prismacloud/api/cwpp/_stats.py b/prismacloudapi/cwpp/_stats.py
similarity index 82%
rename from prismacloud/api/cwpp/_stats.py
rename to prismacloudapi/cwpp/_stats.py
index 5353aab..f44bcc2 100644
--- a/prismacloud/api/cwpp/_stats.py
+++ b/prismacloudapi/cwpp/_stats.py
@@ -22,7 +22,7 @@ def stats_compliance_refresh(self, query_params=None):
def stats_daily_read(self):
# Returns a historical list of per-day statistics for the resources protected by Prisma Cloud Compute,
# including the total number of runtime audits, image vulnerabilities, and compliance violations.
- return self.execute_compute('GET', 'api/v1/stats/daily', paginated=True)
+ return self.execute_compute('GET', 'api/v1/stats/daily')
def stats_trends_read(self):
# Returns statistics about the resources protected by Prisma Cloud Compute,
@@ -38,19 +38,23 @@ def stats_license_read(self):
def stats_vulnerabilities_read(self, query_params=None):
# Returns a list of vulnerabilities (CVEs) in the deployed images, registry images, hosts, and serverless functions affecting your environment.
- return self.execute_compute('GET', 'api/v1/stats/vulnerabilities?', query_params=query_params, paginated=True)
+ # 2025-03 doesn't really use pagination.
+ return self.execute_compute('GET', 'api/v1/stats/vulnerabilities', query_params=query_params)
def stats_vulnerabilities_download(self, query_params=None):
- return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/download?', query_params=query_params)
+ return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/download', query_params=query_params)
- def stats_vulnerabilities_impacted_resoures_read(self, query_params=None):
+ def stats_vulnerabilities_impacted_resources_read(self, query_params=None):
# Generates a list of impacted resources for a specific vulnerability. This endpoint returns a list of all deployed images, registry images, hosts, and serverless functions affected by a given CVE.
- return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/impacted-resources?', query_params=query_params)
+ return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/impacted-resources', query_params=query_params)
- def stats_vulnerabilities_impacted_resoures_download(self, query_params=None):
- return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/impacted-resources/download?', query_params=query_params)
+ def stats_vulnerabilities_impacted_resources_download(self, query_params=None):
+ return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/impacted-resources/download', query_params=query_params)
def stats_vulnerabilities_refresh(self, query_params=None):
# Refreshes the current day's CVE counts and CVE list, as well as their descriptions.
# This endpoint returns the same response as /api/v1/stats/vulnerabilities, but with updated data for the current day.
- return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/refresh?', query_params=query_params, paginated=True)
+ return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/refresh', query_params=query_params)
+
+ def stats_assets_summary(self):
+ return self.execute_compute('GET', 'api/v1/bff/assets/summary')
diff --git a/prismacloud/api/cwpp/_status.py b/prismacloudapi/cwpp/_status.py
similarity index 100%
rename from prismacloud/api/cwpp/_status.py
rename to prismacloudapi/cwpp/_status.py
diff --git a/prismacloud/api/cwpp/_tags.py b/prismacloudapi/cwpp/_tags.py
similarity index 100%
rename from prismacloud/api/cwpp/_tags.py
rename to prismacloudapi/cwpp/_tags.py
diff --git a/prismacloud/api/cwpp/_vms.py b/prismacloudapi/cwpp/_vms.py
similarity index 72%
rename from prismacloud/api/cwpp/_vms.py
rename to prismacloudapi/cwpp/_vms.py
index e82d8d4..f642973 100644
--- a/prismacloud/api/cwpp/_vms.py
+++ b/prismacloudapi/cwpp/_vms.py
@@ -8,6 +8,5 @@ class VMsPrismaCloudAPICWPPMixin:
# VM Image table in Monitor > Vulnerabilities > Hosts > VMs
def vms_list_read(self, query_params=None):
- vms = self.execute_compute(
- 'GET', 'api/v1/vms', query_params=query_params, paginated=True)
+ vms = self.execute_compute_paginated('GET', 'api/v1/vms', query_params=query_params)
return vms
\ No newline at end of file
diff --git a/prismacloudapi/cwpp/cwpp.py b/prismacloudapi/cwpp/cwpp.py
new file mode 100644
index 0000000..23255a1
--- /dev/null
+++ b/prismacloudapi/cwpp/cwpp.py
@@ -0,0 +1,160 @@
+""" Requests and Output """
+
+import json
+import time
+
+import requests
+
+class PrismaCloudAPICWPPMixin():
+ """ Requests and Output """
+
+ def login_compute(self):
+ self.suppress_warnings_when_verify_false()
+ # CWP
+ url = f'https://{self.api_compute}/api/v1/authenticate'
+ # remove previous tokens
+ if 'Authorization' in self.session_compute.headers:
+ del self.session_compute.headers['Authorization']
+ body_params_json = json.dumps({'username': self.identity, 'password': self.secret})
+ api_response = self.session_compute.post(url, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ if api_response.ok:
+ api_response = api_response.json()
+ self.token_compute = api_response.get('token')
+ self.token_compute_timer = time.time()
+ self.session_compute.headers['Authorization'] = f"Bearer {self.token_compute}"
+ else:
+ self.error_and_raise(api_response.status_code,
+ 'API (%s) responded with an error\n%s' % (url, api_response.text))
+
+ def check_extend_login_compute(self):
+ # There is no extend for CWP, just logon again.
+ if not self.token_compute or (int(time.time() - self.token_compute_timer) > self.token_limit):
+ self.token_compute = None
+ self.debug_print('Extending CWPP API Token')
+ self.login_compute()
+
+ # def _check_
+
+ # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements
+ def execute_compute(self, action, endpoint, query_params=None, body_params=None):
+ self.suppress_warnings_when_verify_false()
+ self.check_extend_login_compute()
+ if body_params:
+ body_params_json = json.dumps(body_params)
+ else:
+ body_params_json = None
+ # Endpoints that return large numbers of results use a 'Total-Count' response header.
+ # Pagination is via query parameters for both GET and POST, and the limit has a maximum of 50.
+ url = 'https://%s/%s' % (self.api_compute, endpoint)
+ self.debug_print('API URL: %s' % url)
+ self.debug_print('API Request Headers: (%s)' % self.session_compute.headers)
+ self.debug_print('API Query Params: %s' % query_params)
+ self.debug_print('API Body Params: %s' % body_params_json)
+ api_response = self.session_compute.request(action, url, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ self.debug_print('API Response Status Code: (%s)' % api_response.status_code)
+ self.debug_print('API Response Headers: (%s)' % api_response.headers)
+ # if api_response.status_code in self.retry_status_codes:
+ # for exponential_wait in self.retry_waits:
+ # time.sleep(exponential_wait)
+ # api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ # if api_response.ok:
+ # break # retry loop
+ if api_response.ok:
+ if not api_response.content:
+ return None
+ if api_response.headers.get('Content-Type') == 'application/x-gzip':
+ return api_response.content
+ if api_response.headers.get('Content-Type') == 'text/csv':
+ return api_response.content.decode('utf-8')
+ try:
+ result = api_response.json()
+ except ValueError:
+ self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ self.error_and_raise(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ return result
+ else:
+ self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
+ self.error_and_raise(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
+ return
+
+ def execute_compute_paginated(self, action, endpoint, query_params=None, body_params=None):
+ self.suppress_warnings_when_verify_false()
+ self.check_extend_login_compute()
+ if body_params:
+ body_params_json = json.dumps(body_params)
+ else:
+ body_params_json = None
+ # Endpoints that return large numbers of results use a 'Total-Count' response header.
+ # Pagination is via query parameters for both GET and POST, and the limit has a maximum of 100.
+ offset = 0
+ limit = 100
+ more = True
+ while more is True:
+ self.check_extend_login_compute()
+ url = f'https://{self.api_compute}/{endpoint}?limit={limit}&offset={offset}'
+ self.debug_print('API URL: %s' % url)
+ self.debug_print('API Request Headers: (%s)' % self.session_compute.headers)
+ self.debug_print('API Query Params: %s' % query_params)
+ self.debug_print('API Body Params: %s' % body_params_json)
+ api_response = self.session_compute.request(action, url, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ self.debug_print('API Response Status Code: (%s)' % api_response.status_code)
+ self.debug_print('API Response Headers: (%s)' % api_response.headers)
+ if api_response.ok:
+ if not api_response.content:
+ return
+ if api_response.headers.get('Content-Type') == 'application/x-gzip':
+ # return api_response.content
+ raise RuntimeError("please use .execute instead of execute_paginated")
+ if api_response.headers.get('Content-Type') == 'text/csv':
+ # return api_response.content.decode('utf-8')
+ raise RuntimeError("please use .execute instead of execute_paginated")
+ try:
+ results = api_response.json()
+ except ValueError:
+ self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ self.error_and_raise(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ if 'Total-Count' in api_response.headers:
+ total_count = int(api_response.headers['Total-Count'])
+ self.debug_print(f'Retrieving Next Page of Results: Offset/Total Count: {offset}/{total_count}')
+ else:
+ self.debug_print("No Pagination headers - please use .execute instead of execute_paginated")
+ if results:
+ yield from results
+ return
+ # raise RuntimeError("please use .execute instead of execute_paginated")
+ if not results:
+ return
+ self.debug_print(f"Got {len(results)} results")
+ if total_count > 0:
+ yield from results
+ offset += len(results)
+ more = bool(offset < total_count)
+ else:
+ self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
+ self.error_and_raise(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
+ return
+
+ # The Compute API setting is optional.
+
+ def validate_api_compute(self):
+ if not self.api_compute:
+ self.error_and_exit(500, 'Please specify a Prisma Cloud Compute API URL.')
+
+ # Exit handler (Error).
+
+ @classmethod
+ def error_and_exit(cls, error_code, error_message='', system_message=''):
+ raise SystemExit('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message))
+
+ @classmethod
+ def error_and_raise(cls, error_code, error_message='', system_message=''):
+ raise RuntimeError('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message))
+
+ # various API
+
+ def version(self):
+ return self.execute_compute('GET', 'api/v1/version')
+
+ def ping(self):
+ # unauthenticated call
+ return self.session_compute.get(f'https://{self.api_compute}/api/v1/_ping').text
diff --git a/prismacloud/api/pc_lib_api.py b/prismacloudapi/pc_lib_api.py
similarity index 66%
rename from prismacloud/api/pc_lib_api.py
rename to prismacloudapi/pc_lib_api.py
index 01289f5..ac0e614 100644
--- a/prismacloud/api/pc_lib_api.py
+++ b/prismacloudapi/pc_lib_api.py
@@ -2,12 +2,19 @@
import logging
+import requests
+from requests.adapters import HTTPAdapter, Retry
+
from .cspm import PrismaCloudAPICSPM
from .cwpp import PrismaCloudAPICWPP
from .pccs import PrismaCloudAPIPCCS
from .pc_lib_utility import PrismaCloudUtility
-from .version import version # Import version from your version.py
+
+import importlib.metadata
+version = importlib.metadata.version("prismacloudapi")
+
+
# --Description-- #
@@ -38,18 +45,33 @@ def __init__(self):
self.debug = False
#
self.timeout = None # timeout=(16, 300)
+ self.tenant_id = None
self.token = None
self.token_timer = 0
self.token_limit = 590 # aka 9 minutes
+ self.token_compute = None
+ self.token_compute_timer= 0
self.retry_status_codes = [425, 429, 500, 502, 503, 504]
self.retry_waits = [1, 2, 4, 8, 16, 32]
+ self.retry_allowed_methods = frozenset(["GET", "POST"])
self.max_workers = 8
#
self.error_log = 'error.log'
self.logger = None
# Set User-Agent
- default_user_agent = f"PrismaCloudAPI/{version}" # Dynamically set default User-Agent
- self.user_agent = default_user_agent
+ self.user_agent = f"PrismaCloudAPI/{version}" # Dynamically set default User-Agent
+ # use a session
+ self.session = requests.session()
+ self.session_compute = requests.session()
+ retries = Retry(total=6, status=6, backoff_factor=1, status_forcelist=self.retry_status_codes,
+ allowed_methods=self.retry_allowed_methods)
+ self.session_adapter = HTTPAdapter(max_retries=retries)
+ # CSPM
+ self.session.headers['User-Agent'] = self.user_agent
+ self.session.headers['Content-Type'] = 'application/json'
+ # CWP
+ self.session_compute.headers['User-Agent'] = self.user_agent
+ self.session_compute.headers['Content-Type'] = 'application/json'
def __repr__(self):
return 'Prisma Cloud API:\n API: (%s)\n Compute API: (%s)\n API Error Count: (%s)\n API Token: (%s)' % (self.api, self.api_compute, self.logger.error.counter, self.token)
@@ -76,17 +98,25 @@ def configure(self, settings, use_meta_info=True):
if url.endswith('.prismacloud.io') or url.endswith('.prismacloud.cn'):
# URL is a Prisma Cloud CSPM API URL.
self.api = url
+ self.session.mount(f"https://{url}", self.session_adapter)
+ self.debug_print(f"Mounted retry adapter on API {url}")
# Use the Prisma Cloud CSPM API to identify the Prisma Cloud CWP API URL.
if use_meta_info:
meta_info = self.meta_info()
if meta_info and 'twistlockUrl' in meta_info:
self.api_compute = PrismaCloudUtility.normalize_url(meta_info['twistlockUrl'])
+ self.session.mount(f"https://{self.api_compute}", self.session_adapter)
+ self.debug_print(f"Mounted retry adapter on API Compute {self.api_compute}")
else:
# URL is a Prisma Cloud CWP API URL.
self.api_compute = PrismaCloudUtility.normalize_url(url)
+ self.session.mount(f"https://{self.api_compute}", self.session_adapter)
+ self.debug_print(f"Mounted retry adapter on API Compute {self.api_compute}")
+ if not self.api and not self.api_compute:
+ self.error_and_exit(418, "Specify a Prisma Cloud URL or Prisma Cloud Compute URL")
# Conditional printing.
def debug_print(self, message):
if self.debug:
- print(message)
+ logging.debug(message)
diff --git a/prismacloud/api/pc_lib_utility.py b/prismacloudapi/pc_lib_utility.py
similarity index 96%
rename from prismacloud/api/pc_lib_utility.py
rename to prismacloudapi/pc_lib_utility.py
index 24441ff..4b286d9 100644
--- a/prismacloud/api/pc_lib_utility.py
+++ b/prismacloudapi/pc_lib_utility.py
@@ -8,8 +8,6 @@
import os
import sys
-from update_checker import UpdateChecker
-from .version import version as api_version
try:
# pylint: disable=redefined-builtin
@@ -38,14 +36,6 @@ class PrismaCloudUtility():
CONFIG_DIRECTORY = os.path.join(homefolder, '.prismacloud')
DEFAULT_CONFIG_FILE = os.path.join(CONFIG_DIRECTORY, 'credentials.json')
- @classmethod
- def package_version_check(cls, package_name='prismacloud-api'):
- package_version_message = 'version: %s' % api_version
- checker = UpdateChecker()
- result = checker.check(package_name, api_version)
- if result:
- package_version_message = "version update available: %s -> %s\nrun 'pip3 install --upgrade %s' to update" % (api_version, result.available_version, package_name)
- return package_version_message
# Default command line arguments.
# (Sync with pcs_configure.py.)
@@ -110,7 +100,6 @@ def get_arg_parser(self):
'--debug',
action='store_true',
help='(Optional) - Output debugging information')
- get_arg_parser.epilog=self.package_version_check()
return get_arg_parser
# Read arguments from the command line and/or a settings file.
diff --git a/prismacloud/api/pccs/README.md b/prismacloudapi/pccs/README.md
similarity index 100%
rename from prismacloud/api/pccs/README.md
rename to prismacloudapi/pccs/README.md
diff --git a/prismacloud/api/pccs/__init__.py b/prismacloudapi/pccs/__init__.py
similarity index 100%
rename from prismacloud/api/pccs/__init__.py
rename to prismacloudapi/pccs/__init__.py
diff --git a/prismacloud/api/pccs/_checkov_version.py b/prismacloudapi/pccs/_checkov_version.py
similarity index 100%
rename from prismacloud/api/pccs/_checkov_version.py
rename to prismacloudapi/pccs/_checkov_version.py
diff --git a/prismacloud/api/pccs/_code_policies.py b/prismacloudapi/pccs/_code_policies.py
similarity index 100%
rename from prismacloud/api/pccs/_code_policies.py
rename to prismacloudapi/pccs/_code_policies.py
diff --git a/prismacloud/api/pccs/_errors.py b/prismacloudapi/pccs/_errors.py
similarity index 92%
rename from prismacloud/api/pccs/_errors.py
rename to prismacloudapi/pccs/_errors.py
index 4c1a856..56299cb 100644
--- a/prismacloud/api/pccs/_errors.py
+++ b/prismacloudapi/pccs/_errors.py
@@ -9,7 +9,7 @@ def errors_files_list(self, criteria):
return self.execute_code_security('POST', 'code/api/v1/errors/files', body_params=criteria)
def errors_file_list(self, criteria):
- return self.execute_code_security('POST', 'code/api/v1/errors/file', body_params=criteria, paginated=True)
+ return self.execute_code_security_paginated('POST', 'code/api/v1/errors/file', body_params=criteria)
def errors_list_last_authors(self, query_params=None):
return self.execute_code_security('GET', 'code/api/v1/errors/gitBlameAuthors', query_params=query_params)
diff --git a/prismacloud/api/pccs/_fixes.py b/prismacloudapi/pccs/_fixes.py
similarity index 100%
rename from prismacloud/api/pccs/_fixes.py
rename to prismacloudapi/pccs/_fixes.py
diff --git a/prismacloud/api/pccs/_packages.py b/prismacloudapi/pccs/_packages.py
similarity index 100%
rename from prismacloud/api/pccs/_packages.py
rename to prismacloudapi/pccs/_packages.py
diff --git a/prismacloud/api/pccs/_repositories.py b/prismacloudapi/pccs/_repositories.py
similarity index 100%
rename from prismacloud/api/pccs/_repositories.py
rename to prismacloudapi/pccs/_repositories.py
diff --git a/prismacloud/api/pccs/_rules.py b/prismacloudapi/pccs/_rules.py
similarity index 100%
rename from prismacloud/api/pccs/_rules.py
rename to prismacloudapi/pccs/_rules.py
diff --git a/prismacloud/api/pccs/_scans.py b/prismacloudapi/pccs/_scans.py
similarity index 100%
rename from prismacloud/api/pccs/_scans.py
rename to prismacloudapi/pccs/_scans.py
diff --git a/prismacloud/api/pccs/_suppressions.py b/prismacloudapi/pccs/_suppressions.py
similarity index 100%
rename from prismacloud/api/pccs/_suppressions.py
rename to prismacloudapi/pccs/_suppressions.py
diff --git a/prismacloudapi/pccs/pccs.py b/prismacloudapi/pccs/pccs.py
new file mode 100644
index 0000000..e0a33aa
--- /dev/null
+++ b/prismacloudapi/pccs/pccs.py
@@ -0,0 +1,104 @@
+""" Requests and Output """
+
+import json
+import time
+
+import requests
+
+class PrismaCloudAPIPCCSMixin():
+ """ Requests and Output """
+
+ # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements
+ def execute_code_security(self, action, endpoint, query_params=None, body_params=None, request_headers=None):
+ self.suppress_warnings_when_verify_false()
+ if not self.token:
+ self.login()
+ if int(time.time() - self.token_timer) > self.token_limit:
+ self.extend_login()
+ if body_params:
+ body_params_json = json.dumps(body_params)
+ else:
+ body_params_json = None
+ # Endpoints that return large numbers of results use a 'hasNext' key.
+ # Pagination is via query parameters for both GET and POST, and appears to be specific to "List File Errors - POST".
+ url = 'https://%s/%s' % (self.api, endpoint)
+ self.debug_print('API URL: %s' % url)
+ self.debug_print('API Headers: %s' % request_headers)
+ self.debug_print('API Query Params: %s' % query_params)
+ self.debug_print('API Body Params: %s' % body_params_json)
+ api_response = self.session.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ self.debug_print('API Response Status Code: %s' % api_response.status_code)
+ self.debug_print('API Response Headers: (%s)' % api_response.headers)
+ if api_response.ok:
+ if not api_response.content:
+ return None
+ if api_response.headers.get('Content-Type') == 'application/x-gzip':
+ return api_response.content
+ if api_response.headers.get('Content-Type') == 'text/csv':
+ return api_response.content.decode('utf-8')
+ try:
+ result = api_response.json()
+ except ValueError:
+ self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ self.error_and_exit(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ return result
+ else:
+ self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
+ self.error_and_exit(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
+ return None
+
+ def execute_code_security_paginated(self, action, endpoint, query_params=None, body_params=None, request_headers=None):
+ self.suppress_warnings_when_verify_false()
+ if not self.token:
+ self.login()
+ if int(time.time() - self.token_timer) > self.token_limit:
+ self.extend_login()
+ if body_params:
+ body_params_json = json.dumps(body_params)
+ else:
+ body_params_json = None
+ # Endpoints that return large numbers of results use a 'hasNext' key.
+ # Pagination is via query parameters for both GET and POST, and appears to be specific to "List File Errors - POST".
+ offset = 0
+ limit = 100
+ more = False
+ while offset == 0 or more is True:
+ if int(time.time() - self.token_timer) > self.token_limit:
+ self.extend_login()
+ url = 'https://%s/%s?limit=%s&offset=%s' % (self.api, endpoint, limit, offset)
+ self.debug_print('API URL: %s' % url)
+ self.debug_print('API Headers: %s' % request_headers)
+ self.debug_print('API Query Params: %s' % query_params)
+ self.debug_print('API Body Params: %s' % body_params_json)
+ api_response = self.session.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout)
+ self.debug_print('API Response Status Code: %s' % api_response.status_code)
+ self.debug_print('API Response Headers: (%s)' % api_response.headers)
+ if api_response.ok:
+ if not api_response.content:
+ return None
+ if api_response.headers.get('Content-Type') == 'application/x-gzip':
+ return api_response.content
+ if api_response.headers.get('Content-Type') == 'text/csv':
+ return api_response.content.decode('utf-8')
+ try:
+ result = api_response.json()
+ except ValueError:
+ self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ self.error_and_exit(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % (url, query_params, body_params, api_response.content))
+ yield from result['data']
+ if 'hasNext' in result:
+ self.debug_print('Retrieving Next Page of Results')
+ offset += limit
+ more = result['hasNext']
+ else:
+ return
+ else:
+ self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % (url, api_response.status_code, query_params, body_params))
+ self.error_and_exit(api_response.status_code, 'API: (%s) with query params: (%s) and body params: (%s) responded with an error and this response:\n%s' % (url, query_params, body_params, api_response.text))
+ return
+
+ # Exit handler (Error).
+
+ @classmethod
+ def error_and_exit(cls, error_code, error_message='', system_message=''):
+ raise SystemExit('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message))
diff --git a/pyproject.toml b/pyproject.toml
index 7fd26b9..dbef61d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,32 @@
[build-system]
-requires = ["setuptools"]
-build-backend = "setuptools.build_meta"
\ No newline at end of file
+requires = ["setuptools", "setuptools_scm", "setuptools_scm[toml]"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "prismacloudapi"
+authors = [
+ { name = "Loic Jaquemet", email = "loic.jaquemet+python@gmail.com" },
+ { name = "Tom Kishel", email = "tkishel@paloaltonetworks.com" }
+]
+maintainers = [{ name = "Loic Jaquemet", email = "loic.jaquemet+python@gmail.com" }]
+license = { text = "License :: OSI Approved :: MIT License" }
+description = "Prisma Cloud API SDK for Python - loic version"
+readme = "README.md"
+keywords = ["prisma", "cloud", "api", "prismacloud", "prismacloudapi"]
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Topic :: Utilities"
+]
+dependencies = ["requests"]
+requires-python = ">=3.10"
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://github.com/trolldbois/prismacloud-api-python"
+Download = "https://github.com/trolldbois/prismacloud-api-python/releases"
+Original = "https://github.com/PaloAltoNetworks/prismacloud-api-python"
+
+[tool.setuptools_scm]
+
diff --git a/requirements.txt b/requirements.txt
index 5f88e26..f229360 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1 @@
requests
-update_checker
\ No newline at end of file
diff --git a/scripts/examples/pcs_vuln_container_with_cve_2022_22965.py b/scripts/examples/pcs_vuln_container_with_cve_2022_22965.py
index 7b81c6e..a083c96 100644
--- a/scripts/examples/pcs_vuln_container_with_cve_2022_22965.py
+++ b/scripts/examples/pcs_vuln_container_with_cve_2022_22965.py
@@ -2,7 +2,7 @@
import os
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_account_groups_by_tags.py b/scripts/pcs_account_groups_by_tags.py
index 8f4e8eb..12169c9 100644
--- a/scripts/pcs_account_groups_by_tags.py
+++ b/scripts/pcs_account_groups_by_tags.py
@@ -4,7 +4,7 @@
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_agentless_logs.py b/scripts/pcs_agentless_logs.py
index 5447d7b..fa56573 100644
--- a/scripts/pcs_agentless_logs.py
+++ b/scripts/pcs_agentless_logs.py
@@ -1,7 +1,7 @@
""" Download and Save Agentless Logs """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_alert_rule_add_compliance_policies.py b/scripts/pcs_alert_rule_add_compliance_policies.py
index c2c8aad..73fb91f 100644
--- a/scripts/pcs_alert_rule_add_compliance_policies.py
+++ b/scripts/pcs_alert_rule_add_compliance_policies.py
@@ -1,7 +1,7 @@
""" Add Policies to an alert rule based on compliance standard """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_alert_rule_export.py b/scripts/pcs_alert_rule_export.py
index 7a4b218..42e3a90 100644
--- a/scripts/pcs_alert_rule_export.py
+++ b/scripts/pcs_alert_rule_export.py
@@ -1,7 +1,7 @@
""" Export Alert Rules """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_alert_rule_import.py b/scripts/pcs_alert_rule_import.py
index 890bab3..41b2709 100644
--- a/scripts/pcs_alert_rule_import.py
+++ b/scripts/pcs_alert_rule_import.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_alerts_read.py b/scripts/pcs_alerts_read.py
index 336a067..1d87085 100644
--- a/scripts/pcs_alerts_read.py
+++ b/scripts/pcs_alerts_read.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_apis_ingested.py b/scripts/pcs_apis_ingested.py
index 0812e04..21f9ef1 100644
--- a/scripts/pcs_apis_ingested.py
+++ b/scripts/pcs_apis_ingested.py
@@ -1,7 +1,7 @@
""" Get a list of Alerts """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_cloud_account_import_azure.py b/scripts/pcs_cloud_account_import_azure.py
index e51d8d3..2011fb3 100644
--- a/scripts/pcs_cloud_account_import_azure.py
+++ b/scripts/pcs_cloud_account_import_azure.py
@@ -1,7 +1,7 @@
""" Import Azure Accounts from a CSV file """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_cloud_account_inventory.py b/scripts/pcs_cloud_account_inventory.py
index 76cc9e9..d1cc60d 100644
--- a/scripts/pcs_cloud_account_inventory.py
+++ b/scripts/pcs_cloud_account_inventory.py
@@ -4,7 +4,7 @@
import csv
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_cloud_discovery_defense_stats.py b/scripts/pcs_cloud_discovery_defense_stats.py
index 4e73828..505a59f 100644
--- a/scripts/pcs_cloud_discovery_defense_stats.py
+++ b/scripts/pcs_cloud_discovery_defense_stats.py
@@ -1,7 +1,7 @@
""" Get statistics from cloud discovery """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_compliance_alerts_read.py b/scripts/pcs_compliance_alerts_read.py
index e569fdc..e03d092 100644
--- a/scripts/pcs_compliance_alerts_read.py
+++ b/scripts/pcs_compliance_alerts_read.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_compliance_export.py b/scripts/pcs_compliance_export.py
index 63da79f..b07037f 100644
--- a/scripts/pcs_compliance_export.py
+++ b/scripts/pcs_compliance_export.py
@@ -1,7 +1,7 @@
""" Export a specific Compliance Standard """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_compliance_import.py b/scripts/pcs_compliance_import.py
index c9d2c2d..9f0725a 100644
--- a/scripts/pcs_compliance_import.py
+++ b/scripts/pcs_compliance_import.py
@@ -7,7 +7,7 @@
import requests
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# TODO: Do not update policy.rule.name when policy.systemDefault == True ?
diff --git a/scripts/pcs_compliance_uuid_read.py b/scripts/pcs_compliance_uuid_read.py
index 62cdaba..3edb541 100644
--- a/scripts/pcs_compliance_uuid_read.py
+++ b/scripts/pcs_compliance_uuid_read.py
@@ -1,7 +1,7 @@
""" Get the UUID of a specific Compliance Standard (or Requirement or Section) """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_compute_container_observed_connections.py b/scripts/pcs_compute_container_observed_connections.py
index b11cfde..e1b2082 100644
--- a/scripts/pcs_compute_container_observed_connections.py
+++ b/scripts/pcs_compute_container_observed_connections.py
@@ -3,7 +3,7 @@
import urllib.parse
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_compute_endpoint_client.py b/scripts/pcs_compute_endpoint_client.py
index 7bf541e..35f41f2 100644
--- a/scripts/pcs_compute_endpoint_client.py
+++ b/scripts/pcs_compute_endpoint_client.py
@@ -4,7 +4,7 @@
from sys import exit as sys_exit, stderr, stdout
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_compute_forward_to_siem.py b/scripts/pcs_compute_forward_to_siem.py
index 2b878ad..319586b 100644
--- a/scripts/pcs_compute_forward_to_siem.py
+++ b/scripts/pcs_compute_forward_to_siem.py
@@ -19,7 +19,7 @@
import requests
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_configure.py b/scripts/pcs_configure.py
index 91f68e7..4c825e4 100644
--- a/scripts/pcs_configure.py
+++ b/scripts/pcs_configure.py
@@ -1,7 +1,7 @@
""" Configure """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_container_count.py b/scripts/pcs_container_count.py
index 60e0b94..b94bb6f 100644
--- a/scripts/pcs_container_count.py
+++ b/scripts/pcs_container_count.py
@@ -1,7 +1,7 @@
""" Get a Count of Protected Containers """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_container_csp.py b/scripts/pcs_container_csp.py
index b0fa28a..0315f86 100644
--- a/scripts/pcs_container_csp.py
+++ b/scripts/pcs_container_csp.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_container_vulnerabilities_on_running_hosts.py b/scripts/pcs_container_vulnerabilities_on_running_hosts.py
index 0b0d893..98f91dc 100644
--- a/scripts/pcs_container_vulnerabilities_on_running_hosts.py
+++ b/scripts/pcs_container_vulnerabilities_on_running_hosts.py
@@ -7,7 +7,7 @@
from dateutil import tz
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_container_vulnerabilities_read.py b/scripts/pcs_container_vulnerabilities_read.py
index 632f610..d29ea28 100644
--- a/scripts/pcs_container_vulnerabilities_read.py
+++ b/scripts/pcs_container_vulnerabilities_read.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_cs_errors_for_file.py b/scripts/pcs_cs_errors_for_file.py
index 34d2c00..26b798f 100644
--- a/scripts/pcs_cs_errors_for_file.py
+++ b/scripts/pcs_cs_errors_for_file.py
@@ -1,7 +1,7 @@
""" Returns a list of potential Code Security policy violations for the specified file path """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_cs_repositories_read.py b/scripts/pcs_cs_repositories_read.py
index 8cdbe50..f47081a 100644
--- a/scripts/pcs_cs_repositories_read.py
+++ b/scripts/pcs_cs_repositories_read.py
@@ -1,7 +1,7 @@
""" Returns a list of repositories that are integrated with Prisma Cloud Code Security """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_current_registry_scan.py b/scripts/pcs_current_registry_scan.py
index a0b9312..bea57ac 100644
--- a/scripts/pcs_current_registry_scan.py
+++ b/scripts/pcs_current_registry_scan.py
@@ -7,7 +7,7 @@
import time
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_defender_report_by_cloud_account.py b/scripts/pcs_defender_report_by_cloud_account.py
index c1b4427..fb0a5ac 100644
--- a/scripts/pcs_defender_report_by_cloud_account.py
+++ b/scripts/pcs_defender_report_by_cloud_account.py
@@ -1,7 +1,7 @@
""" Get a Count of Protected Containers """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_forensics_download.py b/scripts/pcs_forensics_download.py
index ae22581..cc1f629 100644
--- a/scripts/pcs_forensics_download.py
+++ b/scripts/pcs_forensics_download.py
@@ -1,7 +1,7 @@
""" Download and Save Forensics """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_hosts_vulnerabilities_read.py b/scripts/pcs_hosts_vulnerabilities_read.py
index bd49c2d..b1f6598 100644
--- a/scripts/pcs_hosts_vulnerabilities_read.py
+++ b/scripts/pcs_hosts_vulnerabilities_read.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_images_packages_read.py b/scripts/pcs_images_packages_read.py
index 31b06f9..e4557f9 100644
--- a/scripts/pcs_images_packages_read.py
+++ b/scripts/pcs_images_packages_read.py
@@ -3,7 +3,7 @@
from packaging import version
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_incident_archiver.py b/scripts/pcs_incident_archiver.py
index db13f1f..781bff4 100644
--- a/scripts/pcs_incident_archiver.py
+++ b/scripts/pcs_incident_archiver.py
@@ -3,7 +3,7 @@
# See workflow in scripts/README.md
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_outdated_defenders.py b/scripts/pcs_outdated_defenders.py
index d96fbf8..564853a 100644
--- a/scripts/pcs_outdated_defenders.py
+++ b/scripts/pcs_outdated_defenders.py
@@ -1,7 +1,7 @@
""" Get Outdated Defenders """
from packaging import version
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_policy_custom_export.py b/scripts/pcs_policy_custom_export.py
index 5a4834b..167d859 100644
--- a/scripts/pcs_policy_custom_export.py
+++ b/scripts/pcs_policy_custom_export.py
@@ -1,7 +1,7 @@
""" Export Custom Policies """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_policy_custom_import.py b/scripts/pcs_policy_custom_import.py
index 61308ab..d3cb3c3 100644
--- a/scripts/pcs_policy_custom_import.py
+++ b/scripts/pcs_policy_custom_import.py
@@ -6,7 +6,7 @@
import requests
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_policy_read.py b/scripts/pcs_policy_read.py
index 8eb2d70..a07eff2 100644
--- a/scripts/pcs_policy_read.py
+++ b/scripts/pcs_policy_read.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_policy_set_status.py b/scripts/pcs_policy_set_status.py
index 30e8483..fce2b1a 100644
--- a/scripts/pcs_policy_set_status.py
+++ b/scripts/pcs_policy_set_status.py
@@ -3,7 +3,7 @@
import sys
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_posture_endpoint_client.py b/scripts/pcs_posture_endpoint_client.py
index 5a38df9..b61cf14 100644
--- a/scripts/pcs_posture_endpoint_client.py
+++ b/scripts/pcs_posture_endpoint_client.py
@@ -4,7 +4,7 @@
from sys import exit as sys_exit, stderr, stdout
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_resources_export.py b/scripts/pcs_resources_export.py
index 1cd686b..5f56d85 100644
--- a/scripts/pcs_resources_export.py
+++ b/scripts/pcs_resources_export.py
@@ -1,7 +1,7 @@
""" Get Resources """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_rotate_service_account_access_key.py b/scripts/pcs_rotate_service_account_access_key.py
index 9ab811a..7176837 100644
--- a/scripts/pcs_rotate_service_account_access_key.py
+++ b/scripts/pcs_rotate_service_account_access_key.py
@@ -4,7 +4,7 @@
import time
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_rql_query.py b/scripts/pcs_rql_query.py
index 2e54ee3..666413b 100644
--- a/scripts/pcs_rql_query.py
+++ b/scripts/pcs_rql_query.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_script_example.py b/scripts/pcs_script_example.py
index 76a57af..d77cc44 100644
--- a/scripts/pcs_script_example.py
+++ b/scripts/pcs_script_example.py
@@ -1,7 +1,7 @@
""" Example of Prisma Cloud (and Compute) API Access """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_sync_azure_accounts.py b/scripts/pcs_sync_azure_accounts.py
index 93f2aef..8f5039e 100644
--- a/scripts/pcs_sync_azure_accounts.py
+++ b/scripts/pcs_sync_azure_accounts.py
@@ -3,7 +3,7 @@
import json
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_sync_registries.py b/scripts/pcs_sync_registries.py
index e7095a2..5e6bc01 100644
--- a/scripts/pcs_sync_registries.py
+++ b/scripts/pcs_sync_registries.py
@@ -3,7 +3,7 @@
from operator import itemgetter
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_usage.py b/scripts/pcs_usage.py
index 88eddaa..deefea4 100644
--- a/scripts/pcs_usage.py
+++ b/scripts/pcs_usage.py
@@ -1,7 +1,7 @@
""" Get Usage """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_user_import.py b/scripts/pcs_user_import.py
index 9960419..4cd8915 100644
--- a/scripts/pcs_user_import.py
+++ b/scripts/pcs_user_import.py
@@ -1,7 +1,7 @@
""" Import Users from a CSV file """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_user_update.py b/scripts/pcs_user_update.py
index 87efd93..d25d3f9 100644
--- a/scripts/pcs_user_update.py
+++ b/scripts/pcs_user_update.py
@@ -1,7 +1,7 @@
""" Update a User """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_vuln_container_locations.py b/scripts/pcs_vuln_container_locations.py
index 80cbdfd..bb1140b 100644
--- a/scripts/pcs_vuln_container_locations.py
+++ b/scripts/pcs_vuln_container_locations.py
@@ -1,7 +1,7 @@
""" Get a list of vulnerable containers and their clusters """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
# --Configuration-- #
diff --git a/scripts/pcs_week_alert_trend.py b/scripts/pcs_week_alert_trend.py
index 7738193..705cf9b 100644
--- a/scripts/pcs_week_alert_trend.py
+++ b/scripts/pcs_week_alert_trend.py
@@ -1,7 +1,7 @@
""" Get Resources """
# pylint: disable=import-error
-from prismacloud.api import pc_api, pc_utility
+from prismacloudapi import pc_api, pc_utility
from tabulate import tabulate
import pandas as pd
diff --git a/setup.py b/setup.py
index 1218eb8..bbdee35 100644
--- a/setup.py
+++ b/setup.py
@@ -1,38 +1,7 @@
-import importlib
-import os
-import setuptools
+from setuptools import setup
-with open('README.md', 'r') as fh:
- long_description = fh.read()
-
-spec = importlib.util.spec_from_file_location(
- 'prismacloud.api.version', os.path.join('prismacloud', 'api', 'version.py')
-)
-
-mod = importlib.util.module_from_spec(spec)
-spec.loader.exec_module(mod)
-version = mod.version
-
-setuptools.setup(
- name='prismacloud-api',
- version=version,
- author='Tom Kishel',
- author_email='tkishel@paloaltonetworks.com',
- description='Prisma Cloud API SDK for Python',
- keywords="prisma cloud api",
- long_description=long_description,
- long_description_content_type='text/markdown',
- url='https://github.com/PaloAltoNetworks/prismacloud-api-python',
- packages=setuptools.find_namespace_packages(exclude=['scripts']),
- classifiers=[
- 'Programming Language :: Python :: 3',
- 'License :: OSI Approved :: MIT License',
- 'Operating System :: OS Independent',
- 'Topic :: Utilities'
- ],
- install_requires=[
- 'requests',
- 'update_checker'
- ],
- python_requires='>=3.6'
-)
+if __name__ == "__main__":
+ setup(
+ name="prismacloudapi",
+ use_scm_version=True,
+ setup_requires=["setuptools_scm"])
diff --git a/tests/unit.py b/tests/unit.py
index 54dc843..b41228c 100644
--- a/tests/unit.py
+++ b/tests/unit.py
@@ -5,7 +5,7 @@
import mock
# pylint: disable=import-error
-from prismacloud.api import pc_api
+from prismacloudapi import pc_api
class TestPrismaCloudAPI(unittest.TestCase):
""" Unit Tests with Mocking """
@@ -51,7 +51,7 @@ class TestPrismaCloudAPI(unittest.TestCase):
}
# Decorator
- @mock.patch('prismacloud.api.pc_utility.get_settings')
+ @mock.patch('prismacloudapi.pc_utility.get_settings')
def test_pc_api_configure(self, get_settings):
get_settings.return_value = self.SETTINGS
settings = get_settings()
@@ -60,7 +60,7 @@ def test_pc_api_configure(self, get_settings):
# With
def test_pc_api_current_user(self):
- with mock.patch('prismacloud.api.PrismaCloudAPI.execute') as pc_api_execute:
+ with mock.patch('prismacloudapi.PrismaCloudAPI.execute') as pc_api_execute:
pc_api_execute.return_value = self.USER_PROFILE
result = pc_api.current_user()
self.assertEqual('Example User', result['displayName'])