From b8504502c80da818d05585621387bf73001a187b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Tue, 30 Jul 2024 11:36:03 -0600 Subject: [PATCH 01/34] Update _registry.py with registry_download support https://pan.dev/prisma-cloud/api/cwpp/get-registry-download/ --- prismacloud/api/cwpp/_registry.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prismacloud/api/cwpp/_registry.py b/prismacloud/api/cwpp/_registry.py index a9563aa..8602f94 100644 --- a/prismacloud/api/cwpp/_registry.py +++ b/prismacloud/api/cwpp/_registry.py @@ -5,6 +5,10 @@ class RegistryPrismaCloudAPICWPPMixin: """ Prisma Cloud Compute API Images Endpoints Class """ + def registry_download(self, query_params=None): + registries = self.execute_compute('GET', 'api/v1/registry/download?', query_params=query_params) + return registries + 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) From 19fc200dfa80d93846a17d211cb7d31ef9513b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Tue, 30 Jul 2024 11:37:01 -0600 Subject: [PATCH 02/34] Update _containers.py with containers_download support https://pan.dev/prisma-cloud/api/cwpp/get-containers-download/ --- prismacloud/api/cwpp/_containers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prismacloud/api/cwpp/_containers.py b/prismacloud/api/cwpp/_containers.py index 1346707..1a2a4ae 100644 --- a/prismacloud/api/cwpp/_containers.py +++ b/prismacloud/api/cwpp/_containers.py @@ -11,3 +11,7 @@ def containers_list_read(self, image_id=None, query_params=None): else: containers = self.execute_compute('GET', 'api/v1/containers?', query_params=query_params, paginated=True) return containers + + def containers_download(self, query_params=None): + containers = self.execute_compute('GET', 'api/v1/containers/download?', query_params=query_params) + return containers From ec69a4fb30ad928aa1aeebab0b06a27bc32a508d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Tue, 30 Jul 2024 11:37:48 -0600 Subject: [PATCH 03/34] Update _defenders.py with defenders_download support https://pan.dev/prisma-cloud/api/cwpp/get-defenders-download/ --- prismacloud/api/cwpp/_defenders.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prismacloud/api/cwpp/_defenders.py b/prismacloud/api/cwpp/_defenders.py index 5d5f47e..25e16c5 100644 --- a/prismacloud/api/cwpp/_defenders.py +++ b/prismacloud/api/cwpp/_defenders.py @@ -12,3 +12,7 @@ def defenders_list_read(self, query_params=None): def defenders_names_list_read(self, query_params=None): defenders = self.execute_compute('GET', 'api/v1/defenders/names', query_params=query_params, paginated=True) return defenders + + def defenders_download(self, query_params=None): + defenders = self.execute_compute('GET', 'api/v1/defenders/download?', query_params=query_params) + return defenders From 89f106979da58e606cb7f9ef024396451dee0395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Tue, 30 Jul 2024 11:38:27 -0600 Subject: [PATCH 04/34] Update _scans.py with scans_download support https://pan.dev/prisma-cloud/api/cwpp/get-scans-download/ --- prismacloud/api/cwpp/_scans.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prismacloud/api/cwpp/_scans.py b/prismacloud/api/cwpp/_scans.py index c2687ec..f906117 100644 --- a/prismacloud/api/cwpp/_scans.py +++ b/prismacloud/api/cwpp/_scans.py @@ -11,3 +11,7 @@ def scans_list_read(self, image_id=None): else: images = self.execute_compute('GET', 'api/v1/scans?filterBaseImage=true', paginated=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 From 9a1e21881853f491e6e6c6f6788d21e40a9c5006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Tue, 30 Jul 2024 11:39:19 -0600 Subject: [PATCH 05/34] Update _stats.py to fix typo Fix typo in method name --- prismacloud/api/cwpp/_stats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prismacloud/api/cwpp/_stats.py b/prismacloud/api/cwpp/_stats.py index 5353aab..a118ece 100644 --- a/prismacloud/api/cwpp/_stats.py +++ b/prismacloud/api/cwpp/_stats.py @@ -43,11 +43,11 @@ def stats_vulnerabilities_read(self, query_params=None): def stats_vulnerabilities_download(self, query_params=None): 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) - def stats_vulnerabilities_impacted_resoures_download(self, query_params=None): + 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): From 7bff184e650eca16f180752f73f0b0ea939c1ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Thu, 12 Dec 2024 14:24:53 -0700 Subject: [PATCH 06/34] Update _cloud.py for paginated cloud_discovery_entities --- prismacloud/api/cwpp/_cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prismacloud/api/cwpp/_cloud.py b/prismacloud/api/cwpp/_cloud.py index ab5d799..a877451 100644 --- a/prismacloud/api/cwpp/_cloud.py +++ b/prismacloud/api/cwpp/_cloud.py @@ -23,4 +23,4 @@ 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) + return self.execute_compute('GET', 'api/v1/cloud/discovery/entities', query_params=query_params, paginated=True) From 390fdc7d944e875c23dcd6883b75ea1c7a8af08a Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Wed, 19 Feb 2025 20:08:01 -0700 Subject: [PATCH 07/34] use a requests session and a Retry session adapter. use logging for debug instead of print --- prismacloud/api/cspm/cspm.py | 54 +++++++++++------------------ prismacloud/api/cwpp/cwpp.py | 64 ++++++++++++++++++++++------------- prismacloud/api/pc_lib_api.py | 16 ++++++++- prismacloud/api/pccs/pccs.py | 14 ++++---- prismacloud/api/version.py | 2 +- setup.py | 8 ++--- 6 files changed, 87 insertions(+), 71 deletions(-) diff --git a/prismacloud/api/cspm/cspm.py b/prismacloud/api/cspm/cspm.py index d25434b..47527a7 100644 --- a/prismacloud/api/cspm/cspm.py +++ b/prismacloud/api/cspm/cspm.py @@ -17,33 +17,27 @@ def suppress_warnings_when_verify_false(self): def login(self, url=None): self.suppress_warnings_when_verify_false() if not url: - url = 'https://%s/login' % self.api + url = f'https://{self.api}/login' 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 + api_response = self.session.request(action, url, headers=request_headers, data=body_params_json, verify=self.verify, timeout=self.timeout) + # use a requests retry adapter + # 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) + self.debug_print('New API Token: %s' % self.token) def extend_login(self): self.suppress_warnings_when_verify_false() @@ -52,21 +46,14 @@ def extend_login(self): 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 + api_response = self.session.request(action, url, headers=request_headers, verify=self.verify, timeout=self.timeout) 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') + 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): @@ -97,15 +84,15 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request 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) + 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.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.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 @@ -128,8 +115,7 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request if paginated: results.extend(result['items']) if 'nextPageToken' in result and result['nextPageToken']: - if self.debug: - print('Retrieving Next Page of Results') + self.debug_print('Retrieving Next Page of Results') body_params = {'pageToken': result['nextPageToken']} more = True else: diff --git a/prismacloud/api/cwpp/cwpp.py b/prismacloud/api/cwpp/cwpp.py index cb0e750..5355680 100644 --- a/prismacloud/api/cwpp/cwpp.py +++ b/prismacloud/api/cwpp/cwpp.py @@ -14,21 +14,23 @@ def login_compute(self): self.login() elif self.api_compute: # Login via CWP. - self.login('https://%s/api/v1/authenticate' % self.api_compute) + self.login(f'https://{self.api_compute}/api/v1/authenticate') 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) + self.debug_print(f'New API Token: {self.token}') - def extend_login_compute(self): + def check_extend_login_compute(self): # There is no extend for CWP, just logon again. - self.debug_print('Extending API Token') - self.login_compute() + if not self.token or (int(time.time() - self.token_timer) > self.token_limit): + self.debug_print('Extending 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, request_headers=None, force=False, paginated=False): self.suppress_warnings_when_verify_false() - if not self.token: - self.login_compute() + self.check_extend_login_compute() if not request_headers: request_headers = {'Content-Type': 'application/json'} if body_params: @@ -44,8 +46,7 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None, more = False results = [] while offset == 0 or more is True: - if int(time.time() - self.token_timer) > self.token_limit: - self.extend_login_compute() + self.check_extend_login_compute() if paginated: url = 'https://%s/%s?limit=%s&offset=%s' % (self.api_compute, endpoint, limit, offset) else: @@ -63,22 +64,25 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None, 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) + 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.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.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 + yield None + return if api_response.headers.get('Content-Type') == 'application/x-gzip': - return api_response.content + yield api_response.content + return if api_response.headers.get('Content-Type') == 'text/csv': - return api_response.content.decode('utf-8') + yield api_response.content.decode('utf-8') + return try: result = json.loads(api_response.content) #if result is None: @@ -89,23 +93,26 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None, 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 + yield from results # or continue + return 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) + # results.extend(result) + yield from result offset += limit more = bool(offset < total_count) else: - return result + yield result + 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)) if force: - return results + return 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 + return # The Compute API setting is optional. @@ -118,3 +125,12 @@ def validate_api_compute(self): @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)) + + # various API + + def version(self): + return next(self.execute_compute('GET', 'api/v1/version')) + + def ping(self): + # unauthenticated call + return self.session.get(f'https://{self.api_compute}/api/v1/_ping').text diff --git a/prismacloud/api/pc_lib_api.py b/prismacloud/api/pc_lib_api.py index 01289f5..7792afe 100644 --- a/prismacloud/api/pc_lib_api.py +++ b/prismacloud/api/pc_lib_api.py @@ -2,6 +2,9 @@ import logging +import requests +from requests.adapters import HTTPAdapter, Retry + from .cspm import PrismaCloudAPICSPM from .cwpp import PrismaCloudAPICWPP from .pccs import PrismaCloudAPIPCCS @@ -50,6 +53,11 @@ def __init__(self): # Set User-Agent default_user_agent = f"PrismaCloudAPI/{version}" # Dynamically set default User-Agent self.user_agent = default_user_agent + # use a session + self.session = requests.session() + retries = Retry(total=6, backoff_factor=1, status_forcelist=self.retry_status_codes) + self.session_adapter = HTTPAdapter(max_retries=retries) + # s.mount('http://', ) 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 +84,23 @@ 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}") # Conditional printing. def debug_print(self, message): if self.debug: - print(message) + logging.debug(message) diff --git a/prismacloud/api/pccs/pccs.py b/prismacloud/api/pccs/pccs.py index 3d1dfc6..ab5136b 100644 --- a/prismacloud/api/pccs/pccs.py +++ b/prismacloud/api/pccs/pccs.py @@ -42,15 +42,15 @@ def execute_code_security(self, action, endpoint, query_params=None, body_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) + 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.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.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 diff --git a/prismacloud/api/version.py b/prismacloud/api/version.py index 369881d..3104f7e 100644 --- a/prismacloud/api/version.py +++ b/prismacloud/api/version.py @@ -1 +1 @@ -version = "5.2.24" +version = "5.2.25" diff --git a/setup.py b/setup.py index 1218eb8..d034b5d 100644 --- a/setup.py +++ b/setup.py @@ -16,13 +16,13 @@ setuptools.setup( name='prismacloud-api', version=version, - author='Tom Kishel', - author_email='tkishel@paloaltonetworks.com', - description='Prisma Cloud API SDK for Python', + author='Loic Jaquemet', + author_email='trolldbois', + description='Prisma Cloud API SDK for Python - loic version', keywords="prisma cloud api", long_description=long_description, long_description_content_type='text/markdown', - url='https://github.com/PaloAltoNetworks/prismacloud-api-python', + url='https://github.com/trolldbois/prismacloud-api-python', packages=setuptools.find_namespace_packages(exclude=['scripts']), classifiers=[ 'Programming Language :: Python :: 3', From e10cc343245d1b6bc770a3300fa09f64790457cb Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Wed, 19 Feb 2025 22:33:01 -0700 Subject: [PATCH 08/34] explicit parameters --- prismacloud/api/cspm/_endpoints.py | 153 ++++++++++++++++++++++++----- 1 file changed, 131 insertions(+), 22 deletions(-) diff --git a/prismacloud/api/cspm/_endpoints.py b/prismacloud/api/cspm/_endpoints.py index 8aa6402..864ca72 100644 --- a/prismacloud/api/cspm/_endpoints.py +++ b/prismacloud/api/cspm/_endpoints.py @@ -1,4 +1,7 @@ """ Prisma Cloud API Endpoints Class """ +import logging +import pprint + # TODO: Split into multiple files, one per endpoint ... @@ -49,7 +52,6 @@ def alert_csv_status(self, csv_report_id): def alert_csv_download(self, csv_report_id): return self.execute('GET', 'alert/csv/%s/download' % csv_report_id) - """ Policies @@ -154,7 +156,8 @@ 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) + return self.execute('POST', 'compliance/%s/requirement' % compliance_standard_id, + body_params=compliance_requirement_to_add) """ Compliance Standard Requirements Sections @@ -170,7 +173,8 @@ def compliance_standard_requirement_section_list_read(self, compliance_requireme 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) + return self.execute('POST', 'compliance/%s/section' % compliance_requirement_id, + body_params=compliance_section_to_add) """ Compliance Standard Requirements Policies @@ -216,8 +220,8 @@ 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) - + 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) @@ -355,8 +359,8 @@ 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) - + return self.execute('POST', 'v2/inventory', body_params=body_params) + """ Asset (Resources) Inventory V3 @@ -470,7 +474,7 @@ def resource_list_read(self): 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) @@ -489,7 +493,7 @@ def adoptionadvisor_report_read(self): 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) @@ -535,21 +539,126 @@ def compliance_report_download(self, report_id): [ ] DELETE """ - def search_config_read(self, search_params): - result = [] + 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 - api_response = self.execute( - 'POST', 'search/config', body_params=search_params) + # 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']: - result = api_response['data']['items'] + yield from 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'}) + 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: - result.extend(api_response['items']) + yield from api_response['items'] next_page_token = api_response.pop('nextPageToken', None) - return result + 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) + logging.debug(f"Total items: {api_response.pop('totalRows', 'unknown')}") + 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' @@ -586,7 +695,8 @@ def search_iam_read(self, search_params): 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'}) + '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) @@ -653,14 +763,13 @@ def resource_usage_over_time_v2(self, body_params): 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 From 66bb5cb598f00c395308b4edb8ec126c6a7adf04 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Wed, 19 Feb 2025 22:33:23 -0700 Subject: [PATCH 09/34] enforce Retry on status --- prismacloud/api/pc_lib_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/prismacloud/api/pc_lib_api.py b/prismacloud/api/pc_lib_api.py index 7792afe..d986a43 100644 --- a/prismacloud/api/pc_lib_api.py +++ b/prismacloud/api/pc_lib_api.py @@ -46,6 +46,7 @@ def __init__(self): self.token_limit = 590 # aka 9 minutes 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' @@ -55,7 +56,8 @@ def __init__(self): self.user_agent = default_user_agent # use a session self.session = requests.session() - retries = Retry(total=6, backoff_factor=1, status_forcelist=self.retry_status_codes) + 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) # s.mount('http://', ) From 5d950337949b64bc4460802c86aab6265bc261f1 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Wed, 19 Feb 2025 22:46:27 -0700 Subject: [PATCH 10/34] remove logging --- prismacloud/api/cspm/_endpoints.py | 1 - 1 file changed, 1 deletion(-) diff --git a/prismacloud/api/cspm/_endpoints.py b/prismacloud/api/cspm/_endpoints.py index 864ca72..389b075 100644 --- a/prismacloud/api/cspm/_endpoints.py +++ b/prismacloud/api/cspm/_endpoints.py @@ -650,7 +650,6 @@ def search_config_read_v2(self, query, start_time=None, skip_results=None, limit if 'items' in api_response: yield from api_response['items'] next_page_token = api_response.pop('nextPageToken', None) - logging.debug(f"Total items: {api_response.pop('totalRows', 'unknown')}") 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) From a4768ace0f2a7e4abdd35bca7afd77a853c73dff Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Thu, 20 Feb 2025 11:39:40 -0700 Subject: [PATCH 11/34] try harder on token expiration --- prismacloud/api/cspm/cspm.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/prismacloud/api/cspm/cspm.py b/prismacloud/api/cspm/cspm.py index 47527a7..1d40882 100644 --- a/prismacloud/api/cspm/cspm.py +++ b/prismacloud/api/cspm/cspm.py @@ -1,6 +1,7 @@ """ Requests and Output """ import json +import logging import time import requests @@ -41,7 +42,8 @@ def login(self, url=None): def extend_login(self): self.suppress_warnings_when_verify_false() - url = 'https://%s/auth_token/extend' % self.api + self.debug_print('Extending API Token') + url = f'https://{self.api}/auth_token/extend' action = 'GET' request_headers = {'Content-Type': 'application/json', 'x-redlock-auth': self.token} # Add User-Agent to the headers @@ -52,8 +54,9 @@ def extend_login(self): 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)) - self.debug_print('Extending API Token') + 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, paginated=False): From 316e793424c873286640314b9d40251ee3131d80 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Thu, 20 Feb 2025 12:19:10 -0700 Subject: [PATCH 12/34] better logging --- prismacloud/api/cspm/cspm.py | 2 +- prismacloud/api/cwpp/cwpp.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prismacloud/api/cspm/cspm.py b/prismacloud/api/cspm/cspm.py index 1d40882..5c9117d 100644 --- a/prismacloud/api/cspm/cspm.py +++ b/prismacloud/api/cspm/cspm.py @@ -42,7 +42,7 @@ def login(self, url=None): def extend_login(self): self.suppress_warnings_when_verify_false() - self.debug_print('Extending API Token') + self.debug_print('Extending CSPM API Token') url = f'https://{self.api}/auth_token/extend' action = 'GET' request_headers = {'Content-Type': 'application/json', 'x-redlock-auth': self.token} diff --git a/prismacloud/api/cwpp/cwpp.py b/prismacloud/api/cwpp/cwpp.py index 5355680..216615e 100644 --- a/prismacloud/api/cwpp/cwpp.py +++ b/prismacloud/api/cwpp/cwpp.py @@ -22,7 +22,7 @@ def login_compute(self): def check_extend_login_compute(self): # There is no extend for CWP, just logon again. if not self.token or (int(time.time() - self.token_timer) > self.token_limit): - self.debug_print('Extending API Token') + self.debug_print('Extending CWPP API Token') self.login_compute() # def _check_ From 5bf63a60d18b6ffe1d1545c08387833e00ca2912 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Fri, 21 Feb 2025 14:02:46 -0700 Subject: [PATCH 13/34] more api --- prismacloud/api/cspm/_endpoints.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/prismacloud/api/cspm/_endpoints.py b/prismacloud/api/cspm/_endpoints.py index 389b075..32f762e 100644 --- a/prismacloud/api/cspm/_endpoints.py +++ b/prismacloud/api/cspm/_endpoints.py @@ -207,7 +207,10 @@ def compliance_standard_policy_v2_list_read(self, compliance_standard_name): [x] DELETE """ - def user_list_read(self): + 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): @@ -222,7 +225,16 @@ def user_update(self, user): def user_delete(self, user_id): return self.execute('DELETE', 'user/%s' % user_id) - def user_bypass_sso(self, body_params): + 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) """ From 8b95b9587599e7d173eef30284fa95b0af2c575a Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Fri, 21 Feb 2025 14:12:22 -0700 Subject: [PATCH 14/34] push new version --- prismacloud/api/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prismacloud/api/version.py b/prismacloud/api/version.py index 3104f7e..abb6bac 100644 --- a/prismacloud/api/version.py +++ b/prismacloud/api/version.py @@ -1 +1 @@ -version = "5.2.25" +version = "5.2.26" From aa300465b8428b3727a699f9f24a03d2812f4b0d Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Mon, 24 Feb 2025 10:42:05 -0700 Subject: [PATCH 15/34] bugfix download methods --- prismacloud/api/cspm/_endpoints.py | 4 ++-- prismacloud/api/cwpp/_cloud.py | 2 +- prismacloud/api/cwpp/_containers.py | 2 +- prismacloud/api/cwpp/_defenders.py | 2 +- prismacloud/api/cwpp/_hosts.py | 2 +- prismacloud/api/cwpp/_images.py | 2 +- prismacloud/api/cwpp/_registry.py | 2 +- prismacloud/api/cwpp/_scans.py | 2 +- prismacloud/api/cwpp/_serverless.py | 2 +- prismacloud/api/cwpp/_stats.py | 6 +++--- prismacloud/api/version.py | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/prismacloud/api/cspm/_endpoints.py b/prismacloud/api/cspm/_endpoints.py index 32f762e..747f354 100644 --- a/prismacloud/api/cspm/_endpoints.py +++ b/prismacloud/api/cspm/_endpoints.py @@ -51,7 +51,7 @@ 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) + return next(self.execute('GET', 'alert/csv/%s/download' % csv_report_id)) """ Policies @@ -531,7 +531,7 @@ 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) + return next(self.execute('GET', 'report/%s/download' % report_id)) # TODO: # if response_status == 204: # # download pending diff --git a/prismacloud/api/cwpp/_cloud.py b/prismacloud/api/cwpp/_cloud.py index a877451..f451848 100644 --- a/prismacloud/api/cwpp/_cloud.py +++ b/prismacloud/api/cwpp/_cloud.py @@ -11,7 +11,7 @@ def cloud_discovery_read(self): 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) + return next(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') diff --git a/prismacloud/api/cwpp/_containers.py b/prismacloud/api/cwpp/_containers.py index 1a2a4ae..c724a2a 100644 --- a/prismacloud/api/cwpp/_containers.py +++ b/prismacloud/api/cwpp/_containers.py @@ -13,5 +13,5 @@ def containers_list_read(self, image_id=None, query_params=None): return containers def containers_download(self, query_params=None): - containers = self.execute_compute('GET', 'api/v1/containers/download?', query_params=query_params) + containers = next(self.execute_compute('GET', 'api/v1/containers/download?', query_params=query_params)) return containers diff --git a/prismacloud/api/cwpp/_defenders.py b/prismacloud/api/cwpp/_defenders.py index 25e16c5..e6ea438 100644 --- a/prismacloud/api/cwpp/_defenders.py +++ b/prismacloud/api/cwpp/_defenders.py @@ -14,5 +14,5 @@ def defenders_names_list_read(self, query_params=None): return defenders def defenders_download(self, query_params=None): - defenders = self.execute_compute('GET', 'api/v1/defenders/download?', query_params=query_params) + defenders = next(self.execute_compute('GET', 'api/v1/defenders/download?', query_params=query_params)) return defenders diff --git a/prismacloud/api/cwpp/_hosts.py b/prismacloud/api/cwpp/_hosts.py index 6893cfe..35cc7d8 100644 --- a/prismacloud/api/cwpp/_hosts.py +++ b/prismacloud/api/cwpp/_hosts.py @@ -15,7 +15,7 @@ def hosts_info_list_read(self, query_params=None): return hosts def hosts_download(self, query_params=None): - hosts = self.execute_compute('GET', 'api/v1/hosts/download?', query_params=query_params) + hosts = next(self.execute_compute('GET', 'api/v1/hosts/download?', query_params=query_params)) return hosts def hosts_scan(self): diff --git a/prismacloud/api/cwpp/_images.py b/prismacloud/api/cwpp/_images.py index d5a612c..ad29e58 100644 --- a/prismacloud/api/cwpp/_images.py +++ b/prismacloud/api/cwpp/_images.py @@ -13,5 +13,5 @@ def images_list_read(self, image_id=None, query_params=None): return images def images_download(self, query_params=None): - images = self.execute_compute('GET', 'api/v1/images/download?', query_params=query_params) + images = next(self.execute_compute('GET', 'api/v1/images/download?', query_params=query_params)) return images diff --git a/prismacloud/api/cwpp/_registry.py b/prismacloud/api/cwpp/_registry.py index 8602f94..19a8240 100644 --- a/prismacloud/api/cwpp/_registry.py +++ b/prismacloud/api/cwpp/_registry.py @@ -6,7 +6,7 @@ class RegistryPrismaCloudAPICWPPMixin: """ Prisma Cloud Compute API Images Endpoints Class """ def registry_download(self, query_params=None): - registries = self.execute_compute('GET', 'api/v1/registry/download?', query_params=query_params) + registries = next(self.execute_compute('GET', 'api/v1/registry/download?', query_params=query_params)) return registries def registry_list_read(self, image_id=None): diff --git a/prismacloud/api/cwpp/_scans.py b/prismacloud/api/cwpp/_scans.py index f906117..b8ddb6d 100644 --- a/prismacloud/api/cwpp/_scans.py +++ b/prismacloud/api/cwpp/_scans.py @@ -13,5 +13,5 @@ def scans_list_read(self, image_id=None): return images def scans_download(self, query_params=None): - scans = self.execute_compute('GET', 'api/v1/scans/download?', query_params=query_params) + scans = next(self.execute_compute('GET', 'api/v1/scans/download?', query_params=query_params)) return scans diff --git a/prismacloud/api/cwpp/_serverless.py b/prismacloud/api/cwpp/_serverless.py index eb4acb3..7295709 100644 --- a/prismacloud/api/cwpp/_serverless.py +++ b/prismacloud/api/cwpp/_serverless.py @@ -8,7 +8,7 @@ def serverless_list_read(self, query_params=None): # 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) + result = next(self.execute_compute('GET', 'api/v1/serverless/download?', query_params=query_params)) return result # Start serverless function scan diff --git a/prismacloud/api/cwpp/_stats.py b/prismacloud/api/cwpp/_stats.py index a118ece..b5459bb 100644 --- a/prismacloud/api/cwpp/_stats.py +++ b/prismacloud/api/cwpp/_stats.py @@ -12,7 +12,7 @@ def stats_compliance_read(self, query_params=None): return self.execute_compute('GET', 'api/v1/stats/compliance?', query_params=query_params) def stats_compliance_download(self, query_params=None): - return self.execute_compute('GET', 'api/v1/stats/compliance/download?', query_params=query_params) + return next(self.execute_compute('GET', 'api/v1/stats/compliance/download?', query_params=query_params)) def stats_compliance_refresh(self, query_params=None): # Refreshes the current day's list and counts of compliance issues, as well as the list of affected running resources. @@ -41,14 +41,14 @@ def stats_vulnerabilities_read(self, query_params=None): return self.execute_compute('GET', 'api/v1/stats/vulnerabilities?', query_params=query_params, paginated=True) def stats_vulnerabilities_download(self, query_params=None): - return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/download?', query_params=query_params) + return next(self.execute_compute('GET', 'api/v1/stats/vulnerabilities/download?', query_params=query_params)) 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) 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) + return next(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. diff --git a/prismacloud/api/version.py b/prismacloud/api/version.py index abb6bac..65d1568 100644 --- a/prismacloud/api/version.py +++ b/prismacloud/api/version.py @@ -1 +1 @@ -version = "5.2.26" +version = "5.2.27" From 76d1c5fb549645d5087df9614a44a837421eb924 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Mon, 3 Mar 2025 15:13:52 -0700 Subject: [PATCH 16/34] redo authentication methods --- prismacloud/api/cspm/cspm.py | 95 +++++++++++++++++-------------- prismacloud/api/cwpp/cwpp.py | 103 +++++++++++++++++++++------------- prismacloud/api/pc_lib_api.py | 10 +++- prismacloud/api/pccs/pccs.py | 55 +++++++++++++----- 4 files changed, 166 insertions(+), 97 deletions(-) diff --git a/prismacloud/api/cspm/cspm.py b/prismacloud/api/cspm/cspm.py index 5c9117d..4462051 100644 --- a/prismacloud/api/cspm/cspm.py +++ b/prismacloud/api/cspm/cspm.py @@ -18,24 +18,18 @@ def suppress_warnings_when_verify_false(self): def login(self, url=None): self.suppress_warnings_when_verify_false() if not url: + # CSPM url = f'https://{self.api}/login' - action = 'POST' - request_headers = {'Content-Type': 'application/json'} - # Add User-Agent to the headers - request_headers['User-Agent'] = self.user_agent + # 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.request(action, url, headers=request_headers, data=body_params_json, verify=self.verify, timeout=self.timeout) - # use a requests retry adapter - # 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 + api_response = self.session.post(url, data=body_params_json, verify=self.verify, timeout=self.timeout) if api_response.ok: - api_response = json.loads(api_response.content) + 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: self.error_and_exit(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) @@ -44,22 +38,59 @@ 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' - 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 = self.session.request(action, url, headers=request_headers, verify=self.verify, timeout=self.timeout) + api_response = self.session.get(url, verify=self.verify, timeout=self.timeout) if api_response.ok: - api_response = json.loads(api_response.content) + 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, paginated=False): + 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_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_paginated(self, action, endpoint, query_params=None, body_params=None, request_headers=None, paginated=False): self.suppress_warnings_when_verify_false() if not self.token: self.login() @@ -72,11 +103,7 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request 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 + url = f'https://{self.api}/{endpoint}' if body_params: body_params_json = json.dumps(body_params) else: @@ -90,12 +117,6 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request 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.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 @@ -104,16 +125,9 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request 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)) + 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 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']) @@ -127,11 +141,8 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request 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 diff --git a/prismacloud/api/cwpp/cwpp.py b/prismacloud/api/cwpp/cwpp.py index 216615e..b525edf 100644 --- a/prismacloud/api/cwpp/cwpp.py +++ b/prismacloud/api/cwpp/cwpp.py @@ -9,36 +9,81 @@ 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(f'https://{self.api_compute}/api/v1/authenticate') + 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_exit(418, "Specify a Prisma Cloud URL or Prisma Cloud Compute URL") - self.debug_print(f'New API Token: {self.token}') + self.error_and_exit(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 or (int(time.time() - self.token_timer) > self.token_limit): + 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, request_headers=None, force=False, paginated=False): + 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_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 + + def execute_compute_paginated(self, action, endpoint, query_params=None, body_params=None, paginated=False): self.suppress_warnings_when_verify_false() self.check_extend_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 @@ -51,20 +96,11 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None, 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 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) - # Add User-Agent to the headers - request_headers['User-Agent'] = self.user_agent - api_response = self.session.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout) + 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: @@ -84,23 +120,14 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None, yield api_response.content.decode('utf-8') return 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)) + 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: - yield from results # or continue - return 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) yield from result offset += limit more = bool(offset < total_count) @@ -109,8 +136,6 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None, 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)) - if force: - return 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 @@ -129,8 +154,8 @@ def error_and_exit(cls, error_code, error_message='', system_message=''): # various API def version(self): - return next(self.execute_compute('GET', 'api/v1/version')) + return self.execute_compute('GET', 'api/v1/version') def ping(self): # unauthenticated call - return self.session.get(f'https://{self.api_compute}/api/v1/_ping').text + return self.session_compute.get(f'https://{self.api_compute}/api/v1/_ping').text diff --git a/prismacloud/api/pc_lib_api.py b/prismacloud/api/pc_lib_api.py index d986a43..1c1265e 100644 --- a/prismacloud/api/pc_lib_api.py +++ b/prismacloud/api/pc_lib_api.py @@ -56,10 +56,16 @@ def __init__(self): self.user_agent = 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) - # s.mount('http://', ) + # 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) @@ -100,6 +106,8 @@ def configure(self, settings, use_meta_info=True): 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. diff --git a/prismacloud/api/pccs/pccs.py b/prismacloud/api/pccs/pccs.py index ab5136b..497e6de 100644 --- a/prismacloud/api/pccs/pccs.py +++ b/prismacloud/api/pccs/pccs.py @@ -9,14 +9,50 @@ 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): + 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, 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: @@ -34,8 +70,6 @@ def execute_code_security(self, action, endpoint, query_params=None, body_params 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) @@ -59,16 +93,9 @@ def execute_code_security(self, action, endpoint, query_params=None, body_params 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)) + 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 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']) @@ -82,8 +109,6 @@ def execute_code_security(self, action, endpoint, query_params=None, body_params 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 From 0bf0c33ff6c299d044e73611bc3ce041fa0615be Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Mon, 3 Mar 2025 15:16:44 -0700 Subject: [PATCH 17/34] split paginated from single results --- prismacloud/api/cspm/_endpoints.py | 6 +++--- prismacloud/api/cspm/cspm.py | 2 +- prismacloud/api/cwpp/_audits.py | 6 +++--- prismacloud/api/cwpp/_cloud.py | 6 +++--- prismacloud/api/cwpp/_collections.py | 4 ++-- prismacloud/api/cwpp/_containers.py | 6 +++--- prismacloud/api/cwpp/_defenders.py | 6 +++--- prismacloud/api/cwpp/_hosts.py | 6 +++--- prismacloud/api/cwpp/_images.py | 12 ++++++------ prismacloud/api/cwpp/_registry.py | 12 ++++++------ prismacloud/api/cwpp/_scans.py | 12 ++++++------ prismacloud/api/cwpp/_serverless.py | 4 ++-- prismacloud/api/cwpp/_stats.py | 12 ++++++------ prismacloud/api/cwpp/_vms.py | 2 +- prismacloud/api/cwpp/cwpp.py | 2 +- prismacloud/api/pccs/_errors.py | 2 +- prismacloud/api/pccs/pccs.py | 2 +- 17 files changed, 51 insertions(+), 51 deletions(-) diff --git a/prismacloud/api/cspm/_endpoints.py b/prismacloud/api/cspm/_endpoints.py index 747f354..4daff3c 100644 --- a/prismacloud/api/cspm/_endpoints.py +++ b/prismacloud/api/cspm/_endpoints.py @@ -42,7 +42,7 @@ 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) + return self.execute_paginated('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) @@ -51,7 +51,7 @@ 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 next(self.execute('GET', 'alert/csv/%s/download' % csv_report_id)) + return self.execute('GET', 'alert/csv/%s/download' % csv_report_id) """ Policies @@ -531,7 +531,7 @@ def compliance_report_delete(self, report_id): return self.execute('DELETE', 'report/%s' % report_id) def compliance_report_download(self, report_id): - return next(self.execute('GET', 'report/%s/download' % report_id)) + return self.execute('GET', 'report/%s/download' % report_id) # TODO: # if response_status == 204: # # download pending diff --git a/prismacloud/api/cspm/cspm.py b/prismacloud/api/cspm/cspm.py index 4462051..155f110 100644 --- a/prismacloud/api/cspm/cspm.py +++ b/prismacloud/api/cspm/cspm.py @@ -90,7 +90,7 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request 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_paginated(self, action, endpoint, query_params=None, body_params=None, request_headers=None, paginated=False): + def execute_paginated(self, action, endpoint, query_params=None, body_params=None, request_headers=None, paginated=True): self.suppress_warnings_when_verify_false() if not self.token: self.login() diff --git a/prismacloud/api/cwpp/_audits.py b/prismacloud/api/cwpp/_audits.py index 49af2ab..7b7fd17 100644 --- a/prismacloud/api/cwpp/_audits.py +++ b/prismacloud/api/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, paginated=True) 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, paginated=True) 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, paginated=True) return audits # Compute > Manage > History diff --git a/prismacloud/api/cwpp/_cloud.py b/prismacloud/api/cwpp/_cloud.py index f451848..cdb0c5e 100644 --- a/prismacloud/api/cwpp/_cloud.py +++ b/prismacloud/api/cwpp/_cloud.py @@ -11,7 +11,7 @@ def cloud_discovery_read(self): 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 next(self.execute_compute('GET', 'api/v1/cloud/discovery/download', 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') @@ -20,7 +20,7 @@ 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) + return self.execute_compute_paginated('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, paginated=True) + return self.execute_compute_paginated('GET', 'api/v1/cloud/discovery/entities', query_params=query_params, paginated=True) diff --git a/prismacloud/api/cwpp/_collections.py b/prismacloud/api/cwpp/_collections.py index 8847c87..e09207d 100644 --- a/prismacloud/api/cwpp/_collections.py +++ b/prismacloud/api/cwpp/_collections.py @@ -6,10 +6,10 @@ 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) + return self.execute_compute_paginated('GET', 'api/v1/collections', query_params=query_params, paginated=True) 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, paginated=True) # 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/prismacloud/api/cwpp/_containers.py b/prismacloud/api/cwpp/_containers.py index c724a2a..366369c 100644 --- a/prismacloud/api/cwpp/_containers.py +++ b/prismacloud/api/cwpp/_containers.py @@ -7,11 +7,11 @@ class ContainersPrismaCloudAPICWPPMixin: 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) + containers = self.execute_compute_paginated('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) + containers = self.execute_compute_paginated('GET', 'api/v1/containers?', query_params=query_params, paginated=True) return containers def containers_download(self, query_params=None): - containers = next(self.execute_compute('GET', 'api/v1/containers/download?', query_params=query_params)) + containers = self.execute_compute('GET', 'api/v1/containers/download?', query_params=query_params) return containers diff --git a/prismacloud/api/cwpp/_defenders.py b/prismacloud/api/cwpp/_defenders.py index e6ea438..80b4499 100644 --- a/prismacloud/api/cwpp/_defenders.py +++ b/prismacloud/api/cwpp/_defenders.py @@ -6,13 +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, paginated=True) 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, paginated=True) return defenders def defenders_download(self, query_params=None): - defenders = next(self.execute_compute('GET', 'api/v1/defenders/download?', query_params=query_params)) + defenders = self.execute_compute('GET', 'api/v1/defenders/download?', query_params=query_params) return defenders diff --git a/prismacloud/api/cwpp/_hosts.py b/prismacloud/api/cwpp/_hosts.py index 35cc7d8..78bfb3d 100644 --- a/prismacloud/api/cwpp/_hosts.py +++ b/prismacloud/api/cwpp/_hosts.py @@ -7,15 +7,15 @@ 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, paginated=True) 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, paginated=True) return hosts def hosts_download(self, query_params=None): - hosts = next(self.execute_compute('GET', 'api/v1/hosts/download?', query_params=query_params)) + hosts = self.execute_compute('GET', 'api/v1/hosts/download?', query_params=query_params) return hosts def hosts_scan(self): diff --git a/prismacloud/api/cwpp/_images.py b/prismacloud/api/cwpp/_images.py index ad29e58..0cfbf6b 100644 --- a/prismacloud/api/cwpp/_images.py +++ b/prismacloud/api/cwpp/_images.py @@ -5,13 +5,13 @@ 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, paginated=True) return images def images_download(self, query_params=None): - images = next(self.execute_compute('GET', 'api/v1/images/download?', query_params=query_params)) + images = self.execute_compute('GET', 'api/v1/images/download?', query_params=query_params) return images diff --git a/prismacloud/api/cwpp/_registry.py b/prismacloud/api/cwpp/_registry.py index 19a8240..7ac5dc7 100644 --- a/prismacloud/api/cwpp/_registry.py +++ b/prismacloud/api/cwpp/_registry.py @@ -6,14 +6,14 @@ class RegistryPrismaCloudAPICWPPMixin: """ Prisma Cloud Compute API Images Endpoints Class """ def registry_download(self, query_params=None): - registries = next(self.execute_compute('GET', 'api/v1/registry/download?', query_params=query_params)) + registries = self.execute_compute('GET', 'api/v1/registry/download?', query_params=query_params) return registries - 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_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', paginated=True) return images def registry_list_image_names(self, query_params=None): diff --git a/prismacloud/api/cwpp/_scans.py b/prismacloud/api/cwpp/_scans.py index b8ddb6d..3c18063 100644 --- a/prismacloud/api/cwpp/_scans.py +++ b/prismacloud/api/cwpp/_scans.py @@ -5,13 +5,13 @@ 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) + 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', paginated=True) return images def scans_download(self, query_params=None): - scans = next(self.execute_compute('GET', 'api/v1/scans/download?', query_params=query_params)) + scans = self.execute_compute('GET', 'api/v1/scans/download?', query_params=query_params) return scans diff --git a/prismacloud/api/cwpp/_serverless.py b/prismacloud/api/cwpp/_serverless.py index 7295709..f4bd7f1 100644 --- a/prismacloud/api/cwpp/_serverless.py +++ b/prismacloud/api/cwpp/_serverless.py @@ -3,12 +3,12 @@ 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) + result = self.execute_compute_paginated('GET', 'api/v1/serverless', query_params=query_params, paginated=True) return result # Download serverless function scan results def serverless_download(self, query_params=None): - result = next(self.execute_compute('GET', 'api/v1/serverless/download?', query_params=query_params)) + result = self.execute_compute('GET', 'api/v1/serverless/download?', query_params=query_params) return result # Start serverless function scan diff --git a/prismacloud/api/cwpp/_stats.py b/prismacloud/api/cwpp/_stats.py index b5459bb..5d6bc6b 100644 --- a/prismacloud/api/cwpp/_stats.py +++ b/prismacloud/api/cwpp/_stats.py @@ -12,7 +12,7 @@ def stats_compliance_read(self, query_params=None): return self.execute_compute('GET', 'api/v1/stats/compliance?', query_params=query_params) def stats_compliance_download(self, query_params=None): - return next(self.execute_compute('GET', 'api/v1/stats/compliance/download?', query_params=query_params)) + return self.execute_compute('GET', 'api/v1/stats/compliance/download?', query_params=query_params) def stats_compliance_refresh(self, query_params=None): # Refreshes the current day's list and counts of compliance issues, as well as the list of affected running resources. @@ -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_paginated('GET', 'api/v1/stats/daily', paginated=True) def stats_trends_read(self): # Returns statistics about the resources protected by Prisma Cloud Compute, @@ -38,19 +38,19 @@ 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) + return self.execute_compute_paginated('GET', 'api/v1/stats/vulnerabilities?', query_params=query_params, paginated=True) def stats_vulnerabilities_download(self, query_params=None): - return next(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_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) def stats_vulnerabilities_impacted_resources_download(self, query_params=None): - return next(self.execute_compute('GET', 'api/v1/stats/vulnerabilities/impacted-resources/download?', query_params=query_params)) + 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_paginated('GET', 'api/v1/stats/vulnerabilities/refresh?', query_params=query_params, paginated=True) diff --git a/prismacloud/api/cwpp/_vms.py b/prismacloud/api/cwpp/_vms.py index e82d8d4..080845c 100644 --- a/prismacloud/api/cwpp/_vms.py +++ b/prismacloud/api/cwpp/_vms.py @@ -8,6 +8,6 @@ class VMsPrismaCloudAPICWPPMixin: # VM Image table in Monitor > Vulnerabilities > Hosts > VMs def vms_list_read(self, query_params=None): - vms = self.execute_compute( + vms = self.execute_compute_paginated( 'GET', 'api/v1/vms', query_params=query_params, paginated=True) return vms \ No newline at end of file diff --git a/prismacloud/api/cwpp/cwpp.py b/prismacloud/api/cwpp/cwpp.py index b525edf..ddec8fc 100644 --- a/prismacloud/api/cwpp/cwpp.py +++ b/prismacloud/api/cwpp/cwpp.py @@ -77,7 +77,7 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None) 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 - def execute_compute_paginated(self, action, endpoint, query_params=None, body_params=None, paginated=False): + def execute_compute_paginated(self, action, endpoint, query_params=None, body_params=None, paginated=True): self.suppress_warnings_when_verify_false() self.check_extend_login_compute() if body_params: diff --git a/prismacloud/api/pccs/_errors.py b/prismacloud/api/pccs/_errors.py index 4c1a856..a42c1de 100644 --- a/prismacloud/api/pccs/_errors.py +++ b/prismacloud/api/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, paginated=True) 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/pccs.py b/prismacloud/api/pccs/pccs.py index 497e6de..1fa1b62 100644 --- a/prismacloud/api/pccs/pccs.py +++ b/prismacloud/api/pccs/pccs.py @@ -47,7 +47,7 @@ def execute_code_security(self, action, endpoint, query_params=None, 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, paginated=False): + def execute_code_security_paginated(self, action, endpoint, query_params=None, body_params=None, request_headers=None, paginated=True): self.suppress_warnings_when_verify_false() if not self.token: self.login() From cfb75c32c8cb00635efe548cce0579044bbe1a9f Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 4 Mar 2025 11:39:16 -0700 Subject: [PATCH 18/34] split execute and execute_paginated from single function, to allow for iterators. Add a few APIs, compliance posture, permission groups, template --- prismacloud/api/cspm/_endpoints.py | 202 +++++++++++++++++++++++++-- prismacloud/api/cspm/cspm.py | 39 +++--- prismacloud/api/cwpp/_audits.py | 6 +- prismacloud/api/cwpp/_cloud.py | 4 +- prismacloud/api/cwpp/_collections.py | 7 +- prismacloud/api/cwpp/_containers.py | 4 +- prismacloud/api/cwpp/_defenders.py | 4 +- prismacloud/api/cwpp/_hosts.py | 4 +- prismacloud/api/cwpp/_images.py | 2 +- prismacloud/api/cwpp/_registry.py | 2 +- prismacloud/api/cwpp/_scans.py | 2 +- prismacloud/api/cwpp/_serverless.py | 16 ++- prismacloud/api/cwpp/_stats.py | 13 +- prismacloud/api/cwpp/_vms.py | 3 +- prismacloud/api/cwpp/cwpp.py | 51 +++---- prismacloud/api/pc_lib_api.py | 3 + prismacloud/api/pccs/_errors.py | 2 +- prismacloud/api/pccs/pccs.py | 35 ++--- 18 files changed, 283 insertions(+), 116 deletions(-) diff --git a/prismacloud/api/cspm/_endpoints.py b/prismacloud/api/cspm/_endpoints.py index 4daff3c..abe2b47 100644 --- a/prismacloud/api/cspm/_endpoints.py +++ b/prismacloud/api/cspm/_endpoints.py @@ -39,10 +39,12 @@ def current_user(self): """ 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): - return self.execute_paginated('POST', 'v2/alert', query_params=query_params, body_params=body_params, paginated=True) + # returns items in results['items']. But really does not respect paginatin request. + return self.execute_paginated('POST', 'v2/alert', query_params=query_params, body_params=body_params) def alert_csv_create(self, body_params=None): return self.execute('POST', 'alert/csv', body_params=body_params) @@ -120,6 +122,76 @@ def saved_search_read(self, saved_search_id, message=None): 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): + """Get Compliance Statistics Breakdown V2 + `PAN Api docs `_ + """ + return self.execute('GET', 'v2/compliance/posture') + + 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): + """Get Compliance Statistics for Standard ID V2 + `PAN Api docs `_ + """ + return self.execute('GET', f'v2/compliance/posture/{compliance_id}') + + 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): + """Get Compliance Statistics for Requirement ID V2 + `PAN Api docs `_ + """ + return self.execute('GET', f'v2/compliance/posture/{compliance_id}/{requirement_id}') + + 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): + """Get Compliance Trend V2 + `PAN Api docs `_ + """ + return self.execute('GET', 'v2/compliance/posture/trend') + + 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): + """Get Compliance Trend for Requirement ID V2 + `PAN Api docs `_ + """ + return self.execute('GET', f'v2/compliance/posture/trend/{compliance_id}/{requirement_id}') + + 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 @@ -471,6 +543,11 @@ def integration_list_read(self): 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 @@ -531,15 +608,29 @@ 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: + """ + 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}') + """ Search @@ -683,13 +774,13 @@ def search_event_read(self, search_params, subsearch=None): search_url = 'search/event' if subsearch and subsearch in ['aggregate', 'filtered']: search_url = 'search/event/%s' % subsearch - api_response = self.execute( + 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( + 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']) @@ -699,13 +790,13 @@ def search_event_read(self, search_params, subsearch=None): def search_iam_read(self, search_params): result = [] next_page_token = None - api_response = self.execute( + 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( + 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: @@ -781,6 +872,41 @@ def saml_config_create(self, 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 @@ -823,3 +949,51 @@ def anomaly_settings_config(self, body_params, policy_id): def check(self): return self.execute('GET', 'check') + + """ + Background jobs, Reports + """ + def report_metadata(self, query_params=None): + """ + This endpoint is available on the Prisma Cloud Darwin release only. + + Get Reports Metadata + `PAN Api docs `_ + """ + return self.execute('GET', 'report-service/api/v1/report', query_params=query_params) + + + + """ + 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') diff --git a/prismacloud/api/cspm/cspm.py b/prismacloud/api/cspm/cspm.py index 155f110..3e3a36a 100644 --- a/prismacloud/api/cspm/cspm.py +++ b/prismacloud/api/cspm/cspm.py @@ -30,6 +30,8 @@ def login(self, url=None): 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_exit(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) @@ -90,7 +92,7 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request 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_paginated(self, action, endpoint, query_params=None, body_params=None, request_headers=None, paginated=True): + 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() @@ -98,8 +100,8 @@ def execute_paginated(self, action, endpoint, query_params=None, body_params=Non 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 - results = [] while more is True: if int(time.time() - self.token_timer) > self.token_limit: self.extend_login() @@ -112,37 +114,40 @@ def execute_paginated(self, action, endpoint, query_params=None, body_params=Non 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 = 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 + return if api_response.headers.get('Content-Type') == 'application/x-gzip': - return api_response.content + # 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') + #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_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']: - self.debug_print('Retrieving Next Page of Results') - body_params = {'pageToken': result['nextPageToken']} - more = True - else: - more = False + 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: - return result + 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_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 + return # Exit handler (Error). @classmethod diff --git a/prismacloud/api/cwpp/_audits.py b/prismacloud/api/cwpp/_audits.py index 7b7fd17..c7f2ed3 100644 --- a/prismacloud/api/cwpp/_audits.py +++ b/prismacloud/api/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_paginated('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_paginated('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_paginated('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/prismacloud/api/cwpp/_cloud.py b/prismacloud/api/cwpp/_cloud.py index cdb0c5e..994fbc7 100644 --- a/prismacloud/api/cwpp/_cloud.py +++ b/prismacloud/api/cwpp/_cloud.py @@ -20,7 +20,7 @@ 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, paginated=True) + 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, paginated=True) + return self.execute_compute_paginated('GET', 'api/v1/cloud/discovery/entities', query_params=query_params) diff --git a/prismacloud/api/cwpp/_collections.py b/prismacloud/api/cwpp/_collections.py index e09207d..a262a57 100644 --- a/prismacloud/api/cwpp/_collections.py +++ b/prismacloud/api/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_paginated('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_paginated('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/prismacloud/api/cwpp/_containers.py b/prismacloud/api/cwpp/_containers.py index 366369c..f46d1d0 100644 --- a/prismacloud/api/cwpp/_containers.py +++ b/prismacloud/api/cwpp/_containers.py @@ -7,9 +7,9 @@ class ContainersPrismaCloudAPICWPPMixin: 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, paginated=True) + 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, paginated=True) + containers = self.execute_compute_paginated('GET', 'api/v1/containers?', query_params=query_params) return containers def containers_download(self, query_params=None): diff --git a/prismacloud/api/cwpp/_defenders.py b/prismacloud/api/cwpp/_defenders.py index 80b4499..cb465c5 100644 --- a/prismacloud/api/cwpp/_defenders.py +++ b/prismacloud/api/cwpp/_defenders.py @@ -6,11 +6,11 @@ class DefendersPrismaCloudAPICWPPMixin: """ Prisma Cloud Compute API Defenders Endpoints Class """ def defenders_list_read(self, query_params=None): - defenders = self.execute_compute_paginated('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_paginated('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): diff --git a/prismacloud/api/cwpp/_hosts.py b/prismacloud/api/cwpp/_hosts.py index 78bfb3d..39439ff 100644 --- a/prismacloud/api/cwpp/_hosts.py +++ b/prismacloud/api/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_paginated('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_paginated('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/prismacloud/api/cwpp/_images.py index 0cfbf6b..8ea731c 100644 --- a/prismacloud/api/cwpp/_images.py +++ b/prismacloud/api/cwpp/_images.py @@ -9,7 +9,7 @@ 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, paginated=True) + 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/_registry.py b/prismacloud/api/cwpp/_registry.py index 7ac5dc7..3ac8591 100644 --- a/prismacloud/api/cwpp/_registry.py +++ b/prismacloud/api/cwpp/_registry.py @@ -13,7 +13,7 @@ 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', paginated=True) + 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/prismacloud/api/cwpp/_scans.py b/prismacloud/api/cwpp/_scans.py index 3c18063..b3a1d0c 100644 --- a/prismacloud/api/cwpp/_scans.py +++ b/prismacloud/api/cwpp/_scans.py @@ -9,7 +9,7 @@ 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', paginated=True) + images = self.execute_compute_paginated('GET', 'api/v1/scans?filterBaseImage=true') return images def scans_download(self, query_params=None): diff --git a/prismacloud/api/cwpp/_serverless.py b/prismacloud/api/cwpp/_serverless.py index f4bd7f1..a1c60e1 100644 --- a/prismacloud/api/cwpp/_serverless.py +++ b/prismacloud/api/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_paginated('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/_stats.py b/prismacloud/api/cwpp/_stats.py index 5d6bc6b..e2bcbad 100644 --- a/prismacloud/api/cwpp/_stats.py +++ b/prismacloud/api/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_paginated('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,20 @@ 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_paginated('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_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_resources_download(self, query_params=None): - return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/impacted-resources/download?', query_params=query_params) + 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_paginated('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) diff --git a/prismacloud/api/cwpp/_vms.py b/prismacloud/api/cwpp/_vms.py index 080845c..f642973 100644 --- a/prismacloud/api/cwpp/_vms.py +++ b/prismacloud/api/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_paginated( - '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/prismacloud/api/cwpp/cwpp.py b/prismacloud/api/cwpp/cwpp.py index ddec8fc..f380f0d 100644 --- a/prismacloud/api/cwpp/cwpp.py +++ b/prismacloud/api/cwpp/cwpp.py @@ -77,7 +77,7 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None) 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 - def execute_compute_paginated(self, action, endpoint, query_params=None, body_params=None, paginated=True): + 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: @@ -85,17 +85,13 @@ def execute_compute_paginated(self, action, endpoint, query_params=None, body_pa 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. + # Pagination is via query parameters for both GET and POST, and the limit has a maximum of 100. offset = 0 - limit = 50 - more = False - results = [] - while offset == 0 or more is True: + limit = 100 + more = True + while more is True: self.check_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) + 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) @@ -103,37 +99,36 @@ def execute_compute_paginated(self, action, endpoint, query_params=None, body_pa 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: - yield None return if api_response.headers.get('Content-Type') == 'application/x-gzip': - yield api_response.content - return + # return api_response.content + raise RuntimeError("please use .execute instead of execute_paginated") if api_response.headers.get('Content-Type') == 'text/csv': - yield api_response.content.decode('utf-8') - return + # return api_response.content.decode('utf-8') + raise RuntimeError("please use .execute instead of execute_paginated") try: - result = api_response.json() + 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_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: - yield from result - offset += limit - more = bool(offset < total_count) + self.debug_print(f'Retrieving Next Page of Results: Offset/Total Count: {offset}/{total_count}') else: - yield result + 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_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)) diff --git a/prismacloud/api/pc_lib_api.py b/prismacloud/api/pc_lib_api.py index 1c1265e..6719644 100644 --- a/prismacloud/api/pc_lib_api.py +++ b/prismacloud/api/pc_lib_api.py @@ -41,9 +41,12 @@ 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"]) diff --git a/prismacloud/api/pccs/_errors.py b/prismacloud/api/pccs/_errors.py index a42c1de..56299cb 100644 --- a/prismacloud/api/pccs/_errors.py +++ b/prismacloud/api/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_paginated('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/pccs.py b/prismacloud/api/pccs/pccs.py index 1fa1b62..e0a33aa 100644 --- a/prismacloud/api/pccs/pccs.py +++ b/prismacloud/api/pccs/pccs.py @@ -47,7 +47,7 @@ def execute_code_security(self, action, endpoint, query_params=None, 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, paginated=True): + 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() @@ -60,31 +60,19 @@ def execute_code_security_paginated(self, action, endpoint, query_params=None, b # 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 + limit = 100 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) + 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) - # Add User-Agent to the headers - request_headers['User-Agent'] = self.user_agent 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.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 @@ -97,20 +85,17 @@ def execute_code_security_paginated(self, action, endpoint, query_params=None, b 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)) - 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 + yield from result['data'] + if 'hasNext' in result: + self.debug_print('Retrieving Next Page of Results') + offset += limit + more = result['hasNext'] else: - return result + 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 results + return # Exit handler (Error). From 6ac92f7391eee5dc7730edac24465b562ce08020 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 4 Mar 2025 11:58:00 -0700 Subject: [PATCH 19/34] Trying to use git-based versioning --- prismacloud/api/version.py | 4 +++- pyproject.toml | 30 ++++++++++++++++++++++++++-- setup.py | 40 +++----------------------------------- 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/prismacloud/api/version.py b/prismacloud/api/version.py index 65d1568..b51d697 100644 --- a/prismacloud/api/version.py +++ b/prismacloud/api/version.py @@ -1 +1,3 @@ -version = "5.2.27" +import importlib.metadata +version = importlib.metadata.version("prismacloud") +# version = "5.2.27" diff --git a/pyproject.toml b/pyproject.toml index 7fd26b9..986406e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,29 @@ [build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" \ No newline at end of file +requires = ["setuptools>=56.2", "setuptools_scm[toml]>=7"] +build-backend = "setuptools.build_meta" + +[project] +name = "prismacloud-api" +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" +keywords = ["prisma", "cloud", "api", "prismacloud"] +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" + diff --git a/setup.py b/setup.py index d034b5d..7f1a176 100644 --- a/setup.py +++ b/setup.py @@ -1,38 +1,4 @@ -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='Loic Jaquemet', - author_email='trolldbois', - description='Prisma Cloud API SDK for Python - loic version', - keywords="prisma cloud api", - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/trolldbois/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() From 3d97f1447654dd0bfcc7303923957443ba06d9f5 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 4 Mar 2025 12:17:50 -0700 Subject: [PATCH 20/34] simplify package --- prismacloud/api/version.py | 3 --- {prismacloud/api => prismacloudapi}/README.md | 0 {prismacloud/api => prismacloudapi}/__init__.py | 8 ++------ {prismacloud/api => prismacloudapi}/cspm/README.md | 0 {prismacloud/api => prismacloudapi}/cspm/__init__.py | 0 {prismacloud/api => prismacloudapi}/cspm/_endpoints.py | 0 {prismacloud/api => prismacloudapi}/cspm/_extended.py | 0 {prismacloud/api => prismacloudapi}/cspm/cspm.py | 0 {prismacloud/api => prismacloudapi}/cwpp/README.md | 0 {prismacloud/api => prismacloudapi}/cwpp/__init__.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_audits.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_cloud.py | 0 .../api => prismacloudapi}/cwpp/_collections.py | 0 .../api => prismacloudapi}/cwpp/_containers.py | 0 .../api => prismacloudapi}/cwpp/_credentials.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_defenders.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_feeds.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_hosts.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_images.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_logs.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_policies.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_registry.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_scans.py | 0 .../api => prismacloudapi}/cwpp/_serverless.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_settings.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_stats.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_status.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_tags.py | 0 {prismacloud/api => prismacloudapi}/cwpp/_vms.py | 0 {prismacloud/api => prismacloudapi}/cwpp/cwpp.py | 0 {prismacloud/api => prismacloudapi}/pc_lib_api.py | 2 +- {prismacloud/api => prismacloudapi}/pc_lib_utility.py | 10 ---------- {prismacloud/api => prismacloudapi}/pccs/README.md | 0 {prismacloud/api => prismacloudapi}/pccs/__init__.py | 0 .../api => prismacloudapi}/pccs/_checkov_version.py | 0 .../api => prismacloudapi}/pccs/_code_policies.py | 0 {prismacloud/api => prismacloudapi}/pccs/_errors.py | 0 {prismacloud/api => prismacloudapi}/pccs/_fixes.py | 0 {prismacloud/api => prismacloudapi}/pccs/_packages.py | 0 .../api => prismacloudapi}/pccs/_repositories.py | 0 {prismacloud/api => prismacloudapi}/pccs/_rules.py | 0 {prismacloud/api => prismacloudapi}/pccs/_scans.py | 0 .../api => prismacloudapi}/pccs/_suppressions.py | 0 {prismacloud/api => prismacloudapi}/pccs/pccs.py | 0 pyproject.toml | 4 ++-- requirements.txt | 1 - 46 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 prismacloud/api/version.py rename {prismacloud/api => prismacloudapi}/README.md (100%) rename {prismacloud/api => prismacloudapi}/__init__.py (54%) rename {prismacloud/api => prismacloudapi}/cspm/README.md (100%) rename {prismacloud/api => prismacloudapi}/cspm/__init__.py (100%) rename {prismacloud/api => prismacloudapi}/cspm/_endpoints.py (100%) rename {prismacloud/api => prismacloudapi}/cspm/_extended.py (100%) rename {prismacloud/api => prismacloudapi}/cspm/cspm.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/README.md (100%) rename {prismacloud/api => prismacloudapi}/cwpp/__init__.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_audits.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_cloud.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_collections.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_containers.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_credentials.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_defenders.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_feeds.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_hosts.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_images.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_logs.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_policies.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_registry.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_scans.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_serverless.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_settings.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_stats.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_status.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_tags.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/_vms.py (100%) rename {prismacloud/api => prismacloudapi}/cwpp/cwpp.py (100%) rename {prismacloud/api => prismacloudapi}/pc_lib_api.py (98%) rename {prismacloud/api => prismacloudapi}/pc_lib_utility.py (96%) rename {prismacloud/api => prismacloudapi}/pccs/README.md (100%) rename {prismacloud/api => prismacloudapi}/pccs/__init__.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_checkov_version.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_code_policies.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_errors.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_fixes.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_packages.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_repositories.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_rules.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_scans.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/_suppressions.py (100%) rename {prismacloud/api => prismacloudapi}/pccs/pccs.py (100%) diff --git a/prismacloud/api/version.py b/prismacloud/api/version.py deleted file mode 100644 index b51d697..0000000 --- a/prismacloud/api/version.py +++ /dev/null @@ -1,3 +0,0 @@ -import importlib.metadata -version = importlib.metadata.version("prismacloud") -# version = "5.2.27" diff --git a/prismacloud/api/README.md b/prismacloudapi/README.md similarity index 100% rename from prismacloud/api/README.md rename to prismacloudapi/README.md 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..797466a 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 prismacloud.pc_lib_api import PrismaCloudAPI +from prismacloud.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/prismacloud/api/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py similarity index 100% rename from prismacloud/api/cspm/_endpoints.py rename to prismacloudapi/cspm/_endpoints.py 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/prismacloud/api/cspm/cspm.py b/prismacloudapi/cspm/cspm.py similarity index 100% rename from prismacloud/api/cspm/cspm.py rename to prismacloudapi/cspm/cspm.py 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 100% rename from prismacloud/api/cwpp/_audits.py rename to prismacloudapi/cwpp/_audits.py diff --git a/prismacloud/api/cwpp/_cloud.py b/prismacloudapi/cwpp/_cloud.py similarity index 100% rename from prismacloud/api/cwpp/_cloud.py rename to prismacloudapi/cwpp/_cloud.py diff --git a/prismacloud/api/cwpp/_collections.py b/prismacloudapi/cwpp/_collections.py similarity index 100% rename from prismacloud/api/cwpp/_collections.py rename to prismacloudapi/cwpp/_collections.py diff --git a/prismacloud/api/cwpp/_containers.py b/prismacloudapi/cwpp/_containers.py similarity index 100% rename from prismacloud/api/cwpp/_containers.py rename to prismacloudapi/cwpp/_containers.py 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 100% rename from prismacloud/api/cwpp/_defenders.py rename to prismacloudapi/cwpp/_defenders.py 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 100% rename from prismacloud/api/cwpp/_hosts.py rename to prismacloudapi/cwpp/_hosts.py diff --git a/prismacloud/api/cwpp/_images.py b/prismacloudapi/cwpp/_images.py similarity index 100% rename from prismacloud/api/cwpp/_images.py rename to prismacloudapi/cwpp/_images.py 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 100% rename from prismacloud/api/cwpp/_registry.py rename to prismacloudapi/cwpp/_registry.py diff --git a/prismacloud/api/cwpp/_scans.py b/prismacloudapi/cwpp/_scans.py similarity index 100% rename from prismacloud/api/cwpp/_scans.py rename to prismacloudapi/cwpp/_scans.py diff --git a/prismacloud/api/cwpp/_serverless.py b/prismacloudapi/cwpp/_serverless.py similarity index 100% rename from prismacloud/api/cwpp/_serverless.py rename to prismacloudapi/cwpp/_serverless.py 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 100% rename from prismacloud/api/cwpp/_stats.py rename to prismacloudapi/cwpp/_stats.py 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 100% rename from prismacloud/api/cwpp/_vms.py rename to prismacloudapi/cwpp/_vms.py diff --git a/prismacloud/api/cwpp/cwpp.py b/prismacloudapi/cwpp/cwpp.py similarity index 100% rename from prismacloud/api/cwpp/cwpp.py rename to prismacloudapi/cwpp/cwpp.py diff --git a/prismacloud/api/pc_lib_api.py b/prismacloudapi/pc_lib_api.py similarity index 98% rename from prismacloud/api/pc_lib_api.py rename to prismacloudapi/pc_lib_api.py index 6719644..210d60f 100644 --- a/prismacloud/api/pc_lib_api.py +++ b/prismacloudapi/pc_lib_api.py @@ -78,7 +78,7 @@ def configure(self, settings, use_meta_info=True): self.identity = settings.get('identity') self.secret = settings.get('secret') self.verify = settings.get('verify', True) - self.debug = settings.get('debug', False) + self.debug = True # settings.get('debug', False) # FIXME self.user_agent = settings.get('user_agent', self.user_agent) # # self.logger = settings['logger'] 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..271cd5a 100644 --- a/prismacloud/api/pc_lib_utility.py +++ b/prismacloudapi/pc_lib_utility.py @@ -8,7 +8,6 @@ import os import sys -from update_checker import UpdateChecker from .version import version as api_version try: @@ -38,14 +37,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 +101,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 100% rename from prismacloud/api/pccs/_errors.py rename to prismacloudapi/pccs/_errors.py 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/prismacloud/api/pccs/pccs.py b/prismacloudapi/pccs/pccs.py similarity index 100% rename from prismacloud/api/pccs/pccs.py rename to prismacloudapi/pccs/pccs.py diff --git a/pyproject.toml b/pyproject.toml index 986406e..f28b951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=56.2", "setuptools_scm[toml]>=7"] build-backend = "setuptools.build_meta" [project] -name = "prismacloud-api" +name = "prismacloudapi" authors = [ { name = "Loic Jaquemet", email = "loic.jaquemet+python@gmail.com" }, { name = "Tom Kishel", email = "tkishel@paloaltonetworks.com" } @@ -11,7 +11,7 @@ authors = [ 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" -keywords = ["prisma", "cloud", "api", "prismacloud"] +keywords = ["prisma", "cloud", "api", "prismacloud", "prismacloudapi"] classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", 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 From fa2c9fe3abc6c689d2bcaa2e93c7e891b36ad2d4 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 4 Mar 2025 12:18:19 -0700 Subject: [PATCH 21/34] revert debug --- prismacloudapi/pc_lib_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prismacloudapi/pc_lib_api.py b/prismacloudapi/pc_lib_api.py index 210d60f..3851376 100644 --- a/prismacloudapi/pc_lib_api.py +++ b/prismacloudapi/pc_lib_api.py @@ -78,7 +78,7 @@ def configure(self, settings, use_meta_info=True): self.identity = settings.get('identity') self.secret = settings.get('secret') self.verify = settings.get('verify', True) - self.debug = True # settings.get('debug', False) # FIXME + self.debug = settings.get('debug', False) # FIXME self.user_agent = settings.get('user_agent', self.user_agent) # # self.logger = settings['logger'] From 24dffc687d00b8c372c6b2c3e3ae7f2aa70f522d Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 4 Mar 2025 12:46:08 -0700 Subject: [PATCH 22/34] make versioning work --- prismacloudapi/pc_lib_api.py | 2 +- pyproject.toml | 4 +++- setup.py | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/prismacloudapi/pc_lib_api.py b/prismacloudapi/pc_lib_api.py index 3851376..6719644 100644 --- a/prismacloudapi/pc_lib_api.py +++ b/prismacloudapi/pc_lib_api.py @@ -78,7 +78,7 @@ def configure(self, settings, use_meta_info=True): self.identity = settings.get('identity') self.secret = settings.get('secret') self.verify = settings.get('verify', True) - self.debug = settings.get('debug', False) # FIXME + self.debug = settings.get('debug', False) self.user_agent = settings.get('user_agent', self.user_agent) # # self.logger = settings['logger'] diff --git a/pyproject.toml b/pyproject.toml index f28b951..39e1439 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=56.2", "setuptools_scm[toml]>=7"] +requires = ["setuptools", "setuptools_scm", "setuptools_scm[toml]"] build-backend = "setuptools.build_meta" [project] @@ -27,3 +27,5 @@ 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/setup.py b/setup.py index 7f1a176..bbdee35 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,7 @@ from setuptools import setup if __name__ == "__main__": - setup() + setup( + name="prismacloudapi", + use_scm_version=True, + setup_requires=["setuptools_scm"]) From 971ced125740d089566715518a0f0bdb9a01b4dc Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 4 Mar 2025 12:51:40 -0700 Subject: [PATCH 23/34] change package name --- prismacloudapi/__init__.py | 4 ++-- scripts/examples/pcs_vuln_container_with_cve_2022_22965.py | 2 +- scripts/pcs_account_groups_by_tags.py | 2 +- scripts/pcs_agentless_logs.py | 2 +- scripts/pcs_alert_rule_add_compliance_policies.py | 2 +- scripts/pcs_alert_rule_export.py | 2 +- scripts/pcs_alert_rule_import.py | 2 +- scripts/pcs_alerts_read.py | 2 +- scripts/pcs_apis_ingested.py | 2 +- scripts/pcs_cloud_account_import_azure.py | 2 +- scripts/pcs_cloud_account_inventory.py | 2 +- scripts/pcs_cloud_discovery_defense_stats.py | 2 +- scripts/pcs_compliance_alerts_read.py | 2 +- scripts/pcs_compliance_export.py | 2 +- scripts/pcs_compliance_import.py | 2 +- scripts/pcs_compliance_uuid_read.py | 2 +- scripts/pcs_compute_container_observed_connections.py | 2 +- scripts/pcs_compute_endpoint_client.py | 2 +- scripts/pcs_compute_forward_to_siem.py | 2 +- scripts/pcs_configure.py | 2 +- scripts/pcs_container_count.py | 2 +- scripts/pcs_container_csp.py | 2 +- scripts/pcs_container_vulnerabilities_on_running_hosts.py | 2 +- scripts/pcs_container_vulnerabilities_read.py | 2 +- scripts/pcs_cs_errors_for_file.py | 2 +- scripts/pcs_cs_repositories_read.py | 2 +- scripts/pcs_current_registry_scan.py | 2 +- scripts/pcs_defender_report_by_cloud_account.py | 2 +- scripts/pcs_forensics_download.py | 2 +- scripts/pcs_hosts_vulnerabilities_read.py | 2 +- scripts/pcs_images_packages_read.py | 2 +- scripts/pcs_incident_archiver.py | 2 +- scripts/pcs_outdated_defenders.py | 2 +- scripts/pcs_policy_custom_export.py | 2 +- scripts/pcs_policy_custom_import.py | 2 +- scripts/pcs_policy_read.py | 2 +- scripts/pcs_policy_set_status.py | 2 +- scripts/pcs_posture_endpoint_client.py | 2 +- scripts/pcs_resources_export.py | 2 +- scripts/pcs_rotate_service_account_access_key.py | 2 +- scripts/pcs_rql_query.py | 2 +- scripts/pcs_script_example.py | 2 +- scripts/pcs_sync_azure_accounts.py | 2 +- scripts/pcs_sync_registries.py | 2 +- scripts/pcs_usage.py | 2 +- scripts/pcs_user_import.py | 2 +- scripts/pcs_user_update.py | 2 +- scripts/pcs_vuln_container_locations.py | 2 +- scripts/pcs_week_alert_trend.py | 2 +- tests/unit.py | 6 +++--- 50 files changed, 53 insertions(+), 53 deletions(-) diff --git a/prismacloudapi/__init__.py b/prismacloudapi/__init__.py index 797466a..6592acb 100644 --- a/prismacloudapi/__init__.py +++ b/prismacloudapi/__init__.py @@ -2,8 +2,8 @@ import sys -from prismacloud.pc_lib_api import PrismaCloudAPI -from prismacloud.pc_lib_utility import PrismaCloudUtility +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/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/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']) From dd4a8ff98d68e2a7dbcd28bba0aa3e39b233f23f Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 4 Mar 2025 12:56:47 -0700 Subject: [PATCH 24/34] bugfixes --- prismacloudapi/pc_lib_api.py | 9 ++++++--- prismacloudapi/pc_lib_utility.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/prismacloudapi/pc_lib_api.py b/prismacloudapi/pc_lib_api.py index 6719644..ac0e614 100644 --- a/prismacloudapi/pc_lib_api.py +++ b/prismacloudapi/pc_lib_api.py @@ -10,7 +10,11 @@ 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-- # @@ -55,8 +59,7 @@ def __init__(self): 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() diff --git a/prismacloudapi/pc_lib_utility.py b/prismacloudapi/pc_lib_utility.py index 271cd5a..4b286d9 100644 --- a/prismacloudapi/pc_lib_utility.py +++ b/prismacloudapi/pc_lib_utility.py @@ -8,7 +8,6 @@ import os import sys -from .version import version as api_version try: # pylint: disable=redefined-builtin From 20686650b226fb02b28230122d12dca0eb0275ed Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Mon, 10 Mar 2025 09:25:40 -0600 Subject: [PATCH 25/34] Cleanup --- README.md | 49 ++++++++++++++++++++++++++++++++-------- prismacloudapi/README.md | 40 -------------------------------- pyproject.toml | 1 + 3 files changed, 40 insertions(+), 50 deletions(-) delete mode 100644 prismacloudapi/README.md 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/prismacloudapi/README.md b/prismacloudapi/README.md deleted file mode 100644 index 671afde..0000000 --- a/prismacloudapi/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/pyproject.toml b/pyproject.toml index 39e1439..dbef61d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ authors = [ 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", From cb9853f983a0b1a946613cc5cdc9aa1bb723b4d2 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Fri, 21 Mar 2025 16:58:19 -0600 Subject: [PATCH 26/34] add query parameters to cloud_discovery API --- prismacloudapi/cwpp/_cloud.py | 42 +++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/prismacloudapi/cwpp/_cloud.py b/prismacloudapi/cwpp/_cloud.py index 994fbc7..48dd3ab 100644 --- a/prismacloudapi/cwpp/_cloud.py +++ b/prismacloudapi/cwpp/_cloud.py @@ -1,12 +1,30 @@ """ 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_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'} @@ -24,3 +42,23 @@ def cloud_discovery_vms(self, query_params=None): 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) From 94f17fa2536e56f2eabaa104d2070db309281b50 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Mon, 24 Mar 2025 16:07:46 -0600 Subject: [PATCH 27/34] add background jobs endpoints --- prismacloudapi/cspm/_endpoints.py | 81 +++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py index abe2b47..bddf006 100644 --- a/prismacloudapi/cspm/_endpoints.py +++ b/prismacloudapi/cspm/_endpoints.py @@ -38,6 +38,9 @@ def current_user(self): [x] LIST (v2) """ + def alert_filter_suggest(self): + return self.execute('GET', 'filter/alert/suggest') + 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) @@ -55,6 +58,10 @@ def alert_csv_status(self, 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): + return self.execute('GET', 'alert/policy') + + """ Policies @@ -604,8 +611,21 @@ def compliance_report_list_read(self): 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', 'report/%s' % report_id) + return self.execute('DELETE', f'report/{report_id}') def compliance_report_download(self, report_id): """ @@ -631,6 +651,52 @@ def compliance_report_type_get(self, report_id): """ 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 @@ -950,19 +1016,6 @@ def anomaly_settings_config(self, body_params, policy_id): def check(self): return self.execute('GET', 'check') - """ - Background jobs, Reports - """ - def report_metadata(self, query_params=None): - """ - This endpoint is available on the Prisma Cloud Darwin release only. - - Get Reports Metadata - `PAN Api docs `_ - """ - return self.execute('GET', 'report-service/api/v1/report', query_params=query_params) - - """ Notifications Templates From 081db1fc52e091272625f23c9e2676f77e6d5c21 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Fri, 11 Apr 2025 14:39:11 -0600 Subject: [PATCH 28/34] add cspm collections --- prismacloudapi/cspm/_endpoints.py | 108 +++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py index bddf006..861e17d 100644 --- a/prismacloudapi/cspm/_endpoints.py +++ b/prismacloudapi/cspm/_endpoints.py @@ -61,7 +61,6 @@ def alert_csv_download(self, csv_report_id): def alert_count_by_policy(self, query_params): return self.execute('GET', 'alert/policy') - """ Policies @@ -138,6 +137,7 @@ def saved_search_delete(self, saved_search_id): [ ] UPDATE [ ] DELETE """ + def compliance_posture_statistics(self): """Get Compliance Statistics Breakdown V2 `PAN Api docs `_ @@ -196,8 +196,8 @@ def compliance_posture_trend_for_requirement(self, compliance_id, requirement_id 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) - + return self.execute('POST', f'v2/compliance/posture/trend/{compliance_id}/{requirement_id}', + body_params=body_params) """ Compliance Standards @@ -554,7 +554,6 @@ def integration_list(self, tenant_id): # use return self.execute('GET', f'v1/tenant/{tenant_id}/integration') - """ Resource Lists @@ -611,7 +610,7 @@ def compliance_report_list_read(self): 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): + 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 @@ -697,7 +696,6 @@ def jobs_report_download(self, report_id): """ return self.execute('GET', f'report-service/api/v1/report/{report_id}/download') - """ Search @@ -798,8 +796,8 @@ def search_config_read_by_search_id(self, search_id, limit=100, with_resource_js 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): + with_resource_json=None, # time_range=None, + sort=None, next_page_token=None, paginate=True): """ Perform Config Search V2 `PAN Api docs `_ @@ -807,7 +805,7 @@ def search_config_read_v2(self, query, start_time=None, skip_results=None, limit # 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, + withResourceJson=with_resource_json, # timeRange=time_range, skipResult=skip_results, sort=sort, nextPageToken=next_page_token) @@ -827,7 +825,6 @@ def search_config_read_v2(self, query, start_time=None, skip_results=None, limit next_page_token = api_response.pop('nextPageToken', None) return - def search_network_read(self, search_params, filtered=False): search_url = 'search' if filtered: @@ -948,6 +945,7 @@ def oidc_config_read(self): """ Permission groups """ + def permission_group_list(self): """ Get All Permission Groups @@ -955,14 +953,14 @@ def permission_group_list(self): """ return self.execute('GET', 'authz/v1/permission_group') - def permission_group_get(self, group_id, include_associated_roles: bool=None): + 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) + 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): @@ -972,7 +970,6 @@ def permission_group_feature_list(self): """ return self.execute('GET', 'authz/v1/feature') - """ Enterprise Settings @@ -1016,10 +1013,10 @@ def anomaly_settings_config(self, body_params, policy_id): def check(self): return self.execute('GET', 'check') - """ Notifications Templates """ + def templates_list(self): """ List Templates @@ -1037,6 +1034,7 @@ def templates_get(self, template_id): """ Cloud Ingested Logs """ + def aws_eventbridge_configuration_for_account(self, tenant_id, account_id): """ Get AWS Eventbridge configuration details @@ -1049,4 +1047,84 @@ 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') + 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 From 69e0c1486e86cecf69355170ba589c5a9888c40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Tue, 6 May 2025 15:10:47 -0600 Subject: [PATCH 29/34] Various update to parameters, and avoiding SystemExit --- prismacloudapi/cspm/_endpoints.py | 20 ++++++++++---------- prismacloudapi/cspm/cspm.py | 14 +++++++++----- prismacloudapi/cwpp/cwpp.py | 14 +++++++++----- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py index 861e17d..8ef1106 100644 --- a/prismacloudapi/cspm/_endpoints.py +++ b/prismacloudapi/cspm/_endpoints.py @@ -138,41 +138,41 @@ def saved_search_delete(self, saved_search_id): [ ] DELETE """ - def compliance_posture_statistics(self): + def compliance_posture_statistics(self, query_params): """Get Compliance Statistics Breakdown V2 `PAN Api docs `_ """ - return self.execute('GET', 'v2/compliance/posture') + 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): + 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}') + 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): + 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}') + 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): + def compliance_posture_trend(self, query_params): """Get Compliance Trend V2 `PAN Api docs `_ """ - return self.execute('GET', 'v2/compliance/posture/trend') + return self.execute('GET', 'v2/compliance/posture/trend', query_params=query_params) def compliance_posture_trend_post(self, body_params): """Get Compliance Trend V2""" @@ -188,11 +188,11 @@ 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): + 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}') + 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 """ diff --git a/prismacloudapi/cspm/cspm.py b/prismacloudapi/cspm/cspm.py index 3e3a36a..fcf8c47 100644 --- a/prismacloudapi/cspm/cspm.py +++ b/prismacloudapi/cspm/cspm.py @@ -33,7 +33,7 @@ def login(self, url=None): # save tenant_id self.tenant_id = api_response['customerNames'][0]['prismaId'] else: - self.error_and_exit(api_response.status_code, 'API (%s) responded with an error\n%s' % (url, api_response.text)) + 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): @@ -85,11 +85,11 @@ def execute(self, action, endpoint, query_params=None, body_params=None, request 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_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)) + 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_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)) + 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): @@ -130,7 +130,7 @@ def execute_paginated(self, action, endpoint, query_params=None, body_params=Non 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)) + 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']) @@ -146,7 +146,7 @@ def execute_paginated(self, action, endpoint, query_params=None, body_params=Non 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_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)) + 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). @@ -154,6 +154,10 @@ def execute_paginated(self, action, endpoint, query_params=None, body_params=Non 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): diff --git a/prismacloudapi/cwpp/cwpp.py b/prismacloudapi/cwpp/cwpp.py index f380f0d..23255a1 100644 --- a/prismacloudapi/cwpp/cwpp.py +++ b/prismacloudapi/cwpp/cwpp.py @@ -23,7 +23,7 @@ def login_compute(self): self.token_compute_timer = time.time() self.session_compute.headers['Authorization'] = f"Bearer {self.token_compute}" else: - self.error_and_exit(api_response.status_code, + 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): @@ -70,11 +70,11 @@ def execute_compute(self, action, endpoint, query_params=None, body_params=None) 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)) + 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_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)) + 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): @@ -112,7 +112,7 @@ def execute_compute_paginated(self, action, endpoint, query_params=None, body_pa 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_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)) + 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}') @@ -131,7 +131,7 @@ def execute_compute_paginated(self, action, endpoint, query_params=None, body_pa 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_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)) + 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. @@ -146,6 +146,10 @@ def validate_api_compute(self): 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): From 53c85ed0b01e31a740528557246ee5ffadd8cdfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Fri, 9 May 2025 14:28:14 -0600 Subject: [PATCH 30/34] Update params on cloud account groups --- prismacloudapi/cspm/_endpoints.py | 33 +++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py index 8ef1106..01ae733 100644 --- a/prismacloudapi/cspm/_endpoints.py +++ b/prismacloudapi/cspm/_endpoints.py @@ -418,17 +418,38 @@ def cloud_types_list_read(self, query_params=None): [x] DELETE """ - def cloud_account_group_list_read(self): - return self.execute('GET', 'cloud/group') + 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. - def cloud_account_group_create(self, cloud_account_group_to_add): - return self.execute('POST', 'cloud/group', body_params=cloud_account_group_to_add) + `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, 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_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) From 9a7df9efcf685596cd84b0a425736e2dc5066c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Fri, 13 Jun 2025 09:10:27 -0600 Subject: [PATCH 31/34] Add some endpoints --- prismacloudapi/cspm/_endpoints.py | 46 +++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py index 01ae733..8cf5fca 100644 --- a/prismacloudapi/cspm/_endpoints.py +++ b/prismacloudapi/cspm/_endpoints.py @@ -2,7 +2,6 @@ import logging import pprint - # TODO: Split into multiple files, one per endpoint ... # pylint: disable=too-many-public-methods @@ -46,7 +45,7 @@ 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): - # returns items in results['items']. But really does not respect paginatin request. + # 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_csv_create(self, body_params=None): @@ -59,7 +58,20 @@ 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): - return self.execute('GET', 'alert/policy') + """ + `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 @@ -588,6 +600,9 @@ def integration_list(self, tenant_id): 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) @@ -899,6 +914,31 @@ def search_iam_granter_to_dest(self, 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 From effc266fc95a8161e111efc9b6ca30a8aaa62bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Wed, 9 Jul 2025 11:40:23 -0600 Subject: [PATCH 32/34] Add an endpoint for account status --- prismacloudapi/cspm/_endpoints.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py index 8cf5fca..2b9f612 100644 --- a/prismacloudapi/cspm/_endpoints.py +++ b/prismacloudapi/cspm/_endpoints.py @@ -417,6 +417,9 @@ def cloud_account_update(self, cloud_type, cloud_account_id, cloud_account_updat 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) From 4a776d06be9e94be2f8f06f024b8a9b5f0e42621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Mon, 14 Jul 2025 15:40:10 -0600 Subject: [PATCH 33/34] Add an endpoints for inventory --- prismacloudapi/cspm/_endpoints.py | 61 ++++++++++++++++++++++++++++++- prismacloudapi/cwpp/_stats.py | 3 ++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py index 2b9f612..49f8b2b 100644 --- a/prismacloudapi/cspm/_endpoints.py +++ b/prismacloudapi/cspm/_endpoints.py @@ -37,8 +37,11 @@ def current_user(self): [x] LIST (v2) """ - def alert_filter_suggest(self): - return self.execute('GET', 'filter/alert/suggest') + 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 @@ -486,6 +489,7 @@ 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) """ @@ -507,6 +511,26 @@ def asset_inventory_list_read_v3(self, query_params=None): 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 @@ -541,6 +565,39 @@ def resource_scan_info_read(self, body_params=None): 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 diff --git a/prismacloudapi/cwpp/_stats.py b/prismacloudapi/cwpp/_stats.py index e2bcbad..f44bcc2 100644 --- a/prismacloudapi/cwpp/_stats.py +++ b/prismacloudapi/cwpp/_stats.py @@ -55,3 +55,6 @@ 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) + + def stats_assets_summary(self): + return self.execute_compute('GET', 'api/v1/bff/assets/summary') From fde67adc8d34a46fdadecd9f5e9f0e6610357806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Jaquemet?= Date: Mon, 8 Sep 2025 11:56:14 -0600 Subject: [PATCH 34/34] Add an endpoints for alert --- prismacloudapi/cspm/_endpoints.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prismacloudapi/cspm/_endpoints.py b/prismacloudapi/cspm/_endpoints.py index 49f8b2b..2bd1db6 100644 --- a/prismacloudapi/cspm/_endpoints.py +++ b/prismacloudapi/cspm/_endpoints.py @@ -51,6 +51,9 @@ 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)