├── .coverage ├── .github └── workflows │ ├── manual_release.yml │ ├── pylint.yml │ └── pypi.yml ├── .gitignore ├── .pylintrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SUPPORT.md ├── prismacloud └── api │ ├── README.md │ ├── __init__.py │ ├── cspm │ ├── README.md │ ├── __init__.py │ ├── _endpoints.py │ ├── _extended.py │ └── cspm.py │ ├── cwpp │ ├── README.md │ ├── __init__.py │ ├── _audits.py │ ├── _cloud.py │ ├── _collections.py │ ├── _containers.py │ ├── _credentials.py │ ├── _defenders.py │ ├── _feeds.py │ ├── _hosts.py │ ├── _images.py │ ├── _logs.py │ ├── _policies.py │ ├── _registry.py │ ├── _scans.py │ ├── _serverless.py │ ├── _settings.py │ ├── _stats.py │ ├── _status.py │ ├── _tags.py │ ├── _vms.py │ └── cwpp.py │ ├── pc_lib_api.py │ ├── pc_lib_utility.py │ ├── pccs │ ├── README.md │ ├── __init__.py │ ├── _checkov_version.py │ ├── _code_policies.py │ ├── _errors.py │ ├── _fixes.py │ ├── _packages.py │ ├── _repositories.py │ ├── _rules.py │ ├── _scans.py │ ├── _suppressions.py │ └── pccs.py │ └── version.py ├── pyproject.toml ├── requirements.txt ├── requirements_test.txt ├── scripts ├── README.md ├── examples │ ├── README.md │ └── pcs_vuln_container_with_cve_2022_22965.py ├── pc_account_add_example_standalone.py ├── pc_compute_cloud_discovery_example_standalone.py ├── pcs_account_groups_by_tags.py ├── pcs_agentless_logs.py ├── pcs_alert_rule_add_compliance_policies.py ├── pcs_alert_rule_export.py ├── pcs_alert_rule_import.py ├── pcs_alerts_read.py ├── pcs_apis_ingested.py ├── pcs_cloud_account_import_azure.py ├── pcs_cloud_account_inventory.py ├── pcs_cloud_discovery_defense_stats.py ├── pcs_compliance_alerts_read.py ├── pcs_compliance_export.py ├── pcs_compliance_import.py ├── pcs_compliance_uuid_read.py ├── pcs_compute_container_observed_connections.py ├── pcs_compute_endpoint_client.py ├── pcs_compute_forward_to_siem.py ├── pcs_configure.py ├── pcs_container_count.py ├── pcs_container_csp.py ├── pcs_container_vulnerabilities_on_running_hosts.py ├── pcs_container_vulnerabilities_read.py ├── pcs_cs_errors_for_file.py ├── pcs_cs_repositories_read.py ├── pcs_current_registry_scan.py ├── pcs_defender_report_by_cloud_account.py ├── pcs_forensics_download.py ├── pcs_hosts_vulnerabilities_read.py ├── pcs_images_packages_read.py ├── pcs_incident_archiver.py ├── pcs_outdated_defenders.py ├── pcs_policy_custom_export.py ├── pcs_policy_custom_import.py ├── pcs_policy_read.py ├── pcs_policy_set_status.py ├── pcs_posture_endpoint_client.py ├── pcs_resources_export.py ├── pcs_rotate_service_account_access_key.py ├── pcs_rql_query.py ├── pcs_script_example.py ├── pcs_ssl_configure.py ├── pcs_sync_azure_accounts.py ├── pcs_sync_registries.py ├── pcs_usage.py ├── pcs_user_import.py ├── pcs_user_update.py ├── pcs_vuln_container_locations.py ├── pcs_week_alert_trend.py ├── requirements.txt └── templates │ ├── prisma_cloud_account_import_azure_template.csv │ └── prisma_cloud_user_import_template.csv ├── setup.py └── tests ├── data.py ├── test_cwpp_cwpp.py └── test_unit.py /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaloAltoNetworks/prismacloud-api-python/d1ea5322c3dbf6faf666ba6d486c481b0108b909/.coverage -------------------------------------------------------------------------------- /.github/workflows/manual_release.yml: -------------------------------------------------------------------------------- 1 | name: Manual Deploy 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | deploy: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 10 | - name: Set up Python 11 | uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4 12 | with: 13 | python-version: '3.9' 14 | 15 | - name: Install Dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install pylint 19 | pip install build 20 | pip install -r requirements_test.txt 21 | 22 | - name: Test API 23 | run: | 24 | pylint prismacloud/api 25 | 26 | - name: Build package 27 | run: python -m build 28 | 29 | - name: Run tests 30 | run: | 31 | coverage run -m unittest discover -v -s "./tests" -p "test*.py" 32 | 33 | - name: Publish 34 | uses: pypa/gh-action-pypi-publish@release/v1 35 | with: 36 | user: __token__ 37 | password: ${{ secrets.PYPI_API_TOKEN }} 38 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Python Pylint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | python-version: [3.9] 17 | 18 | steps: 19 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 20 | 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@3542bca2639a428e1796aaa6a2ffef0c0f575566 # v3 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - name: Install Dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install pylint 30 | pip install -r requirements_test.txt 31 | 32 | - name: Test API 33 | run: | 34 | pylint prismacloud/api 35 | 36 | - name: Test Scripts 37 | run: | 38 | coverage run -m unittest discover -v -s "./tests" -p "test*.py" 39 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 2 | 3 | name: Upload Python Package 4 | 5 | on: 6 | release: 7 | types: [published] 8 | pull_request: 9 | types: 10 | - closed 11 | 12 | jobs: 13 | create-github-release: 14 | if: github.event.pull_request.merged == true 15 | runs-on: ubuntu-latest 16 | permissions: write-all 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 20 | 21 | - name: Extract version from prismacloud/cli/version.py 22 | run: | 23 | version=$(grep 'version = ' prismacloud/api/version.py | sed -E "s/version = \"([^\"]+)\"/\1/") 24 | echo "PRISMA_CLOUD_API_VERSION=$version" >> $GITHUB_ENV 25 | 26 | - name: Create GitHub Release 27 | run: | 28 | gh release create ${{ env.PRISMA_CLOUD_API_VERSION }} --generate-notes --latest 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | deploy: 33 | runs-on: ubuntu-latest 34 | needs: 35 | - create-github-release 36 | 37 | steps: 38 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 39 | 40 | - name: Set up Python 41 | uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4 42 | with: 43 | python-version: '3.9' 44 | 45 | - name: Install Dependencies 46 | run: | 47 | python -m pip install --upgrade pip 48 | pip install pylint 49 | pip install build 50 | pip install -r requirements_test.txt 51 | 52 | - name: Test API 53 | run: | 54 | pylint prismacloud/api 55 | 56 | - name: Build package 57 | run: python -m build 58 | 59 | - name: Run tests 60 | run: | 61 | coverage run -m unittest discover -v -s "./tests" -p "test*.py" 62 | 63 | - name: Publish 64 | uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # release/v1 65 | with: 66 | user: __token__ 67 | password: ${{ secrets.PYPI_API_TOKEN }} 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test Output 2 | 3 | TJK* 4 | error.log 5 | 6 | # Configuration Files 7 | 8 | *.conf 9 | 10 | # Python 11 | 12 | __pycache__/ 13 | *egg* 14 | *.py[cod] 15 | *$py.class 16 | dist/* 17 | build/* 18 | 19 | # OSX 20 | 21 | .DS_Store 22 | 23 | # Environments 24 | 25 | .env 26 | .venv 27 | env/ 28 | venv/ 29 | ENV/ 30 | env.bak/ 31 | venv.bak/ 32 | ENV.bak/ 33 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Use multiple processes to speed up Pylint. 4 | # Specifying 0 will auto-detect the number of processors available to use. 5 | # 6 | jobs=2 7 | fail-under=9.0 8 | 9 | [BASIC] 10 | 11 | # Regular expression matching correct constant names. Overrides const-naming-style. 12 | # 13 | const-rgx=(([A-Z_][A-Z0-9_]*)|([a-z_][a-z0-9_]*)|(__.*__))$ 14 | 15 | [DESIGN] 16 | 17 | # Minimum number of public methods for a class (see R0903). 18 | # 19 | min-public-methods=1 20 | max-public-methods=24 21 | max-args=8 22 | max-branches=16 23 | max-nested-blocks=8 24 | max-returns=12 25 | max-statements=64 26 | 27 | [FORMAT] 28 | 29 | # Maximum number of characters on a single line. 30 | # 31 | max-line-length=255 32 | 33 | [MESSAGES CONTROL] 34 | 35 | # Disable the message, report, category or checker with the given id(s). 36 | # You can either give multiple identifiers separated by comma (,) 37 | # or put this option multiple times (only on the command line, 38 | # not in the configuration file where it should appear only once). 39 | # 40 | # You can also use "--disable=all" to 41 | # disable everything first and then reenable specific checks. 42 | # 43 | # For example, if you want to run only the similarities checker, 44 | # you can use "--disable=all --enable=similarities". 45 | # 46 | # If you want to run only the classes checker, but have no Warning level messages displayed, 47 | # use "--disable=all --enable=classes --disable=W". 48 | # 49 | disable= 50 | consider-using-dict-items, 51 | consider-using-f-string, 52 | consider-using-with, 53 | duplicate-code, 54 | fixme, 55 | missing-function-docstring, 56 | pointless-string-statement, 57 | unspecified-encoding 58 | 59 | [REPORTS] 60 | 61 | # Set the output format. 62 | # Available formats are text, parseable, colorized, json and msvs (visual studio). 63 | # 64 | output-format=colorized 65 | 66 | [TYPECHECK] 67 | ignored-modules=socket 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | Following these guidelines helps keep the project maintainable, easy to contribute to, and more secure. 4 | Thank you for taking the time to read this. 5 | 6 | ## Where To Start 7 | 8 | There are many ways to contribute. 9 | You can fix a bug, improve the documentation, submit feature requests and issues, or work on a feature you need for yourself. 10 | 11 | Pull requests are necessary for all contributions of code, documentation, or examples. 12 | If you are new to open source and not sure what a pull request is ... welcome, we're glad to have you! 13 | All of us once had a contribution to make and didn't know where to start. 14 | 15 | Even if you don't write code for your job, don't worry, the skills you learn during your first contribution to open source can be applied in so many ways, you'll wonder what you ever did before you had this knowledge. 16 | Here are a few resources on how to contribute to open source for the first time. 17 | 18 | - [First Contributions](https://github.com/firstcontributions/first-contributions/blob/master/README.md) 19 | - [Public Learning Paths](https://lab.github.com/githubtraining/paths) 20 | 21 | ## Pull Requests 22 | 23 | - Make a pull request from your own fork of this repository 24 | - Please use clear commit messages, so everyone understands what each commit does 25 | - Validate your code using `pylint` as per below, and test your changes 26 | - We might offer feedback or request modifications before merging 27 | 28 | 29 | ``` 30 | pylint pc_lib/*.py pc_lib/*/*.py scripts/*.py 31 | ``` 32 | 33 | ## Adding Endpoints 34 | 35 | Endpoints for each feature set (CSPM, CWP, CCS) are located in the `prismacloud/api` directory: 36 | 37 | - [CSPM API Reference](https://prisma.pan.dev/api/cloud/cspm) (`prismacloud/api/posture`) 38 | - [CWP API Reference](https://prisma.pan.dev/api/cloud/cwpp) (`prismacloud/api/compute`) 39 | - [CCS API Reference](https://prisma.pan.dev/api/cloud/code) (`prismacloud/api/code_security`) 40 | 41 | Note: We will normalize the disparate acronyms in this repository as part of a future refactor. 42 | 43 | Please review the READMEs in the each directory for details on adding endpoints. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, Palo Alto Networks Inc. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python SDK for the Prisma Cloud APIs 2 | 3 | This project includes a Python SDK for the Prisma Cloud APIs (CSPM, CWPP, and CCS) in the form of a Python package. 4 | It also includes reference scripts that utilize this SDK. 5 | 6 | Major changes with Version 5.0: 7 | 8 | * Command-line argument and configuration file changes. 9 | 10 | ## Table of Contents 11 | 12 | * [Setup](#Setup) 13 | * [Support](#Support) 14 | 15 | 16 | ## Setup 17 | 18 | Install the SDK via `pip3`: 19 | 20 | ``` 21 | pip3 install prismacloud-api 22 | ``` 23 | 24 | Please refer to [PyPI](https://pypi.org/project/prismacloud-api) for details. 25 | 26 | ### Example Scripts 27 | 28 | Please refer to this [scripts](https://github.com/PaloAltoNetworks/prismacloud-api-python/tree/main/scripts) directory for configuration, documentation, and usage. 29 | 30 | If you prefer to use this SDK without using command-line options, consider these minimal examples: 31 | 32 | #### Prisma Cloud Enterprise Edition 33 | 34 | ``` 35 | import os 36 | from prismacloud.api import pc_api 37 | 38 | # Settings for Prisma Cloud Enterprise Edition 39 | 40 | settings = { 41 | "url": "https://api.prismacloud.io/", 42 | "identity": "access_key", 43 | "secret": "secret_key" 44 | } 45 | 46 | pc_api.configure(settings) 47 | 48 | print('Prisma Cloud API Current User:') 49 | print() 50 | print(pc_api.current_user()) 51 | print() 52 | print('Prisma Cloud Compute API Intelligence:') 53 | print() 54 | print(pc_api.statuses_intelligence()) 55 | print() 56 | 57 | print('Prisma Cloud API Object:') 58 | print() 59 | print(pc_api) 60 | print() 61 | ``` 62 | 63 | #### Prisma Cloud Compute Edition 64 | 65 | ``` 66 | import os 67 | from prismacloud.api import pc_api 68 | 69 | # Settings for Prisma Cloud Compute Edition 70 | 71 | settings = { 72 | "url": "https://console.example.com/", 73 | "identity": "username", 74 | "secret": "password" 75 | } 76 | 77 | pc_api.configure(settings) 78 | 79 | print('Prisma Cloud Compute API Intelligence:') 80 | print() 81 | print(pc_api.statuses_intelligence()) 82 | print() 83 | 84 | print('Prisma Cloud API Object:') 85 | print() 86 | print(pc_api) 87 | print() 88 | ``` 89 | 90 | Settings can also be defined as environment variables: 91 | 92 | #### Environment Variables 93 | 94 | ``` 95 | settings = { 96 | "url": os.environ.get('PC_URL'), 97 | "identity": os.environ.get('PC_IDENTITY'), 98 | "secret": os.environ.get('PC_SECRET') 99 | } 100 | ``` 101 | 102 | ## Support 103 | 104 | This project has been developed by members of the Prisma Cloud CS and SE teams, it is not Supported by Palo Alto Networks. 105 | Nevertheless, the maintainers will make a best-effort to address issues, and (of course) contributors are encouraged to submit issues and pull requests. 106 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | Community Supported 2 | 3 | The software and templates in the repo are released under an as-is, best effort, 4 | support policy. This software should be seen as community supported and Palo 5 | Alto Networks will contribute our expertise as and when possible. We do not 6 | provide technical support or help in using or troubleshooting the components of 7 | the project through our normal support options such as Palo Alto Networks 8 | support teams, or ASC (Authorized Support Centers) partners and backline support 9 | options. The underlying product used (the VM-Series firewall) by the scripts or 10 | templates are still supported, but the support is only for the product 11 | functionality and not for help in deploying or using the template or script 12 | itself. Unless explicitly tagged, all projects or work posted in our GitHub 13 | repository (at https://github.com/PaloAltoNetworks) or sites other than our 14 | official Downloads page on https://support.paloaltonetworks.com are provided 15 | under the best effort policy. -------------------------------------------------------------------------------- /prismacloud/api/README.md: -------------------------------------------------------------------------------- 1 | # Python SDK for the Prisma Cloud APIs 2 | 3 | This is a Python SDK for the Prisma Cloud APIs (CSPM, CWPP, and PCCS) in the form of a Python package. 4 | 5 | 6 | ## Table of Contents 7 | 8 | * [Setup](#Usage) 9 | * [Support](#Support) 10 | * [References](#References) 11 | 12 | 13 | ## Setup 14 | 15 | Install the SDK via: 16 | 17 | ``` 18 | pip3 install prismacloud-api 19 | ``` 20 | 21 | 22 | ## Support 23 | 24 | This project has been developed by Prisma Cloud SEs, it is not Supported by Palo Alto Networks. 25 | Nevertheless, the maintainers will make a best-effort to address issues, and (of course) contributors are encouraged to submit issues and pull requests. 26 | 27 | 28 | ### References 29 | 30 | Prisma Cloud APIs: 31 | 32 | https://prisma.pan.dev/api/cloud/ 33 | 34 | Access Keys: 35 | 36 | https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/manage-prisma-cloud-administrators/create-access-keys.html 37 | 38 | Permissions: 39 | 40 | https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/manage-prisma-cloud-administrators/prisma-cloud-admin-permissions.html -------------------------------------------------------------------------------- /prismacloud/api/__init__.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud API Class """ 2 | 3 | import sys 4 | 5 | from .pc_lib_api import PrismaCloudAPI 6 | from .pc_lib_utility import PrismaCloudUtility 7 | from .version import version as api_version 8 | 9 | __author__ = 'Palo Alto Networks CSE/SE/SA Teams' 10 | __version__ = api_version 11 | 12 | MIN_PYTHON = (3, 6) 13 | if sys.version_info < MIN_PYTHON: 14 | raise SystemExit("Python %s.%s or later is required.\n" % MIN_PYTHON) 15 | 16 | # --Class Instances-- # 17 | 18 | pc_api = PrismaCloudAPI() 19 | pc_utility = PrismaCloudUtility() 20 | -------------------------------------------------------------------------------- /prismacloud/api/cspm/README.md: -------------------------------------------------------------------------------- 1 | ## Adding Endpoints 2 | 3 | While most of the endpoints documented in the [CSPM API Reference](https://prisma.pan.dev/api/cloud/cspm) are defined as methods in this SDK, 4 | some are not, as endpoints are added to Prisma Cloud as features are added. 5 | 6 | To add an method for an endpoint to `_endpoints.py`, refer to the above API Reference, and use the existing methods as examples. 7 | 8 | --- 9 | 10 | class EndpointsPrismaCloudAPIMixin(): 11 | """ Prisma Cloud API Endpoints Class """ 12 | 13 | def example_list(self): 14 | return self.execute('GET', 'example/endpoint') -------------------------------------------------------------------------------- /prismacloud/api/cspm/__init__.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud CSPM API Class """ 2 | 3 | import sys 4 | 5 | from .cspm import * 6 | from ._endpoints import * 7 | from ._extended import * 8 | 9 | mixin_classes_as_strings = list(filter(lambda x: x.endswith('PrismaCloudAPIMixin'), dir())) 10 | mixin_classes = [getattr(sys.modules[__name__], x) for x in mixin_classes_as_strings] 11 | 12 | # pylint: disable=too-few-public-methods 13 | class PrismaCloudAPICSPM(*mixin_classes): 14 | """ Prisma Cloud CSPM API Class """ 15 | -------------------------------------------------------------------------------- /prismacloud/api/cspm/_extended.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud API Endpoints Aggregation Class """ 2 | 3 | import concurrent.futures 4 | 5 | # TODO: Rename this class ... 6 | 7 | class ExtendedPrismaCloudAPIMixin(): 8 | """ Prisma Cloud API Endpoints Aggregation Class """ 9 | 10 | def get_policies_with_saved_searches(self, policy_list_current): 11 | result = {'policies': {}, 'searches': {}} 12 | if not policy_list_current: 13 | return result 14 | # pylint: disable=consider-using-with 15 | thread_pool_executor = concurrent.futures.ThreadPoolExecutor(self.max_workers) 16 | self.progress('API - Getting the Custom Policies ...') 17 | futures = [] 18 | for policy_current in policy_list_current: 19 | self.progress('Scheduling Policy Request: %s' % policy_current['name']) 20 | thread_progress = 'Getting Policy: %s' % policy_current['name'] 21 | futures.append(thread_pool_executor.submit(self.policy_read, policy_current['policyId'], message=thread_progress)) 22 | concurrent.futures.wait(futures) 23 | for future in concurrent.futures.as_completed(futures): 24 | policy_current = future.result() 25 | result['policies'][policy_current['policyId']] = policy_current 26 | self.progress('Done.') 27 | self.progress(' ') 28 | self.progress('API - Getting the Custom Policies Saved Searches ...') 29 | futures = [] 30 | for policy_current in policy_list_current: 31 | if not 'parameters' in policy_current['rule']: 32 | continue 33 | if not 'savedSearch' in policy_current['rule']['parameters']: 34 | continue 35 | if policy_current['rule']['parameters']['savedSearch'] == 'true': 36 | self.progress('Scheduling Saved Search Request: %s' % policy_current['name']) 37 | thread_progress = 'Getting Saved Search: %s' % policy_current['name'] 38 | futures.append(thread_pool_executor.submit(self.saved_search_read, policy_current['rule']['criteria'], message=thread_progress)) 39 | concurrent.futures.wait(futures) 40 | for future in concurrent.futures.as_completed(futures): 41 | saved_search = future.result() 42 | result['searches'][saved_search['id']] = saved_search 43 | self.progress('Done.') 44 | self.progress(' ') 45 | return result 46 | 47 | def get_cloud_resources(self, cloud_account_resource_list): 48 | result = [] 49 | if not cloud_account_resource_list: 50 | return result 51 | # pylint: disable=consider-using-with 52 | thread_pool_executor = concurrent.futures.ThreadPoolExecutor(self.max_workers) 53 | self.progress('API - Getting the Resources ...') 54 | futures = [] 55 | for cloud_account_resource in cloud_account_resource_list: 56 | if not 'rrn' in cloud_account_resource: 57 | continue 58 | self.progress('Scheduling Resource Request: %s' % cloud_account_resource['rrn']) 59 | thread_progress = 'Getting Resource: %s' % cloud_account_resource['rrn'] 60 | futures.append(thread_pool_executor.submit(self.resource_read, body_params={'rrn': cloud_account_resource['rrn']}, force=True, message=thread_progress)) 61 | concurrent.futures.wait(futures) 62 | for future in concurrent.futures.as_completed(futures): 63 | resource = future.result() 64 | if resource: 65 | result.append(resource) 66 | self.progress('Done.') 67 | return result 68 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/README.md: -------------------------------------------------------------------------------- 1 | ## Adding Endpoints 2 | 3 | While most of the endpoints documented in the [CWP API Reference](https://prisma.pan.dev/api/cloud/cwpp) are defined as methods in this SDK, 4 | some are not, as endpoints are added to Prisma Cloud as features are added. 5 | 6 | To add an method for an endpoint, refer to the above API Reference, and use the existing methods as examples. 7 | 8 | Each logical set of endpoints (defined by URL prefix) is grouped into its own file, and imported in `__init__.py `. 9 | 10 | When adding a new group of endpoints, create a new file, and import it using the existing imports as examples. 11 | 12 | --- 13 | 14 | In `__init__.py `: 15 | 16 | ``` 17 | from ._example.py import * 18 | ``` 19 | 20 | In `_example.py`: 21 | 22 | ``` 23 | """ Prisma Cloud Compute API Example Endpoints Class """ 24 | 25 | class ExamplePrismaCloudAPIComputeMixin: 26 | """ Prisma Cloud Compute API Example Endpoints Class """ 27 | 28 | def example_list(self): 29 | return self.execute_compute('GET', '/api/v1/example/endpoint') 30 | ``` -------------------------------------------------------------------------------- /prismacloud/api/cwpp/__init__.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud CWP API Class """ 2 | 3 | import sys 4 | 5 | from .cwpp import * 6 | from ._audits import * 7 | from ._cloud import * 8 | from ._collections import * 9 | from ._containers import * 10 | from ._credentials import * 11 | from ._defenders import * 12 | from ._feeds import * 13 | from ._hosts import * 14 | from ._images import * 15 | from ._logs import * 16 | from ._policies import * 17 | from ._registry import * 18 | from ._scans import * 19 | from ._settings import * 20 | from ._serverless import * 21 | from ._stats import * 22 | from ._status import * 23 | from ._tags import * 24 | from ._vms import * 25 | 26 | mixin_classes_as_strings = list( 27 | filter(lambda x: x.endswith('PrismaCloudAPICWPPMixin'), dir())) 28 | mixin_classes = [getattr(sys.modules[__name__], x) 29 | for x in mixin_classes_as_strings] 30 | 31 | # pylint: disable=too-few-public-methods 32 | 33 | 34 | class PrismaCloudAPICWPP(*mixin_classes): 35 | """ Prisma Cloud CWP API Class """ 36 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_audits.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Audits Endpoints Class """ 2 | 3 | class AuditsPrismaCloudAPICWPPMixin: 4 | """ Prisma Cloud Compute API Audit Endpoints Class """ 5 | 6 | # The audits/incidents endpoint is the only documented audits endpoint. 7 | # It maps to the table in Compute > Monitor > Runtime > Incident Explorer in the Console. 8 | # Reference: https://prisma.pan.dev/api/cloud/cwpp/audits 9 | 10 | def audits_list_read(self, audit_type='incidents', query_params=None): 11 | audits = self.execute_compute('GET', 'api/v1/audits/%s' % audit_type, query_params=query_params, paginated=True) 12 | return audits 13 | 14 | # Other related and undocumented endpoints. 15 | 16 | # Forensics (Here in audits, as this endpoint is also undocumented like audits.) 17 | 18 | def forensic_read(self, workload_id, workload_type, defender_hostname): 19 | query_params = {'hostname': defender_hostname} 20 | if workload_type in ['container', 'app-embedded']: 21 | response = self.execute_compute('GET', 'api/v1/profiles/%s/%s/forensic/bundle' % (workload_type, workload_id), query_params=query_params) 22 | elif workload_type == 'host': 23 | response = self.execute_compute('GET', 'api/v1/profiles/%s/%s/forensic/download' % (workload_type, workload_id), query_params=query_params) 24 | else: 25 | response = self.execute_compute('GET', 'api/v1/profiles/%s/%s/forensic' % (workload_type, workload_id), query_params=query_params, paginated=True) 26 | return response 27 | 28 | # Monitor / Runtime > Incident Explorer 29 | 30 | def audits_ack_incident(self, incident_id, ack_status=True): 31 | body_params = {'acknowledged': ack_status} 32 | response = self.execute_compute('PATCH', 'api/v1/audits/incidents/acknowledge/%s' % incident_id, body_params=body_params) 33 | return response 34 | 35 | # Compute > Monitor > Events 36 | 37 | @staticmethod 38 | def compute_audit_types(): 39 | return [ 40 | # Containers 41 | 'access', 42 | 'admission', 43 | 'firewall/app/app-embedded', 44 | 'firewall/app/app-embedded', 45 | 'firewall/network/container', 46 | 'kubernetes', 47 | 'runtime/app-embedded', 48 | 'runtime/container', 49 | 'trust', 50 | # Hosts 51 | 'firewall/app/host', 52 | 'firewall/network/host', 53 | 'runtime/file-integrity', 54 | 'runtime/host', 55 | 'runtime/log-inspection', 56 | # Serverless 57 | 'firewall/app/serverless', 58 | 'runtime/serverless', 59 | # Incidents 60 | 'incidents' 61 | ] 62 | 63 | # Hosts > Host Activities 64 | 65 | def host_forensic_activities_list_read(self, query_params=None): 66 | audits = self.execute_compute('GET', 'api/v1/forensic/activities', query_params=query_params, paginated=True) 67 | return audits 68 | 69 | # Compute > Manage > History 70 | 71 | def console_history_list_read(self, query_params=None): 72 | logs = self.execute_compute('GET', 'api/v1/audits/mgmt', query_params=query_params) 73 | return logs 74 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_cloud.py: -------------------------------------------------------------------------------- 1 | """ Prisma Compute API Cloud Endpoints Class """ 2 | 3 | # Cloud 4 | 5 | class CloudPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Cloud Endpoints Class """ 7 | 8 | def cloud_discovery_read(self): 9 | return self.execute_compute('GET', 'api/v1/cloud/discovery') 10 | 11 | def cloud_discovery_download(self, query_params=None): 12 | # request_headers = {'Content-Type': 'text/csv'} 13 | # return self.execute_compute('GET', 'api/v1/cloud/discovery/download?', request_headers=request_headers, query_params=query_params) 14 | return self.execute_compute('GET', 'api/v1/cloud/discovery/download', query_params=query_params) 15 | 16 | def cloud_discovery_scan(self): 17 | return self.execute_compute('POST', 'api/v1/cloud/discovery/scan') 18 | 19 | def cloud_discovery_scan_stop(self): 20 | return self.execute_compute('POST', 'api/v1/cloud/discovery/stop') 21 | 22 | def cloud_discovery_vms(self, query_params=None): 23 | return self.execute_compute('GET', 'api/v1/cloud/discovery/vms', query_params=query_params, paginated=True) 24 | 25 | def cloud_discovery_entities(self, query_params=None): 26 | return self.execute_compute('GET', 'api/v1/cloud/discovery/entities', query_params=query_params) 27 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_collections.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Collections Endpoints Class """ 2 | 3 | # Containers 4 | 5 | class CollectionsPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Collections Endpoints Class """ 7 | 8 | def collections_list_read(self, query_params=None): 9 | return self.execute_compute('GET', 'api/v1/collections', query_params=query_params, paginated=True) 10 | 11 | def collection_usages(self, collection_id): 12 | return self.execute_compute('GET', 'api/v1/collections/%s/usages' % collection_id, paginated=True) 13 | 14 | # Note: No response is returned upon successful execution of POST, PUT, and DELETE. 15 | # You must verify the collection via collections_list_read() or the Console. 16 | 17 | def collection_create(self, body_params): 18 | return self.execute_compute('POST', 'api/v1/collections', body_params=body_params) 19 | 20 | def collection_update(self, collection_id, body_params): 21 | return self.execute_compute('PUT', 'api/v1/collections/%s' % collection_id, body_params=body_params) 22 | 23 | def collection_delete(self, collection_id): 24 | return self.execute_compute('DELETE', 'api/v1/collections/%s' % collection_id) 25 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_containers.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Containers Endpoints Class """ 2 | 3 | # Containers 4 | 5 | class ContainersPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Containers Endpoints Class """ 7 | 8 | def containers_list_read(self, image_id=None, query_params=None): 9 | if image_id: 10 | containers = self.execute_compute('GET', 'api/v1/containers?imageId=%s' % image_id, query_params=query_params, paginated=True) 11 | else: 12 | containers = self.execute_compute('GET', 'api/v1/containers?', query_params=query_params, paginated=True) 13 | return containers 14 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_credentials.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Images Endpoints Class """ 2 | 3 | import urllib.parse 4 | 5 | # Credentials (Manage > Authentication > Credentials store) 6 | 7 | 8 | class CredentialsPrismaCloudAPICWPPMixin: 9 | """ Prisma Cloud Compute API Credentials Endpoints Class """ 10 | 11 | def credential_list_read(self): 12 | return self.execute_compute('GET', 'api/v1/credentials') 13 | 14 | def credential_list_create(self, body): 15 | return self.execute_compute( 16 | 'POST', 'api/v1/credentials?project=Central+Console', 17 | body_params=body 18 | ) 19 | 20 | def credential_list_delete(self, cred): 21 | return self.execute_compute( 22 | 'DELETE', 'api/v1/credentials/%s' % urllib.parse.quote(cred) 23 | ) 24 | 25 | def credential_list_usages_read(self, cred): 26 | return self.execute_compute( 27 | 'GET', 'api/v1/credentials/%s/usages' % urllib.parse.quote(cred) 28 | ) 29 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_defenders.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Defenders Endpoints Class """ 2 | 3 | # Containers 4 | 5 | class DefendersPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Defenders Endpoints Class """ 7 | 8 | def defenders_list_read(self, query_params=None): 9 | defenders = self.execute_compute('GET', 'api/v1/defenders', query_params=query_params, paginated=True) 10 | return defenders 11 | 12 | def defenders_names_list_read(self, query_params=None): 13 | defenders = self.execute_compute('GET', 'api/v1/defenders/names', query_params=query_params, paginated=True) 14 | return defenders 15 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_feeds.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Custom Feeds Endpoints Class """ 2 | 3 | # Custom Feeds (Manage > System) 4 | 5 | class FeedsPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Custom Feeds Endpoints Class """ 7 | 8 | # body_params = {"feed": ["10.10.10.10", "10.10.10.200"] } 9 | # 10 | def feeds_ips_write(self, body_params): 11 | return self.execute_compute('PUT', 'api/v1/feeds/custom/ips', body_params=body_params) 12 | 13 | # body_params = { 14 | # "feed": [ 15 | # { 16 | # "name": "example", 17 | # "md5": "cdb55ac14abdf2b868a06f90e939fba6", 18 | # "allowed": False 19 | # }, 20 | # { 21 | # "name": "example_02", 22 | # "md5": "d3af4a715add78009b4483acb95e4c34", 23 | # "allowed": False 24 | # } 25 | # ] 26 | # } 27 | # 28 | def feeds_malware_write(self, body_params): 29 | return self.execute_compute('PUT', 'api/v1/feeds/custom/malware', body_params=body_params) 30 | 31 | # body_params = { 32 | # "_id": "", 33 | # "modified": "2023-02-15T22:22:21.804Z", 34 | # "feed": [ 35 | # { 36 | # "name": "example_malware", 37 | # "md5": "cdb55ac14abdf2b868a06f90e939fba6", 38 | # "allowed": True 39 | # } 40 | # ], 41 | # "digest": "0880945af4ab1be95aa073305526c811" 42 | # } 43 | # 44 | # def feeds_trusted_executables_write(self, body_params): 45 | # return self.execute_compute('PUT', 'api/v1/feeds/custom/malware', body_params=body_params) 46 | 47 | # body_params = { 48 | # "_id": "customVulnerabilities", 49 | # "rules": [ 50 | # { 51 | # "name": "example_01", 52 | # "type": "package", 53 | # "package": "example_package_01", 54 | # "minVersionInclusive": "1.0", 55 | # "maxVersionInclusive": "2.0", 56 | # "md5": "" 57 | # }, 58 | # { 59 | # "_id": "", 60 | # "name": "example_02", 61 | # "package": "example_package_02", 62 | # "type": "package", 63 | # "minVersionInclusive": "2.0", 64 | # "maxVersionInclusive": "3.0", 65 | # "md5": "" 66 | # } 67 | # ], 68 | # "digest": "e9c36ba88b22338d0f9b6f7fe4c0113d" 69 | # } 70 | # 71 | # def feeds_trusted_executables_write(self, body_params): 72 | # return self.execute_compute('PUT', 'api/v1/feeds/custom/custom-vulnerabilities', body_params=body_params) 73 | 74 | # body_params = {"rules": [{"cve":"CVE-12345", "description":""}] } 75 | # 76 | # def feeds_cve_allow_list_write(self, body_params): 77 | # return self.execute_compute('PUT', 'api/v1/feeds/custom/cve-allow-list', body_params=body_params) 78 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_hosts.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Hosts Endpoints Class """ 2 | 3 | # Containers 4 | 5 | class HostsPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Hosts Endpoints Class """ 7 | 8 | # Running hosts table in Monitor > Vulnerabilities > Hosts > Running Hosts 9 | def hosts_list_read(self, query_params=None): 10 | hosts = self.execute_compute('GET', 'api/v1/hosts', query_params=query_params, paginated=True) 11 | return hosts 12 | 13 | def hosts_info_list_read(self, query_params=None): 14 | hosts = self.execute_compute('GET', 'api/v1/hosts/info', query_params=query_params, paginated=True) 15 | return hosts 16 | 17 | def hosts_download(self, query_params=None): 18 | hosts = self.execute_compute('GET', 'api/v1/hosts/download?', query_params=query_params) 19 | return hosts 20 | 21 | def hosts_scan(self): 22 | result = self.execute_compute('POST', 'api/v1/hosts/scan') 23 | return result 24 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_images.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Images Endpoints Class """ 2 | 3 | # Images (Monitor > Vulnerabilities/Compliance > Images > Deployed) 4 | 5 | class ImagesPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Images Endpoints Class """ 7 | 8 | def images_list_read(self, image_id=None, query_params=None): 9 | if image_id: 10 | images = self.execute_compute('GET', 'api/v1/images?id=%s' % image_id, query_params=query_params) 11 | else: 12 | images = self.execute_compute('GET', 'api/v1/images?', query_params=query_params, paginated=True) 13 | return images 14 | 15 | def images_download(self, query_params=None): 16 | images = self.execute_compute('GET', 'api/v1/images/download?', query_params=query_params) 17 | return images 18 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_logs.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Logs Endpoints Class """ 2 | 3 | # Containers 4 | 5 | class LogsPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Logs Endpoints Class """ 7 | 8 | # Undocumented endpoints. 9 | 10 | def agentless_logs_read(self, query_params=None): 11 | logs = self.execute_compute('GET', 'api/v1/logs/agentless/download', query_params=query_params) 12 | return logs 13 | 14 | def defender_logs_list_read(self, host_name, query_params=None): 15 | logs = self.execute_compute('GET', 'api/v1/logs/defender/download?hostname=%s' % host_name, query_params=query_params) 16 | return logs 17 | 18 | def console_logs_list_read(self, query_params=None): 19 | logs = self.execute_compute('GET', 'api/v1/logs/console', query_params=query_params) 20 | return logs 21 | 22 | def system_logs_list_read(self, query_params=None): 23 | logs = self.execute_compute('GET', 'api/v1/logs/system/download', query_params=query_params) 24 | return logs 25 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_policies.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Policies Endpoints Class """ 2 | 3 | # Credentials (Defend > Compliance) 4 | 5 | class PoliciesPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Credentials Endpoints Class """ 7 | 8 | def policies_cloud_platforms_read(self): 9 | return self.execute_compute('GET', 'api/v1/policies/cloud-platforms') 10 | 11 | def policies_cloud_platforms_write(self, body_params): 12 | return self.execute_compute('PUT', 'api/v1/policies/cloud-platforms', body_params=body_params) 13 | 14 | # These implement multiple endpoints. See: https://prisma.pan.dev/api/cloud/cwpp/policies 15 | 16 | def policies_read(self, policy_path): 17 | return self.execute_compute('GET', 'api/v1/policies/%s' % policy_path) 18 | 19 | def policies_write(self, policy_path, body_params): 20 | return self.execute_compute('PUT', 'api/v1/policies/%s' % policy_path, body_params=body_params) 21 | 22 | def policies_delete(self, policy_path): 23 | return self.execute_compute('PUT', 'api/v1/policies/%s' % policy_path, body_params={}) 24 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_registry.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Images Endpoints Class """ 2 | 3 | # Images (Monitor > Vulnerabilities/Compliance > Images > Deployed) 4 | 5 | class RegistryPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Images Endpoints Class """ 7 | 8 | def registry_list_read(self, image_id=None): 9 | if image_id: 10 | images = self.execute_compute('GET', 'api/v1/registry?id=%s&filterBaseImage=true' % image_id) 11 | else: 12 | images = self.execute_compute('GET', 'api/v1/registry?filterBaseImage=true', paginated=True) 13 | return images 14 | 15 | def registry_list_image_names(self, query_params=None): 16 | result = self.execute_compute('GET', 'api/v1/registry/names?', query_params=query_params) 17 | return result 18 | 19 | def registry_scan(self, body_params=None): 20 | result = self.execute_compute('POST', 'api/v1/registry/scan', body_params=body_params) 21 | return result 22 | 23 | def registry_scan_select(self, body_params=None): 24 | result = self.execute_compute('POST', 'api/v1/registry/scan/select', body_params=body_params) 25 | return result 26 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_scans.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Scans Endpoints Class """ 2 | 3 | # Scans (Monitor > Vulnerabilities/Compliance > Images > CI) 4 | 5 | class ScansPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Scans Endpoints Class """ 7 | 8 | def scans_list_read(self, image_id=None): 9 | if image_id: 10 | images = self.execute_compute('GET', 'api/v1/scans?imageID=%s&filterBaseImage=true' % image_id) 11 | else: 12 | images = self.execute_compute('GET', 'api/v1/scans?filterBaseImage=true', paginated=True) 13 | return images 14 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_serverless.py: -------------------------------------------------------------------------------- 1 | class ServerlessPrismaCloudAPICWPPMixin: 2 | """ Prisma Cloud Compute Serverless Endpoints Class """ 3 | 4 | # Get serverless function scan results 5 | def serverless_list_read(self, query_params=None): 6 | result = self.execute_compute('GET', 'api/v1/serverless', query_params=query_params, paginated=True) 7 | return result 8 | 9 | # Download serverless function scan results 10 | def serverless_download(self, query_params=None): 11 | result = self.execute_compute('GET', 'api/v1/serverless/download?', query_params=query_params) 12 | return result 13 | 14 | # Start serverless function scan 15 | def serverless_start_scan(self): 16 | result = self.execute_compute('POST', 'api/v1/serverless/scan') 17 | return result 18 | 19 | # Stop serverless function scan 20 | def serverless_stop_scan(self): 21 | result = self.execute_compute('POST', 'api/v1/serverless/stop') 22 | return result 23 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_settings.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Settings Endpoints Class """ 2 | 3 | # Credentials (Defend > Compliance) 4 | 5 | class SettingsPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Settings Endpoints Class """ 7 | 8 | def settings_serverless_scan_read(self): 9 | return self.execute_compute('get', 'api/v1/settings/serverless-scan') 10 | 11 | def settings_serverless_scan_write(self, body): 12 | return self.execute_compute( 13 | 'put', 'api/v1/settings/serverless-scan', 14 | body_params=body 15 | ) 16 | 17 | def settings_registry_read(self): 18 | return self.execute_compute('get', 'api/v1/settings/registry') 19 | 20 | def settings_registry_write(self, body): 21 | return self.execute_compute( 22 | 'put', 'api/v1/settings/registry', 23 | body_params=body 24 | ) 25 | 26 | def settings_host_auto_deploy_read(self): 27 | return self.execute_compute('get', 'api/v1/settings/host-auto-deploy') 28 | 29 | def settings_host_auto_deploy_write(self, body): 30 | return self.execute_compute( 31 | 'post', 'api/v1/settings/host-auto-deploy', 32 | body_params=body 33 | ) 34 | 35 | def settings_serverless_auto_deploy_read(self): 36 | return self.execute_compute('get', 'api/v1/settings/serverless-auto-deploy') 37 | 38 | def settings_serverless_auto_deploy_write(self, body): 39 | return self.execute_compute( 40 | 'post', 'api/v1/settings/serverless-auto-deploy', 41 | body_params=body 42 | ) 43 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_stats.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Stats Endpoints Class """ 2 | 3 | class StatsPrismaCloudAPICWPPMixin: 4 | """ Prisma Cloud Compute API Stats Endpoints Class """ 5 | 6 | def stats_app_firewall_count_read(self): 7 | # Returns the number of app firewalls in use. 8 | return self.execute_compute('GET', 'api/v1/stats/app-firewall/count') 9 | 10 | def stats_compliance_read(self, query_params=None): 11 | # Maps to the table in Monitor > Compliance > Compliance Explorer 12 | return self.execute_compute('GET', 'api/v1/stats/compliance?', query_params=query_params) 13 | 14 | def stats_compliance_download(self, query_params=None): 15 | return self.execute_compute('GET', 'api/v1/stats/compliance/download?', query_params=query_params) 16 | 17 | def stats_compliance_refresh(self, query_params=None): 18 | # Refreshes the current day's list and counts of compliance issues, as well as the list of affected running resources. 19 | # This endpoint returns the same response as /api/v1/stats/compliance, but with updated data for the current day. 20 | return self.execute_compute('GET', 'api/v1/stats/compliance/refresh?', query_params=query_params) 21 | 22 | def stats_daily_read(self): 23 | # Returns a historical list of per-day statistics for the resources protected by Prisma Cloud Compute, 24 | # including the total number of runtime audits, image vulnerabilities, and compliance violations. 25 | return self.execute_compute('GET', 'api/v1/stats/daily', paginated=True) 26 | 27 | def stats_trends_read(self): 28 | # Returns statistics about the resources protected by Prisma Cloud Compute, 29 | # including the total number of runtime audits, image vulnerabilities, and compliance violations. 30 | return self.execute_compute('GET', 'api/v1/stats/dashboard') 31 | 32 | def stats_events_read(self, query_params=None): 33 | # Returns events statistics for your environment. 34 | return self.execute_compute('GET', 'api/v1/stats/events?', query_params=query_params) 35 | 36 | def stats_license_read(self): 37 | return self.execute_compute('GET', 'api/v1/stats/license') 38 | 39 | def stats_vulnerabilities_read(self, query_params=None): 40 | # Returns a list of vulnerabilities (CVEs) in the deployed images, registry images, hosts, and serverless functions affecting your environment. 41 | return self.execute_compute('GET', 'api/v1/stats/vulnerabilities?', query_params=query_params, paginated=True) 42 | 43 | def stats_vulnerabilities_download(self, query_params=None): 44 | return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/download?', query_params=query_params) 45 | 46 | def stats_vulnerabilities_impacted_resoures_read(self, query_params=None): 47 | # 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. 48 | return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/impacted-resources?', query_params=query_params) 49 | 50 | def stats_vulnerabilities_impacted_resoures_download(self, query_params=None): 51 | return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/impacted-resources/download?', query_params=query_params) 52 | 53 | def stats_vulnerabilities_refresh(self, query_params=None): 54 | # Refreshes the current day's CVE counts and CVE list, as well as their descriptions. 55 | # This endpoint returns the same response as /api/v1/stats/vulnerabilities, but with updated data for the current day. 56 | return self.execute_compute('GET', 'api/v1/stats/vulnerabilities/refresh?', query_params=query_params, paginated=True) 57 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_status.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Statuses Endpoints Class """ 2 | 3 | class StatusPrismaCloudAPICWPPMixin: 4 | """ Prisma Cloud Compute API Statuses Endpoints Class """ 5 | 6 | def statuses_intelligence(self): 7 | return self.execute_compute('GET', 'api/v1/statuses/intelligence') 8 | 9 | def statuses_registry(self): 10 | return self.execute_compute('GET', 'api/v1/statuses/registry') 11 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_tags.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API Tags Endpoints Class """ 2 | 3 | # Tags are predefined labels that help you manage your vulnerabilities via the Console UI and Prisma Cloud Compute API. 4 | 5 | class TagsPrismaCloudAPICWPPMixin: 6 | """ Prisma Cloud Compute API Tags Endpoints Class """ 7 | 8 | def tags_list_read(self): 9 | tags = self.execute_compute('GET', 'api/v1/tags') 10 | return tags 11 | 12 | def tag_add(self, body_params=None): 13 | result = self.execute_compute('POST', 'api/v1/tags', body_params=body_params) 14 | return result 15 | 16 | def tag_delete(self, tag_id): 17 | result = self.execute_compute('DELETE', 'api/v1/tags/%s' % tag_id) 18 | return result 19 | 20 | def tag_update(self, tag_id, body_params): 21 | result = self.execute_compute('PUT', 'api/v1/tags/%s' % tag_id, body_params=body_params) 22 | return result 23 | 24 | def tag_delete_vulnerability(self, tag_id, body_params): 25 | result = self.execute_compute('DELETE', 'api/v1/tags/%s/vuln' % tag_id, body_params=body_params) 26 | return result 27 | 28 | def tag_set_vulnerability(self, tag_id, body_params): 29 | result = self.execute_compute('POST', 'api/v1/tags/%s/vuln' % tag_id, body_params=body_params) 30 | return result 31 | -------------------------------------------------------------------------------- /prismacloud/api/cwpp/_vms.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Compute API VMs Endpoints Class """ 2 | 3 | # Containers 4 | 5 | 6 | class VMsPrismaCloudAPICWPPMixin: 7 | """ Prisma Cloud Compute API VMs Endpoints Class """ 8 | 9 | # VM Image table in Monitor > Vulnerabilities > Hosts > VMs 10 | def vms_list_read(self, query_params=None): 11 | vms = self.execute_compute( 12 | 'GET', 'api/v1/vms', query_params=query_params, paginated=True) 13 | return vms -------------------------------------------------------------------------------- /prismacloud/api/cwpp/cwpp.py: -------------------------------------------------------------------------------- 1 | """ Requests and Output """ 2 | 3 | import json 4 | import time 5 | 6 | import requests 7 | from requests.adapters import HTTPAdapter, Retry 8 | 9 | 10 | class PrismaCloudAPICWPPMixin(): 11 | """ Requests and Output """ 12 | 13 | def login_compute(self): 14 | if self.api: 15 | # Login via CSPM. 16 | self.login() 17 | elif self.api_compute: 18 | # Login via CWP. 19 | self.login('https://%s/api/v1/authenticate' % self.api_compute) 20 | else: 21 | self.error_and_exit( 22 | 418, "Specify a Prisma Cloud URL or Prisma Cloud Compute URL") 23 | self.debug_print('New API Token: %s' % self.token) 24 | 25 | def extend_login_compute(self): 26 | # There is no extend for CWP, just logon again. 27 | self.debug_print('Extending API Token') 28 | self.login_compute() 29 | 30 | # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements 31 | def execute_compute(self, action, endpoint, query_params=None, body_params=None, request_headers=None, force=False, paginated=False): 32 | self.suppress_warnings_when_verify_false() 33 | if not self.token: 34 | self.login_compute() 35 | if not request_headers: 36 | request_headers = {'Content-Type': 'application/json'} 37 | if body_params: 38 | body_params_json = json.dumps(body_params) 39 | else: 40 | body_params_json = None 41 | # Set User Agent 42 | request_headers['User-Agent'] = "W" 43 | # Endpoints that return large numbers of results use a 'Total-Count' response header. 44 | # Pagination is via query parameters for both GET and POST, and the limit has a maximum of 50. 45 | offset = 0 46 | limit = 50 47 | more = False 48 | results = [] 49 | 50 | retries = Retry(total=self.retry_number, 51 | status_forcelist=self.retry_status_codes, raise_on_status=False) 52 | 53 | with requests.Session() as session: 54 | session.mount('https://%s/%s' % (self.api_compute, 55 | endpoint), adapter=HTTPAdapter(max_retries=retries)) 56 | 57 | while offset == 0 or more is True: 58 | if int(time.time() - self.token_timer) > self.token_limit: 59 | self.extend_login_compute() 60 | if paginated: 61 | url = 'https://%s/%s?limit=%s&offset=%s' % ( 62 | self.api_compute, endpoint, limit, offset) 63 | else: 64 | url = 'https://%s/%s' % (self.api_compute, endpoint) 65 | if self.token: 66 | if self.api: 67 | # Authenticate via CSPM 68 | request_headers['x-redlock-auth'] = self.token 69 | else: 70 | # Authenticate via CWP 71 | request_headers['Authorization'] = "Bearer %s" % self.token 72 | self.debug_print('API URL: %s' % url) 73 | self.debug_print('API Request Headers: (%s)' % request_headers) 74 | self.debug_print('API Query Params: %s' % query_params) 75 | self.debug_print('API Body Params: %s' % body_params_json) 76 | # Add User-Agent to the headers 77 | request_headers['User-Agent'] = self.user_agent 78 | api_response = session.request(action, url, headers=request_headers, params=query_params, 79 | data=body_params_json, verify=self.verify, timeout=self.timeout) 80 | self.debug_print('API Response Status Code: (%s)' % 81 | api_response.status_code) 82 | self.debug_print('API Response Headers: (%s)' % 83 | api_response.headers) 84 | if api_response.ok: 85 | if not api_response.content: 86 | return None 87 | if api_response.headers.get('Content-Type') == 'application/x-gzip': 88 | return api_response.content 89 | if api_response.headers.get('Content-Type') == 'text/csv': 90 | return api_response.content.decode('utf-8') 91 | try: 92 | result = json.loads(api_response.content) 93 | # if result is None: 94 | # 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)) 95 | # if force: 96 | # return results # or continue 97 | # 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)) 98 | except ValueError: 99 | self.logger.error('JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % ( 100 | url, query_params, body_params, api_response.content)) 101 | if force: 102 | return results # or continue 103 | self.error_and_exit(api_response.status_code, 'JSON raised ValueError, API: (%s) with query params: (%s) and body params: (%s) parsing response: (%s)' % ( 104 | url, query_params, body_params, api_response.content)) 105 | if 'Total-Count' in api_response.headers: 106 | self.debug_print('Retrieving Next Page of Results: Offset/Total Count: %s/%s' % ( 107 | offset, api_response.headers['Total-Count'])) 108 | total_count = int(api_response.headers['Total-Count']) 109 | if total_count > 0: 110 | results.extend(result) 111 | offset += limit 112 | more = bool(offset < total_count) 113 | else: 114 | return result 115 | else: 116 | self.logger.error('API: (%s) responded with a status of: (%s), with query: (%s) and body params: (%s)' % ( 117 | url, api_response.status_code, query_params, body_params)) 118 | if force: 119 | return results 120 | 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' % ( 121 | url, query_params, body_params, api_response.text)) 122 | return results 123 | 124 | # The Compute API setting is optional. 125 | 126 | def validate_api_compute(self): 127 | if not self.api_compute: 128 | self.error_and_exit( 129 | 500, 'Please specify a Prisma Cloud Compute API URL.') 130 | 131 | # Exit handler (Error). 132 | 133 | @classmethod 134 | def error_and_exit(cls, error_code, error_message='', system_message=''): 135 | raise SystemExit('\n\nStatus Code: %s\n%s\n%s\n' % 136 | (error_code, error_message, system_message)) 137 | -------------------------------------------------------------------------------- /prismacloud/api/pc_lib_api.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud API Class """ 2 | 3 | import logging 4 | 5 | from .cspm import PrismaCloudAPICSPM 6 | from .cwpp import PrismaCloudAPICWPP 7 | from .pccs import PrismaCloudAPIPCCS 8 | 9 | from .pc_lib_utility import PrismaCloudUtility 10 | from .version import version # Import version from your version.py 11 | 12 | # --Description-- # 13 | 14 | # Prisma Cloud API library. 15 | 16 | # pylint: disable=too-few-public-methods 17 | class CallCounter: 18 | """ Decorator to determine number of calls for a method """ 19 | def __init__(self, method): 20 | self.method = method 21 | self.counter = 0 22 | 23 | def __call__(self, *args, **kwargs): 24 | self.counter += 1 25 | return self.method(*args, **kwargs) 26 | 27 | # pylint: disable=too-many-instance-attributes 28 | class PrismaCloudAPI(PrismaCloudAPICSPM, PrismaCloudAPICWPP, PrismaCloudAPIPCCS): 29 | """ Prisma Cloud API Class """ 30 | # pylint: disable=super-init-not-called 31 | def __init__(self): 32 | self.name = '' 33 | self.api = '' 34 | self.api_compute = '' 35 | self.identity = None 36 | self.secret = None 37 | self.verify = True 38 | self.debug = False 39 | # 40 | self.timeout = None # timeout=(16, 300) 41 | self.token = None 42 | self.token_timer = 0 43 | self.token_limit = 590 # aka 9 minutes 44 | self.retry_status_codes = [425, 429, 500, 502, 503, 504] 45 | self.retry_waits = [1, 2, 4, 8, 16, 32] 46 | self.retry_number = 6 47 | self.max_workers = 8 48 | # 49 | self.error_log = 'error.log' 50 | self.logger = None 51 | # Set User-Agent 52 | default_user_agent = f"PrismaCloudAPI/{version}" # Dynamically set default User-Agent 53 | self.user_agent = default_user_agent 54 | 55 | def __repr__(self): 56 | 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) 57 | 58 | def configure(self, settings, use_meta_info=True): 59 | self.name = settings.get('name', '') 60 | self.identity = settings.get('identity') 61 | self.secret = settings.get('secret') 62 | self.verify = settings.get('verify', True) 63 | self.debug = settings.get('debug', False) 64 | self.user_agent = settings.get('user_agent', self.user_agent) 65 | # 66 | # self.logger = settings['logger'] 67 | self.logger = logging.getLogger(__name__) 68 | formatter = logging.Formatter(fmt='%(asctime)s: %(levelname)s: %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p') 69 | filehandler = logging.FileHandler(self.error_log, delay=True) 70 | filehandler.setLevel(level=logging.DEBUG) 71 | filehandler.setFormatter(formatter) 72 | self.logger.addHandler(filehandler) 73 | self.logger.error = CallCounter(self.logger.error) 74 | # 75 | url = PrismaCloudUtility.normalize_url(settings.get('url', '')) 76 | if url: 77 | if url.endswith('.prismacloud.io') or url.endswith('.prismacloud.cn'): 78 | # URL is a Prisma Cloud CSPM API URL. 79 | self.api = url 80 | # Use the Prisma Cloud CSPM API to identify the Prisma Cloud CWP API URL. 81 | if use_meta_info: 82 | meta_info = self.meta_info() 83 | if meta_info and 'twistlockUrl' in meta_info: 84 | self.api_compute = PrismaCloudUtility.normalize_url(meta_info['twistlockUrl']) 85 | else: 86 | # URL is a Prisma Cloud CWP API URL. 87 | self.api_compute = PrismaCloudUtility.normalize_url(url) 88 | 89 | # Conditional printing. 90 | 91 | def debug_print(self, message): 92 | if self.debug: 93 | print(message) 94 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/README.md: -------------------------------------------------------------------------------- 1 | ## Adding Endpoints 2 | 3 | While most of the endpoints documented in the [CCS API Reference](https://prisma.pan.dev/api/cloud/code) are defined as methods in this SDK, 4 | some are not, as endpoints are added to Prisma Cloud as features are added. 5 | 6 | To add an method for an endpoint, refer to the above API Reference, and use the existing methods as examples. 7 | 8 | Each logical set of endpoints (defined by URL prefix) is grouped into its own file, and imported in `__init__.py `. 9 | 10 | When adding a new group of endpoints, create a new file, and import it using the existing imports as examples. 11 | 12 | --- 13 | 14 | In `__init__.py `: 15 | 16 | ``` 17 | from ._example.py import * 18 | ``` 19 | 20 | In `_example.py`: 21 | 22 | ``` 23 | """ Prisma Cloud Code Security Example Endpoints Class """ 24 | 25 | class ExamplePrismaCloudAPICodeSecurityMixin: 26 | """ Prisma Cloud Code Security API Example Endpoints Class """ 27 | 28 | def example_list(self): 29 | return self.execute_code_security('GET', 'code/api/v1/example/endpoint') 30 | ``` 31 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/__init__.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security API Class """ 2 | 3 | import sys 4 | 5 | from .pccs import * 6 | from ._checkov_version import * 7 | from ._errors import * 8 | from ._fixes import * 9 | from ._repositories import * 10 | from ._rules import * 11 | from ._scans import * 12 | from ._suppressions import * 13 | from ._packages import * 14 | from ._code_policies import * 15 | 16 | mixin_classes_as_strings = list(filter(lambda x: x.endswith('PrismaCloudAPIPCCSMixin'), dir())) 17 | mixin_classes = [getattr(sys.modules[__name__], x) for x in mixin_classes_as_strings] 18 | 19 | # pylint: disable=too-few-public-methods 20 | class PrismaCloudAPIPCCS(*mixin_classes): 21 | """ Prisma Cloud Code Security API Class """ 22 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_checkov_version.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security Checkov Version Endpoint Class """ 2 | 3 | # Checkov Version 4 | 5 | class CheckovVersionPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Checkov Version Endpoint Class """ 7 | 8 | def checkov_version(self): 9 | version = self.execute_code_security('GET', 'code/api/v1/checkovVersion') 10 | return version 11 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_code_policies.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security API Code Policies Endpoints Class """ 2 | 3 | # Code Policies 4 | 5 | class CodePoliciesPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Code Policies Endpoints Class """ 7 | 8 | def code_policies_list_read(self, policy_id=None): 9 | if policy_id: 10 | # Fetch details for a specific policy 11 | return self.execute_code_security('GET', 'code/api/v2/policies/%s' % policy_id) 12 | else: 13 | # Fetch a list of all policies if no specific policy_id is provided 14 | return self.execute_code_security('GET', 'code/api/v2/policies') 15 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_errors.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security Errors Endpoints Class """ 2 | 3 | # Errors 4 | 5 | class ErrorsPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Errors Endpoints Class """ 7 | 8 | def errors_files_list(self, criteria): 9 | return self.execute_code_security('POST', 'code/api/v1/errors/files', body_params=criteria) 10 | 11 | def errors_file_list(self, criteria): 12 | return self.execute_code_security('POST', 'code/api/v1/errors/file', body_params=criteria, paginated=True) 13 | 14 | def errors_list_last_authors(self, query_params=None): 15 | return self.execute_code_security('GET', 'code/api/v1/errors/gitBlameAuthors', query_params=query_params) 16 | 17 | def fix_or_suppress_scan_results(self, criteria): 18 | return self.execute_code_security('POST', 'code/api/v1/errors/submitActions', body_params=criteria) 19 | 20 | def fixed_resource_code(self, criteria): 21 | return self.execute_code_security('POST', 'code/api/v1/errors/getFixedCode', body_params=criteria) 22 | 23 | def resources_list(self, body_params=None): 24 | return self.execute_code_security('POST', 'code/api/v2/errors/branch_scan/resources', body_params=body_params) 25 | 26 | def policies_list(self, resource_uuid, body_params=None): 27 | return self.execute_code_security('POST', 'code/api/v2/errors/branch_scan/resources/%s/policies' % resource_uuid, body_params=body_params) 28 | 29 | def vulnerabilities_list(self, resource_uuid, query_params): 30 | return self.execute_code_security('GET', 'code/api/v2/summaries/resource/%s/Vulnerabilities' % resource_uuid, query_params=query_params) 31 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_fixes.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security Fixes Endpoints Class """ 2 | 3 | # Fixes 4 | 5 | class FixesPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Fixes Endpoints Class """ 7 | 8 | def fixes_list(self, body_params): 9 | return self.execute_code_security('POST', 'code/api/v1/fixes/checkov', body_params=body_params) 10 | 11 | def fixed_resource(self, criteria): 12 | return self.execute_code_security('POST', 'code/api/v2/actions/branch_fixes', body_params=criteria) 13 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_packages.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security Scans Endpoints Class """ 2 | 3 | # Scans 4 | 5 | class PackagesPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Packages Endpoints Class """ 7 | 8 | def list_cves_per_package(self, package_uuid, query_params=None): 9 | return self.execute_code_security('GET', 'code/api/v1/vulnerabilities/packages/%s/cves' % package_uuid, query_params=query_params) 10 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_repositories.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security Repositories Endpoints Class """ 2 | 3 | # Repositories 4 | 5 | class RepositoriesPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Repositories Endpoints Class """ 7 | 8 | def repositories_list_read(self, query_params=None): 9 | return self.execute_code_security('GET', 'code/api/v1/repositories', query_params=query_params) 10 | 11 | def repository_name(self, body_params=None): 12 | return self.execute_code_security('POST', 'code/api/v1/repositories/query', body_params=body_params) 13 | 14 | def repository_branches(self, query_params=None): 15 | return self.execute_code_security('GET', 'code/api/v1/repositories/branches', query_params=query_params) 16 | 17 | def repositories_update(self, body_params): 18 | return self.execute_code_security('POST', 'code/api/v1/repositories', body_params=body_params) 19 | 20 | def repositories_list_read_v2(self, query_params=None): 21 | return self.execute_code_security('GET', 'code/api/v2/repositories', query_params=query_params) 22 | 23 | def vcs_repositories_list_read(self, query_params=None): 24 | return self.execute_code_security('POST', 'code/api/v1/vcs-repository/repositories', query_params=query_params) 25 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_rules.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security API Enforcement Rules Endpoints Class """ 2 | 3 | # enforcement-rules 4 | 5 | class RulesPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Enforcement Rules Endpoints Class """ 7 | 8 | def enforcement_rules_read(self): 9 | return self.execute_code_security('GET', 'code/api/v1/enforcement-rules') 10 | 11 | def enforcement_rules_update(self, rules): 12 | return self.execute_code_security('PUT', 'code/api/v1/enforcement-rules', body_params=rules) 13 | 14 | def enforcement_rules_exception_create(self, policy_id, rule): 15 | return self.execute_code_security('POST', 'code/api/v1/enforcement-rules', body_params=rule) 16 | 17 | def enforcement_rules_exception_delete(self, rule_id): 18 | return self.execute_code_security('DELETE', 'code/api/v1/enforcement-rules/%s' % (rule_id)) 19 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_scans.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security Scans Endpoints Class """ 2 | 3 | # Scans 4 | 5 | class ScansPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Scans Endpoints Class """ 7 | 8 | def scan(self): 9 | return self.execute_code_security('POST', 'code/api/v1/scans/integrations') 10 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/_suppressions.py: -------------------------------------------------------------------------------- 1 | """ Prisma Cloud Code Security Suppressions Endpoints Class """ 2 | 3 | # Suppressions 4 | 5 | class SuppressionsPrismaCloudAPIPCCSMixin: 6 | """ Prisma Cloud Code Security API Suppressions Endpoints Class """ 7 | 8 | def suppressions_list_read(self): 9 | return self.execute_code_security('GET', 'code/api/v1/suppressions') 10 | 11 | def suppressions_create(self, policy_id, rule): 12 | return self.execute_code_security('POST', 'code/api/v1/suppressions/%s' % policy_id, body_params=rule) 13 | 14 | def suppressions_update(self, policy_id, rule_id, rule): 15 | return self.execute_code_security('PUT', 'code/api/v1/suppressions/%s/justifications/%s' % (policy_id, rule_id), body_params=rule) 16 | 17 | def suppressions_delete(self, policy_id, rule_id): 18 | return self.execute_code_security('DELETE', 'code/api/v1/suppressions/%s/justifications/%s' % (policy_id, rule_id)) 19 | 20 | def suppressions_justifications_list_read(self, policy_id, query_params): 21 | return self.execute_code_security('GET', 'code/api/v1/suppressions/%s/justifications' % policy_id, query_params=query_params) 22 | -------------------------------------------------------------------------------- /prismacloud/api/pccs/pccs.py: -------------------------------------------------------------------------------- 1 | """ Requests and Output """ 2 | 3 | import json 4 | import time 5 | 6 | import requests 7 | 8 | class PrismaCloudAPIPCCSMixin(): 9 | """ Requests and Output """ 10 | 11 | # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements 12 | def execute_code_security(self, action, endpoint, query_params=None, body_params=None, request_headers=None, force=False, paginated=False): 13 | self.suppress_warnings_when_verify_false() 14 | if not self.token: 15 | self.login() 16 | if int(time.time() - self.token_timer) > self.token_limit: 17 | self.extend_login() 18 | if not request_headers: 19 | request_headers = {'Content-Type': 'application/json'} 20 | if body_params: 21 | body_params_json = json.dumps(body_params) 22 | else: 23 | body_params_json = None 24 | # Endpoints that return large numbers of results use a 'hasNext' key. 25 | # Pagination is via query parameters for both GET and POST, and appears to be specific to "List File Errors - POST". 26 | offset = 0 27 | limit = 50 28 | more = False 29 | results = [] 30 | while offset == 0 or more is True: 31 | if int(time.time() - self.token_timer) > self.token_limit: 32 | self.extend_login() 33 | if paginated: 34 | url = 'https://%s/%s?limit=%s&offset=%s' % (self.api, endpoint, limit, offset) 35 | else: 36 | url = 'https://%s/%s' % (self.api, endpoint) 37 | if self.token: 38 | request_headers['authorization'] = self.token 39 | self.debug_print('API URL: %s' % url) 40 | self.debug_print('API Headers: %s' % request_headers) 41 | self.debug_print('API Query Params: %s' % query_params) 42 | self.debug_print('API Body Params: %s' % body_params_json) 43 | # Add User-Agent to the headers 44 | request_headers['User-Agent'] = self.user_agent 45 | api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout) 46 | self.debug_print('API Response Status Code: %s' % api_response.status_code) 47 | self.debug_print('API Response Headers: (%s)' % api_response.headers) 48 | if api_response.status_code in self.retry_status_codes: 49 | for exponential_wait in self.retry_waits: 50 | time.sleep(exponential_wait) 51 | api_response = requests.request(action, url, headers=request_headers, params=query_params, data=body_params_json, verify=self.verify, timeout=self.timeout) 52 | if api_response.ok: 53 | break # retry loop 54 | if api_response.ok: 55 | if not api_response.content: 56 | return None 57 | if api_response.headers.get('Content-Type') == 'application/x-gzip': 58 | return api_response.content 59 | if api_response.headers.get('Content-Type') == 'text/csv': 60 | return api_response.content.decode('utf-8') 61 | try: 62 | result = json.loads(api_response.content) 63 | #if result is None: 64 | # 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)) 65 | # if force: 66 | # return results # or continue 67 | # 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)) 68 | except ValueError: 69 | 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)) 70 | if force: 71 | return results # or continue 72 | 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)) 73 | if paginated: 74 | results.extend(result['data']) 75 | if 'hasNext' in result: 76 | self.debug_print('Retrieving Next Page of Results') 77 | offset += limit 78 | more = result['hasNext'] 79 | else: 80 | return results 81 | else: 82 | return result 83 | else: 84 | 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)) 85 | if force: 86 | return results 87 | 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)) 88 | return results 89 | 90 | # Exit handler (Error). 91 | 92 | @classmethod 93 | def error_and_exit(cls, error_code, error_message='', system_message=''): 94 | raise SystemExit('\n\nStatus Code: %s\n%s\n%s\n' % (error_code, error_message, system_message)) 95 | -------------------------------------------------------------------------------- /prismacloud/api/version.py: -------------------------------------------------------------------------------- 1 | version = "5.3.0" 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "prismacloud-api" 7 | version = "5.3.0" 8 | dependencies = [] 9 | dynamic = ["description", "readme", "requires-python", "classifiers", "authors"] 10 | 11 | [project.optional-dependencies] 12 | test = ["coverage==7.6.10", "responses==0.25.3"] 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | update_checker -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | coverage==7.6.10 2 | responses==0.25.3 3 | update_checker -------------------------------------------------------------------------------- /scripts/examples/README.md: -------------------------------------------------------------------------------- 1 | ## Support 2 | 3 | This project has been developed by Prisma Cloud SEs, it is not Supported by Palo Alto Networks. 4 | Nevertheless, the maintainers will make a best-effort to address issues, and (of course) contributors are encouraged to submit issues and pull requests. 5 | 6 | ## Example Script 7 | 8 | Prior to running the script ensure that the prismacloud-api is configured to run as described in [README.md](#scripts/README.md) 9 | 10 | ### pcs_vuln_container_with_cve_2022_22965.py 11 | 12 | List packages that contain CVE-2022-22965 and JDK **other than** 1.8. 13 | The results are stored to a *CVE-2022-22965-WITHOUT-JAVA8.csv* in the same directory. 14 | 15 | ``` 16 | python3 pcs_vuln_container_with_cve_2022_22965.py 17 | ``` 18 | -------------------------------------------------------------------------------- /scripts/examples/pcs_vuln_container_with_cve_2022_22965.py: -------------------------------------------------------------------------------- 1 | """ Get a list of vulnerable containers and their clusters """ 2 | 3 | import os 4 | 5 | from prismacloud.api import pc_api, pc_utility 6 | 7 | # --Configuration-- # 8 | 9 | parser = pc_utility.get_arg_parser() 10 | parser.add_argument( 11 | '--cve', 12 | type=str, 13 | required=False, 14 | default='CVE-2022-22965', 15 | help='(Required) - ID of the CVE.') 16 | parser.add_argument( 17 | '--image_id', 18 | type=str, 19 | help='(Optional) - ID of the Image (sha256:...).') 20 | parser.add_argument( 21 | '--mode', 22 | type=str, 23 | choices=['registry', 'deployed', 'all'], 24 | default='all', 25 | help='(Optional) - Report on Registry, Deployed, or all Images.') 26 | args = parser.parse_args() 27 | 28 | # --Helpers-- # 29 | 30 | # --Initialize-- # 31 | 32 | settings = pc_utility.get_settings(args) 33 | pc_api.configure(settings) 34 | pc_api.validate_api_compute() 35 | 36 | # --Main-- # 37 | 38 | print('Testing Compute API Access ...', end='') 39 | intelligence = pc_api.statuses_intelligence() 40 | print(' done.') 41 | print() 42 | 43 | print('Searching for CVE: (%s) Limiting Search to Image ID: (%s)' % (args.cve, args.image_id)) 44 | print() 45 | 46 | # Image_ID, Registry, Repo, Tag, CVE_ID, Application, Version, path 47 | vulnerableImageDetails = [] 48 | vulnerableImageDetails.append("Image_ID,Registry,Repo,Tag,CVE_ID,Application,Version,Path\n") 49 | 50 | # Monitor > Vulnerabilities/Compliance > Images > Registries 51 | if args.mode in ['registry', 'all']: 52 | registry_images = pc_api.registry_list_read(args.image_id) 53 | print('Getting Registry Images ...', end='') 54 | print(' done.') 55 | print('Found %s Registry Images' % len(registry_images)) 56 | print() 57 | for image in registry_images: 58 | image_id = image['_id'] 59 | vulnerabilities = image['vulnerabilities'] 60 | if not vulnerabilities: 61 | # print('No vulnerabilities for Image ID: %s' % image_id) 62 | continue 63 | vulnerable = False 64 | for vulnerability in vulnerabilities: 65 | if args.cve and vulnerability['cve'] == args.cve: 66 | vulnerable = True 67 | # print('Image ID: %s is vulnerable to CVE: %s' % (image_id, args.cve)) 68 | #check if it contains the java application 69 | if 'applications' in image: 70 | for app in image['applications']: 71 | if ((app['name'] == 'java') and ('1.8' not in app['version'])): 72 | _registry = image['tags'][0]['registry'] 73 | _repo = image['tags'][0]['repo'] 74 | _tag = image['tags'][0]['tag'] 75 | _vulnerabilityDetails = image['_id'] + ',' + _registry + ',' + _repo + ',' + _tag + ',' + vulnerability['cve'] + ',' + app['name'] + ',' + app['version'] + ',' + app['path'] + '\n' 76 | vulnerableImageDetails.append(_vulnerabilityDetails) 77 | #print('Applicaiton Name: %s' % app) 78 | #print('Image ID: %s is vulnerable to CVE: %s' % (image_id, args.cve)) 79 | #print('Registry: %s' % image['tags']) 80 | break 81 | break 82 | print() 83 | 84 | # Monitor > Vulnerabilities/Compliance > Images > Deployed 85 | if args.mode in ['deployed', 'all']: 86 | print('Getting Deployed Images ...', end='') 87 | deployed_images = pc_api.images_list_read(image_id=args.image_id, query_params={'filterBaseImage': 'true'}) 88 | print(' done.') 89 | print('Found %s Deployed Images' % len(deployed_images)) 90 | print() 91 | # Image_ID, Registry, Repo, Tag, CVE_ID, Application, Version, path 92 | # vulnerableImageDetails = [] 93 | vulnerableImageDetails.append("Image_ID,Registry,Repo,Tag,CVE_ID,Application,Version,Path\n") 94 | for image in deployed_images: 95 | image_id = image['_id'] 96 | vulnerabilities = image['vulnerabilities'] 97 | 98 | if not vulnerabilities: 99 | # print('No vulnerabilities for Image ID: %s' % image_id) 100 | continue 101 | vulnerable = False 102 | for vulnerability in vulnerabilities: 103 | if args.cve and vulnerability['cve'] == args.cve: 104 | vulnerable = True 105 | # Check if it contains the java application 106 | if 'applications' in image: 107 | for app in image['applications']: 108 | if ((app['name'] == 'java') and ('1.8' not in app['version'])): 109 | _registry = image['tags'][0]['registry'] 110 | _repo = image['tags'][0]['repo'] 111 | _tag = image['tags'][0]['tag'] 112 | _vulnerabilityDetails = image['_id'] + ',' + _registry + ',' + _repo + ',' + _tag + ',' + vulnerability['cve'] + ',' + app['name'] + ',' + app['version'] + ',' + app['path'] + '\n' 113 | vulnerableImageDetails.append(_vulnerabilityDetails) 114 | #print('Applicaiton Name: %s' % app) 115 | #print('Image ID: %s is vulnerable to CVE: %s' % (image_id, args.cve)) 116 | #print('Registry: %s' % image['tags']) 117 | break 118 | break 119 | print() 120 | #print(vulnerableImageDetails) 121 | 122 | file_name_and_path = os.path.join(os.getcwd(), 'CVE-2022-22965-WITHOUT-JAVA8.csv') 123 | # pylint: disable=broad-except 124 | try: 125 | with open(file_name_and_path, 'w') as file: 126 | for line in vulnerableImageDetails: 127 | file.write(line) 128 | except Exception as ex: 129 | print('Failed to write csv file. %s', ex) 130 | -------------------------------------------------------------------------------- /scripts/pc_account_add_example_standalone.py: -------------------------------------------------------------------------------- 1 | """ Standalone Example: Add a Cloud Account """ 2 | 3 | import json 4 | 5 | import requests 6 | 7 | ## This is an example of using the Prisma Cloud API to: 8 | # 1. Log in using an API key (https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin/manage-prisma-cloud-administrators/create-access-keys.html) 9 | # 2. Using the resulting token from login to add a new AWS account to Prisma Cloud. 10 | # Note: This is a very simple example to demonstrate the basic use of the API for Prisma Cloud Compute. 11 | 12 | # Documentation of the Prisma Cloud API: 13 | # https://api.docs.prismacloud.io/reference#login 14 | 15 | # --Configuration-- # 16 | 17 | PRISMA_CLOUD_API_ACCESS_KEY = 'prisma-cloud-access-key-here' 18 | PRISMA_CLOUD_API_SECRET_KEY = 'prisma-cloud-secret-key-here' 19 | 20 | # Cloud Account Information for the API Call (This is information about your AWS account and AWS Prisma Cloud Role). 21 | CLOUD_TYPE = 'aws' 22 | CLOUD_ACCOUNT_ID_TO_ADD = 'some-aws-account-number' 23 | CLOUD_ACCOUNT_ENABLED_FOR_SCANNING_IN_PRISMA_CLOUD = True 24 | CLOUD_ACCOUNT_EXTERNAL_ID_FROM_AWS_ROLE = 'external-id-used-in-the-prisma-cloud-role-in-aws' 25 | CLOUD_ACCOUNT_ROLE_ARN_FROM_AWS = 'the-arn-from-the-prisma-cloud-role-created-in-aws' 26 | PRISMA_CLOUD_ACCOUNT_GROUP_IDS = ['prisma-account-group-id-from-api-or-url-in-ui', 'another-prisma-account-group-id-from-api-or-url-in-ui'] 27 | PRISMA_CLOUD_ACCOUNT_FRENDLY_NAME = 'some-friendly-account-name-to-show-in-prisma-cloud' 28 | PRISMA_CLOUD_MONITOR_MODE = 'MONITOR_AND_PROTECT' 29 | 30 | ################################ 31 | 32 | # Set the API 33 | headers = {'Content-Type': 'application/json'} 34 | api_url = 'https://api.prismacloud.io/login' 35 | action = 'POST' 36 | 37 | # Set the POST 38 | data = {} 39 | data['username'] = PRISMA_CLOUD_API_ACCESS_KEY 40 | data['password'] = PRISMA_CLOUD_API_SECRET_KEY 41 | data_json = json.dumps(data) 42 | 43 | # POST 44 | response_raw = requests.request(action, api_url, headers=headers, data=data_json, timeout=16) 45 | response_data = response_raw.json() 46 | token = response_data['token'] 47 | 48 | # --Main-- # 49 | 50 | ## Example of the Add Cloud Account API 51 | 52 | headers = {'Content-Type': 'application/json', 'x-redlock-auth': token} 53 | api_url = 'https://api.prismacloud.io/cloud/' + CLOUD_TYPE 54 | action = 'POST' 55 | 56 | # Set the POST 57 | data = {} 58 | data['accountId'] = CLOUD_ACCOUNT_ID_TO_ADD 59 | data['enabled'] = CLOUD_ACCOUNT_ENABLED_FOR_SCANNING_IN_PRISMA_CLOUD 60 | data['externalId'] = CLOUD_ACCOUNT_EXTERNAL_ID_FROM_AWS_ROLE 61 | data['groupIds'] = PRISMA_CLOUD_ACCOUNT_GROUP_IDS 62 | data['name'] = PRISMA_CLOUD_ACCOUNT_FRENDLY_NAME 63 | data['protectionMode'] = PRISMA_CLOUD_MONITOR_MODE 64 | data['roleArn'] = CLOUD_ACCOUNT_ROLE_ARN_FROM_AWS 65 | data_json = json.dumps(data) 66 | 67 | # POST 68 | response_raw = requests.request(action, api_url, headers=headers, data=data_json, timeout=16) 69 | print(response_raw) 70 | -------------------------------------------------------------------------------- /scripts/pc_compute_cloud_discovery_example_standalone.py: -------------------------------------------------------------------------------- 1 | """ Standalone Example: Enable Cloud Discovery """ 2 | 3 | import json 4 | 5 | import requests 6 | 7 | # pylint: disable=import-error 8 | from requests.auth import HTTPBasicAuth 9 | 10 | ## This is an example of using the Prisma Cloud Compute API to: 11 | # 1. Add an AWS Access Key and Secret to Prisma Cloud Compute (SaaS) 12 | # 2. Add the new Access Key to the Cloud Discovery policy. 13 | # Note: This is a very simple example to demonstrate the basic use of the API for Prisma Cloud Compute. 14 | 15 | # Documentation of the Prisma Cloud Compute API: 16 | # https://cdn.twistlock.com/docs/api/twistlock_api.html 17 | 18 | # --Configuration-- # 19 | 20 | PRISMA_CLOUD_API_ACCESS_KEY = 'prisma-cloud-access-key-here' 21 | PRISMA_CLOUD_API_SECRET_KEY = 'prisma-cloud-secret-key-here' 22 | 23 | # AWS Access Key / Secret Key to add: 24 | AWS_ACCESS_KEY = 'aws-access-key-here' 25 | AWS_SECRET_KEY = 'aws-secret-key-here' 26 | 27 | # Descriptive name for the Credentials in Prisma Cloud Compute. 28 | PRISMA_CLOUD_CREDENTIAL_FRIENDLY_NAME = 'unique-descriptive-name-for-this-credential' 29 | 30 | # Console URL for your Prisma Cloud Compute Instance. 31 | PRISMA_CLOUD_COMPUTE_CONSOLE_URL = 'prisma-cloud-compute-console-url-here' 32 | 33 | # API and Version URL suffix. 34 | PRISMA_CLOUD_COMPUTE_CONSOLE_API_VERSION = '/api/v1' 35 | 36 | # --Main-- # 37 | 38 | # Set the API 39 | headers = {'Content-Type': 'application/json'} 40 | api_url = 'https://' + PRISMA_CLOUD_COMPUTE_CONSOLE_URL + PRISMA_CLOUD_COMPUTE_CONSOLE_API_VERSION + '/credentials' 41 | action = 'POST' 42 | 43 | # Add User-Agent to the headers 44 | request_headers['User-Agent'] = self.user_agent 45 | 46 | # Set the POST 47 | data = {} 48 | data['_id'] = PRISMA_CLOUD_CREDENTIAL_FRIENDLY_NAME 49 | data['accountID'] = AWS_ACCESS_KEY 50 | data['apiToken'] = {} 51 | data['caCert'] = '' 52 | data['secret'] = {} 53 | data['secret']['plain'] = AWS_SECRET_KEY 54 | data['serviceAccount'] = {} 55 | data['type'] = 'aws' 56 | data_json = json.dumps(data) 57 | 58 | # POST 59 | response_raw = requests.request(action, api_url, auth=HTTPBasicAuth(PRISMA_CLOUD_API_ACCESS_KEY, PRISMA_CLOUD_API_SECRET_KEY), headers=headers, data=data_json, timeout=16) 60 | print(response_raw) 61 | 62 | ################################ 63 | 64 | ## Example of the updating Policy via the Policy Update API 65 | 66 | # Get the existing Cloud Discovery List 67 | 68 | # Set the API 69 | headers = {'Content-Type': 'application/json'} 70 | api_url = 'https://' + PRISMA_CLOUD_COMPUTE_CONSOLE_URL + PRISMA_CLOUD_COMPUTE_CONSOLE_API_VERSION + '/policies/cloud-platforms' 71 | action = 'GET' 72 | 73 | # Add User-Agent to the headers 74 | request_headers['User-Agent'] = self.user_agent 75 | 76 | # GET 77 | response_raw = requests.request(action, api_url, auth=HTTPBasicAuth(PRISMA_CLOUD_API_ACCESS_KEY, PRISMA_CLOUD_API_SECRET_KEY), headers=headers, timeout=16) 78 | response_data = response_raw.json() 79 | 80 | rules_list = response_data['rules'] 81 | 82 | # Set the API 83 | headers = {'Content-Type': 'application/json'} 84 | api_url = 'https://' + PRISMA_CLOUD_COMPUTE_CONSOLE_URL + PRISMA_CLOUD_COMPUTE_CONSOLE_API_VERSION + '/policies/cloud-platforms' 85 | action = 'PUT' 86 | 87 | # Add User-Agent to the headers 88 | request_headers['User-Agent'] = self.user_agent 89 | 90 | # Set the POST 91 | new_policy_object = {} 92 | new_policy_object['awsRegionType'] = 'regular' 93 | new_policy_object['credentialId'] = PRISMA_CLOUD_CREDENTIAL_FRIENDLY_NAME 94 | new_policy_object['discoveryEnabled'] = True 95 | new_policy_object['roleArn'] = '' 96 | new_policy_object['serverlessRadarEnabled'] = False 97 | new_policy_object['vmTagsEnabled'] = False 98 | rules_list.append(new_policy_object) 99 | data = {} 100 | data['rules'] = rules_list 101 | data_json = json.dumps(data) 102 | 103 | # POST 104 | response_raw = requests.request(action, api_url, auth=HTTPBasicAuth(PRISMA_CLOUD_API_ACCESS_KEY, PRISMA_CLOUD_API_SECRET_KEY), headers=headers, data=data_json, timeout=16) 105 | print(response_raw) 106 | -------------------------------------------------------------------------------- /scripts/pcs_account_groups_by_tags.py: -------------------------------------------------------------------------------- 1 | """ 2 | Use cloud provider tags applied to cloud accounts to automatically place them into Prisma Cloud Account Groups. 3 | """ 4 | 5 | 6 | # pylint: disable=import-error 7 | from prismacloud.api import pc_api, pc_utility 8 | 9 | # --Configuration-- # 10 | 11 | parser = pc_utility.get_arg_parser() 12 | parser.add_argument( 13 | '--cloud_provider', 14 | type=str, 15 | choices=['aws', 'azure', 'gcp'], 16 | default='aws', 17 | help='Cloud Provider.') 18 | parser.add_argument( 19 | '--key', 20 | type=str, 21 | default='owner', 22 | help='Tag Key to use to assign Cloud Account Groups.') 23 | args = parser.parse_args() 24 | 25 | # --Initialize-- # 26 | 27 | settings = pc_utility.get_settings(args) 28 | pc_api.configure(settings) 29 | 30 | # --Helpers --# 31 | 32 | def summarize_account_groups(groups): 33 | results = {} 34 | for group in groups: 35 | if 'description' not in group: 36 | account_group['description'] = '' 37 | results[groups['name']] = { 38 | 'id': group['id'], 39 | 'name': group['name'], 40 | 'description': group['description'], 41 | 'accountIds': group['accountIds'] 42 | } 43 | return results 44 | 45 | # --Main-- # 46 | 47 | # Only RQL returns cloud account tags. 48 | # But there is no guarantee that all accounts returned by RQL have been onboarded. 49 | # 50 | # TODO: Merge the Cloud Accounts endpoints results (including parents) with RQL results. 51 | # Use those IDs to create or update Cloud Account Groups. 52 | 53 | aws_rql = "config from cloud.resource where api.name = 'aws-organizations-account'" 54 | azure_rql = "config from cloud.resource where cloud.service = 'Azure Subscriptions'" 55 | gcp_rql = "config from cloud.resource where api.name = 'gcloud-compute-project-info'" 56 | 57 | if args.cloud_provider == 'aws': 58 | search_params = {'query': aws_rql} 59 | if args.cloud_provider == 'azure': 60 | search_params = {'query': azure_rql} 61 | if args.cloud_provider == 'gcp': 62 | search_params = {'query': gcp_rql} 63 | 64 | print('Using Tag Key: %s' % (args.key)) 65 | print() 66 | 67 | print('Reading Cloud Accounts') 68 | cloud_accounts = pc_api.search_config_read(search_params) 69 | print('%d Cloud Accounts Found' % len(cloud_accounts)) 70 | print() 71 | 72 | print('Reading Cloud Account Groups') 73 | account_groups = pc_api.cloud_account_group_list_read() 74 | print('%d Cloud Account Groups Found' % len(account_groups)) 75 | print() 76 | 77 | account_groups = summarize_account_groups(account_groups) 78 | 79 | print('Matching Cloud Accounts:') 80 | print() 81 | for cloud_account in cloud_accounts: 82 | account_data = cloud_account['data'] 83 | for tag in account_data['tags']: 84 | if tag['key'] == args.key: 85 | if tag['value'] in account_groups: 86 | account_group = account_groups[tag['value']] 87 | if cloud_account['id'] not in account_group['accountIds']: 88 | print('Adding Cloud Account (%s) to Cloud Account Group (%s)' % (account_data['name'], account_group['name'])) 89 | account_group['accountIds'].append(cloud_account['id']) 90 | payload = { 91 | 'name': account_group['name'], 92 | 'description': account_group['description'], 93 | 'accountIds': account_group['accountIds'] 94 | } 95 | result = pc_api.cloud_account_group_update(account_group['id'], payload) 96 | print(result) 97 | else: 98 | print('Creating Cloud Account Group (%s) and adding Cloud Account (%s)' % (tag['value'], account_data['name'])) 99 | payload = { 100 | 'name': tag['value'], 101 | 'description': account_group['description'], 102 | 'accountIds': [cloud_account['id']] 103 | } 104 | result = pc_api.cloud_account_group_create(payload) 105 | print(result) 106 | account_groups[tag['value']] = { 107 | 'id': result['id'], 108 | 'name': result['name'], 109 | 'description': result['description'], 110 | 'accountIds': result['accountIds'] 111 | } 112 | 113 | """ 114 | [{ 115 | 'id': '1234-5678-1234-5678-12345678', 116 | 'name': 'TJKCloudAccounts', 117 | 'description': '', 118 | 'accountIds': ['1234567890'], 119 | 'nonOnboardedCloudAccountIds': [], 120 | 'autoCreated': False, 121 | 'accounts': [{ 122 | 'id': '1234567890', 123 | 'name': 'TJK - AWS Account', 124 | 'type': 'aws' 125 | }] 126 | }] 127 | """ 128 | -------------------------------------------------------------------------------- /scripts/pcs_agentless_logs.py: -------------------------------------------------------------------------------- 1 | """ Download and Save Agentless Logs """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | DEFAULT_FILENAME = '/tmp/agentless_logs.tgz' 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | '--file', 13 | type=str, 14 | default=DEFAULT_FILENAME, 15 | help='Download agentless logs to this file. Default: %s' % DEFAULT_FILENAME 16 | ) 17 | args = parser.parse_args() 18 | 19 | # --Initialize-- # 20 | 21 | pc_api.configure(pc_utility.get_settings(args)) 22 | 23 | # --Main-- # 24 | 25 | print('Downloading agentless logs ...') 26 | 27 | data = pc_api.agentless_logs_read() 28 | 29 | with open(args.file, 'wb') as download: 30 | download.write(data) 31 | 32 | print('Saved agentless logs to: %s' % args.file) 33 | -------------------------------------------------------------------------------- /scripts/pcs_alert_rule_add_compliance_policies.py: -------------------------------------------------------------------------------- 1 | """ Add Policies to an alert rule based on compliance standard """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | 'alert_rule_name', 11 | type=str, 12 | help='Name of the Alert Rule to update' 13 | ) 14 | parser.add_argument( 15 | 'compliance_standard_name', 16 | type=str, 17 | help='Name of the Compliance Standard to add Policies from' 18 | ) 19 | args = parser.parse_args() 20 | 21 | # --Initialize-- # 22 | 23 | settings = pc_utility.get_settings(args) 24 | pc_api.configure(settings) 25 | 26 | # --Main-- # 27 | 28 | # Get Compliance policies 29 | 30 | print('API - Getting list of Policies by Compliance Standard ...', end='') 31 | policy_list = pc_api.compliance_standard_policy_v2_list_read(args.compliance_standard_name) 32 | compliance_policy_ids = [] 33 | for policy in policy_list: 34 | compliance_policy_ids.append(policy['policyId']) 35 | print(' done.') 36 | print() 37 | 38 | # Get Alert Rule 39 | 40 | print('API - Getting list of Alert Rules ...', end='') 41 | alert_rule_list = pc_api.alert_rule_list_read() 42 | for alert_rule in alert_rule_list: 43 | if alert_rule.get('name') == args.alert_rule_name: 44 | alert_rule_original = alert_rule 45 | print(' done.') 46 | print() 47 | 48 | # Update Alert Rule 49 | print('API - Updating Alert Rule with new Policy list ...', end='') 50 | alert_rule_original['policies'] = alert_rule_original.get('policies', []) + compliance_policy_ids 51 | pc_api.alert_rule_update(alert_rule_original['policyScanConfigId'], alert_rule_original) 52 | print(' done.') 53 | print() 54 | -------------------------------------------------------------------------------- /scripts/pcs_alert_rule_export.py: -------------------------------------------------------------------------------- 1 | """ Export Alert Rules """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | '--alert_rule', 11 | type=str, 12 | help='(Optional) - Export a single Alert Rule with the given name' 13 | ) 14 | parser.add_argument( 15 | 'export_file_name', 16 | type=str, 17 | help='Export file name for the Alert Rules.' 18 | ) 19 | args = parser.parse_args() 20 | 21 | # --Initialize-- # 22 | 23 | settings = pc_utility.get_settings(args) 24 | pc_api.configure(settings) 25 | 26 | # --Main-- # 27 | 28 | # Alert Rule Export 29 | 30 | export_file_data = {} 31 | export_file_data['alert_rule_list_original'] = [] 32 | 33 | print('API - Getting list of Alert Rules ...', end='') 34 | alert_rule_list = pc_api.alert_rule_list_read() 35 | if args.alert_rule: 36 | for alert_rule in alert_rule_list: 37 | if alert_rule.get('name') == args.alert_rule: 38 | export_file_data['alert_rule_list_original'] = [alert_rule] 39 | else: 40 | export_file_data['alert_rule_list_original'] = alert_rule_list 41 | print(' done.') 42 | print() 43 | 44 | print('Writing export file ...', end='') 45 | pc_utility.write_json_file(args.export_file_name, export_file_data) 46 | print(' done.') 47 | print() 48 | -------------------------------------------------------------------------------- /scripts/pcs_alert_rule_import.py: -------------------------------------------------------------------------------- 1 | """ Import Alert Rules """ 2 | 3 | import json 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | CUSTOM_POLICY_ID_MAP_FILE = 'PolicyIdMap.json' 11 | 12 | parser = pc_utility.get_arg_parser() 13 | parser.add_argument( 14 | '--alert_rule', 15 | type=str, 16 | help='(Optional) - Import a single Alert Rule with the given name' 17 | ) 18 | parser.add_argument( 19 | '--update_existing', 20 | action='store_true', 21 | help='(Optional) - Update the Alert Rule, if it exists.' 22 | ) 23 | parser.add_argument( 24 | '--skip_existing', 25 | action='store_true', 26 | help='(Optional) - Skip the Alert Rule, if it exists rather than erroring out.' 27 | ) 28 | parser.add_argument( 29 | '--skip_missing_policies', 30 | action='store_true', 31 | help='(Optional) - Skip the policies that do not exist in the new tenant rather than erroring out.' 32 | ) 33 | parser.add_argument( 34 | '--skip_missing_account_group', 35 | action='store_true', 36 | help='(Optional) - Skip the account groups that do not exist in the new tenant rather than erroring out.' 37 | ) 38 | parser.add_argument( 39 | '--skip_notifications', 40 | action='store_true', 41 | help='(Optional) - Skip third party notifications when creating the imported rule.' 42 | ) 43 | parser.add_argument( 44 | '--use_default_account_group', 45 | action='store_true', 46 | help='(Optional) - Use Default Account Group instead of those listed in export file. Useful when copying an alert rule to a new tenant with different accounts/account groups.' 47 | ) 48 | parser.add_argument( 49 | 'import_file_name', 50 | type=str, 51 | help='Import file name for Alert Rules.' 52 | ) 53 | args = parser.parse_args() 54 | 55 | # --Initialize-- # 56 | 57 | pc_utility.prompt_for_verification_to_continue(args) 58 | settings = pc_utility.get_settings(args) 59 | pc_api.configure(settings) 60 | 61 | try: 62 | custom_policy_id_map = json.load(open(CUSTOM_POLICY_ID_MAP_FILE, 'r')) 63 | except (ValueError, FileNotFoundError): 64 | custom_policy_id_map = {} 65 | 66 | 67 | # --Main-- # 68 | 69 | # Alert Rule Import 70 | 71 | import_file_data = pc_utility.read_json_file(args.import_file_name) 72 | 73 | # Validation 74 | if 'alert_rule_list_original' not in import_file_data: 75 | pc_utility.error_and_exit(404, 'alert_rule_list_original section not found. Please verify the import file and name.') 76 | 77 | alert_rule_list_original = import_file_data['alert_rule_list_original'] 78 | if alert_rule_list_original is None: 79 | pc_utility.error_and_exit(400, 'Alert Rules not found in the import file. Please verify the import file and name.') 80 | if args.alert_rule: 81 | alert_rule_export = False 82 | for alert_rule_original in alert_rule_list_original: 83 | if alert_rule_original['name'] == args.alert_rule: 84 | alert_rule_export = True 85 | if not alert_rule_export: 86 | pc_utility.error_and_exit(400, 'Alert Rule not found in the import file. Please verify the import file and it\'s contents.') 87 | 88 | # Alert Rules 89 | 90 | print('API - Getting list of Alert Rules ...', end='') 91 | alert_rule_list = pc_api.alert_rule_list_read() 92 | print(' done.') 93 | print() 94 | 95 | print('API - Getting list of Policies ...', end='') 96 | policy_new_list = pc_api.policy_v2_list_read() 97 | print(' done.') 98 | print() 99 | 100 | print('API - Getting list of Account Groups ...', end='') 101 | account_groups_new_list = pc_api.cloud_account_group_list_read() 102 | print(' done.') 103 | print() 104 | 105 | if args.update_existing: 106 | print('API - Adding/Updating Alert Rules ...') 107 | else: 108 | print('API - Adding Alert Rules ...') 109 | 110 | added = 0 111 | updated = 0 112 | skipped = 0 113 | 114 | for alert_rule_original in alert_rule_list_original: 115 | print(f'{alert_rule_original["name"]} ... ', end='') 116 | alert_rule_method = 'create' 117 | alert_rule_update_id = None 118 | # See if an Alert Rule with the same name already exists 119 | for alert_rule in alert_rule_list: 120 | if alert_rule['name'] == alert_rule_original['name']: 121 | if args.update_existing: 122 | alert_rule_method = 'update' 123 | alert_rule_update_id = alert_rule['policyScanConfigId'] 124 | elif args.skip_existing: 125 | alert_rule_method = 'skip' 126 | else: 127 | pc_utility.error_and_exit(400, 'Alert Rule already exists. Please verify the new Alert Rule name, or delete the existing AlertRule.') 128 | # Add/update Alert Rule 129 | if alert_rule_method == 'skip': 130 | skipped += 1 131 | print(' skipped.') 132 | continue 133 | 134 | # Verify Policies 135 | new_policies_list = [] 136 | for policy_original_id in alert_rule_original['policies']: 137 | # First see if there is mapping in PolicyIdMap 138 | old_policy_id = policy_original_id 139 | if policy_original_id in custom_policy_id_map: 140 | new_policy_id = custom_policy_id_map[old_policy_id] 141 | else: 142 | new_policy_id = old_policy_id 143 | policy_found = False 144 | for policy_new in policy_new_list: 145 | if policy_new['policyId'] == new_policy_id: 146 | policy_found = True 147 | break 148 | if not policy_found: 149 | if not args.skip_missing_policies: 150 | pc_utility.error_and_exit(400, f'Policy not found in new tenant ({new_policy_id}). You might need to export custom Policies from the old tenant and import them to the new tenant first.') 151 | continue 152 | new_policies_list.append(new_policy_id) 153 | alert_rule_original['policies'] = new_policies_list 154 | 155 | # Verify Account Groups 156 | default_account_group_new = None 157 | new_account_group_list = [] 158 | if args.use_default_account_group: 159 | for account_group_new in account_groups_new_list: 160 | if account_group_new['name'] == "Default Account Group": 161 | default_account_group_new_id = account_group_new['id'] 162 | if not default_account_group_new: 163 | pc_utility.error_and_exit(400, 'Could not find Default Account Group') 164 | new_account_group_list = [default_account_group_new_id] 165 | else: 166 | for account_group_original_id in alert_rule_original['target']['accountGroups']: 167 | account_group_found = False 168 | for account_group_new in account_groups_new_list: 169 | if account_group_original_id == account_group_new['id']: 170 | account_group_found = True 171 | break 172 | if not account_group_found: 173 | if not args.skip_missing_account_groups: 174 | pc_utility.error_and_exit(400, f'Account Group not found in new tenant ({account_group_original_id}). You might need to export Account Groups from the old tenant and import them to the new tenant first.') 175 | continue 176 | new_account_group_list.append(account_group_original_id) 177 | alert_rule_original['target']['accountGroups'] = new_account_group_list 178 | 179 | # Skip Notifications 180 | if args.skip_notifications: 181 | alert_rule_original['notificationChannels'] = [] 182 | if alert_rule_method == 'create': 183 | del alert_rule_original['policyScanConfigId'] 184 | pc_api.alert_rule_create(alert_rule_original) 185 | added += 1 186 | print(' added.') 187 | elif alert_rule_method == 'update': 188 | pc_api.alert_rule_update(alert_rule_update_id, alert_rule_original) 189 | updated += 1 190 | print(' updated.') 191 | print() 192 | print('Import Complete.') 193 | print(f'Summary: {added} added, {updated} updated, {skipped} skipped.') 194 | -------------------------------------------------------------------------------- /scripts/pcs_alerts_read.py: -------------------------------------------------------------------------------- 1 | """ Get a list of Alerts """ 2 | 3 | import json 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | '--detailed', 13 | action='store_true', 14 | help='(Optional) - Get Alert details.') 15 | parser.add_argument( 16 | '-fas', 17 | '--alertstatus', 18 | type=str, 19 | choices=['open', 'resolved', 'snoozed', 'dismissed'], 20 | help='(Optional) - Filter - Alert Status.') 21 | parser.add_argument( 22 | '-fpt', 23 | '--policytype', 24 | type=str, 25 | help='(Optional) - Filter - Policy Type.') 26 | parser.add_argument( 27 | '-fpn', 28 | '--policyname', 29 | type=str, 30 | help='(Optional) - Filter - Policy Name.') 31 | parser.add_argument( 32 | '-tr', 33 | '--timerange', 34 | type=int, 35 | default=30, 36 | help='(Optional) - Time Range in days (default 30).') 37 | args = parser.parse_args() 38 | 39 | # --Initialize-- # 40 | 41 | settings = pc_utility.get_settings(args) 42 | pc_api.configure(settings) 43 | 44 | # --Main-- # 45 | 46 | # ALERT GET 47 | 48 | # Sort out and build the filters. 49 | 50 | alerts_filter = {} 51 | if args.detailed: 52 | alerts_filter['detailed'] = True 53 | else: 54 | alerts_filter['detailed'] = False 55 | alerts_filter['filters'] = [] 56 | alerts_filter['limit'] = 1000 57 | alerts_filter['offset'] = 0 58 | alerts_filter['sortBy'] = ['id:asc'] 59 | alerts_filter['timeRange'] = {} 60 | alerts_filter['timeRange']['type'] = 'relative' 61 | alerts_filter['timeRange']['value'] = {} 62 | alerts_filter['timeRange']['value']['unit'] = 'day' 63 | alerts_filter['timeRange']['value']['amount'] = args.timerange 64 | if args.alertstatus is not None: 65 | temp_filter = {} 66 | temp_filter['name'] = 'alert.status' 67 | temp_filter['operator'] = '=' 68 | temp_filter['value'] = args.alertstatus 69 | alerts_filter['filters'].append(temp_filter) 70 | if args.policytype is not None: 71 | temp_filter = {} 72 | temp_filter['name'] = 'policy.type' 73 | temp_filter['operator'] = '=' 74 | temp_filter['value'] = args.policytype 75 | alerts_filter['filters'].append(temp_filter) 76 | if args.policyname is not None: 77 | temp_filter = {} 78 | temp_filter['name'] = 'policy.name' 79 | temp_filter['operator'] = '=' 80 | temp_filter['value'] = args.policyname 81 | alerts_filter['filters'].append(temp_filter) 82 | 83 | print('API - Getting the Alerts list ...', end='') 84 | alerts_list = pc_api.alert_v2_list_read(body_params=alerts_filter) 85 | print(' done.') 86 | print() 87 | 88 | print('Alerts:') 89 | print(json.dumps(alerts_list)) 90 | -------------------------------------------------------------------------------- /scripts/pcs_apis_ingested.py: -------------------------------------------------------------------------------- 1 | """ Get a list of Alerts """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | args = parser.parse_args() 10 | 11 | # --Initialize-- # 12 | 13 | settings = pc_utility.get_settings(args) 14 | pc_api.configure(settings) 15 | 16 | # --Main-- # 17 | 18 | clouds = ['alibaba-', 'aws-', 'azure-', 'gcloud-', 'gcp-', 'oci-'] 19 | apis = [] 20 | 21 | for cloud in clouds: 22 | query = {} 23 | query['query'] = "config from cloud.resource where api.name = '%s" % cloud 24 | query['cursor'] = len(query['query']) 25 | result = pc_api.search_suggest_list_read(query_to_suggest=query) 26 | if 'suggestions' in result: 27 | suggestions = result['suggestions'] 28 | single = [s.replace("'", '') for s in suggestions] 29 | double = [s.replace('"', '') for s in single] 30 | apis.extend(double) 31 | 32 | apis.sort() 33 | for api in apis: 34 | print(api) 35 | -------------------------------------------------------------------------------- /scripts/pcs_cloud_account_import_azure.py: -------------------------------------------------------------------------------- 1 | """ Import Azure Accounts from a CSV file """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | 'import_file_name', 11 | type=str, 12 | help='Import (CSV) file name for the Cloud Accounts.') 13 | args = parser.parse_args() 14 | 15 | # --Initialize-- # 16 | 17 | pc_utility.prompt_for_verification_to_continue(args) 18 | settings = pc_utility.get_settings(args) 19 | pc_api.configure(settings) 20 | 21 | # --Main-- # 22 | 23 | # Import. 24 | 25 | import_file_data = pc_utility.read_csv_file_text(args.import_file_name) 26 | 27 | cloud_accounts_to_import = [] 28 | for cloud_account in import_file_data: 29 | if cloud_account['monitorFlowLogs'].lower() == 'true': 30 | cloud_account['monitorFlowLogs'] = True 31 | else: 32 | cloud_account['monitorFlowLogs'] = False 33 | if cloud_account['enabled'].lower() == 'true': 34 | cloud_account['enabled'] = True 35 | else: 36 | cloud_account['enabled'] = False 37 | cloud_account_new = {} 38 | cloud_account_new['cloudAccount'] = {} 39 | cloud_account_new['clientId'] = cloud_account['clientId'] 40 | cloud_account_new['cloudAccount']['accountId'] = cloud_account['accountId'] 41 | cloud_account_new['cloudAccount']['enabled'] = cloud_account['enabled'] 42 | cloud_account_new['cloudAccount']['groupIds'] = [] 43 | cloud_account_new['cloudAccount']['groupIds'].append(cloud_account['groupIds']) 44 | cloud_account_new['cloudAccount']['name'] = cloud_account['name'] 45 | cloud_account_new['key'] = cloud_account['key'] 46 | cloud_account_new['monitorFlowLogs'] = cloud_account['monitorFlowLogs'] 47 | cloud_account_new['servicePrincipalId'] = cloud_account['servicePrincipalId'] 48 | cloud_account_new['tenantId'] = cloud_account['tenantId'] 49 | cloud_accounts_to_import.append(cloud_account_new) 50 | 51 | # TODO: Check list for any duplicates (in CSV). See pc-user-import.py. 52 | 53 | print('API - Getting the current list of Cloud Accounts ...', end='') 54 | cloud_accounts_list = pc_api.cloud_accounts_list_read() 55 | print(' done.') 56 | print() 57 | 58 | # TODO: Check list for any duplicates (in Prisma Cloud). See pc-user-import.py. 59 | 60 | # Import the account list into Prisma Cloud 61 | print('API - Creating Cloud Accounts ...') 62 | for cloud_account_to_import in cloud_accounts_to_import: 63 | print('Adding Cloud Account: %s' % cloud_account_to_import['cloudAccount']['name']) 64 | pc_api.cloud_accounts_create('azure', cloud_account_to_import) 65 | print('Done.') 66 | -------------------------------------------------------------------------------- /scripts/pcs_cloud_account_inventory.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=redefined-outer-name 2 | """ Add discovered registries to Vulnerability->Images->Registry settings """ 3 | 4 | import csv 5 | 6 | # pylint: disable=import-error 7 | from prismacloud.api import pc_api, pc_utility 8 | 9 | # --Configuration-- # 10 | 11 | parser = pc_utility.get_arg_parser() 12 | parser.add_argument( 13 | '--output', 14 | default='output.csv', 15 | type=str, 16 | help='(Optional) - Name of output file, defaults to output.csv') 17 | args = parser.parse_args() 18 | 19 | # --Helpers-- # 20 | 21 | def process_account(acct_obj,parent): 22 | if acct_obj['numberOfChildAccounts'] > 0: 23 | c_accounts=pc_api.cloud_accounts_children_list_read(acct_obj['cloudType'],acct_obj['accountId']) 24 | for account in c_accounts: 25 | process_account(account,acct_obj['accountId']) 26 | if acct_obj['numberOfChildAccounts'] > 0 and parent == "": 27 | pass 28 | else: 29 | a_dict = { 30 | "name" : acct_obj['name'], 31 | "cloud" : acct_obj['cloudType'], 32 | "parent" : parent, 33 | "type" : acct_obj['accountType'], 34 | "id" : acct_obj['accountId'] 35 | } 36 | master_account_list.append(a_dict) 37 | return 0 38 | 39 | # --Initialize-- # 40 | 41 | settings = pc_utility.get_settings(args) 42 | pc_api.configure(settings) 43 | 44 | # --Initialize-- # 45 | 46 | cloud_accounts_list = pc_api.cloud_accounts_list_read() 47 | master_account_list=[] 48 | for account in cloud_accounts_list: 49 | process_account(account,"") 50 | 51 | keys = master_account_list[0].keys() 52 | with open(args.output, 'w', newline='') as a_file: 53 | dict_writer = csv.DictWriter(a_file, keys) 54 | dict_writer.writeheader() 55 | dict_writer.writerows(master_account_list) 56 | -------------------------------------------------------------------------------- /scripts/pcs_cloud_discovery_defense_stats.py: -------------------------------------------------------------------------------- 1 | """ Get statistics from cloud discovery """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | '--fargate', 11 | action="store_true", 12 | help="(Optional) - Only Fargate stats" 13 | ) 14 | parser.add_argument( 15 | '--eks', 16 | action="store_true", 17 | help="(Optional) - Only EKS stats" 18 | ) 19 | parser.add_argument( 20 | '--detailed', 21 | action="store_true", 22 | help="(Optional) - Detailed results" 23 | ) 24 | args = parser.parse_args() 25 | 26 | # --Helpers-- # 27 | 28 | 29 | # --Initialize-- # 30 | 31 | settings = pc_utility.get_settings(args) 32 | pc_api.configure(settings) 33 | 34 | # --Main-- # 35 | 36 | # Note: default provider is aws 37 | # To-do: support other cloud providers 38 | 39 | discovery = pc_api.cloud_discovery_read() 40 | 41 | aws_ec2 = 0 42 | aws_ec2_defended = 0 43 | aws_lambda = 0 44 | aws_lambda_defended = 0 45 | aws_ecs = 0 46 | aws_ecs_defended = 0 47 | aws_eks = 0 48 | aws_eks_defended = 0 49 | aws_eks_clusters = [] 50 | aws_ecr = 0 51 | aws_ecr_defended = 0 52 | 53 | print('Account, Region, Service, Defended Count, Total Count') 54 | services = {'aws-ec2', 'aws-lambda','aws-ecs','aws-eks','aws-ecr'} 55 | if args.fargate: 56 | services = {'aws-fargate'} 57 | elif args.eks: 58 | services = {'aws-eks'} 59 | for item in discovery: 60 | if 'err' in item: 61 | continue 62 | if item['provider'] == 'aws': 63 | service = item['serviceType'] 64 | if service not in services: 65 | continue 66 | if service == 'aws-ec2': 67 | print('%s, %s, %s, %s, %s' % (item['accountID'], item['region'], service, item['defended'], item['total'])) 68 | aws_ec2 += item['total'] 69 | aws_ec2_defended += item['defended'] 70 | elif service == 'aws-lambda': 71 | print('%s, %s, %s, %s, %s' % (item['accountID'], item['region'], service, item['defended'], item['total'])) 72 | aws_lambda += item['total'] 73 | aws_lambda_defended += item['defended'] 74 | elif service == 'aws-ecs': 75 | for entity in item['entities']: 76 | print('%s, %s, %s, %s, %s' % (item['accountID'], item['region'], service, item['defended'], entity['runningTasksCount'])) 77 | aws_ecs += entity['runningTasksCount'] 78 | aws_ecs_defended += item['defended'] 79 | elif service == 'aws-eks': 80 | if args.detailed: 81 | item.pop('collections') 82 | if args.detailed: 83 | aws_eks_clusters.append(item) 84 | print('%s, %s, %s, %s, %s' % (item['accountID'], item['region'], service, item['defended'], item['total'])) 85 | aws_eks += item['total'] 86 | aws_eks_defended += item['defended'] 87 | elif service == 'aws-ecr': 88 | print('%s, %s, %s, %s, %s' % (item['accountID'], item['region'], service, item['defended'], item['total'])) 89 | aws_ecr += item['total'] 90 | aws_ecr_defended += item['defended'] 91 | else: 92 | print('Unknown AWS service: %s' % service) 93 | 94 | if not args.detailed: 95 | print('Totals') 96 | print('EC2: %d/%d' % (aws_ec2_defended, aws_ec2)) 97 | print('EKS: %d/%d' % (aws_eks_defended, aws_eks)) 98 | print('Lambda: %d/%d' % (aws_lambda_defended, aws_lambda)) 99 | print('ECS tasks: %d/%d' % (aws_ecs_defended, aws_ecs)) 100 | print('ECR: %d/%d' % (aws_ecr_defended, aws_ecr)) 101 | 102 | if args.eks and args.detailed: 103 | print('EKS: %d/%d' % (aws_eks_defended, aws_eks)) 104 | print('Account, Region, Cluster, Defended, ARN') 105 | for item in aws_eks_clusters: 106 | for entity in item['entities']: 107 | print('%s, %s, %s, %s, %s' % (item['accountID'], item['region'], entity['name'], item['defended'], entity['arn'])) 108 | -------------------------------------------------------------------------------- /scripts/pcs_compliance_alerts_read.py: -------------------------------------------------------------------------------- 1 | """ Get a list of Alerts for a specific Compliance Standard and Cloud Account """ 2 | 3 | import json 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | 'compliance_standard_name', 13 | type=str, 14 | help='Name of the Compliance Standard.') 15 | parser.add_argument( 16 | 'cloud_account_name', 17 | type=str, 18 | help='Name of the Cloud Account.') 19 | args = parser.parse_args() 20 | 21 | # --Initialize-- # 22 | 23 | settings = pc_utility.get_settings(args) 24 | pc_api.configure(settings) 25 | 26 | # --Main-- # 27 | 28 | # COMPLIANCE ALERTS GET 29 | 30 | compliance_standard_name = args.compliance_standard_name 31 | cloud_account_name = args.cloud_account_name 32 | 33 | alert_detail = True 34 | alert_status = 'open' 35 | time_range_type = 'to_now' 36 | time_range_value = 'epoch' 37 | 38 | print('API - Getting the Compliance Standard Policy list ...', end='') 39 | compliance_standard_policy_list = pc_api.compliance_standard_policy_list_read(compliance_standard_name) 40 | print(' done.') 41 | print() 42 | 43 | # Loop through the Policy list to collect the related Alerts for the Cloud Account. 44 | alert_list = [] 45 | for compliance_policy in compliance_standard_policy_list: 46 | alert_filter = {'detailed': alert_detail, 47 | 'timeRange': {'type': time_range_type, 'value': time_range_value}, 48 | 'filters': [{'operator': '=', 'name': 'alert.status', 'value': alert_status}, 49 | {'operator': '=', 'name': 'cloud.account', 'value': cloud_account_name}, 50 | {'name': 'policy.id', 'operator': '=', 'value': compliance_policy['policyId']}]} 51 | print('API - Getting the Alerts for Policy: %s ...' % compliance_policy['name'], end='') 52 | filtered_alert_list = pc_api.alert_list_read(body_params=alert_filter) 53 | alert_list.extend(filtered_alert_list) 54 | print(' done.') 55 | print() 56 | 57 | print('Alerts:') 58 | print(json.dumps(alert_list)) 59 | -------------------------------------------------------------------------------- /scripts/pcs_compliance_export.py: -------------------------------------------------------------------------------- 1 | """ Export a specific Compliance Standard """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | DEFAULT_COMPLIANCE_EXPORT_FILE_VERSION = 3 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | '--concurrency', 13 | type=int, 14 | default=0, 15 | help='(Optional) - Number of concurrent API calls. (1-16)') 16 | parser.add_argument( 17 | 'compliance_standard_name', 18 | type=str, 19 | help='Name of the Compliance Standard to export.') 20 | parser.add_argument( 21 | 'export_file_name', 22 | type=str, 23 | help='Export file name for the Compliance Standard.') 24 | args = parser.parse_args() 25 | 26 | # --Initialize-- # 27 | 28 | settings = pc_utility.get_settings(args) 29 | pc_api.configure(settings) 30 | 31 | # --Main-- # 32 | 33 | # Avoid API rate limits. 34 | if args.concurrency > 0 and args.concurrency <= 16: 35 | pc_api.max_workers = args.concurrency 36 | print('Limiting concurrent API calls to: (%s)' % pc_api.max_workers) 37 | print() 38 | 39 | # Compliance Export 40 | 41 | export_file_data = {} 42 | export_file_data['export_file_version'] = DEFAULT_COMPLIANCE_EXPORT_FILE_VERSION 43 | export_file_data['compliance_section_list_original'] = {} 44 | export_file_data['policy_list_original'] = [] 45 | export_file_data['policy_object_original'] = {} 46 | export_file_data['search_object_original'] = {} 47 | 48 | print('API - Getting the current list of Compliance Standards ...', end='') 49 | compliance_standard_list_current = pc_api.compliance_standard_list_read() 50 | compliance_standard_original = pc_utility.search_list_object_lower(compliance_standard_list_current, 'name', args.compliance_standard_name) 51 | if compliance_standard_original is None: 52 | pc_utility.error_and_exit(400, 'Compliance Standard to export not found. Please verify the Compliance Standard name.') 53 | export_file_data['compliance_standard_original'] = compliance_standard_original 54 | print(' done.') 55 | print() 56 | 57 | print('API - Getting the Compliance Standard Requirements ...', end='') 58 | compliance_requirement_list_original = pc_api.compliance_standard_requirement_list_read(compliance_standard_original['id']) 59 | export_file_data['compliance_requirement_list_original'] = compliance_requirement_list_original 60 | print(' done.') 61 | print() 62 | 63 | print('API - Getting the Compliance Standard Sections ...', end='') 64 | for compliance_requirement_original in compliance_requirement_list_original: 65 | compliance_section_list_original = pc_api.compliance_standard_requirement_section_list_read(compliance_requirement_original['id']) 66 | export_file_data['compliance_section_list_original'][compliance_requirement_original['id']] = compliance_section_list_original 67 | print(' done.') 68 | print() 69 | 70 | print('API - Getting the Compliance Standard Policy List (please wait) ...', end='') 71 | policy_list_current = pc_api.compliance_standard_policy_v2_list_read(compliance_standard_original['name']) 72 | export_file_data['policy_list_original'] = policy_list_current 73 | print(' done.') 74 | print() 75 | 76 | # Threaded Queries. 77 | result = pc_api.get_policies_with_saved_searches(policy_list_current) 78 | 79 | export_file_data['policy_object_original'] = result['policies'] 80 | export_file_data['search_object_original'] = result['searches'] 81 | pc_utility.write_json_file(args.export_file_name, export_file_data) 82 | print('Exported to: %s' % args.export_file_name) 83 | -------------------------------------------------------------------------------- /scripts/pcs_compliance_uuid_read.py: -------------------------------------------------------------------------------- 1 | """ Get the UUID of a specific Compliance Standard (or Requirement or Section) """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | 'compliance_standard_name', 11 | type=str, 12 | help='(Required) - Name of the Compliance Standard.') 13 | parser.add_argument( 14 | '-rid', 15 | '--requirementId', 16 | type=str, 17 | help='(Optional) - Only required to find the UUID of a Requirement or a Section. This will be the "REQUIREMENT" from the UI. ' 18 | 'Note: This requirement must exist in the specified Compliance Standard. If it is in another standard, the lookup will fail.') 19 | parser.add_argument( 20 | '-sid', 21 | '--sectionId', 22 | type=str, 23 | help='(Optional) - Only required to find the UUID of a Section. This will be the "SECTION" from the UI. ' 24 | 'Note: This section must exist in the specified Standard and the specified Compliance Requirement. ' 25 | 'If it is in another standard, or another Requirement, the lookup will fail.') 26 | args = parser.parse_args() 27 | 28 | if args.sectionId is not None: 29 | if args.requirementId is None: 30 | pc_utility.error_and_exit(400, 'A Requirement is required if you want to get the UUID of a Section. ' 31 | 'Please enter the correct Requirement for the desired Section.') 32 | 33 | # --Initialize-- # 34 | 35 | settings = pc_utility.get_settings(args) 36 | pc_api.configure(settings) 37 | 38 | # --Main-- # 39 | 40 | # Compliance Get UUID 41 | 42 | print('API - Getting the Compliance Standards list ...', end='') 43 | compliance_standard_list = pc_api.compliance_standard_list_read() 44 | compliance_standard = pc_utility.search_list_object_lower(compliance_standard_list, 'name', args.compliance_standard_name) 45 | print(' done.') 46 | print() 47 | 48 | if compliance_standard is None: 49 | pc_utility.error_and_exit(400, 'Compliance Standard not found. Please verify the Compliance Standard name.') 50 | 51 | print() 52 | print('Compliance Standard Name: ' + compliance_standard['name'] + "\nUUID: " + compliance_standard['id']) 53 | print() 54 | 55 | if args.requirementId is not None: 56 | print('API - Getting Requirements List for Compliance Standard ...', end='') 57 | compliance_requirement_list = pc_api.compliance_standard_requirement_list_read(compliance_standard['id']) 58 | print(' done.') 59 | print() 60 | 61 | compliance_requirement = pc_utility.search_list_object_lower(compliance_requirement_list, 'requirementId', args.requirementId) 62 | if compliance_requirement is None: 63 | pc_utility.error_and_exit(400, 'Requirement not found in the specified Compliance Standard. ' 64 | 'Please verify the Compliance Standard and Requirement names.') 65 | 66 | print() 67 | print('Requirement Name: ' + compliance_requirement['requirementId'] + "\nUUID: " + compliance_requirement['id']) 68 | print() 69 | 70 | if args.sectionId is not None: 71 | print('API - Getting Sections for Requirement ...', end='') 72 | compliance_section_list = pc_api.compliance_standard_requirement_section_list_read(compliance_requirement['id']) 73 | print(' done.') 74 | print() 75 | 76 | compliance_section = pc_utility.search_list_object_lower(compliance_section_list, 'sectionId', args.sectionId) 77 | if compliance_section is None: 78 | pc_utility.error_and_exit(400, 'Section not found in the specified Requirement. ' 79 | 'Please verify the Compliance Standard, Requirement, and Section names.') 80 | 81 | print() 82 | print('Section Name: ' + compliance_section['sectionId'] + "\nUUID: " + compliance_section['id']) 83 | print() 84 | -------------------------------------------------------------------------------- /scripts/pcs_compute_container_observed_connections.py: -------------------------------------------------------------------------------- 1 | """ Get a list of vulnerable containers and their clusters """ 2 | 3 | import urllib.parse 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | '--cluster', 13 | type=str, 14 | required=True, 15 | help='(Required) - Cluster.') 16 | parser.add_argument( 17 | '--collection', 18 | type=str, 19 | help='(Optional) - Collection.') 20 | parser.add_argument( 21 | '--namespace', 22 | type=str, 23 | help='(Optional) - Namespace.') 24 | parser.add_argument( 25 | '--exclude_external', 26 | action='store_true', 27 | help='(Optional) - Exclude connections external to the specified collection and/or namespace.') 28 | args = parser.parse_args() 29 | 30 | # --Helpers-- # 31 | 32 | # --Initialize-- # 33 | 34 | settings = pc_utility.get_settings(args) 35 | pc_api.configure(settings) 36 | pc_api.validate_api_compute() 37 | 38 | # --Main-- # 39 | 40 | print('Getting container connections for Cluster: (%s) Collection: (%s) Namespace: (%s).' % (args.cluster, args.collection, args.namespace)) 41 | if args.exclude_external: 42 | print('Excluding connections external to the specified collection and/or namespace.') 43 | print() 44 | 45 | # Define variables. 46 | 47 | connections = [] 48 | containers = {} 49 | 50 | # Note: the cluster, collection, and namespace script arguments are singletons, 51 | # but could be comma-delimited arrays, as the endpoint parameters accept arrays. 52 | 53 | if ',' in args.cluster: 54 | pc_utility.error_and_exit(400, 'This script is arbitrarily limited to querying one cluster') 55 | 56 | cluster = urllib.parse.quote(args.cluster) 57 | 58 | if args.collection: 59 | collection = '&collections=%s' % urllib.parse.quote(args.collection) 60 | else: 61 | collection = '' 62 | 63 | if args.namespace: 64 | namespace = '&namespaces=%s' % urllib.parse.quote(args.namespace) 65 | else: 66 | namespace = '' 67 | 68 | # Query the endpoint. 69 | 70 | radar = pc_api.execute_compute('GET', 'api/v1/radar/container?project=Central+Console&clusters=%s%s%s' % (cluster, collection, namespace)) 71 | if not radar or not radar['radar']: 72 | pc_utility.error_and_exit(400, 'No containers match the specified parameters.') 73 | radar_containers = radar['radar'] 74 | 75 | # Convert the list of container dictionaries into a dictionary of container dictionaries. 76 | 77 | for container in radar_containers: 78 | containers[container['_id']] = container 79 | 80 | # Convert containers into a list of connection dictionaries. 81 | 82 | for radar_container in radar_containers: 83 | for incoming_connection in radar_container['incomingConnections']: 84 | for port in incoming_connection['ports']: 85 | src_container = containers.get(incoming_connection['profileID']) 86 | if src_container: 87 | src_name = src_container['imageNames'][0].rsplit('/', 1)[-1] 88 | src_namespace = src_container['namespace'] 89 | else: 90 | if args.exclude_external: 91 | # Exclude connections external to the specified collection and/or namespace. 92 | continue 93 | src_name = 'EXTERNAL TO QUERY' 94 | src_namespace = 'EXTERNAL TO QUERY' 95 | connections.append({ 96 | 'dst_name': radar_container['imageNames'][0].rsplit('/', 1)[-1], 97 | 'dst_port': port['port'], 98 | 'dst_namespace': radar_container['namespace'], 99 | 'src_name': src_name, 100 | 'src_namespace': src_namespace 101 | }) 102 | 103 | # Output the list of connection dictionaries. 104 | 105 | sorted_connections = sorted(connections, key=lambda d: (d['dst_name'], d['dst_port'])) 106 | 107 | for connection in sorted_connections: 108 | print(connection) 109 | -------------------------------------------------------------------------------- /scripts/pcs_compute_endpoint_client.py: -------------------------------------------------------------------------------- 1 | """ Generic Prisma Cloud API Endpoint Client. """ 2 | 3 | from json import dumps as json_dumps 4 | from sys import exit as sys_exit, stderr, stdout 5 | 6 | # pylint: disable=import-error 7 | from prismacloud.api import pc_api, pc_utility 8 | 9 | # --Configuration-- # 10 | 11 | parser = pc_utility.get_arg_parser() 12 | 13 | parser.add_argument( 14 | 'http_method', 15 | type=str, 16 | help='HTTP Method for HTTP request') 17 | 18 | parser.add_argument( 19 | 'uri_path', 20 | type=str, 21 | help='URI Path to HTTP endpoint') 22 | 23 | parser.add_argument( 24 | '--uri_params', 25 | type=str, 26 | help='(Optional) URI Parameters for HTTP request') 27 | 28 | parser.add_argument( 29 | '--request_body', 30 | type=str, 31 | help='(Optional) Import file name for file containing the HTTP request body data.') 32 | 33 | parser.add_argument( 34 | '--response_file', 35 | type=str, 36 | help='(Optional) Export file name for file containing the HTTP response body data.') 37 | 38 | parser.add_argument( 39 | '--pretty', 40 | action='store_true', 41 | help='(Optional) Specify pretty JSON output.') 42 | 43 | args = parser.parse_args() 44 | 45 | # For readability: these arguments default to class 'NoneType' and (default False) 46 | 47 | if not args.uri_params: 48 | args.uri_params = None 49 | 50 | if not args.request_body: 51 | args.request_body = None 52 | 53 | if not args.pretty: 54 | args.pretty = False 55 | 56 | # --Initialize-- # 57 | 58 | settings = pc_utility.get_settings(args) 59 | pc_api.configure(settings) 60 | 61 | # --Main-- # 62 | 63 | if args.request_body is None: 64 | request_body_data = None 65 | if args.uri_params is None: 66 | print('API - Executing HTTP request "%s %s"' % (args.http_method, args.uri_path), file=stderr) 67 | else: 68 | print('API - Executing HTTP request "%s %s&%s"' % (args.http_method, args.uri_path, args.uri_params), file=stderr) 69 | else: 70 | # Import the data file 71 | print('API - Importing file data from file %s' % args.request_body, file=stderr) 72 | try: 73 | request_body_data = pc_utility.read_json_file(args.request_body) 74 | print('API - Import of file data %s completed' % args.request_body, file=stderr) 75 | if args.uri_params == '': 76 | print('API - Executing HTTP request "%s %s" with request body:\n%s' % (args.http_method, args.uri_path, request_body_data), file=stderr) 77 | else: 78 | print('API - Executing HTTP request "%s %s&%s" with request body:\n%s' % (args.http_method, args.uri_path, args.uri_params, request_body_data), file=stderr) 79 | except (ValueError, FileNotFoundError): 80 | print('API - Failed to import file %s' % args.request_body, file=stderr) 81 | sys_exit(2) 82 | 83 | # HUGE WARNING: NO VALIDATION HERE IN THIS EXAMPLE (so that it fits the broadest set of use-cases) 84 | 85 | # Prompt for warning if interactive 86 | pc_utility.prompt_for_verification_to_continue(args) 87 | 88 | # Make the HTTP request 89 | 90 | try: 91 | response=pc_api.execute_compute(args.http_method,args.uri_path,query_params=args.uri_params,body_params=request_body_data) 92 | if args.response_file is None: 93 | print('API - HTTP request response is:', file=stderr) 94 | if args.pretty: 95 | print(json_dumps(response, indent=4, separators=(', ', ': ')), file=stdout) 96 | else: 97 | print(json_dumps(response), file=stdout) 98 | else: 99 | print('API - HTTP request response stored in file %s' % args.response_file, file=stderr) 100 | pc_utility.write_json_file(args.response_file, response, pretty=bool(args.pretty)) 101 | except Exception: # pylint: disable=W0703 102 | print('API - HTTP request failed', file=stderr) 103 | sys_exit(1) 104 | -------------------------------------------------------------------------------- /scripts/pcs_configure.py: -------------------------------------------------------------------------------- 1 | """ Configure """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | args = parser.parse_args() 10 | 11 | # --Initialize-- # 12 | 13 | pc_api.configure(pc_utility.get_settings(args), False) 14 | 15 | # --Main-- # 16 | 17 | if not args.save: 18 | print() 19 | 20 | # (Sync with get_arg_parser() in pc_lib_utility.py.) 21 | 22 | if pc_api.name: 23 | print('# Prisma Cloud Tenant (or Compute Console) Name:') 24 | print(pc_api.name) 25 | print() 26 | 27 | if pc_api.api: 28 | print('# Prisma Cloud API URL:') 29 | print(pc_api.api) 30 | print() 31 | 32 | if pc_api.api_compute: 33 | print('# Prisma Cloud Compute API URL:') 34 | print(pc_api.api_compute) 35 | print() 36 | 37 | print('# Prisma Cloud Access Key (or Compute Username):') 38 | print(pc_api.identity) 39 | print() 40 | 41 | print('# Prisma Cloud Secret Key (or Compute Password):') 42 | print(pc_api.secret) 43 | print() 44 | 45 | print('# SSL Verification:') 46 | print(pc_api.verify) 47 | print() 48 | -------------------------------------------------------------------------------- /scripts/pcs_container_count.py: -------------------------------------------------------------------------------- 1 | """ Get a Count of Protected Containers """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | args = parser.parse_args() 10 | 11 | # --Helpers-- # 12 | 13 | 14 | # --Initialize-- # 15 | 16 | settings = pc_utility.get_settings(args) 17 | pc_api.configure(settings) 18 | 19 | # --Main-- # 20 | 21 | containers = pc_api.execute_compute('GET', 'api/v1/containers/count') 22 | print(containers) 23 | -------------------------------------------------------------------------------- /scripts/pcs_container_csp.py: -------------------------------------------------------------------------------- 1 | """ Get Vulnerabilities in Containers (Deployed Images) """ 2 | 3 | import json 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | args = parser.parse_args() 12 | 13 | # --Helpers-- # 14 | 15 | def optional_print(txt='', mode=True): 16 | if mode: 17 | print(txt) 18 | 19 | # --Initialize-- # 20 | 21 | settings = pc_utility.get_settings(args) 22 | pc_api.configure(settings) 23 | pc_api.validate_api_compute() 24 | 25 | # --Main-- # 26 | 27 | print('Testing Compute API Access ...', end='') 28 | intelligence = pc_api.statuses_intelligence() 29 | print(' done.') 30 | print() 31 | 32 | vulnerabilities_by_container = [] 33 | 34 | print('Getting Deployed Images (please wait) ...', end='') 35 | images = pc_api.images_list_read(query_params={'filterBaseImage': 'true'}) 36 | print(' done.') 37 | print() 38 | 39 | print('Getting Containers (please wait) ...', end='') 40 | containers = pc_api.containers_list_read() 41 | print(' done.') 42 | print() 43 | 44 | images_dictionary = {} 45 | for image in images: 46 | if pc_api.debug: 47 | print(json.dumps(image, indent=4)) 48 | image_id = image['_id'] 49 | images_dictionary[image_id] = image 50 | 51 | print('Container Name\tProvider\tRegion') 52 | for container in containers: 53 | if pc_api.debug: 54 | print(json.dumps(container, indent=4)) 55 | print('%s\t%s\t%s' % (container['info']['imageName'], container['info']['cloudMetadata']['provider'], container['info']['cloudMetadata']['region'])) 56 | 57 | -------------------------------------------------------------------------------- /scripts/pcs_container_vulnerabilities_on_running_hosts.py: -------------------------------------------------------------------------------- 1 | """ Get Vulnerabilities in Containers (Deployed Images) on Recently Running Hosts """ 2 | 3 | import csv 4 | import datetime 5 | 6 | import dateutil.parser as date_parser 7 | from dateutil import tz 8 | 9 | # pylint: disable=import-error 10 | from prismacloud.api import pc_api, pc_utility 11 | 12 | # --Configuration-- # 13 | 14 | DEFAULT_FILE_NAME = 'vulnerabilities.csv' 15 | DEFAULT_HOURS = 24 16 | 17 | parser = pc_utility.get_arg_parser() 18 | parser.add_argument( 19 | '--csv_file_name', 20 | type=str, 21 | default=DEFAULT_FILE_NAME, 22 | help="(Optional) - Export to the given file name. (Default %s)" % DEFAULT_FILE_NAME) 23 | parser.add_argument( 24 | '--hours', 25 | type=int, 26 | default=DEFAULT_HOURS, 27 | help="(Optional) - Number of hours for a container host to be considered online. (Default %s)" % DEFAULT_HOURS) 28 | parser.add_argument( 29 | '--multiples', 30 | type=bool, 31 | choices=[True, False], 32 | default=False, 33 | help="(Optional) - Multiple hosts are running hosts." 34 | ) 35 | args = parser.parse_args() 36 | 37 | # --Initialize-- # 38 | 39 | settings = pc_utility.get_settings(args) 40 | pc_api.configure(settings) 41 | pc_api.validate_api_compute() 42 | 43 | # --Helpers-- # 44 | 45 | # TODO: Validate Date Comparison 46 | def recent(datetime_string, delta_hours): 47 | if delta_hours == 0: 48 | return True 49 | now = datetime.datetime.now().astimezone(tz.tzlocal()) 50 | dat = date_parser.isoparse(datetime_string).astimezone(tz.tzlocal()) 51 | if now - datetime.timedelta(hours=delta_hours) <= dat <= now: 52 | return True 53 | return False 54 | 55 | # --Main-- # 56 | 57 | print('Testing Compute API Access ...', end='') 58 | intelligence = pc_api.statuses_intelligence() 59 | print(' done.') 60 | print() 61 | 62 | # https://prisma.pan.dev/api/cloud/cwpp/hosts#operation/get-hosts 63 | print('Getting Hosts (please wait) ...', end='') 64 | hosts = pc_api.hosts_list_read() 65 | print(' done.') 66 | print() 67 | 68 | print(hosts, file=open('hosts.txt', 'w')) 69 | 70 | hosts_dictionary = {} 71 | for host in hosts: 72 | # _id or hostname ? 73 | hosts_dictionary[host['_id']] = host 74 | 75 | # https://prisma.pan.dev/api/cloud/cwpp/images#operation/get-images 76 | print('Getting Deployed Images (please wait) ...', end='') 77 | result = pc_api.execute_compute('GET', 'api/v1/images/download?', query_params={'filterBaseImage': 'true'}) 78 | print(result, file=open('temp.csv', 'w')) 79 | print(' done.') 80 | print() 81 | 82 | images = pc_utility.read_csv_file_text('temp.csv') 83 | headers = images[0].keys() 84 | 85 | print("* Exporting Vulnerabilities (please wait) ...") 86 | 87 | # TODO: Validate line breaks in fields. 88 | with open(args.csv_file_name, 'w', encoding='UTF8') as f: 89 | writer = csv.writer(f) 90 | writer.writerow(headers) 91 | for image in images: 92 | if 'Hosts' in image: 93 | if image['Hosts'].isnumeric(): 94 | if args.multiples: 95 | writer.writerow(image.values()) 96 | else: 97 | print("Skipping Container: Multiple Parent Hosts. ID: (%s)" % (image['Id'])) 98 | continue 99 | if image['Hosts'] in hosts_dictionary: 100 | host = hosts_dictionary[image['Hosts']] 101 | if recent(host['scanTime'], args.hours): 102 | writer.writerow(image.values()) 103 | else: 104 | print("Skipping Container: Parent Host (%s) Last Scan Time: (%s) older than (%s) Hours" % (image['Hosts'], host['scanTime'], args.hours)) 105 | else: 106 | print("Skipping Container: Parent Host (%s) not found in Hosts. ID: (%s)" % (image['Hosts'], image['Id'])) 107 | else: 108 | print("Skipping Container: Parent Host not defined in Deployed Images. ID: (%s)" % (image['Id'])) 109 | 110 | print("* Vulnerabilities Exported") 111 | print() 112 | print("Saved to: %s" % args.csv_file_name) 113 | print() 114 | -------------------------------------------------------------------------------- /scripts/pcs_container_vulnerabilities_read.py: -------------------------------------------------------------------------------- 1 | """ Get Vulnerabilities in Containers (Deployed Images) """ 2 | 3 | import json 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | args = parser.parse_args() 12 | 13 | # --Helpers-- # 14 | 15 | def optional_print(txt='', mode=True): 16 | if mode: 17 | print(txt) 18 | 19 | # --Initialize-- # 20 | 21 | settings = pc_utility.get_settings(args) 22 | pc_api.configure(settings) 23 | pc_api.validate_api_compute() 24 | 25 | # --Main-- # 26 | 27 | print('Testing Compute API Access ...', end='') 28 | intelligence = pc_api.statuses_intelligence() 29 | print(' done.') 30 | print() 31 | 32 | vulnerabilities_by_container = [] 33 | 34 | print('Getting Deployed Images (please wait) ...', end='') 35 | images = pc_api.images_list_read(query_params={'filterBaseImage': 'true'}) 36 | print(' done.') 37 | print() 38 | 39 | print('Getting Containers (please wait) ...', end='') 40 | containers = pc_api.containers_list_read() 41 | print(' done.') 42 | print() 43 | 44 | images_dictionary = {} 45 | for image in images: 46 | if pc_api.debug: 47 | print(json.dumps(image, indent=4)) 48 | image_id = image['_id'] 49 | images_dictionary[image_id] = image 50 | 51 | for container in containers: 52 | if pc_api.debug: 53 | print(json.dumps(container, indent=4)) 54 | if 'imageID' in container['info']: 55 | image_id = container['info']['imageID'] 56 | image_name = container['info']['imageName'] 57 | if image_id in images_dictionary: 58 | if 'vulnerabilities' in images_dictionary[image_id] and images_dictionary[image_id]['vulnerabilities']: 59 | vulnerabilities = images_dictionary[image_id]['vulnerabilities'] 60 | else: 61 | vulnerabilities = [] 62 | else: 63 | vulnerabilities = [] 64 | vulnerabilities_by_container.append({'name': container['info']['name'], 'host': container['hostname'], 'image': image_name, 'vulnerabilities': vulnerabilities}) 65 | 66 | print('Container Name\tHost Name\tImage Name\tVulnerability Count') 67 | for container in vulnerabilities_by_container: 68 | print('%s\t%s\t%s\t%s' % (container['name'], container['host'], container['image'], len(container['vulnerabilities']))) 69 | print() 70 | -------------------------------------------------------------------------------- /scripts/pcs_cs_errors_for_file.py: -------------------------------------------------------------------------------- 1 | """ Returns a list of potential Code Security policy violations for the specified file path """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | '--filepath', 11 | required=True, 12 | type=str, 13 | help='File') 14 | parser.add_argument( 15 | '--repository', 16 | required=True, 17 | type=str, 18 | help='Repository name') 19 | parser.add_argument( 20 | '--sourcetype', 21 | required=True, 22 | choices=['Github', 'Bitbucket', 'Gitlab', 'AzureRepos', 'cli', 'AWS', 'Azure', 'GCP', 'Docker', 'githubEnterprise', 'gitlabEnterprise', 'bitbucketEnterprise', 'terraformCloud', 'githubActions', 'circleci', 'codebuild', 'jenkins', 'tfcRunTasks'], 23 | type=str, 24 | help='Source') 25 | args = parser.parse_args() 26 | 27 | # --Initialize-- # 28 | 29 | settings = pc_utility.get_settings(args) 30 | pc_api.configure(settings) 31 | 32 | # --Main-- # 33 | 34 | criteria = { 35 | 'filePath': args.filepath, 36 | 'repository': args.repository, 37 | 'sourceTypes': args.sourcetype, 38 | } 39 | 40 | print('API - Getting the policy violations for the specified file path ...', end='') 41 | errors = pc_api.errors_file_list(criteria=criteria) 42 | print(' done.') 43 | print() 44 | 45 | for error in errors: 46 | print('File Path: %s' % error['filePath']) 47 | print('\tID: %s' % error['errorId']) 48 | print('\tStatus: %s' % error['status']) 49 | print() 50 | 51 | print('Total number of issues/errors/volations: %s' % len(errors)) 52 | -------------------------------------------------------------------------------- /scripts/pcs_cs_repositories_read.py: -------------------------------------------------------------------------------- 1 | """ Returns a list of repositories that are integrated with Prisma Cloud Code Security """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | args = parser.parse_args() 10 | 11 | # --Initialize-- # 12 | 13 | settings = pc_utility.get_settings(args) 14 | pc_api.configure(settings) 15 | 16 | # --Main-- # 17 | 18 | print('API - Getting the list of repositories that are integrated with Prisma Cloud Code Security ...', end='') 19 | repositories = pc_api.repositories_list_read(query_params = {'errorsCount': 'true'}) 20 | print(' done.') 21 | print() 22 | 23 | print('Code Repositories:') 24 | 25 | for repository in repositories: 26 | if repository['source'].lower() == 'cli': 27 | continue 28 | print('Code Repository: %s/%s/%s' % (repository['source'].lower(), repository['owner'], repository['repository'])) 29 | print('\tID: %s' % repository['id']) 30 | if repository['lastScanDate']: 31 | print('\tLast Scan Date: %s' % repository['lastScanDate']) 32 | if 'errors' in repository: 33 | print('\tErrors: %s' % repository['errors']) 34 | print() 35 | -------------------------------------------------------------------------------- /scripts/pcs_current_registry_scan.py: -------------------------------------------------------------------------------- 1 | """ Triggers a registry scan and returns a file containing all the results from that scan """ 2 | 3 | # This provides a view of the current state of the registries 4 | # (i.e. not including images that no longer exist in the registries). 5 | 6 | import datetime 7 | import time 8 | 9 | # pylint: disable=import-error 10 | from prismacloud.api import pc_api, pc_utility 11 | 12 | # --Configuration-- # 13 | 14 | parser = pc_utility.get_arg_parser() 15 | parser.add_argument( 16 | '--registry', 17 | type=str, 18 | help='(Optional) - The scanned registry') 19 | parser.add_argument( 20 | '--repository', 21 | type=str, 22 | help='(Optional) - The scanned repository') 23 | parser.add_argument( 24 | '--tag', 25 | type=str, 26 | help='(Optional) - The scanned tag') 27 | args = parser.parse_args() 28 | 29 | # --Helpers-- # 30 | 31 | def registry_scan_wait(): 32 | registry_scan_idle = False 33 | while registry_scan_idle is False: 34 | registry_status = pc_api.statuses_registry() 35 | if registry_status and registry_status.get('completed', False) is True: 36 | registry_scan_idle = True 37 | else: 38 | print('.') 39 | time.sleep(4) 40 | 41 | # --Initialize-- # 42 | 43 | settings = pc_utility.get_settings(args) 44 | pc_api.configure(settings) 45 | pc_api.validate_api_compute() 46 | 47 | registry_params = None 48 | if args.registry or args.repository or args.repository: 49 | registry_params = { 50 | 'registry': args.registry, 51 | 'repository': args.repository, 52 | 'tag': args.tag 53 | } 54 | 55 | OUTPUT_FILE='current_registry_results.json' 56 | 57 | # --Main-- # 58 | 59 | print('Testing Compute API Access ...', end='') 60 | intelligence = pc_api.statuses_intelligence() 61 | print(' done.') 62 | print() 63 | 64 | print('Waiting until any already-running registry scan has completed ...') 65 | registry_scan_wait() 66 | print('Done.') 67 | print() 68 | 69 | now = datetime.datetime.now() 70 | print('Scanning registry starting at %s ...' % now.strftime("%Y-%m-%d %H:%M:%S"), end='') 71 | pc_api.registry_scan(body_params=registry_params) 72 | print(' done.') 73 | print() 74 | 75 | print('Waiting until the current running registry scan has completed ...') 76 | registry_scan_wait() 77 | print('Done.') 78 | print() 79 | 80 | registry_list = pc_api.registry_list_read() 81 | 82 | pc_utility.write_json_file(OUTPUT_FILE, registry_list, pretty=True) 83 | print('Registry scan data written to: %s' % OUTPUT_FILE) 84 | print() 85 | -------------------------------------------------------------------------------- /scripts/pcs_defender_report_by_cloud_account.py: -------------------------------------------------------------------------------- 1 | """ Get a Count of Protected Containers """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | args = parser.parse_args() 10 | 11 | # --Helpers-- # 12 | 13 | 14 | # --Initialize-- # 15 | 16 | settings = pc_utility.get_settings(args) 17 | pc_api.configure(settings) 18 | 19 | # --Main-- # 20 | 21 | defenders = pc_api.defenders_list_read() 22 | accounts = {' Unknown Unknown': 0} 23 | 24 | for defender in defenders: 25 | if 'provider' in defender['cloudMetadata'] and 'accountID' in defender['cloudMetadata']: 26 | account = f"{defender['cloudMetadata']['provider']} {defender['cloudMetadata']['accountID']}" 27 | if account in accounts: 28 | accounts[account] += 1 29 | else: 30 | accounts[account] = 1 31 | else: 32 | accounts[' Unknown Unknown'] += 1 33 | 34 | for account in sorted(accounts.keys()): 35 | print(f'Cloud Account: {account}') 36 | print(f' Defenders: {accounts[account]}') 37 | print(f' Credits Used: {accounts[account] * 7}') 38 | print() 39 | -------------------------------------------------------------------------------- /scripts/pcs_forensics_download.py: -------------------------------------------------------------------------------- 1 | """ Download and Save Forensics """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | '--workload_id', 11 | type=str, 12 | help='Workload ID.') 13 | parser.add_argument( 14 | '--workload_type', 15 | type=str, 16 | choices=['host', 'container', 'app-embedded'], 17 | help='Workload Type.') 18 | parser.add_argument( 19 | '--defender_hostname', 20 | type=str, 21 | help='Defender Hostname.') 22 | parser.add_argument( 23 | '--file', 24 | type=str, 25 | help='Download forensics bundle to this file (extension auto-appended).' 26 | ) 27 | args = parser.parse_args() 28 | 29 | # --Initialize-- # 30 | 31 | pc_api.configure(pc_utility.get_settings(args)) 32 | 33 | # --Main-- # 34 | 35 | print('Downloading forensics ...') 36 | 37 | data = pc_api.forensic_read(workload_id=args.workload_id, workload_type=args.workload_type, defender_hostname=args.defender_hostname) 38 | 39 | if args.workload_type in ['container', 'app-embedded']: 40 | filename = "%s%s" % (args.file, '.tgz') 41 | with open(filename, 'wb') as download: 42 | download.write(data) 43 | print('Downloaded forensic bundle to: %s' % filename) 44 | elif args.workload_type == 'host': 45 | filename = "%s%s" % (args.file, '.csv') 46 | with open(filename, 'w') as download: 47 | download.write(data) 48 | print('Downloaded host forensics to: %s' % filename) 49 | else: 50 | filename = "%s%s" % (args.file, '.csv') 51 | with open(filename, 'w') as download: 52 | for item in data: 53 | download.write("%s\n" % item) 54 | print('Downloaded forensics to: %s' % filename) 55 | -------------------------------------------------------------------------------- /scripts/pcs_hosts_vulnerabilities_read.py: -------------------------------------------------------------------------------- 1 | """ Get Vulnerabilities in Hosts (Running Hosts) """ 2 | 3 | import json 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | args = parser.parse_args() 12 | 13 | # --Helpers-- # 14 | 15 | def optional_print(txt='', mode=True): 16 | if mode: 17 | print(txt) 18 | 19 | # --Initialize-- # 20 | 21 | settings = pc_utility.get_settings(args) 22 | pc_api.configure(settings) 23 | pc_api.validate_api_compute() 24 | 25 | # --Main-- # 26 | 27 | print('Testing Compute API Access ...', end='') 28 | intelligence = pc_api.statuses_intelligence() 29 | print(' done.') 30 | print() 31 | 32 | vulnerabilities_by_container = [] 33 | 34 | print('Getting Hosts (please wait) ...', end='') 35 | hosts = pc_api.hosts_list_read() 36 | print(' done.') 37 | print() 38 | 39 | for host in hosts: 40 | if pc_api.debug: 41 | print(json.dumps(host, indent=4)) 42 | host_id = "%s %s" % (host['_id'], host['hostname']) 43 | vulnerabilities = host['vulnerabilities'] 44 | print('Host: %s' % host_id) 45 | if not vulnerabilities: 46 | continue 47 | for vulnerability in sorted(vulnerabilities, key=lambda v: v['cve']): 48 | print(' %s (%s)' % (vulnerability['cve'], vulnerability['vecStr'])) 49 | print() 50 | -------------------------------------------------------------------------------- /scripts/pcs_incident_archiver.py: -------------------------------------------------------------------------------- 1 | """ Bulk archive compute runtime incidents """ 2 | 3 | # See workflow in scripts/README.md 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | "input_csv", 13 | type=str, 14 | help="Name of input CSV with incidents to archive in the 'ID' field. This CSV MUST have a header row.", 15 | ) 16 | args = parser.parse_args() 17 | 18 | # --Initialize-- # 19 | 20 | settings = pc_utility.get_settings(args) 21 | pc_api.configure(settings) 22 | 23 | # --Main-- # 24 | 25 | # Get incident IDs from CSV 26 | incidents_in = pc_utility.read_csv_file_text(args.input_csv) 27 | 28 | # Remove duplicate IDs 29 | incidents_to_archive = {s["ID"] for s in incidents_in} 30 | 31 | # Provide a chance to back out 32 | print(f"Preparing to archive {len(incidents_to_archive)} incidents ...") 33 | pc_utility.prompt_for_verification_to_continue(args) 34 | 35 | for incident in incidents_to_archive: 36 | pc_api.audits_ack_incident(incident, ack_status=True) 37 | print(f"Archived incident {incident}") 38 | -------------------------------------------------------------------------------- /scripts/pcs_outdated_defenders.py: -------------------------------------------------------------------------------- 1 | """ Get Outdated Defenders """ 2 | from packaging import version 3 | 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | '--csv', 11 | action="store_true", 12 | help="(Optional) - Save output to 'outdated_defenders.csv'" 13 | ) 14 | parser.add_argument( 15 | '--quiet', 16 | action="store_true", 17 | help="(Optional) - Suppress console output to screen" 18 | ) 19 | parser.add_argument( 20 | '--all', 21 | action="store_true", 22 | help="(Optional) - Print All Defenders, otherwise only print outdated" 23 | ) 24 | parser.add_argument( 25 | '--summary', 26 | action="store_true", 27 | help="(Optional) - Only Print summary information" 28 | ) 29 | parser.add_argument( 30 | '--disconnected', 31 | action="store_true", 32 | help="(Optional) - Only Print summary information" 33 | ) 34 | 35 | args = parser.parse_args() 36 | 37 | # --Helpers-- # 38 | 39 | def output(*a): 40 | if not args.quiet: 41 | print(*a) 42 | if args.csv: 43 | print(*a, file=csvoutfile) 44 | 45 | # --Initialize-- # 46 | 47 | settings = pc_utility.get_settings(args) 48 | pc_api.configure(settings) 49 | pc_api.validate_api_compute() 50 | 51 | # --Main-- # 52 | 53 | # Note: Default provider is AWS. 54 | # To-Do: Support other cloud providers 55 | 56 | csvoutfile = None 57 | 58 | if args.csv: 59 | csvoutfile = open('outdated_defenders.csv', 'w') 60 | 61 | current_version = pc_api.execute_compute('GET', 'api/v1/version') 62 | 63 | output('Current Console Version: %s' % current_version) 64 | 65 | if args.disconnected: 66 | status="false" 67 | else: 68 | status="true" 69 | 70 | defenders = pc_api.defenders_list_read(query_params={'connected': status }) 71 | 72 | output('Total Defenders in Console: %s ' % len(defenders)) 73 | if not args.summary: 74 | output('Provider, Cloud Account, Region, Defender, Version, Type, Outdated') 75 | 76 | count=0 77 | for defender in defenders: 78 | count+=1 79 | if defender['version'] != '': 80 | outdated = version.parse(defender['version']) < version.parse(current_version) 81 | 82 | provider = defender["cloudMetadata"].get('provider', 'Unknown') 83 | account = defender["cloudMetadata"].get('accountID', 'Unknown') 84 | region = defender["cloudMetadata"].get('region', 'Unknown') 85 | 86 | if not args.all and outdated is False: 87 | continue 88 | if not args.summary: 89 | output('%s, %s, %s, %s, %s, %s, %s' % (provider, account, region, defender['hostname'], defender['version'], defender['type'], outdated)) 90 | 91 | output('Total Defenders in List: %s ' % count) 92 | -------------------------------------------------------------------------------- /scripts/pcs_policy_custom_export.py: -------------------------------------------------------------------------------- 1 | """ Export Custom Policies """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | DEFAULT_POLICY_EXPORT_FILE_VERSION = 2 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | '--concurrency', 13 | type=int, 14 | default=0, 15 | help='(Optional) - Number of concurrent API calls. (1-16)') 16 | parser.add_argument( 17 | 'export_file_name', 18 | type=str, 19 | help='Export file name for the Custom Policies.') 20 | args = parser.parse_args() 21 | 22 | # --Initialize-- # 23 | 24 | settings = pc_utility.get_settings(args) 25 | pc_api.configure(settings) 26 | 27 | # --Main-- # 28 | 29 | # Avoid API rate limits. 30 | if args.concurrency > 0 and args.concurrency <= 16: 31 | pc_api.max_workers = args.concurrency 32 | print('Limiting concurrent API calls to: (%s)' % pc_api.max_workers) 33 | print() 34 | 35 | # Policy Custom Export 36 | 37 | export_file_data = {} 38 | export_file_data['export_file_version'] = DEFAULT_POLICY_EXPORT_FILE_VERSION 39 | export_file_data['policy_list_original'] = [] 40 | export_file_data['policy_object_original'] = {} 41 | export_file_data['search_object_original'] = {} 42 | 43 | print('API - Getting the current list of Custom Policies ...', end='') 44 | policy_list_current = pc_api.policy_custom_v2_list_read() 45 | export_file_data['policy_list_original'] = policy_list_current 46 | print(' done.') 47 | print() 48 | 49 | # Threaded Queries. 50 | result = pc_api.get_policies_with_saved_searches(policy_list_current) 51 | 52 | export_file_data['policy_object_original'] = result['policies'] 53 | export_file_data['search_object_original'] = result['searches'] 54 | pc_utility.write_json_file(args.export_file_name, export_file_data) 55 | print('Exported to: %s' % args.export_file_name) 56 | -------------------------------------------------------------------------------- /scripts/pcs_policy_custom_import.py: -------------------------------------------------------------------------------- 1 | """ Import Custom Policies """ 2 | 3 | import json 4 | import time 5 | 6 | import requests 7 | 8 | # pylint: disable=import-error 9 | from prismacloud.api import pc_api, pc_utility 10 | 11 | # --Configuration-- # 12 | 13 | CUSTOM_POLICY_ID_MAP_FILE = 'PolicyIdMap.json' 14 | DEFAULT_POLICY_IMPORT_FILE_VERSION = 2 15 | WAIT_TIMER = 5 16 | 17 | parser = pc_utility.get_arg_parser() 18 | parser.add_argument( 19 | 'import_file_name', 20 | type=str, 21 | help='Import file name for Custom Policies.') 22 | parser.add_argument( 23 | '--maintain_status', 24 | action='store_true', 25 | help='(Optional) - Maintain the status of imported Custom Policies. By default, imported Policies will be disabled.') 26 | args = parser.parse_args() 27 | 28 | # --Initialize-- # 29 | 30 | pc_utility.prompt_for_verification_to_continue(args) 31 | settings = pc_utility.get_settings(args) 32 | pc_api.configure(settings) 33 | 34 | # --Main-- # 35 | 36 | # Custom Policy Import 37 | 38 | import_file_data = pc_utility.read_json_file(args.import_file_name) 39 | 40 | # Validation 41 | if 'policy_list_original' not in import_file_data: 42 | pc_utility.error_and_exit(404, 'policy_list_original section not found. Please verify the import file and name.') 43 | if 'policy_object_original' not in import_file_data: 44 | pc_utility.error_and_exit(404, 'policy_object_original section not found. Please verify the import file and name.') 45 | if 'export_file_version' not in import_file_data: 46 | pc_utility.error_and_exit(404, 'export_file_version section not found. Please verify the import file and name.') 47 | if 'search_object_original' not in import_file_data: 48 | pc_utility.error_and_exit(404, 'search_object_original section not found. Please verify the import file and name.') 49 | 50 | # The following will check the export version for the correct level. 51 | # If you have an older version that you want to try to import, you can comment out this line, 52 | # but please be aware it will be untested on older versions of an export file. 53 | # At this moment, it *should* still work... 54 | if import_file_data['export_file_version'] != DEFAULT_POLICY_IMPORT_FILE_VERSION: 55 | pc_utility.error_and_exit(404, 'Import file appears to be an unexpected export version. Please verify the import file and name.') 56 | 57 | policy_object_original = import_file_data['policy_object_original'] 58 | search_object_original = import_file_data['search_object_original'] 59 | 60 | # For duplicate policy name check. 61 | print('API - Getting the current list of Policies ...', end='') 62 | policy_list_current = pc_api.policy_v2_list_read() 63 | print(' done.') 64 | print() 65 | 66 | print('API - Importing Custom Policies ...') 67 | try: 68 | custom_policy_id_map = json.load(open(CUSTOM_POLICY_ID_MAP_FILE, 'r')) 69 | except (ValueError, FileNotFoundError): 70 | custom_policy_id_map = {} 71 | 72 | for policy_id, policy_object in policy_object_original.items(): 73 | duplicate_found = False 74 | for policy_current in policy_list_current: 75 | if policy_object['name'].lower() == policy_current['name'].lower(): 76 | duplicate_found = True 77 | break 78 | if duplicate_found: 79 | print('Skipping Duplicate (by name) Policy: %s' % policy_object['name']) 80 | else: 81 | if not args.maintain_status: 82 | policy_object['enabled'] = False 83 | # Strip out denormalized data not required to import. 84 | if 'complianceMetadata' in policy_object: 85 | policy_object['complianceMetadata'] = [] 86 | policy_object.pop('createdBy', None) 87 | policy_object.pop('createdOn', None) 88 | policy_object.pop('deleted', None) 89 | policy_object.pop('lastModifiedBy', None) 90 | policy_object.pop('lastModifiedOn', None) 91 | policy_object.pop('policyID', None) 92 | policy_object.pop('remediable', None) 93 | policy_object.pop('ruleLastModifiedOn', None) 94 | if 'savedSearch' in policy_object['rule']['parameters']: 95 | if policy_object['rule']['parameters']['savedSearch'] == 'true': 96 | search_id_to_match = policy_object['rule']['criteria'] 97 | for search_object_id, search_object in search_object_original.items(): 98 | if 'id' in search_object: 99 | if search_object['id'] == search_id_to_match: 100 | body_data = {'query': search_object['query'], 'saved': False, 'timeRange': {'type':'relative', 'value': {'unit': 'hour', 'amount': 24}}} 101 | new_search = pc_api.saved_search_create(search_object['searchType'], body_data) 102 | 103 | policy_object['rule']['criteria'] = new_search['id'] 104 | search_object.pop('id', None) 105 | search_object['name'] = '%s _Imported_%s' % (policy_object['name'], int(time.time())) 106 | if not search_object['description']: 107 | search_object['description'] = 'Imported' 108 | search_object['saved'] = True 109 | # pc_api.saved_search_create(new_search['id'], search_object) 110 | new_policy_id = None 111 | if not args.maintain_status: 112 | policy_object['enabled'] = False 113 | try: 114 | print('Importing: %s' % policy_object['name']) 115 | new_policy = pc_api.policy_create(policy_object) 116 | new_policy_id = new_policy['policyId'] 117 | except requests.exceptions.HTTPError as ex: 118 | print('Error importing: %s' + policy_object['name']) 119 | print('Possibly, the cloud provider for this Policy is not supported in the destination (esp: api.prismacloud.cn).') 120 | if new_policy_id is not None: 121 | custom_policy_id_map[policy_id] = new_policy_id 122 | print('') 123 | json.dump(custom_policy_id_map, open(CUSTOM_POLICY_ID_MAP_FILE, 'w')) 124 | print('Done.') 125 | print() 126 | 127 | print('Import Complete.') 128 | -------------------------------------------------------------------------------- /scripts/pcs_policy_read.py: -------------------------------------------------------------------------------- 1 | """ Get a Policy """ 2 | 3 | import json 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | '-rql', 13 | '--rql', 14 | action='store_true', 15 | help='(Optional) - Output the RQL (Saved Search) of the Policy.') 16 | parser.add_argument( 17 | 'policy_name', 18 | type=str, 19 | help='(Required) - Name of the Policy.') 20 | args = parser.parse_args() 21 | 22 | # --Initialize-- # 23 | 24 | settings = pc_utility.get_settings(args) 25 | pc_api.configure(settings) 26 | 27 | # --Main-- # 28 | 29 | # Get Policy 30 | 31 | print('API - Getting the Policy list ...', end='') 32 | policy_list = pc_api.policy_list_read() 33 | print(' done.') 34 | print() 35 | 36 | # TODO: Replace with library function. 37 | 38 | policy_id = None 39 | for policy in policy_list: 40 | if policy['name'].lower() == args.policy_name.lower(): 41 | policy_id = policy['policyId'] 42 | break 43 | if policy_id is None: 44 | pc_utility.error_and_exit(500, 'Policy was not found. Please verify the Policy name.') 45 | 46 | print() 47 | print('Policy from Policy list:') 48 | print(policy_id) 49 | print() 50 | 51 | print('API - Getting the Policy ...', end='') 52 | policy = pc_api.policy_read(policy_id) 53 | print(' done.') 54 | print() 55 | 56 | print('Policy:') 57 | print(json.dumps(policy)) 58 | print() 59 | 60 | if args.rql: 61 | print('API - Getting the RQL (Saved Search) ...', end='') 62 | policy_search = pc_api.saved_search_read(policy['rule']['criteria']) 63 | print(' done.') 64 | print() 65 | 66 | print('RQL (Saved Search):') 67 | print(json.dumps(policy_search)) 68 | print() 69 | -------------------------------------------------------------------------------- /scripts/pcs_policy_set_status.py: -------------------------------------------------------------------------------- 1 | """ Set the Status (enable or disable) of a Policy """ 2 | 3 | import sys 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | '--all_policies', 13 | action='store_true', 14 | help='Enable or disable all Policies') 15 | parser.add_argument( 16 | '--cloud_type', 17 | type=str, 18 | choices=['aws', 'azure', 'gcp', 'oci', 'alibaba_cloud'], 19 | help='Enable or disable Policies by Cloud Type') 20 | parser.add_argument( 21 | '--compliance_standard', 22 | type=str, 23 | help='Enable or disable Policies by Compliance Standard (ignoring other selectors)') 24 | parser.add_argument( 25 | '--policy_severity', 26 | type=str, 27 | choices=['informational', 'low', 'medium', 'high', 'critical'], 28 | help='Enable or disable Policies by Severity') 29 | parser.add_argument( 30 | '--policy_mode', 31 | type=str, 32 | choices=['Default', 'Custom'], 33 | help='Enable or disable Policies by Mode - Default or Custom') 34 | parser.add_argument( 35 | '--policy_type', 36 | type=str, 37 | choices=['anomaly', 'attack_path', 'audit_event', 'config', 'data', 'iam', 'network', 'workload_incident', 'workload_vulnerability'], 38 | help='Enable or disable Policies by Type') 39 | parser.add_argument( 40 | '--policy_subtype', 41 | type=str, 42 | choices=['build', 'run'], 43 | help='Enable or disable Policies by Subtype') 44 | parser.add_argument( 45 | '--policy_label', 46 | type=str, 47 | choices=['Prisma_Cloud'], 48 | help='Enable or disable Policies by Labels') 49 | parser.add_argument( 50 | '--policy_descriptor', 51 | type=str, 52 | choices=['PC-ALL-ALL', 'PC-AWS', 'PC-GCP', 'PC-AZR', 'PC-OCI', 'PC-ALB', 'blank'], 53 | help='Enable or disable Policies by Descriptor') 54 | parser.add_argument( 55 | 'status', 56 | type=str, 57 | choices=['enable', 'disable'], 58 | help="Policy Status to set ('enable' or 'disable')") 59 | args = parser.parse_args() 60 | 61 | # --Initialize-- # 62 | 63 | pc_utility.prompt_for_verification_to_continue(args) 64 | 65 | settings = pc_utility.get_settings(args) 66 | pc_api.configure(settings) 67 | 68 | # --Helpers-- # 69 | 70 | # Define policy selectors. 71 | 72 | def policy_selectors(argz): 73 | result = [] 74 | if argz.cloud_type is not None: 75 | result.append({'selector_name': 'cloud_type', 'selector_value': argz.cloud_type.lower()}) 76 | if argz.policy_severity is not None: 77 | result.append({'selector_name': 'policy_severity', 'selector_value': argz.policy_severity.lower()}) 78 | if argz.policy_mode is not None: 79 | result.append({'selector_name': 'policy_mode', 'selector_value': argz.policy_mode.lower()}) 80 | if argz.policy_type is not None: 81 | result.append({'selector_name': 'policy_type', 'selector_value': argz.policy_type.lower()}) 82 | if argz.policy_subtype is not None: 83 | result.append({'selector_name': 'policy_subtype', 'selector_value': argz.policy_subtype.lower()}) 84 | if argz.policy_label is not None: 85 | result.append({'selector_name': 'policy_label', 'selector_value': argz.policy_label}) 86 | if argz.policy_descriptor is not None: 87 | result.append({'selector_name': 'policy_descriptor', 'selector_value': argz.policy_descriptor}) 88 | return result 89 | 90 | # Evaluate policy selectors for a policy. 91 | 92 | def policy_matches_selector(this_policy, this_selector_name, this_selector_value): 93 | if not this_selector_value: 94 | return False 95 | if this_selector_name == 'cloud_type' and this_selector_value == policy['cloudType']: 96 | return True 97 | if this_selector_name == 'policy_severity' and this_selector_value == policy['severity']: 98 | return True 99 | if this_selector_name == 'policy_mode' and this_selector_value == policy['policyMode']: 100 | return True 101 | if this_selector_name == 'policy_type' and this_selector_value == policy['policyType']: 102 | return True 103 | if this_selector_name == 'policy_subtype' and this_selector_value in policy['policySubTypes']: 104 | return True 105 | if this_selector_name == 'policy_label' and this_selector_value in policy['labels']: 106 | return True 107 | if this_selector_name == 'policy_descriptor': 108 | if 'policyUpi' in this_policy: 109 | return this_policy['policyUpi'].startswith(this_selector_value) 110 | return this_selector_value == 'blank' 111 | return False 112 | 113 | # Update a list of policies, enabling or disabling each. 114 | 115 | def update_policies(this_policy_list, args_status): 116 | # Transform the status argument for use with Python and the API. 117 | policy_status = bool(args_status.lower() == 'enable') 118 | policy_status_string = str(policy_status).lower() 119 | if this_policy_list: 120 | print('API - Evaluating %s Policies ...' % len(this_policy_list)) 121 | counter=0 122 | for this_policy in this_policy_list: 123 | counter+=1 124 | # Do not update a policy if it is already has the specified status. 125 | if this_policy['enabled'] is policy_status: 126 | print('Skipping Policy with specified status (%s / %s): %s' % (policy_status, this_policy['enabled'], this_policy['name'])) 127 | continue 128 | print('API - Updating Policy: %s' % this_policy['name']) 129 | pc_api.policy_status_update(this_policy['policyId'], policy_status_string) 130 | if counter % 10 == 0: 131 | print('Progress: %.0f%% ' % (int(counter) / int(len(this_policy_list))*100)) 132 | print('Done.') 133 | print() 134 | else: 135 | print('API - No Policies match the specified parameters.') 136 | print() 137 | 138 | # --Main-- # 139 | 140 | # Get all policies, or all policies mapped to a specific compliance standard. 141 | 142 | if args.compliance_standard is None: 143 | print('API - Getting list of Policies ...', end='') 144 | policy_list = pc_api.policy_v2_list_read() 145 | else: 146 | print('API - Getting list of Policies by Compliance Standard (%s) ...' % args.compliance_standard, end='') 147 | policy_list = pc_api.compliance_standard_policy_v2_list_read(args.compliance_standard) 148 | print(' done.') 149 | print() 150 | 151 | # Optionally update all policies and exit early. 152 | 153 | if args.all_policies: 154 | update_policies(policy_list, args.status) 155 | sys.exit(0) 156 | 157 | # Otherwise, select and update selected policies. 158 | 159 | policy_selector_list = policy_selectors(args) 160 | selected_policies = [] 161 | for policy in policy_list: 162 | selector_matches_list = [] 163 | for policy_selector in policy_selector_list: 164 | if policy_matches_selector(policy, policy_selector['selector_name'], policy_selector['selector_value']): 165 | selector_matches_list.append(policy_selector['selector_name']) 166 | if len(selector_matches_list) == len(policy_selector_list): 167 | selected_policies.append(policy) 168 | update_policies(selected_policies, args.status) 169 | -------------------------------------------------------------------------------- /scripts/pcs_posture_endpoint_client.py: -------------------------------------------------------------------------------- 1 | """ Generic Prisma Cloud API Endpoint Client. """ 2 | 3 | from json import dumps as json_dumps 4 | from sys import exit as sys_exit, stderr, stdout 5 | 6 | # pylint: disable=import-error 7 | from prismacloud.api import pc_api, pc_utility 8 | 9 | # --Configuration-- # 10 | 11 | parser = pc_utility.get_arg_parser() 12 | 13 | parser.add_argument( 14 | 'http_method', 15 | type=str, 16 | help='HTTP Method for HTTP request') 17 | 18 | parser.add_argument( 19 | 'uri_path', 20 | type=str, 21 | help='URI Path to HTTP endpoint') 22 | 23 | parser.add_argument( 24 | '--uri_params', 25 | type=str, 26 | help='(Optional) URI Parameters for HTTP request') 27 | 28 | parser.add_argument( 29 | '--request_body', 30 | type=str, 31 | help='(Optional) Import file name for file containing the HTTP request body data.') 32 | 33 | parser.add_argument( 34 | '--response_file', 35 | type=str, 36 | help='(Optional) Export file name for file containing the HTTP response body data.') 37 | 38 | parser.add_argument( 39 | '--pretty', 40 | action='store_true', 41 | help='(Optional) Specify pretty JSON output.') 42 | 43 | args = parser.parse_args() 44 | 45 | # For readability: these arguments default to class 'NoneType' and (default False) 46 | 47 | if not args.uri_params: 48 | args.uri_params = None 49 | 50 | if not args.request_body: 51 | args.request_body = None 52 | 53 | if not args.pretty: 54 | args.pretty = False 55 | 56 | # --Initialize-- # 57 | 58 | settings = pc_utility.get_settings(args) 59 | pc_api.configure(settings) 60 | 61 | # --Main-- # 62 | 63 | if args.request_body is None: 64 | request_body_data = None 65 | if args.uri_params is None: 66 | print('API - Executing HTTP request "%s %s"' % (args.http_method, args.uri_path), file=stderr) 67 | else: 68 | print('API - Executing HTTP request "%s %s&%s"' % (args.http_method, args.uri_path, args.uri_params), file=stderr) 69 | else: 70 | # Import the data file 71 | print('API - Importing file data from file %s' % args.request_body, file=stderr) 72 | try: 73 | request_body_data = pc_utility.read_json_file(args.request_body) 74 | print('API - Import of file data %s completed' % args.request_body, file=stderr) 75 | if args.uri_params == '': 76 | print('API - Executing HTTP request "%s %s" with request body:\n%s' % (args.http_method, args.uri_path, request_body_data), file=stderr) 77 | else: 78 | print('API - Executing HTTP request "%s %s&%s" with request body:\n%s' % (args.http_method, args.uri_path, args.uri_params, request_body_data), file=stderr) 79 | except (ValueError, FileNotFoundError): 80 | print('API - Failed to import file %s' % args.request_body, file=stderr) 81 | sys_exit(2) 82 | 83 | # HUGE WARNING: NO VALIDATION HERE IN THIS EXAMPLE (so that it fits the broadest set of use-cases) 84 | 85 | # Prompt for warning if interactive 86 | pc_utility.prompt_for_verification_to_continue(args) 87 | 88 | # Make the HTTP request 89 | 90 | try: 91 | response=pc_api.execute(args.http_method,args.uri_path,query_params=args.uri_params,body_params=request_body_data) 92 | if args.response_file is None: 93 | print('API - HTTP request response is:', file=stderr) 94 | if args.pretty: 95 | print(json_dumps(response, indent=4, separators=(', ', ': ')), file=stdout) 96 | else: 97 | print(json_dumps(response), file=stdout) 98 | else: 99 | print('API - HTTP request response stored in file %s' % args.response_file, file=stderr) 100 | pc_utility.write_json_file(args.response_file, response, pretty=bool(args.pretty)) 101 | except Exception: # pylint: disable=W0703 102 | print('API - HTTP request failed', file=stderr) 103 | sys_exit(1) 104 | -------------------------------------------------------------------------------- /scripts/pcs_resources_export.py: -------------------------------------------------------------------------------- 1 | """ Get Resources """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | '--cloud_account_name', 11 | type=str, 12 | help='Name of the Cloud Account to get Resources.') 13 | parser.add_argument( 14 | '--concurrency', 15 | type=int, 16 | default=0, 17 | help='(Optional) - Number of concurrent API calls. (1-16)') 18 | parser.add_argument( 19 | 'export_file_name', 20 | type=str, 21 | help='Export file name for the Resources.') 22 | args = parser.parse_args() 23 | 24 | # --Initialize-- # 25 | 26 | settings = pc_utility.get_settings(args) 27 | pc_api.configure(settings) 28 | 29 | # --Main-- # 30 | 31 | print('API - Getting the current list of Cloud Accounts ...', end='') 32 | cloud_accounts_list = pc_api.cloud_accounts_list_read(query_params={'excludeAccountGroupDetails': 'true'}) 33 | print(' done.') 34 | print() 35 | 36 | # Optionally filter the list of cloud accounts down to the one specified on the command line. 37 | if args.cloud_account_name: 38 | cloud_accounts_list = [next(item for item in cloud_accounts_list if item['name'] == args.cloud_account_name)] 39 | 40 | # Avoid API rate limits. 41 | if args.concurrency > 0 and args.concurrency <= 16: 42 | pc_api.max_workers = args.concurrency 43 | print('Limiting concurrent API calls to: (%s)' % pc_api.max_workers) 44 | print() 45 | 46 | resource_list = [] 47 | 48 | for cloud_account in cloud_accounts_list: 49 | body_params = { 50 | 'filters':[ 51 | {'operator':'=', 'name':'includeEventForeignEntities', 'value': 'false'}, 52 | {'operator':'=', 'name':'asset.severity', 'value': 'all'}, 53 | {'operator':'=', 'name':'cloud.account', 'value': '%s' % cloud_account['name']}, 54 | {'operator':'=', 'name':'cloud.type', 'value': '%s' % cloud_account['deploymentType']}, 55 | {'operator':'=', 'name':'scan.status', 'value': 'all'}], 56 | 'limit': 1000, 57 | 'timeRange': {'type': 'to_now'} 58 | } 59 | print('API - Getting the current Resources for Cloud Account: %s ...' % cloud_account['name']) 60 | cloud_account_resource_list = pc_api.resource_scan_info_read(body_params=body_params) 61 | print('Done.') 62 | # Threaded Queries. 63 | resource_list.append(pc_api.get_cloud_resources(cloud_account_resource_list)) 64 | 65 | pc_utility.write_json_file(args.export_file_name, resource_list) 66 | print() 67 | print('Exported to: %s' % args.export_file_name) 68 | 69 | pc_api.error_report() 70 | -------------------------------------------------------------------------------- /scripts/pcs_rotate_service_account_access_key.py: -------------------------------------------------------------------------------- 1 | """ Rotate (replace) a Service Account Access Key """ 2 | 3 | import re 4 | import time 5 | 6 | # pylint: disable=import-error 7 | from prismacloud.api import pc_api, pc_utility 8 | 9 | # --Configuration-- # 10 | 11 | DEFAULT_DAYS = 0 12 | 13 | parser = pc_utility.get_arg_parser() 14 | parser.add_argument( 15 | '--days', 16 | type=int, 17 | default=DEFAULT_DAYS, 18 | help=f'(Optional) - Expiration in days from now, with 0 equaling no expiration date. (Default: {DEFAULT_DAYS})') 19 | parser.add_argument( 20 | 'base_key_name', 21 | type=str, 22 | help="(Required) - Access Key name (not including a ' vN' version suffix) of the Access Key to rotate") 23 | args = parser.parse_args() 24 | 25 | # --Initialize-- # 26 | 27 | settings = pc_utility.get_settings(args) 28 | pc_api.configure(settings) 29 | 30 | # --Helpers-- # 31 | 32 | # Identify a matching access key by name. 33 | 34 | def matching_access_key(access_key_name, base_key_name): 35 | access_key_name_lower = access_key_name.lower() 36 | base_key_name_lower = base_key_name.lower() 37 | # Exact name match. 38 | if access_key_name_lower == base_key_name_lower: 39 | return True 40 | # Name with version match. 41 | matches = re.search(r'(.+) v(\d+)$', access_key_name) 42 | if matches: 43 | access_key_base_name_lower = matches.group(1).lower() 44 | if access_key_base_name_lower == base_key_name_lower: 45 | return True 46 | # No match. 47 | return False 48 | 49 | # Identify an access key version number by name. 50 | 51 | def get_current_version(access_key_name): 52 | matches = re.search(r'(.+) v(\d+)$', access_key_name) 53 | if matches: 54 | return int(matches.group(2)) 55 | return 0 56 | 57 | # --Main-- # 58 | 59 | # Queries. 60 | 61 | current_user = pc_api.current_user() 62 | 63 | access_keys_list = pc_api.access_keys_list_read() 64 | 65 | # Selection. 66 | 67 | matching_access_keys = [] 68 | for access_key in access_keys_list: 69 | if matching_access_key(access_key['name'], args.base_key_name): 70 | matching_access_keys.append(access_key) 71 | 72 | if len(matching_access_keys) == 0: 73 | pc_utility.error_and_exit(500, 'Access Key Not Found') 74 | 75 | if len(matching_access_keys) == 1: 76 | previous_access_key = None 77 | current_access_key = matching_access_keys[0] 78 | elif len(matching_access_keys) == 2: 79 | matching_access_keys = sorted(matching_access_keys, key=lambda item: get_current_version(item['name'])) 80 | previous_access_key = matching_access_keys[0] 81 | current_access_key = matching_access_keys[1] 82 | else: 83 | pc_utility.error_and_exit(500, 'Access Key not unique: matches more than two Access Keys') 84 | 85 | # Safeguards. 86 | 87 | if current_access_key['id'].lower() == pc_api.identity.lower(): 88 | pc_utility.error_and_exit(500, 'This script cannot rotate its own Access Key') 89 | 90 | if previous_access_key and previous_access_key['id'].lower() == pc_api.identity.lower(): 91 | pc_utility.error_and_exit(500, 'This script cannot rotate its own Access Key') 92 | 93 | if current_access_key['username'].lower() == current_user['email'].lower(): 94 | pc_utility.error_and_exit(500, 'This script can only rotate Service Account Access Keys') 95 | 96 | if previous_access_key and previous_access_key['username'].lower() == current_user['email'].lower(): 97 | pc_utility.error_and_exit(500, 'This script can only rotate Service Account Access Keys') 98 | 99 | # Expiration: none, or the specified expiration in days as a timestamp. 100 | 101 | if args.days == 0: 102 | expires = args.days 103 | else: 104 | expires = round(time.time() * 1000) + (86400000 * args.days) 105 | 106 | # Increment the version number. 107 | 108 | version = get_current_version(current_access_key['name']) + 1 109 | name_and_version = '%s v%s' % (args.base_key_name, version) 110 | 111 | next_access_key = { 112 | 'expiresOn': expires, 113 | 'name': name_and_version, 114 | 'serviceAccountName': current_access_key['username'], 115 | } 116 | 117 | # Delete the previous access key, if it exists. 118 | 119 | if previous_access_key: 120 | result = pc_api.access_key_delete(previous_access_key['id']) 121 | 122 | # Create the next access key. 123 | 124 | new_access_key = pc_api.access_key_create(next_access_key) 125 | 126 | # Output the next access key. 127 | 128 | new_access_key['name'] = name_and_version 129 | print('Next Access Key: %s' % new_access_key) 130 | -------------------------------------------------------------------------------- /scripts/pcs_rql_query.py: -------------------------------------------------------------------------------- 1 | """ Get a list of Alerts """ 2 | 3 | import json 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | 'query', 13 | type=str, 14 | help='RQL') 15 | parser.add_argument( 16 | '-tr', 17 | '--timerange', 18 | type=int, 19 | default=30, 20 | help='(Optional) - Time Range in days (default 30).') 21 | args = parser.parse_args() 22 | 23 | # --Initialize-- # 24 | 25 | settings = pc_utility.get_settings(args) 26 | pc_api.configure(settings) 27 | 28 | # --Main-- # 29 | 30 | # Config: "config from cloud.resource where api.name = 'aws-ec2-describe-instances'" 31 | # Network: "network from vpc.flow_record where bytes > 0 AND threat.source = 'AutoFocus' AND threat.tag.group = 'Cryptominer'" 32 | # Event: "event from cloud.audit_logs where operation IN ( 'AddUserToGroup', 'AttachGroupPolicy', 'AttachUserPolicy' , 'AttachRolePolicy' , 'CreateAccessKey', 'CreateKeyPair', 'DeleteKeyPair', 'DeleteLogGroup' )" 33 | 34 | if not args.query: 35 | pc_utility.error_and_exit(500, 'Please specify an RQL query.') 36 | 37 | search_params = {} 38 | search_params['limit'] = 100 39 | search_params['timeRange'] = {} 40 | search_params['timeRange']['type'] = 'relative' 41 | search_params['timeRange']['value'] = {} 42 | search_params['timeRange']['value']['unit'] = 'day' 43 | search_params['timeRange']['value']['amount'] = args.timerange 44 | search_params['withResourceJson'] = False 45 | search_params['query'] = args.query 46 | 47 | print('API - Getting the RQL results ...', end='') 48 | if args.query.startswith('config from'): 49 | result_list = pc_api.search_config_read(search_params=search_params) 50 | elif args.query.startswith('network from'): 51 | result_list = pc_api.search_network_read(search_params=search_params) 52 | elif args.query.startswith('event from'): 53 | result_list = pc_api.search_event_read(search_params=search_params) 54 | else: 55 | pc_utility.error_and_exit(500, 'Unknown RQL query type (limited to: config|network|event).') 56 | print(' done.') 57 | print() 58 | 59 | print('Results:') 60 | print(json.dumps(result_list)) 61 | print() 62 | 63 | print('Result Count:') 64 | print(len(result_list)) 65 | -------------------------------------------------------------------------------- /scripts/pcs_script_example.py: -------------------------------------------------------------------------------- 1 | """ Example of Prisma Cloud (and Compute) API Access """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | # ADD SCRIPT-SPECIFIC ARGS HERE 10 | parser.add_argument('--example', type=str, default='', help='(Optional) - Example') 11 | args = parser.parse_args() 12 | 13 | # --Initialize-- # 14 | 15 | pc_api.configure(pc_utility.get_settings(args)) 16 | 17 | # --Main-- # 18 | 19 | # REPLACE CODE HERE 20 | 21 | if pc_api.api: 22 | print() 23 | print('Prisma Cloud API Test:') 24 | print() 25 | print(pc_api.current_user()) 26 | print() 27 | print('Prisma Cloud Compute API Info:') 28 | print() 29 | print(pc_api.compute_config()) 30 | 31 | if pc_api.api_compute: 32 | print() 33 | print('Prisma Cloud Compute API Test:') 34 | print() 35 | print(pc_api.statuses_intelligence()) 36 | print() 37 | 38 | if pc_api.api: 39 | print() 40 | print('Prisma Cloud Code Security API Test:') 41 | print() 42 | print('Checkov Version: %s' % pc_api.checkov_version()) 43 | print() 44 | 45 | print(pc_api) 46 | print() 47 | -------------------------------------------------------------------------------- /scripts/pcs_ssl_configure.py: -------------------------------------------------------------------------------- 1 | """ Utility to create a certificate bundle including intercepting proxy certificates """ 2 | 3 | import argparse 4 | import socket 5 | import ssl 6 | import sys 7 | import OpenSSL 8 | 9 | import certifi 10 | 11 | # --Description-- # 12 | 13 | # Utility to create a certificate bundle including intercepting proxy certificates (requires 'pip install certifi pyopenssl') 14 | # Defaults to include certificates used by Global Protect. 15 | 16 | # --Configuration-- # 17 | 18 | pc_arg_parser = argparse.ArgumentParser() 19 | pc_arg_parser.add_argument( 20 | '--api', 21 | default='api.prismacloud.io', 22 | type=str, 23 | help='(Optional) - URL') 24 | pc_arg_parser.add_argument( 25 | '--api_port', 26 | default=443, 27 | type=int, 28 | help='(Optional) - Port.') 29 | pc_arg_parser.add_argument( 30 | '--ca_bundle', 31 | default='globalprotect_certifi.txt', 32 | type=str, 33 | help='(Optional) - Certificate bundle filename to create') 34 | pc_arg_parser.add_argument( 35 | '-s', 36 | dest='custom_subjects', 37 | default=[ 38 | '/C=US/ST=CA/O=paloalto networks/OU=IT/CN=decrypt.paloaltonetworks.com', 39 | '/DC=local/DC=paloaltonetworks/CN=Palo Alto Networks Inc Domain CA', 40 | '/C=US/O=Palo Alto Networks Inc/CN=Palo Alto Networks Inc Root CA' 41 | ], 42 | nargs='*', 43 | type=str, 44 | help='(Optional) - List of certificate subjects (or substrings) to match') 45 | pc_arg_parser.add_argument( 46 | '-i', '--insensitive', 47 | action='store_true', 48 | help='(Default: disabled) - Enable case-insensitive substring matching') 49 | args = pc_arg_parser.parse_args() 50 | 51 | src_ca_file = certifi.where() 52 | dst_ca_file = args.ca_bundle 53 | host_name = args.api 54 | port = args.api_port 55 | custom_subjects = args.custom_subjects 56 | ssl_context_method = OpenSSL.SSL.TLSv1_2_METHOD # SSL.SSLv23_METHOD 57 | 58 | if args.insensitive: 59 | custom_subjects = [cs.lower() for cs in custom_subjects] 60 | 61 | # --Main-- # 62 | 63 | print('Connecting to %s:%s' % (host_name, port)) 64 | print('Searching the peer certificate chain for certificates with subjects matching:') 65 | print() 66 | for cs in custom_subjects: 67 | print(' %s' % cs) 68 | print() 69 | if args.insensitive: 70 | print('Using case-insensitive substring matching') 71 | print() 72 | 73 | with open(src_ca_file, 'r') as root_ca_file: 74 | root_certificates = root_ca_file.read() 75 | 76 | with open(dst_ca_file, 'w') as custom_ca_file: 77 | custom_ca_file.write(root_certificates) 78 | context = OpenSSL.SSL.Context(method=ssl_context_method) 79 | 80 | if ssl.OPENSSL_VERSION.startswith('OpenSSL 3'): 81 | # https://github.com/python/cpython/issues/89051 ("ssl.OP_LEGACY_SERVER_CONNECT missing") 82 | # context.set_options(OP_LEGACY_SERVER_CONNECT) 83 | context.set_options(0x4) 84 | 85 | context.load_verify_locations(cafile=src_ca_file) 86 | conn = OpenSSL.SSL.Connection(context, socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)) 87 | conn.settimeout(5) 88 | conn.connect((host_name, port)) 89 | conn.setblocking(1) 90 | 91 | try: 92 | conn.do_handshake() 93 | except: # pylint: disable=bare-except 94 | print() 95 | print('OpenSSL Error: Unsafe Legacy Renegotiation Disabled') 96 | print('To resolve, create an openssl.conf file containing the following content ...') 97 | print() 98 | print("openssl_conf = openssl_init\n[openssl_init]\nssl_conf = ssl_sect\n\n[ssl_sect]\nsystem_default = system_default_sect\n\n[system_default_sect]\nOptions = UnsafeLegacyRenegotiation") 99 | print() 100 | print('... then rerun this script using that file:') 101 | print('OPENSSL_CONF=./openssl.conf python3 pcs_ssl_configure.py') 102 | print() 103 | sys.exit(1) 104 | 105 | found_subjects = [] 106 | 107 | conn.set_tlsext_host_name(host_name.encode()) 108 | for (idx, certificate) in enumerate(conn.get_peer_cert_chain()): 109 | certificate_subject = ''.join("/{0:s}={1:s}".format(name.decode(), value.decode()) for name, value in certificate.get_subject().get_components()) 110 | certificate_issuer = ''.join("/{0:s}={1:s}".format(name.decode(), value.decode()) for name, value in certificate.get_issuer().get_components()) 111 | 112 | if args.insensitive: 113 | subject_to_match = certificate_subject.lower() 114 | else: 115 | subject_to_match = certificate_subject 116 | certificate_subject_matches = any(custom_subject in subject_to_match for custom_subject in custom_subjects) 117 | 118 | if certificate_subject_matches: 119 | certificate_subject_comment = '# Subject: %s' % certificate_subject 120 | certificate_issuer_comment = '# Issuer: %s' % certificate_issuer 121 | certificate_string = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, certificate).decode('utf-8') 122 | custom_ca_file.write("\n") 123 | custom_ca_file.write(certificate_subject_comment) 124 | custom_ca_file.write("\n") 125 | custom_ca_file.write(certificate_issuer_comment) 126 | custom_ca_file.write("\n") 127 | custom_ca_file.write(certificate_string) 128 | found_subjects.append(certificate_subject) 129 | print('Found matching certificate: %s' % certificate_subject) 130 | 131 | conn.close() 132 | 133 | print() 134 | print('Certificate bundle saved as: %s' % dst_ca_file) 135 | -------------------------------------------------------------------------------- /scripts/pcs_sync_registries.py: -------------------------------------------------------------------------------- 1 | """ Add discovered registries to Vulnerability->Images->Registry settings """ 2 | 3 | from operator import itemgetter 4 | 5 | # pylint: disable=import-error 6 | from prismacloud.api import pc_api, pc_utility 7 | 8 | # --Configuration-- # 9 | 10 | parser = pc_utility.get_arg_parser() 11 | parser.add_argument( 12 | "--collection", 13 | type=str, 14 | default="All", 15 | help="(Optional) - Collection to use for scanning, defaults to All.", 16 | ) 17 | parser.add_argument( 18 | "--serviceType", 19 | type=str, 20 | choices=["aws-ecr", "azure-acr", "all"], 21 | default="all", 22 | help="(Optional) - Add all or specific registry types.", 23 | ) 24 | parser.add_argument("--dryrun", action="store_true", help="Set flag for dryrun mode") 25 | args = parser.parse_args() 26 | 27 | # --Helpers-- # 28 | 29 | # --Initialize-- # 30 | 31 | settings = pc_utility.get_settings(args) 32 | pc_api.configure(settings) 33 | pc_api.validate_api_compute() 34 | 35 | # --Main-- # 36 | 37 | print("API - Getting all cloud discovered assets ...", end="") 38 | discovered_cloud_assets = pc_api.cloud_discovery_read() 39 | print(" Success.") 40 | 41 | print("INFO - Filtering all discovered registries ...", end="") 42 | discovered_cloud_registries = [ 43 | item for item in discovered_cloud_assets if "registry" in item 44 | ] 45 | print(" Success.") 46 | print("INFO - Discovered registries ... %s" % len(discovered_cloud_registries)) 47 | print("API - Getting all configured registries ...", end="") 48 | configured_registries = pc_api.settings_registry_read() 49 | print(" Success.") 50 | configured_registries_list = list( 51 | map(itemgetter("registry"), configured_registries["specifications"]) 52 | ) 53 | print("INFO - Configured registries ... %s" % len(configured_registries_list)) 54 | update = 0 55 | for d_registry in discovered_cloud_registries: 56 | if ( 57 | args.serviceType in (d_registry["serviceType"], "all") 58 | ) and (d_registry["registry"] not in configured_registries_list): 59 | print( 60 | "INFO - Adding %s to registry scan queue ..." % d_registry["registry"], 61 | end="", 62 | ) 63 | configured_registries["specifications"].append( 64 | { 65 | "collections": [args.collection], 66 | "cap": 5, 67 | "os": "linux", 68 | "scanners": 2, 69 | "registry": d_registry["registry"], 70 | "version": d_registry["provider"], 71 | "credentialId": d_registry["credentialId"], 72 | } 73 | ) 74 | print(" Success.") 75 | update = 1 76 | if update: 77 | if args.dryrun: 78 | print("DRYRN - Pushing new list of configured registries ... Success.") 79 | else: 80 | print("API - Pushing new list of configured registries ...", end="") 81 | pc_api.settings_registry_write( 82 | {"specifications": configured_registries["specifications"]} 83 | ) 84 | print(" Success.") 85 | else: 86 | print("INFO - No registries to add.") 87 | -------------------------------------------------------------------------------- /scripts/pcs_usage.py: -------------------------------------------------------------------------------- 1 | """ Get Usage """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | '--cloud_account_group_name', 11 | type=str, 12 | required=True, 13 | help='Name of the Cloud Account Group to inspect.') 14 | args = parser.parse_args() 15 | 16 | # --Initialize-- # 17 | 18 | settings = pc_utility.get_settings(args) 19 | pc_api.configure(settings) 20 | 21 | # --Main-- # 22 | 23 | print('API - Getting the current list of Cloud Account Groups ...', end='') 24 | cloud_account_groups_list = pc_api.cloud_account_group_list_read() 25 | print(' done.') 26 | print() 27 | 28 | cloud_account_group = '' 29 | for item in cloud_account_groups_list: 30 | if item['name'] == args.cloud_account_group_name: 31 | cloud_account_group = item 32 | break 33 | 34 | if not cloud_account_group: 35 | pc_utility.error_and_exit(400, "Cloud Account Group (%s) not found." % args.cloud_account_group_name) 36 | 37 | if not cloud_account_group['accounts']: 38 | pc_utility.error_and_exit(400, "No Cloud Accounts in Account Group Group (%s)." % cloud_account_group['name']) 39 | 40 | cloud_account_ids = [cloud_account['id'] for cloud_account in cloud_account_group['accounts']] 41 | body_params = { 42 | 'accountIds': cloud_account_ids, 43 | 'timeRange': {'type':'relative', 'value': {'unit': 'month', 'amount': 1}} 44 | } 45 | 46 | cloud_account_names = [cloud_account['name'] for cloud_account in cloud_account_group['accounts']] 47 | print('Accounts in Cloud Account Group (%s):' % cloud_account_group['name']) 48 | print() 49 | for cloud_account in cloud_account_group['accounts']: 50 | print(dict(sorted(cloud_account.items()))) 51 | print() 52 | 53 | print('API - Getting the Usage for Cloud Account Group (%s) ...' % cloud_account_group['name'], end='') 54 | cloud_account_usage = pc_api.resource_usage_over_time(body_params=body_params) 55 | print(' done.') 56 | print() 57 | 58 | # Example (dataPoints.counts) response from the API. 59 | 60 | """ 61 | { 62 | 'alibaba_cloud': { 63 | 'alibaba_ecs_instance': 0 64 | }, 65 | 'aws': { 66 | 'aws_lb': 0, 67 | 'rds': 0, 68 | 'nat_gateway': 0, 69 | 'redshift': 0, 70 | 'iam': 0, 71 | 'ec2_instance': 0 72 | }, 73 | 'azure': { 74 | 'azure_vm': 0, 75 | 'azure_lb': 0, 76 | 'azure_postgresql_server': 0, 77 | 'azure_sql': 0, 78 | 'azure_sql_managed_instance': 0, 79 | 'azure_iam': 0 80 | }, 81 | 'gcp': { 82 | 'cloudsql': 0, 83 | 'gcp_cloud_load_balancing_backend_service': 0, 84 | 'gce_instance': 0, 85 | 'gcp_compute_nat': 0 86 | }, 87 | 'oci': { 88 | 'oci_compute_instance': 0 89 | }, 90 | 'others': { 91 | 'container': 0, 92 | 'host': 0, 93 | 'serverless': 0, 94 | 'waas': 0 95 | } 96 | } 97 | """ 98 | 99 | # Note: 100 | # It appears the API does not return values for all cloud types when 'cloudType' is not specified as a body parameter. 101 | # We may have to loop through the cloud types and query each of them explicitly. 102 | 103 | clouds = cloud_account_usage['dataPoints'][0]['counts'].keys() 104 | 105 | for data_points in cloud_account_usage['dataPoints']: 106 | print(dict(sorted(data_points['counts'].items()))) 107 | print() 108 | -------------------------------------------------------------------------------- /scripts/pcs_user_import.py: -------------------------------------------------------------------------------- 1 | """ Import Users from a CSV file """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | 'import_file_name', 11 | type=str, 12 | help='Import (CSV) file name for the Users.') 13 | parser.add_argument( 14 | 'role_name', 15 | type=str, 16 | help='Role to assign for the imported Users.') 17 | parser.add_argument( 18 | '--access_keys_allowed', 19 | choices=['true', 'false', None], 20 | type=str, 21 | help='(Optional) - Whether Access Keys are allowed for the imported Users.') 22 | args = parser.parse_args() 23 | 24 | # --Initialize-- # 25 | 26 | pc_utility.prompt_for_verification_to_continue(args) 27 | settings = pc_utility.get_settings(args) 28 | pc_api.configure(settings) 29 | 30 | # --Main-- # 31 | 32 | print('API - Getting the current list of Users ...', end='') 33 | user_list_current = pc_api.user_list_read() 34 | print(' done.') 35 | print() 36 | 37 | user_list_to_import = pc_utility.read_csv_file_text(args.import_file_name) 38 | 39 | print('API - Getting the Roles list ...', end='') 40 | user_role_list = pc_api.user_role_list_read() 41 | print(' done.') 42 | 43 | user_role_id = None 44 | for user_role in user_role_list: 45 | if user_role['name'].lower() == args.role_name.lower(): 46 | user_role_id = user_role['id'] 47 | break 48 | if user_role_id is None: 49 | pc_utility.error_and_exit(400, 'Role not found. Please verify the Role name.') 50 | 51 | users_duplicate_current_count = 0 52 | users_duplicate_file_count = 0 53 | 54 | users_to_import = [] 55 | for user_to_import in user_list_to_import: 56 | user_duplicate = False 57 | if len(user_list_to_import) > 1: 58 | # Remove duplicates from the import file list. 59 | for user_to_import_inner in user_list_to_import: 60 | if user_to_import['email'].lower() == user_to_import_inner['email'].lower(): 61 | users_duplicate_file_count = users_duplicate_file_count + 1 62 | user_duplicate = True 63 | break 64 | if not user_duplicate: 65 | # Remove duplicates based upon the current user list. 66 | for user_current in user_list_current: 67 | if user_to_import['email'].lower() == user_current['email'].lower(): 68 | users_duplicate_current_count = users_duplicate_current_count + 1 69 | user_duplicate = True 70 | break 71 | if not user_duplicate: 72 | user = {} 73 | user['defaultRoleId'] = user_role_id 74 | user['email'] = user_to_import['email'] 75 | user['firstName'] = user_to_import['firstName'] 76 | user['lastName'] = user_to_import['lastName'] 77 | user['roleIds'] = [user_role_id] 78 | user['timeZone'] = 'America/Los_Angeles' 79 | # TODO: Consider allowing 'roleId' in the import file to override the command line. 80 | # if user_to_import['roleId'] is not None: 81 | # user['roleId'] = user_to_import['roleId'] 82 | if args.access_keys_allowed is not None: 83 | user['accessKeysAllowed'] = args.access_keys_allowed 84 | # TODO: Consider allowing 'accessKeysAllowed' in the import file to override the command line. 85 | # if user_to_import['lastName'] is not None: 86 | # user['accessKeysAllowed'] = user_to_import['accessKeysAllowed'] 87 | users_to_import.append(user) 88 | 89 | print('Users to add: %s' % len(users_to_import)) 90 | print('Users skipped (duplicates in Prisma Cloud): %s' % users_duplicate_current_count) 91 | print('Users skipped (duplicates in Import File): %s' % users_duplicate_file_count) 92 | 93 | print('API - Creating Users ...') 94 | for user_to_import in users_to_import: 95 | print('Adding User: %s' % user_to_import['email']) 96 | pc_api.user_create(user_to_import) 97 | print('Done.') 98 | -------------------------------------------------------------------------------- /scripts/pcs_user_update.py: -------------------------------------------------------------------------------- 1 | """ Update a User """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | 'user_email', 11 | type=str, 12 | help='Email address of the User to update.') 13 | parser.add_argument( 14 | '-fn', 15 | '--firstname', 16 | type=str, 17 | help='(Optional) - New First Name for the specified User.') 18 | parser.add_argument( 19 | '-ln', 20 | '--lastname', 21 | type=str, 22 | help='(Optional) - New Last Name for the specified User.') 23 | parser.add_argument( 24 | '-rn', 25 | '--role_name', 26 | type=str, 27 | help='(Optional) - New Role to assign for the specified User.') 28 | parser.add_argument( 29 | '--access_keys_allowed', 30 | choices=['true', 'false', None], 31 | type=str, 32 | help='(Optional) - Whether Access Keys are allowed for the specified User.') 33 | args = parser.parse_args() 34 | 35 | # --Initialize-- # 36 | 37 | pc_utility.prompt_for_verification_to_continue(args) 38 | settings = pc_utility.get_settings(args) 39 | pc_api.configure(settings) 40 | 41 | # --Main-- # 42 | 43 | print('API - Getting the User ...', end='') 44 | user = pc_api.user_read(args.user_email.lower()) 45 | print(' done.') 46 | print() 47 | 48 | update_needed = False 49 | 50 | if args.role_name is not None: 51 | print('API - Getting the Roles list ...', end='') 52 | user_role_list = pc_api.user_role_list_read() 53 | print(' done.') 54 | print() 55 | user_role_id = None 56 | for user_role in user_role_list: 57 | if user_role['name'].lower() == args.role_name.lower(): 58 | user_role_id = user_role['id'] 59 | update_needed = True 60 | break 61 | if user_role_id is None: 62 | pc_utility.error_and_exit(400, 'Role not found. Please verify the Role name.') 63 | user['roleId'] = user_role_id 64 | 65 | if args.firstname is not None: 66 | update_needed = True 67 | user['firstName'] = args.firstname 68 | 69 | if args.lastname is not None: 70 | update_needed = True 71 | user['lastName'] = args.lastname 72 | 73 | if args.access_keys_allowed is not None: 74 | update_needed = True 75 | user['accessKeysAllowed'] = args.access_keys_allowed 76 | 77 | if update_needed: 78 | print('API - Updating the User ...', end='') 79 | pc_api.user_update(user) 80 | print(' done.') 81 | print() 82 | else: 83 | print('No update required: current User attributes match new attributes.') 84 | -------------------------------------------------------------------------------- /scripts/pcs_vuln_container_locations.py: -------------------------------------------------------------------------------- 1 | """ Get a list of vulnerable containers and their clusters """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | 6 | # --Configuration-- # 7 | 8 | parser = pc_utility.get_arg_parser() 9 | parser.add_argument( 10 | '--cve', 11 | type=str, 12 | required=True, 13 | help='(Required) - ID of the CVE.') 14 | parser.add_argument( 15 | '--image_id', 16 | type=str, 17 | help='(Optional) - ID of the Image (sha256:...).') 18 | parser.add_argument( 19 | '--mode', 20 | type=str, 21 | choices=['registry', 'deployed', 'all'], 22 | default='all', 23 | help='(Optional) - Report on Registry, Deployed, or all Images.') 24 | args = parser.parse_args() 25 | 26 | # --Helpers-- # 27 | 28 | # --Initialize-- # 29 | 30 | settings = pc_utility.get_settings(args) 31 | pc_api.configure(settings) 32 | pc_api.validate_api_compute() 33 | 34 | # --Main-- # 35 | 36 | print('Testing Compute API Access ...', end='') 37 | intelligence = pc_api.statuses_intelligence() 38 | print(' done.') 39 | print() 40 | 41 | print('Searching for CVE: (%s) Limiting Search to Image ID: (%s)' % (args.cve, args.image_id)) 42 | print() 43 | 44 | # Monitor > Vulnerabilities/Compliance > Images > Registries 45 | if args.mode in ['registry', 'all']: 46 | registry_images = pc_api.registry_list_read(args.image_id) 47 | print('Getting Registry Images ...', end='') 48 | print(' done.') 49 | print('Found %s Registry Images' % len(registry_images)) 50 | print() 51 | for image in registry_images: 52 | image_id = image['_id'] 53 | vulnerabilities = image['vulnerabilities'] 54 | if not vulnerabilities: 55 | # print('No vulnerabilities for Image ID: %s' % image_id) 56 | continue 57 | vulnerable = False 58 | for vulnerability in vulnerabilities: 59 | if args.cve and vulnerability['cve'] == args.cve: 60 | vulnerable = True 61 | # print('Image ID: %s is vulnerable to CVE: %s' % (image_id, args.cve)) 62 | break 63 | if not vulnerable: 64 | # print('Excluding Image ID: %s is not vulnerable to CVE: %s' % (image_id, args.cve)) 65 | continue 66 | print('Locations for vulnerable Registry Image ID: %s ' % image_id) 67 | print('\tRegistry: %s' % image['repoTag']['registry']) 68 | print('\tRepo: %s' % image['repoTag']['repo']) 69 | print('\tTag: %s' % image['repoTag']['tag']) 70 | print() 71 | print() 72 | 73 | # Monitor > Vulnerabilities/Compliance > Images > Deployed 74 | if args.mode in ['deployed', 'all']: 75 | print('Getting Deployed Images ...', end='') 76 | deployed_images = pc_api.images_list_read(image_id=args.image_id, query_params={'filterBaseImage': 'true'}) 77 | print(' done.') 78 | print('Found %s Deployed Images' % len(deployed_images)) 79 | print() 80 | for image in deployed_images: 81 | image_id = image['_id'] 82 | vulnerabilities = image['vulnerabilities'] 83 | if not vulnerabilities: 84 | # print('No vulnerabilities for Image ID: %s' % image_id) 85 | continue 86 | vulnerable = False 87 | for vulnerability in vulnerabilities: 88 | if args.cve and vulnerability['cve'] == args.cve: 89 | vulnerable = True 90 | # print('Image ID: %s is vulnerable to CVE: %s' % (image_id, args.cve)) 91 | break 92 | if not vulnerable: 93 | # print('Excluding Image ID: %s is not vulnerable to CVE: %s' % (image_id, args.cve)) 94 | continue 95 | print('Locations for vulnerable Deployed Image ID: %s ' % image_id) 96 | containers = pc_api.containers_list_read(image_id=image_id) 97 | if not containers: 98 | print('\tNo containers found for this image') 99 | continue 100 | for container in containers: 101 | print('\tImage Name: %s' % container['info']['imageName']) 102 | if 'cluster' in container['info']: 103 | print('\tCluster: %s' % container['info']['cluster']) 104 | else: 105 | print('\tHostname: %s' % container['hostname']) 106 | print() 107 | -------------------------------------------------------------------------------- /scripts/pcs_week_alert_trend.py: -------------------------------------------------------------------------------- 1 | """ Get Resources """ 2 | 3 | # pylint: disable=import-error 4 | from prismacloud.api import pc_api, pc_utility 5 | from tabulate import tabulate 6 | 7 | import pandas as pd 8 | import time 9 | import datetime 10 | import string 11 | import random 12 | import os 13 | 14 | # --Configuration-- # 15 | 16 | parser = pc_utility.get_arg_parser() 17 | parser.add_argument( 18 | 'week', 19 | type=int, 20 | help="number of week before today") 21 | args = parser.parse_args() 22 | 23 | # --Initialize-- # 24 | 25 | # pc_utility.prompt_for_verification_to_continue(args) 26 | settings = pc_utility.get_settings(args) 27 | pc_api.configure(settings) 28 | 29 | dt = datetime.datetime(year=2022, month=1, day=1) 30 | data = ['critical', 'high', 'medium', 'low', 'information'] 31 | df_trend = pd.DataFrame(data, columns = ['Policy Severity']) 32 | start_ts = time.mktime(dt.timetuple())*1000 33 | 34 | # initializing size of string 35 | N = 5 36 | 37 | # using random.choices() 38 | # generating random strings 39 | res = ''.join(random.choices(string.ascii_uppercase + string.digits, k=N)) 40 | 41 | for x in range(args.week): 42 | end_ts = time.mktime((datetime.datetime.today() - datetime.timedelta(weeks = x)).timetuple())*1000 43 | print('API - Gernerate new CSV Report ...', end='') 44 | body_params = { 45 | "detailed": True, 46 | "fields":[ 47 | "alert.id", 48 | "alert.status", 49 | "alert.time", 50 | "cloud.account", 51 | "cloud.accountId", 52 | "cloud.region", 53 | "resource.id", 54 | "resource.name", 55 | "policy.name", 56 | "policy.type", 57 | "policy.severity" 58 | ], 59 | "filters":[ 60 | {"name":"policy.severity", "operator":"=", "value": "high"}, 61 | {"name":"policy.severity", "operator":"=", "value": "critical"}, 62 | {"name":"policy.severity", "operator":"=", "value": "medium"}, 63 | {"name":"policy.severity", "operator":"=", "value": "low"}, 64 | {"name":"alert.status","operator":"=", "value": "open"} 65 | ], 66 | "groupBy": [ 67 | "cloud.account" 68 | ], 69 | "limit": 2000, 70 | "offset": 0, 71 | "sortBy": [ 72 | "cloud.account" 73 | ], 74 | "timeRange": { 75 | "type": "absolute", 76 | "value": { 77 | "startTime": start_ts, 78 | "endTime": end_ts 79 | } 80 | } 81 | } 82 | 83 | print() 84 | print('Creating the Alert Report...', end='') 85 | print() 86 | alert_report = pc_api.alert_csv_create(body_params) 87 | print('Report Created with Report ID: %s' % alert_report['id']) 88 | report_time = time.strftime("%Y%m%d") 89 | report_filename = "./customer-report-" + report_time + "-" + res + "-" + str(x) + ".csv" 90 | column_name = str(x) + ' Week ago' 91 | print() 92 | 93 | report_ready = False 94 | report_dir = '.' 95 | 96 | while(not report_ready): 97 | alert_report_update = pc_api.alert_csv_status(alert_report['id']) 98 | # print('Getting the Alert Report Status...', alert_report_update['status']) 99 | time.sleep(2.5) 100 | if (alert_report_update['status'] == 'READY_TO_DOWNLOAD'): 101 | csv_report = pc_api.alert_csv_download(alert_report['id']) 102 | # Write Download Report File to Current Report Directory 103 | file = open(report_filename, "w") 104 | file.write(csv_report) 105 | file.close() 106 | # print("Alert Report Downloaded...") 107 | break 108 | 109 | df = pd.read_csv(report_filename, usecols=['Policy Severity']) 110 | df_severity = df.groupby(['Policy Severity'])['Policy Severity'].count().to_frame() 111 | df_severity.columns = [column_name] 112 | df_severity = df_severity.reset_index() 113 | # df_trend = df_trend.merge(df_severity,left_on='Policy Severity',right_on='Policy Severity') 114 | df_trend = df_trend.merge(df_severity, on='Policy Severity', how='left') 115 | df_trend[column_name].fillna(0, inplace=True) 116 | os.remove(report_filename) 117 | 118 | df_trend = df_trend.set_index('Policy Severity').transpose() 119 | print(tabulate(df_trend, headers='keys', tablefmt='psql')) -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | packaging 2 | python-dateutil 3 | pyopenssl 4 | -------------------------------------------------------------------------------- /scripts/templates/prisma_cloud_account_import_azure_template.csv: -------------------------------------------------------------------------------- 1 | accountId,clientId,enabled,groupIds,key,monitorFlowLogs,name,tenantId,servicePrincipalId -------------------------------------------------------------------------------- /scripts/templates/prisma_cloud_user_import_template.csv: -------------------------------------------------------------------------------- 1 | email,firstName,lastName 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import os 3 | import setuptools 4 | 5 | with open('README.md', 'r') as fh: 6 | long_description = fh.read() 7 | 8 | spec = importlib.util.spec_from_file_location( 9 | 'prismacloud.api.version', os.path.join('prismacloud', 'api', 'version.py') 10 | ) 11 | 12 | mod = importlib.util.module_from_spec(spec) 13 | spec.loader.exec_module(mod) 14 | version = mod.version 15 | 16 | setuptools.setup( 17 | name='prismacloud-api', 18 | version=version, 19 | author='Tom Kishel', 20 | author_email='tkishel@paloaltonetworks.com', 21 | description='Prisma Cloud API SDK for Python', 22 | keywords="prisma cloud api", 23 | long_description=long_description, 24 | long_description_content_type='text/markdown', 25 | url='https://github.com/PaloAltoNetworks/prismacloud-api-python', 26 | packages=setuptools.find_namespace_packages(exclude=['scripts']), 27 | classifiers=[ 28 | 'Programming Language :: Python :: 3', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Operating System :: OS Independent', 31 | 'Topic :: Utilities' 32 | ], 33 | install_requires=[ 34 | 'requests', 35 | 'update_checker' 36 | ], 37 | extras_require={ 38 | 'test': ['coverage==7.6.10', 'responses==0.25.3'] 39 | }, 40 | python_requires='>=3.6' 41 | ) 42 | -------------------------------------------------------------------------------- /tests/test_unit.py: -------------------------------------------------------------------------------- 1 | """ Unit Tests """ 2 | 3 | import unittest 4 | 5 | from unittest import mock 6 | 7 | # pylint: disable=import-error 8 | from prismacloud.api import pc_api 9 | from tests.data import META_INFO, SETTINGS, USER_PROFILE 10 | 11 | 12 | class TestPrismaCloudAPI(unittest.TestCase): 13 | """ Unit Tests with Mocking """ 14 | 15 | # Decorator 16 | @mock.patch('prismacloud.api.cspm.EndpointsPrismaCloudAPIMixin.meta_info') 17 | def test_pc_api_configure(self, meta_info): 18 | meta_info.return_value = META_INFO 19 | pc_api.configure(SETTINGS) 20 | self.assertEqual(pc_api.api_compute, 'example.prismacloud.io') 21 | 22 | # With 23 | def test_pc_api_current_user(self): 24 | with mock.patch('prismacloud.api.PrismaCloudAPI.execute') as pc_api_execute: 25 | pc_api_execute.return_value = USER_PROFILE 26 | result = pc_api.current_user() 27 | self.assertEqual(result['displayName'], 'Example User') 28 | --------------------------------------------------------------------------------