├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SUPPORT.md ├── examples.py ├── min_requirements.txt ├── pyproject.toml ├── requirements.txt ├── run_tests.py ├── src └── pcpi │ ├── __init__.py │ ├── _cna_session.py │ ├── _cspm_session.py │ ├── _onprem_cwp_session.py │ ├── _saas_cwp_session.py │ ├── _session_base.py │ ├── _session_types.py │ ├── onprem_session_manager.py │ ├── saas_session_manager.py │ └── session_loader.py └── ssl_gen.py /.gitignore: -------------------------------------------------------------------------------- 1 | #Custom 2 | *.yml 3 | *.yaml 4 | 5 | export_gp_pem.py 6 | 7 | *.json 8 | 9 | test_* 10 | test.* 11 | 12 | globalprotect* 13 | *cert* 14 | 15 | temp/* 16 | 17 | # VSCode 18 | .vscode/* 19 | !.vscode/settings.json 20 | !.vscode/tasks.json 21 | !.vscode/launch.json 22 | !.vscode/extensions.json 23 | *.code-workspace 24 | # Local History for Visual Studio Code 25 | .history/ 26 | 27 | # Common credential files 28 | **/tenant_credentials.yml 29 | tenant_credentials.yml 30 | **/tenant_credentials.json 31 | tenant_credentials.json 32 | **/credentials.json 33 | **/client_secrets.json 34 | **/client_secret.json 35 | *creds* 36 | *.dat 37 | *password* 38 | *.httr-oauth* 39 | 40 | # Private Node Modules 41 | node_modules/ 42 | creds.js 43 | 44 | # Private Files 45 | *.json 46 | *.csv 47 | *.csv.gz 48 | *.tsv 49 | *.tsv.gz 50 | *.xlsx 51 | 52 | 53 | # Mac/OSX 54 | .DS_Store 55 | 56 | 57 | # Byte-compiled / optimized / DLL files 58 | __pycache__/ 59 | *.py[cod] 60 | *$py.class 61 | 62 | # C extensions 63 | *.so 64 | 65 | # Distribution / packaging 66 | .Python 67 | build/ 68 | develop-eggs/ 69 | dist/ 70 | downloads/ 71 | eggs/ 72 | .eggs/ 73 | lib/ 74 | lib64/ 75 | parts/ 76 | sdist/ 77 | var/ 78 | wheels/ 79 | share/python-wheels/ 80 | *.egg-info/ 81 | .installed.cfg 82 | *.egg 83 | MANIFEST 84 | 85 | # PyInstaller 86 | # Usually these files are written by a python script from a template 87 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 88 | *.manifest 89 | *.spec 90 | 91 | # Installer logs 92 | pip-log.txt 93 | pip-delete-this-directory.txt 94 | 95 | # Unit test / coverage reports 96 | htmlcov/ 97 | .tox/ 98 | .nox/ 99 | .coverage 100 | .coverage.* 101 | .cache 102 | nosetests.xml 103 | coverage.xml 104 | *.cover 105 | *.py,cover 106 | .hypothesis/ 107 | .pytest_cache/ 108 | cover/ 109 | 110 | # Byte-compiled / optimized / DLL files 111 | __pycache__/ 112 | *.py[cod] 113 | *$py.class 114 | 115 | # C extensions 116 | *.so 117 | 118 | # Distribution / packaging 119 | .Python 120 | build/ 121 | develop-eggs/ 122 | dist/ 123 | downloads/ 124 | eggs/ 125 | .eggs/ 126 | lib/ 127 | lib64/ 128 | parts/ 129 | sdist/ 130 | var/ 131 | wheels/ 132 | share/python-wheels/ 133 | *.egg-info/ 134 | .installed.cfg 135 | *.egg 136 | MANIFEST 137 | 138 | # PyInstaller 139 | # Usually these files are written by a python script from a template 140 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 141 | *.manifest 142 | *.spec 143 | 144 | # Installer logs 145 | pip-log.txt 146 | pip-delete-this-directory.txt 147 | 148 | # Unit test / coverage reports 149 | htmlcov/ 150 | .tox/ 151 | .nox/ 152 | .coverage 153 | .coverage.* 154 | .cache 155 | nosetests.xml 156 | coverage.xml 157 | *.cover 158 | *.py,cover 159 | .hypothesis/ 160 | .pytest_cache/ 161 | cover/ 162 | 163 | # Translations 164 | *.mo 165 | *.pot 166 | 167 | # Django stuff: 168 | *.log 169 | local_settings.py 170 | db.sqlite3 171 | db.sqlite3-journal 172 | 173 | # Flask stuff: 174 | instance/ 175 | .webassets-cache 176 | 177 | # Scrapy stuff: 178 | .scrapy 179 | 180 | # Sphinx documentation 181 | docs/_build/ 182 | 183 | # PyBuilder 184 | .pybuilder/ 185 | target/ 186 | 187 | # Jupyter Notebook 188 | .ipynb_checkpoints 189 | 190 | # IPython 191 | profile_default/ 192 | ipython_config.py 193 | 194 | # pyenv 195 | # For a library or package, you might want to ignore these files since the code is 196 | # intended to run in multiple environments; otherwise, check them in: 197 | # .python-version 198 | 199 | # pipenv 200 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 201 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 202 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 203 | # install all needed dependencies. 204 | #Pipfile.lock 205 | 206 | # poetry 207 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 208 | # This is especially recommended for binary packages to ensure reproducibility, and is more 209 | # commonly ignored for libraries. 210 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 211 | #poetry.lock 212 | 213 | # pdm 214 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 215 | #pdm.lock 216 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 217 | # in version control. 218 | # https://pdm.fming.dev/#use-with-ide 219 | .pdm.toml 220 | 221 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 222 | __pypackages__/ 223 | 224 | # Celery stuff 225 | celerybeat-schedule 226 | celerybeat.pid 227 | 228 | # SageMath parsed files 229 | *.sage.py 230 | 231 | # Environments 232 | .env 233 | .venv 234 | env/ 235 | venv/ 236 | ENV/ 237 | env.bak/ 238 | venv.bak/ 239 | 240 | # Spyder project settings 241 | .spyderproject 242 | .spyproject 243 | 244 | # Rope project settings 245 | .ropeproject 246 | 247 | # mkdocs documentation 248 | /site 249 | 250 | # mypy 251 | .mypy_cache/ 252 | .dmypy.json 253 | dmypy.json 254 | 255 | # Pyre type checker 256 | .pyre/ 257 | 258 | # pytype static type analyzer 259 | .pytype/ 260 | 261 | # Cython debug symbols 262 | cython_debug/ 263 | 264 | # PyCharm 265 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 266 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 267 | # and can be added to the global gitignore or merged into this file. For a more nuclear 268 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 269 | #.idea/ 270 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | It's people like you that make security open source such a force in preventing 6 | successful cyber-attacks. Following these guidelines helps keep the project 7 | maintainable, easy to contribute to, and more secure. Thank you for taking the 8 | time to follow this guide. 9 | 10 | ## Where to start 11 | 12 | There are many ways to contribute. You can fix a bug, improve the documentation, 13 | submit bug reports and feature requests, or take a first shot at a feature you 14 | need for yourself. 15 | 16 | Pull requests are necessary for all contributions of code or documentation. 17 | 18 | ## New to open source? 19 | 20 | If you're **new to open source** and not sure what a pull request is, welcome!! 21 | We're glad to have you! All of us once had a contribution to make and didn't 22 | know where to start. 23 | 24 | Even if you don't write code for your job, don't worry, the skills you learn 25 | during your first contribution to open source can be applied in so many ways, 26 | you'll wonder what you ever did before you had this knowledge. It's worth 27 | learning. 28 | 29 | [Learn how to make a pull request](https://github.com/PaloAltoNetworks/.github/blob/master/Learn-GitHub.md#learn-how-to-make-a-pull-request) 30 | 31 | ## Fixing a typo, or a one or two line fix 32 | 33 | Many fixes require little effort or review, such as: 34 | 35 | > - Spelling / grammar, typos, white space and formatting changes 36 | > - Comment clean up 37 | > - Change logging messages or debugging output 38 | 39 | These small changes can be made directly in GitHub if you like. 40 | 41 | Click the pencil icon in GitHub above the file to edit the file directly in 42 | GitHub. This will automatically create a fork and pull request with the change. 43 | See: 44 | [Make a small change with a Pull Request](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github/) 45 | 46 | ## Bug fixes and features 47 | 48 | For something that is bigger than a one or two line fix, go through the process 49 | of making a fork and pull request yourself: 50 | 51 | > 1. Create your own fork of the code 52 | > 2. Clone the fork locally 53 | > 3. Make the changes in your local clone 54 | > 4. Push the changes from local to your fork 55 | > 5. Create a pull request to pull the changes from your fork back into the 56 | > upstream repository 57 | 58 | Please use clear commit messages so we can understand what each commit does. 59 | We'll review every PR and might offer feedback or request changes before 60 | merging. 61 | -------------------------------------------------------------------------------- /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. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prisma Cloud Python Integration - PCPI 2 | 3 | ## Python3 toolkit for Prisma Cloud APIs 4 | 5 | # Disclaimer 6 | 7 | This tool is supported under "best effort" policies. Please see SUPPORT.md for details. 8 | 9 | # Installation 10 | This tool requires Python3. Please visit https://www.python.org/downloads/ for Python3 installation guides. 11 | 12 | Depending on your environment, you may have to install the pcpi library in a different way. 13 | 14 | **This tool only needs to be installed in ONE of these ways, do not run all of these commands.** 15 | 16 | Run one of the following commands in a terminal/command prompt: 17 | 18 | Mac/Linux 19 | - ```pip3 install pcpi``` 20 | - ```python3 -m pip install pcpi``` 21 | - ```pip install pcpi``` 22 | 23 | Windows 24 | - ```py -m pip install pcpi``` 25 | - ```python -m pip install pcpi``` 26 | - ```pip install pcpi``` 27 | - ```pip3 install pcpi``` 28 | - ```python3 -m pip install pcpi``` 29 | 30 | To update an existing installation, ad the '--upgrade' flag to the pip command. EX: 31 | - ```pip3 install --upgrade pcpi``` 32 | 33 | # Quick Start 34 | 35 | 1) Create a file with a text editor of your choice (I recommend VS Code). Name this file **script.py** 36 | 2) Open the file and add these lines: 37 | ```python 38 | from pcpi import session_loader 39 | #load_config will create a config file if one does not exist. 40 | #Default path is ~./prismacloud/credentails.json. You will be prompted for credentials. 41 | session_managers = session_loader.load_config() 42 | #load_config returns a list of session_manager objects. If only one is needed, index the list at position 0 43 | # to get the first and only session_manager. 44 | #load_config() will create a credential file for you if the default path or the specified path does not exist. 45 | #load_config() accepts credentials for SaaS and Self hosted Prisma Cloud and smartly returns either a SaaS session manager or 46 | # an On-Prem session manager. 47 | session_man = session_managers[0] 48 | #If you supplied credentials for Prisma Cloud SaaS, you can create a CSPM Session Or a CWP Session. 49 | #create_cspm_session(), create_cwp_session() 50 | #If you supplied credentials for Prisma Cloud Self Hosted/On-prem, you will only be able to create CWP sessions. 51 | #create_cwp_session() 52 | cspm_session = session_man.create_cspm_session() 53 | 54 | res = cspm_session.request('GET', '/cloud') 55 | 56 | print(res.json()) 57 | ``` 58 | 3) Run the script by opening a terminal/command prompt, navigating to your directory/folder then using the Python command and specifying the name of your Python script. You may have to use a different name for the Python program depending on your environment. 59 | 60 | Mac/Linux 61 | ``` 62 | python3 script.py 63 | ``` 64 | Windows 65 | ``` 66 | py script.py 67 | ``` 68 | 4) When running this script for the first time, the pcpi library will prompt you for your Prisma Cloud credentials. Follow the prompts and if all goes well, you should see some ```SUCCESS``` messages being logged to your terminal. If you see ```ERROR```s, then you may have to disable any VPNs you are connected too and you need to ensure you have valid Prisma Cloud credentials. 69 | 70 | 5) This example script calls the ```/cloud``` endpoint. After this script runs, you should see data from your tenant about cloud accounts in JSON format. 71 | 72 | # Scripting Setup Guide 73 | 1) Import pcpi into your python project 74 | 75 | 2) Create a session manager directly or by using a session loader 76 | 2a) Session Loader arguments are all optional. 77 | 2b) Specify a file path, if file_path variable is excluded, the default credential path will be used. 78 | 2c) If the credentials file does not exist, the script will set it up for you at the specified or default file path 79 | 80 | 3) Create a CSPM or CWP session by using the session manager. 81 | 82 | 4) Use the created session object to make API requests 83 | 84 | 5) Run the script 85 | ``` 86 | python3 .py 87 | ``` 88 | 89 | ```python 90 | import pcpi 91 | 92 | session_loader = pcpi.session_loader 93 | 94 | #-- SESSION LOADER FUNCTIONS -- 95 | session_loader.load_config() 96 | session_loader.load_config_env() 97 | session_loader.load_config_user() 98 | 99 | # Each session loader accepts credentials for SaaS and Self hosted Prisma Cloud and smartly returns either a SaaS session manager or 100 | # an On-Prem session manager. 101 | 102 | #-- SESSION LOADER ARGUMENTS -- 103 | #--Session loader arguments are all optional-- 104 | 105 | # load_config() 106 | #file_path -- Path to credentials file. File will be created at the path specified. You will be prompted for credentials. 107 | # Exclude argument to use default path. 108 | #num_tenants -- Number of tenant configurations that will be included in the config JSON file. 109 | # Only applies when a config file is being created. 110 | #min_tenants -- Minimum number of tenants to be included in the config file. 111 | # User setting up config file will be prompted to continue after minimum 112 | # number of tenants have been reached 113 | #You can not use num_tenants and min_tenants at the same time. Only include one or the other. 114 | #logger -- exclude to use default pylogger config or create a py logger object and pass that in for the logger value. 115 | # Can also use a loguru logger object 116 | session_loader.load_config(file_path='', num_tenants=-1, min_tenants=-1, logger=logger) # returns session manager list 117 | 118 | # load_config_user() 119 | #num_tenants -- Number of tenant configurations that will be included in the config JSON file. 120 | # Only applies when a config file is being created. 121 | #min_tenants -- Minimum number of tenants to be included in the config file. 122 | # User setting up config file will be prompted to continue after minimum 123 | # number of tenants have been reached 124 | #You can not use num_tenants and min_tenants at the same time. Only include one or the other. 125 | #logger -- exclude to use default pylogger config or create a py logger object and pass that in for the logger value. 126 | # Can also use a loguru logger object 127 | load_config_user(num_tenants=-1, min_tenants=-1, logger=py_logger) # returns session manager list 128 | 129 | # load_config_env() 130 | #prisma_name='PRISMA_PCPI_NAME' -- overwrites the default env var name for the 'name' Prisma Credential 131 | #identifier_name='PRISMA_PCPI_ID' -- overwrites the default env var name for the 'ID' Prisma Credential 132 | #secret_name='PRISMA_PCPI_SECRET' -- overwrites the default env var name for the 'secret' Prisma Credential 133 | #api_url_name='PRISMA_PCPI_URL' -- overwrites the default env var name for the 'api_url' Prisma Credential 134 | #verify_name='PRISMA_PCPI_VERIFY', -- overwrites the default env var name for the 'verify' Prisma Credential 135 | #logger -- exclude to use default pylogger config or create a py logger object and pass that in for the logger value. 136 | # Can also use a loguru logger object 137 | load_config_env(prisma_name='PRISMA_PCPI_NAME', identifier_name='PRISMA_PCPI_ID', secret_name='PRISMA_PCPI_SECRET', 138 | api_url_name='PRISMA_PCPI_URL', verify_name='PRISMA_PCPI_VERIFY', project_flag_name='PRISMA_PCPI_PROJECT_FLAG', logger=py_logger) # returns single session manager 139 | 140 | #-- SESSION MANAGERS -- 141 | #Session loader returns a list of session managers 142 | session_managers = session_loader.load_config() 143 | #load_config() returns either a SaaS session manager or On-Prem session manager based on credentials used, namely the api url. 144 | my_session_manager = session_managers[0] 145 | 146 | #Session managers return session objects 147 | #-- SESSION MANAGER FUNCTIONS -- 148 | cspm_session = my_session_manager.create_cspm_session() 149 | cwp_session = my_session_manager.create_cwp_session() 150 | 151 | #-- SESSION FUNCTION -- 152 | #Session objects are used to make API requests 153 | cspm_session.request('GET', '', json={}, params={}, verify=True) 154 | cwp_session.request( 'POST', '', json={}, params={}, verify=True) 155 | 156 | #-- SESSION REQUEST ARGUMENTS -- 157 | #method - position 1 - required - the http verb used in the request 158 | #endpoint_url - position 2 - required - the path of the API endpoint 159 | #json - optional - the payload for the API call - converts python dictionaries into json automatically 160 | #data - optional - payload alterative that does not convert python dictionaries into json 161 | #params - optional - query string parameters can be included as a python dictionary 162 | #verify - optional - True, False, or path to certificate file. Disables or overwrites HTTPS Certificate verification. 163 | ``` 164 | 165 | # Prisma Cloud Python Integration Documentation and Examples 166 | 167 | ## RQL Search Pagination Helper Functions 168 | ```python 169 | config_search_request(self, json: dict, verify=None, redlock_ignore: list=None, status_ignore: list=[]): 170 | 171 | config_search_request_function(self, json, function, verify=None, redlock_ignore: list=None, status_ignore: list=[]): 172 | 173 | #EX 174 | # 175 | # 176 | # payload = { 177 | # "query": "config from cloud.resource where resource.status = Active", 178 | # "timeRange": { 179 | # "relativeTimeType": "BACKWARD", 180 | # "type": "relative", 181 | # "value": { 182 | # "amount": 24, 183 | # "unit": "hour" 184 | # } 185 | # }, 186 | # "limit": 2000, 187 | # "withResourceJson": False 188 | # } 189 | # 190 | # OPTION 1 - PCPI will collect each page of data and return all data as one object 191 | #data = cspm_session.config_search_request(payload) 192 | # 193 | # with open(‘out.json’, ‘w’) as outfile: 194 | # json.dump(data, outfile) 195 | # 196 | # 197 | # # OPTION 2 - Handle what is done with each page yourself 198 | # def dump_data(details, data, counter, total_rows): 199 | # if counter == 0: 200 | # with open(f'detailsOut.json', 'w') as outfile: 201 | # json.dump(details, outfile) 202 | # 203 | # with open(f'temp/dataOut{counter}.json', 'w') as outfile: 204 | # json.dump(data, outfile) 205 | # 206 | # total_rows = cspm_session.config_search_request_function(payload, dump_data) 207 | 208 | # print(f'Got {total_rows} Total Rows') 209 | 210 | ``` 211 | 212 | ## Session Loaders 213 | 214 | The session loader is a module that has functions for loading Prisma Cloud credentials from a file, environment variables, or from the user into your program. This ensures you get your script up and running as quickly as possible. 215 | 216 | The session loader module has functions that return a session manager or session manager list that is used to create a CSPM, CWP, or an On-Prem CWP session. The session manager is used to create the Prisma Cloud session object that you will be using to make API calls. 217 | 218 | **Examples** 219 | 220 | SaaS Examples 221 | ```python 222 | from loguru import logger 223 | loguru_logger = logger 224 | 225 | from pcpi import session_loader 226 | 227 | #--DEFAULT OPTION-- 228 | #Defaults to a file named '~./prismacloud/credentials.json' 229 | session_manager_default = session_loader.load_config(logger=loguru_logger) 230 | 231 | #--CUSTOM OPTION-- 232 | #File must be a json file 233 | #If a file that does not exist is specified, the script will build one. Only creates end file, not directory structure. That setup is up to you. 234 | #If using default file path, load_config() will create the ~/.prismacloud/credentials.json file structure. 235 | session_managers = session_loader.load_config(file_path='~/secrets/my_secrets.json') 236 | session_manager = session_managers[0] 237 | 238 | cspm_session = session_manager.create_cspm_session() 239 | cwp_session = session_manager.create_cwp_session() 240 | 241 | response = cspm_session.request('GET', '/cloud') 242 | 243 | print(response.json()) 244 | ``` 245 | 246 | For SaaS CSPM and CWP and Self Hosted/On-Prem CWP 247 | 248 | ```python 249 | from pcpi import session_loader 250 | 251 | session_manager = session_loader.load_config_env() #only returns a single session_manager object 252 | ``` 253 | 254 | ```python 255 | from pcpi import session_loader 256 | 257 | session_managers = session_loader.load_config_user() 258 | session_manager = session_managers[0] 259 | ``` 260 | 261 | ## Session Managers 262 | 263 | If you want to take control on how credentials are stored/loaded into your scripts, use the session managers directly and skip the session loader module. Session manager objects directly accept Prisma Cloud credentials into the object constructor. 264 | 265 | This still creates the same Prisma Cloud session object that you use for API requests. 266 | 267 | The logger object is optional. You can use PyLogger or Loguru with PCPI 268 | 269 | **EXAMPLES** 270 | 271 | ```python 272 | from pcpi import saas_session_manager 273 | import logging 274 | 275 | py_logger = logging.getLogger() 276 | py_logger.setLevel(10) 277 | 278 | session_manager = saas_session_manager.SaaSSessionManager( 279 | tenant_name='My PC Tenant', 280 | a_key='xxxxxxxxxxxxxxxxxxxxxxxxxx', 281 | s_key='xxxxxxxxxxxxxxxxxxxxxxxxxx', 282 | api_url='https://api.prismacloud.io', 283 | logger=py_logger 284 | ) 285 | 286 | cspm_session = session_manager.create_cspm_session() 287 | cwp_session = session_manager.create_cwp_session() 288 | ``` 289 | 290 | ```python 291 | from pcpi import onprem_session_manager 292 | import logging 293 | 294 | 295 | session_manager = onprem_session_manager.CWPSessionManager( 296 | tenant_name='My PC Tenant', 297 | uname='xxxxxxxxxxxxxxxxxxxxxxxxxx', 298 | passwd='xxxxxxxxxxxxxxxxxxxxxxxxxx', 299 | api_url='https://' 300 | ) 301 | 302 | onprem_cwp_session = session_manager.create_cwp_session() 303 | ``` 304 | 305 | ## Requests 306 | 307 | The Prisma Cloud Python Integration uses the Python3 Requests library to make API calls. The Prisma Cloud Session object that this tool provides has a wrapper function for API calls using the requests function from Requests. The main difference is that this tool handles the 'headers' section of the request for you. The remaining fields can be used just like you would use the requests library [requests.request](https://requests.readthedocs.io/en/latest/api/#requests.request). 308 | 309 | **EXAMPLES** 310 | 311 | ```python 312 | from loguru import logger 313 | loguru_logger = logger 314 | 315 | from pcpi import session_loader 316 | 317 | #Default config file will be created at '~/.prismacloud/credentials.json' 318 | session_managers = session_loader.load_config(logger=loguru_logger) 319 | session_manager = session_managers[0] 320 | 321 | cspm_session = session_manager.create_cspm_session() 322 | 323 | response = cspm_session.request('GET', '/cloud') 324 | print(response.json()) 325 | 326 | payload = { 327 | "accountId":"xxxxx", 328 | "accountType":"account", 329 | "enabled":True, 330 | "externalId":"xxxxxx", 331 | "groupIds":["xxxxx"], 332 | "name":"Name", 333 | "protectionMode":"MONITOR", 334 | "roleArn":"xxxxxxx", 335 | "storageScanEnabled":False, 336 | "vulnerabilityAssessmentEnabled":False 337 | } 338 | 339 | query_string = {"skipStatusChecks":1} 340 | 341 | response = cspm_session.request('POST', '/cloud/aws', json=payload, params=query_string, verify='path_to_cert.pem') 342 | print(response.status_code) 343 | ``` 344 | 345 | ## Logging 346 | 347 | Two logging libraries are supported by PCPI. The built in Python logging library (used by default) and Loguru. 348 | Loguru is strongly recommended but it is an additional dependency that you may not want. 349 | 350 | **EXAMPLES** 351 | 352 | ```python 353 | #Minimum setup 354 | from pcpi import session_loader 355 | 356 | #Default pylogger is used if none is specified 357 | session_managers = session_loader.load_config() 358 | session_manager = session_managers[0] 359 | 360 | cspm_session = session_manager.create_cspm_session() 361 | 362 | res = cspm_session.request('GET', '/cloud') 363 | print(res.json()) 364 | 365 | #Recommended setup 366 | from pcpi import session_loader 367 | import loguru 368 | 369 | #Check out loguru docs for details on configuration options. 370 | 371 | session_managers = session_loader.load_config(logger=loguru.logger) 372 | session_manager = session_managers[0] 373 | cspm_session = session_manager.create_cspm_session() 374 | 375 | res = cspm_session.request('GET', '/cloud') 376 | print(res.json()) 377 | 378 | #Custom Setup 379 | #If you wish to change the amount of logging output seen in the terminal, or output logs to a file, 380 | # you can overwrite the default logger with a customer pylogger object or a customer loguru object. 381 | from pcpi import session_loader 382 | import logging 383 | 384 | logging.basicConfig() 385 | py_logger = logging.getLogger("pcpi") 386 | py_logger.setLevel(100) #turns off logging since no default logs have a level of over 50 387 | 388 | session_managers = session_loader.load_config(logger=py_logger) 389 | session_manager = session_managers[0] 390 | cspm_session = session_manager.create_cspm_session() 391 | 392 | res = cspm_session.request('GET', '/cloud') 393 | print(res.json()) 394 | 395 | ``` 396 | 397 | # Function Reference 398 | 399 | # Announcements 400 | 401 | ## Stable release 12/6/2022 402 | **News** 403 | JSON files are replacing yaml files for credential management. 404 | Credential files now cross compatible with [Prisma Cloud API Python](https://github.com/PaloAltoNetworks/prismacloud-api-python). 405 | - New Session Loaders that handle credential management for you 406 | - - load_config(), load_config_env(), and load_config_user() introduced to replace other session managers 407 | - - JSON files now used instead of yaml 408 | - - Default Credential path changes to ~/.prismacloud/credentials.json 409 | - - No longer requires separate credential files for SaaS and On Prem 410 | 411 | **Patch Notes** 412 | - Various bug fixes and stability improvements 413 | 414 | ## Stable release 8/11/2022 415 | **News** 416 | Current version is considered stable as known bugs have been fixed. 417 | Documentation is still a work in progress. 418 | Not all features have been implemented yet. 419 | Current Features: 420 | - SaaS CSPM, SaaS CWP, and On-prem CWP session/JWT management 421 | - - Generates JWT tokens as needed and uses them for full valid duration 422 | - - Progressive back-off algorithm to avoid API DOS protections 423 | - - Automatic retires on error codes 424 | - - Detailed logging support through Python Logging and Loguru modules 425 | - Session Loaders that handle credential management for you to jump start your script development. 426 | - - Supports loading credentials from a file, from environment variables, and directly from the user as a series of prompts. 427 | - - Will build out credential files for you, just specify desired file path 428 | - - Helpful debug messages on missing values for environment variables 429 | - Session Managers that intelligently handle SaaS and On-Prem sessions 430 | - - Reuses existing CSPM SaaS session when making SaaS CWP API calls 431 | - - Direct access to session managers to enable you to handle credentials as you see fit 432 | 433 | **Patch Notes** 434 | - On-prem support 435 | - Fixed Keyboard Interrupt exception handling 436 | - Fixed default logger bug 437 | 438 | ## Beta release 9/15/2022 439 | **Patch Notes** 440 | - Renamed module 441 | - - session -> session_loader 442 | 443 | **Known bugs:** 444 | - Default logging library is missing output. Using loguru solves this in the meantime. 445 | 446 | ## Beta release 9/14/2022 447 | **Patch Notes** 448 | - Renamed repo to match package name 449 | - Reorganized repository to be compatible with PyPI package structure 450 | - Examples file changed to support new file structure 451 | - Package published to PyPi for easy use and updates 452 | 453 | **Known bugs:** 454 | - Default logging library is missing output. Using loguru solves this in the meantime. 455 | 456 | ## Beta release 09/12/2022 457 | **Patch notes** 458 | - Modules and function name changes 459 | - - pc_session -> session 460 | - Spelling and typo fixes 461 | - Small bug fixes 462 | 463 | API Toolkit python package is scheduled for release on 09/15/2022. 464 | With this release there will be a freeze on module/function names. 465 | Until then names are subject to change. 466 | 467 | **Known bugs:** 468 | - Default logging library is missing output. Using loguru solves this in the meantime. 469 | 470 | ## Beta release 09/01/2022 471 | Self Hosted CWP is not yet supported, coming soon. 472 | 473 | No documentation yet but coming soon. Please refer to "examples.py" in the meantime. 474 | 475 | **Known bugs:** 476 | - Default logging library is missing output. Using loguru solves this in the meantime. 477 | -------------------------------------------------------------------------------- /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. 16 | -------------------------------------------------------------------------------- /examples.py: -------------------------------------------------------------------------------- 1 | #Uncomment a section to test 2 | 3 | ###INSTALLED FROM PYPI EXAMPLES 4 | #Minimum setup----------------------------------------------------------------- 5 | # from pcpi import session_loader 6 | 7 | # session_managers = session_loader.load_config() 8 | # session_manager = session_managers[0] 9 | # cspm_session = session_manager.create_cspm_session() 10 | 11 | # res = cspm_session.request('GET', '/cloud') 12 | # print(res.json()) 13 | 14 | #Recommended setup------------------------------------------------------------- 15 | # from pcpi import session_loader 16 | # import loguru 17 | 18 | # session_managers = session_loader.load_config(logger=loguru.logger) 19 | # session_manager = session_managers[0] 20 | # cspm_session = session_manager.create_cspm_session() 21 | 22 | # res = cspm_session.request('GET', '/cloud') 23 | # print(res.json()) 24 | 25 | #CSPM and CWP------------------------------------------------------------------ 26 | # from pcpi import session_loader 27 | # import loguru 28 | 29 | # session_managers = session_loader.load_config(logger=loguru.logger) 30 | # session_manager = session_managers[0] 31 | 32 | # cspm_session = session_manager.create_cspm_session() 33 | # cwp_session = session_manager.create_cwp_session() 34 | 35 | # res = cspm_session.request('GET', '/cloud') 36 | # print(res.json()) 37 | 38 | # print('--------------') 39 | 40 | # res2 = cwp_session.request('GET', 'api/v1/credentials') 41 | # print(res2.json()) 42 | 43 | #Session self healing---------------------------------------------------------- 44 | # from pcpi import session_loader 45 | # import loguru 46 | 47 | # session_managers = session_loader.load_config(logger=loguru.logger) 48 | # session_manager = session_managers[0] 49 | 50 | # cwp_session = session_manager.create_cwp_session() 51 | # cwp_session.cspm_token = 'asdasdasd' 52 | # cwp_session.headers['Authorization'] = 'Bearer ' + 'sdfsdfsdfsdf' 53 | 54 | # res = cwp_session.request('GET', 'api/v1/credentials') 55 | # print(res.json()) 56 | 57 | #Error and debugging output---------------------------------------------------------- 58 | # from pcpi import session_loader 59 | # import loguru 60 | 61 | # session_managers = session_loader.load_config(logger=loguru.logger) 62 | # session_manager = session_managers[0] 63 | 64 | # cwp_session = session_manager.create_cwp_session() 65 | # cwp_session.cspm_token = 'asdasdasd' 66 | # cwp_session.headers['Authorization'] = 'Bearer ' + 'sdfsdfsdfsdf' 67 | 68 | # res = cwp_session.request('GET', 'api/v9/credentials') 69 | 70 | 71 | ###LOCAL CLONED REPO EXAMPLES================================================== 72 | 73 | #Minimum setup----------------------------------------------------------------- 74 | # from src.pcpi import session_loader 75 | 76 | # session_managers = session_loader.load_config() 77 | # session_manager = session_managers[0] 78 | # cspm_session = session_manager.create_cspm_session() 79 | 80 | # res = cspm_session.request('GET', '/cloud') 81 | # print(res.json()) 82 | 83 | #Recommended setup------------------------------------------------------------- 84 | # from src.pcpi import session_loader 85 | # import loguru 86 | 87 | # session_managers = session_loader.load_config(logger=loguru.logger) 88 | # session_manager = session_managers[0] 89 | # cspm_session = session_manager.create_cspm_session() 90 | 91 | # res = cspm_session.request('GET', '/cloud') 92 | # print(res.json()) 93 | 94 | #CSPM and CWP------------------------------------------------------------------ 95 | # from src.pcpi import session_loader 96 | # import loguru 97 | 98 | # session_managers = session_loader.load_config(logger=loguru.logger) 99 | # session_manager = session_managers[0] 100 | 101 | # cspm_session = session_manager.create_cspm_session() 102 | # cwp_session = session_manager.create_cwp_session() 103 | 104 | # res = cspm_session.request('GET', '/cloud') 105 | # print(res.json()) 106 | 107 | # print('--------------') 108 | 109 | # res2 = cwp_session.request('GET', 'api/v1/credentials') 110 | # print(res2.json()) 111 | 112 | #Session self healing---------------------------------------------------------- 113 | # from src.pcpi import session_loader 114 | # import loguru 115 | 116 | # session_managers = session_loader.load_config(logger=loguru.logger) 117 | # session_manager = session_managers[0] 118 | 119 | # cwp_session = session_manager.create_cwp_session() 120 | # cwp_session.cspm_token = 'asdasdasd' 121 | # cwp_session.headers['Authorization'] = 'Bearer ' + 'sdfsdfsdfsdf' 122 | 123 | # res = cwp_session.request('GET', 'api/v1/credentials') 124 | # print(res.json()) 125 | 126 | # #Error and debugging output---------------------------------------------------------- 127 | # from src.pcpi import session_loader 128 | # import loguru 129 | 130 | # session_managers = session_loader.load_config(logger=loguru.logger) 131 | # session_manager = session_managers[0] 132 | 133 | # cwp_session = session_manager.create_cwp_session() 134 | # cwp_session.cspm_token = 'asdasdasd' 135 | # cwp_session.headers['Authorization'] = 'Bearer ' + 'sdfsdfsdfsdf' 136 | 137 | # res = cwp_session.request('GET', 'api/v9/credentials') 138 | 139 | 140 | #Heuristic Search-------------------------------------------------------------- 141 | # #Change RQL query as needed 142 | # query = "config from cloud.resource where resource.status = Active AND api.name = 'aws-ec2-describe-instances'" 143 | 144 | # #Payload can use absolute time range or relative time range. Examples are given of both time ranges. 145 | # payload = { 146 | # "query": query, 147 | # "timeRange": { 148 | # "relativeTimeType": "BACKWARD", 149 | # "type": "relative", 150 | # "value": { 151 | # "amount": 24, 152 | # "unit": "hour" 153 | # } 154 | # }, 155 | # } 156 | 157 | 158 | # # # Option 1 PCPI paginates automatically and returns JSON data as one chunk. 159 | # # json_data = cspm_session.config_search_request(payload) 160 | 161 | # # #Dump json to file 162 | # # with open('totalDataRETURNED.json', 'w') as outfile: 163 | # # json.dump(json_data, outfile) 164 | 165 | # #--------------------------- 166 | 167 | # # Option 2 PCPI paginates automatically but now you pass in a custom function that controls what to do with each page of data. 168 | # def dump_data(details, data, counter, total_rows): 169 | # if counter == 0: 170 | # with open(f'detailsOut.json', 'w') as outfile: 171 | # json.dump(details, outfile) 172 | 173 | # with open(f'temp/dataOut{counter}.json', 'w') as outfile: 174 | # json.dump(data, outfile) 175 | 176 | # total_rows = cspm_session.config_search_request_function(payload, dump_data) 177 | 178 | # print(f'Got {total_rows} Total Rows') 179 | -------------------------------------------------------------------------------- /min_requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2022.6.15 2 | charset-normalizer==2.1.1 3 | idna==3.3 4 | requests==2.28.1 5 | urllib3==1.26.12 -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "pcpi" 7 | version = "1.0.0" 8 | authors = [ 9 | { name="Adam Hamilton-Sutherland", email="ahamiltonsut@paloaltonetworks.com" }, 10 | ] 11 | description = "Python Package for making API calls to the Prisma Cloud API using best practices." 12 | readme = "README.md" 13 | license = { file="LICENSE" } 14 | requires-python = ">=3.7" 15 | dependencies = [ 16 | "PyYAML ~= 6.0", 17 | 'requests < 3', 18 | ] 19 | classifiers = [ 20 | "Programming Language :: Python :: 3", 21 | "License :: OSI Approved :: ISC License (ISCL)", 22 | "Operating System :: OS Independent", 23 | "Development Status :: 4 - Beta" 24 | ] 25 | 26 | [project.urls] 27 | "Homepage" = "https://github.com/PaloAltoNetworks/pc-python-integration" 28 | "Bug Tracker" = "https://github.com/PaloAltoNetworks/pc-python-integration/issues" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2022.6.15 2 | charset-normalizer==2.1.1 3 | idna==3.3 4 | requests==2.28.1 5 | urllib3==1.26.12 -------------------------------------------------------------------------------- /run_tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | from unittest import TestCase 4 | import os 5 | import json 6 | 7 | #Default Logger 8 | import logging 9 | logging.basicConfig() 10 | py_logger = logging.getLogger("pcpi") 11 | py_logger.setLevel(10) 12 | 13 | #HELPER FUNCTIONS============================================================== 14 | def load_environment(): 15 | cfg = {} 16 | with open('local.json', "r") as file: 17 | cfg = json.load(file) 18 | 19 | #Parse cfg for tenant creds and set env 20 | for index,cred in enumerate(cfg): 21 | tenant = cred['name'] 22 | uname = cred['identity'] 23 | passwd = cred['secret'] 24 | api_url = cred['url'] 25 | verify = True 26 | try: 27 | verify = cred['verify'] 28 | if verify.lower() == 'false': 29 | verify = False 30 | if verify.lower() == 'true': 31 | verify = True 32 | except: 33 | pass 34 | 35 | proxies = cred['proxies'] 36 | https_proxy = '' 37 | http_proxy = '' 38 | if proxies: 39 | http_proxy = proxies.get('http','') 40 | https_proxy = proxies.get('https','') 41 | 42 | os.environ[f'PC_TENANT_NAME{index}'] = tenant 43 | os.environ[f'PC_TENANT_API{index}'] = api_url 44 | os.environ[f'PC_TENANT_A_KEY{index}'] = uname 45 | os.environ[f'PC_TENANT_S_KEY{index}'] = passwd 46 | os.environ[f'PC_TENANT_VERIFY{index}'] = str(verify) 47 | os.environ[f'PC_HTTP_PROXY{index}'] = http_proxy 48 | os.environ[f'PC_HTTPS_PROXY{index}'] = https_proxy 49 | 50 | 51 | #UNIT TESTS==================================================================== 52 | 53 | class credentialFileTests(TestCase): 54 | def testLoadConfigBasic(self): 55 | load_environment() 56 | from src.pcpi import session_loader 57 | from src.pcpi import saas_session_manager 58 | name = os.environ['PC_TENANT_NAME0'] 59 | api_url = os.environ['PC_TENANT_API0'] 60 | a_key = os.environ['PC_TENANT_A_KEY0'] 61 | s_key = os.environ['PC_TENANT_S_KEY0'] 62 | verify = os.environ['PC_TENANT_VERIFY0'] 63 | http = os.environ['PC_HTTP_PROXY0'] 64 | https = os.environ['PC_HTTPS_PROXY0'] 65 | proxies = { 66 | 'http': http, 67 | 'https': https 68 | } 69 | 70 | result = session_loader.load_config() 71 | self.assertEqual([result[0].tenant], [saas_session_manager.SaaSSessionManager(name, a_key, s_key, api_url, verify, proxies, py_logger).tenant]) 72 | self.assertEqual([result[0].a_key], [saas_session_manager.SaaSSessionManager(name, a_key, s_key, api_url, verify, proxies, py_logger).a_key]) 73 | self.assertEqual([result[0].s_key], [saas_session_manager.SaaSSessionManager(name, a_key, s_key, api_url, verify, proxies, py_logger).s_key]) 74 | self.assertEqual([result[0].api_url], [saas_session_manager.SaaSSessionManager(name, a_key, s_key, api_url, verify, proxies, py_logger).api_url]) 75 | 76 | 77 | def testLoadConfigEnv(self): 78 | load_environment() 79 | from src.pcpi import session_loader 80 | from src.pcpi import saas_session_manager 81 | name = os.environ['PC_TENANT_NAME0'] 82 | api_url = os.environ['PC_TENANT_API0'] 83 | a_key = os.environ['PC_TENANT_A_KEY0'] 84 | s_key = os.environ['PC_TENANT_S_KEY0'] 85 | verify = os.environ['PC_TENANT_VERIFY0'] 86 | http = os.environ['PC_HTTP_PROXY0'] 87 | https = os.environ['PC_HTTPS_PROXY0'] 88 | proxies = { 89 | 'http': http, 90 | 'https': https 91 | } 92 | 93 | result = session_loader.load_config_env(prisma_name='PC_TENANT_NAME0', identifier_name='PC_TENANT_A_KEY0', secret_name='PC_TENANT_S_KEY0', api_url_name='PC_TENANT_API0', verify_name='PC_TENANT_VERIFY0', http_name='PC_HTTP_PROXY0', https_name='PC_HTTPS_PROXY0') 94 | self.assertEqual(result.tenant, saas_session_manager.SaaSSessionManager(name, a_key, s_key, api_url, verify, proxies, py_logger).tenant) 95 | self.assertEqual(result.a_key, saas_session_manager.SaaSSessionManager(name, a_key, s_key, api_url, verify, proxies, py_logger).a_key) 96 | self.assertEqual(result.s_key, saas_session_manager.SaaSSessionManager(name, a_key, s_key, api_url, verify, proxies, py_logger).s_key) 97 | self.assertEqual(result.api_url, saas_session_manager.SaaSSessionManager(name, a_key, s_key, api_url, verify, proxies, py_logger).api_url) 98 | 99 | class apiRequestTest(TestCase): 100 | def testCSPMRecovery(self): 101 | from src.pcpi import session_loader 102 | manager = session_loader.load_config('local.json')[0] 103 | cspm_session = manager.create_cspm_session() 104 | res = cspm_session.request('GET', '/cloud') 105 | cspm_session.headers = { 106 | 'content-type': 'application/json; charset=UTF-8', 107 | 'x-redlock-auth': 'asd' 108 | } 109 | res1 = cspm_session.request('GET', '/cloud') 110 | 111 | self.assertEqual(res.json(), res1.json()) 112 | 113 | def testCWPRecovery(self): 114 | from src.pcpi import session_loader 115 | manager = session_loader.load_config('local.json')[0] 116 | cwp_session = manager.create_cwp_session() 117 | res = cwp_session.request('GET', '/api/v1/users') 118 | cwp_session.headers = { 119 | 'content-type': 'application/json; charset=UTF-8', 120 | 'Authorization': 'Bearer dfsdfsd' 121 | } 122 | cwp_session.cspm_token = 'sdfsdf' 123 | res1 = cwp_session.request('GET', '/api/v1/users') 124 | 125 | self.assertEqual(res.json(), res1.json()) 126 | 127 | def testCertBypass(self): 128 | from src.pcpi import session_loader 129 | manager = session_loader.load_config('local.json')[0] 130 | cspm_session = manager.create_cspm_session() 131 | res = cspm_session.request('GET', '/cloud', verify=False) 132 | 133 | self.assertEqual(res.status_code, 200) 134 | 135 | # def testCertOverwrite(self): 136 | # from src.pcpi import session_loader 137 | # manager = session_loader.load_from_file() 138 | # cspm_session = manager.create_cspm_session() 139 | # res = cspm_session.request('GET', '/compliance', verify='globalprotect_certifi.txt') 140 | 141 | # self.assertEqual(res.status_code, 200) 142 | 143 | if __name__ == '__main__': 144 | unittest.main() -------------------------------------------------------------------------------- /src/pcpi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaloAltoNetworks/pc-python-integration/63134637bb00d0c01fc820513ce71a009c2fd5d6/src/pcpi/__init__.py -------------------------------------------------------------------------------- /src/pcpi/_cna_session.py: -------------------------------------------------------------------------------- 1 | #Installed 2 | import requests 3 | 4 | from urllib3.exceptions import InsecureRequestWarning 5 | requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) 6 | 7 | #Local 8 | from ._session_base import Session 9 | from ._cspm_session import CSPMSession 10 | 11 | #Python Library 12 | import time 13 | 14 | #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 15 | class CNASession(Session): 16 | def __init__(self, tenant_name: str, a_key: str, s_key: str, api_url: str, verify: bool, proxies: dict, logger, cspm_session={}): 17 | """ 18 | Initializes a Prisma Cloud API session for a given tenant. 19 | 20 | Keyword Arguments: 21 | tenant_name -- Name of tenant associated with session 22 | a_key -- Tenant Access Key 23 | s_key -- Tenant Secret Key 24 | api_url -- API URL Tenant is hosted on 25 | """ 26 | 27 | super().__init__(logger) 28 | 29 | self.tenant = tenant_name 30 | self.a_key = a_key 31 | self.s_key = s_key 32 | self.api_url = api_url 33 | self.verify = verify 34 | self.proxies = proxies 35 | 36 | self.token_time_stamp = 0 37 | 38 | self.logger = logger 39 | 40 | self.cspm_session = {} 41 | self.cspm_token = '' 42 | 43 | if not cspm_session: 44 | self.__get_cspm_session() 45 | else: 46 | self.cspm_session = cspm_session 47 | self.cspm_token = self.cspm_session.token 48 | 49 | self.api_url, self.prisma_id = self.__cna_metadata(self.cspm_session) 50 | 51 | self.auth_key = 'Authorization' 52 | self.auth_style = 'Bearer ' 53 | 54 | self.token = '' 55 | 56 | self.headers = { 57 | 'content-type': 'application/json; charset=UTF-8', 58 | 'X-Namespace': '/' + self.prisma_id, 59 | 'Authorization': 'Bearer ' 60 | } 61 | 62 | self._api_login_wrapper() 63 | 64 | #============================================================================== 65 | def __get_cspm_session(self): 66 | self.cspm_session = CSPMSession(self.tenant, self.a_key, self.s_key, self.api_url, self.verify, self.proxies, self.logger) 67 | self.cspm_token = self.cspm_session.token 68 | 69 | def __cspm_login(self): 70 | self.cspm_token = self.cspm_session._api_login_wrapper() 71 | 72 | def __cna_metadata(self, cspm_session): 73 | res = cspm_session.request('GET', '/cnsConfig') 74 | cna_url = res.json()['public-api'] 75 | 76 | res = cspm_session.request('GET', '/license') 77 | prisma_id = res.json()['prismaId'] 78 | 79 | return (cna_url, prisma_id) 80 | 81 | #============================================================================== 82 | def _api_login(self) -> object: 83 | ''' 84 | Calls the Prisma Cloud API to generate a x-redlock-auth JWT. 85 | 86 | Returns: 87 | x-redlock-auth JWT. 88 | ''' 89 | 90 | #Build request 91 | url = f'{self.api_url}/issue' 92 | 93 | headers = { 94 | 'content-type': 'application/json; charset=UTF-8' 95 | } 96 | 97 | payload = { 98 | "realm": "PCIdentityToken", 99 | "metadata": 100 | { 101 | "token": self.cspm_token 102 | } 103 | } 104 | 105 | self.logger.debug('API - Generating CNA session token.') 106 | 107 | res = object() 108 | try: 109 | start_time = time.time() 110 | self.logger.debug(url) 111 | res = requests.request("POST", url, headers=headers, json=payload, verify=self.verify, proxies=self.proxies) 112 | end_time = time.time() 113 | time_completed = round(end_time-start_time, 3) 114 | 115 | self.token_time_stamp = time.time() 116 | except: 117 | self.logger.error('Failed to connect to API.') 118 | self.logger.warning('Make sure any offending VPNs are disabled.') 119 | 120 | return [res, time_completed] 121 | 122 | def _expired_login(self) -> None: 123 | self.logger.warning('CSPM session expired. Generating new session.') 124 | self.__cspm_login() 125 | 126 | def _api_refresh(self) -> None: 127 | self.logger.debug('API - Refreshing SaaS session token.') 128 | 129 | res = object() 130 | time_completed = 0 131 | try: 132 | res, time_completed = self.cspm_session._api_refresh() 133 | self.token_time_stamp = time.time() 134 | except: 135 | self.logger.error('Failed to connect to API.') 136 | self.logger.warning('Make sure any offending VPNs are disabled.') 137 | 138 | return [res, time_completed] -------------------------------------------------------------------------------- /src/pcpi/_cspm_session.py: -------------------------------------------------------------------------------- 1 | #Installed 2 | import requests 3 | 4 | from urllib3.exceptions import InsecureRequestWarning 5 | requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) 6 | 7 | #Local 8 | from ._session_base import Session 9 | 10 | import time 11 | 12 | #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 13 | class CSPMSession(Session): 14 | def __init__(self, tenant_name: str, a_key: str, s_key: str, api_url: str, verify: bool, proxies: dict, logger): 15 | """ 16 | Initializes a Prisma Cloud API session for a given tenant. 17 | 18 | Keyword arguments: 19 | tenant_name -- Name of tenant associated with session 20 | a_key -- Tenant Access Key 21 | s_key -- Tenant Secret Key 22 | api_url -- API URL Tenant is hosted on 23 | """ 24 | super().__init__(logger) 25 | 26 | self.tenant = tenant_name 27 | self.a_key = a_key 28 | self.s_key = s_key 29 | self.api_url = api_url 30 | self.verify = verify 31 | self.proxies = proxies 32 | 33 | self.token_time_stamp = 0 34 | 35 | self.token = '' 36 | 37 | self.auth_key = 'x-redlock-auth' 38 | self.auth_style = '' 39 | 40 | self.headers = { 41 | 'content-type': 'application/json; charset=UTF-8', 42 | 'x-redlock-auth': '' 43 | } 44 | 45 | self._api_login_wrapper() 46 | 47 | #============================================================================== 48 | def _api_login(self) -> object: 49 | ''' 50 | Calls the Prisma Cloud API to generate a x-redlock-auth JWT. 51 | Returns: 52 | x-redlock-auth JWT. 53 | ''' 54 | 55 | #Build request 56 | url = f'{self.api_url}/login' 57 | 58 | headers = { 59 | 'content-type': 'application/json; charset=UTF-8' 60 | } 61 | 62 | payload = { 63 | "username": f"{self.a_key}", 64 | "password": f"{self.s_key}" 65 | } 66 | 67 | self.logger.debug('API - Generating CSPM session token.') 68 | res = object() 69 | time_completed = 0 70 | try: 71 | start_time = time.time() 72 | self.logger.debug(url) 73 | res = requests.request("POST", url, headers=headers, json=payload, verify=self.verify, proxies=self.proxies) 74 | end_time = time.time() 75 | time_completed = round(end_time-start_time,3) 76 | 77 | self.token_time_stamp = time.time() 78 | except Exception as e: 79 | 80 | self.logger.error('Failed to connect to API.') 81 | self.logger.warning('Make sure any offending VPNs are disabled.') 82 | self.logger.error(e) 83 | 84 | return [res, time_completed] 85 | 86 | def _expired_login(self) -> None: 87 | self.logger.error('FAILED') 88 | self.logger.warning('Invalid Login Credentials. JWT not generated.') 89 | self.logger.warning('Update your custom credentials file or the default file in `~/.prismacloud`. Exiting...') 90 | # print('Invalid Creds. Exiting...') 91 | exit() 92 | 93 | def _api_refresh(self) -> None: 94 | self.logger.debug('API - Refreshing SaaS session token.') 95 | 96 | res = object() 97 | time_completed = 0 98 | try: 99 | start_time = time.time() 100 | self.logger.debug(self.api_url + '/auth_token/extend') 101 | res = requests.request("GET", self.api_url + '/auth_token/extend', headers=self.headers, verify=self.verify, proxies=self.proxies) 102 | end_time = time.time() 103 | time_completed = round(end_time-start_time,3) 104 | self.token_time_stamp = time.time() 105 | except: 106 | self.logger.error('Failed to connect to API.') 107 | self.logger.warning('Make sure any offending VPNs are disabled.') 108 | 109 | return [res, time_completed] 110 | -------------------------------------------------------------------------------- /src/pcpi/_onprem_cwp_session.py: -------------------------------------------------------------------------------- 1 | #Standard Library 2 | import enum 3 | 4 | #Installed 5 | import requests 6 | 7 | from urllib3.exceptions import InsecureRequestWarning 8 | requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) 9 | 10 | #Local 11 | from ._session_base import Session 12 | 13 | import time 14 | 15 | #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 16 | class CWPSession(Session): 17 | def __init__(self, tenant_name: str, api_url: str, uname: str, passwd:str, verify:bool, proxies: dict, project_flag:bool, logger: object): 18 | """ 19 | Initializes a Prisma Cloud API session for a given tenant. 20 | 21 | Keyword Arguments: 22 | tenant_name -- Name of tenant associated with session 23 | api_url -- API URL Tenant is hosted on 24 | uname -- Username 25 | passwd -- Password 26 | token -- Dedicated API token 27 | """ 28 | 29 | super().__init__(logger) 30 | 31 | self.tenant = tenant_name 32 | self.uname = uname 33 | self.passwd = passwd 34 | self.verify = verify 35 | self.proxies = proxies 36 | self.project_flag = project_flag 37 | 38 | self.token_time_stamp = 0 39 | 40 | self.api_url = api_url 41 | 42 | self.logger = logger 43 | 44 | self.auth_key = 'Authorization' 45 | self.auth_style = 'Bearer ' 46 | 47 | self.headers = { 48 | 'content-type': 'application/json; charset=UTF-8', 49 | 'Authorization': 'Bearer ' 50 | } 51 | 52 | self._api_login_wrapper() 53 | 54 | #============================================================================== 55 | def _api_login(self) -> object: 56 | ''' 57 | Calls the Prisma Cloud API to generate a x-redlock-auth JWT. 58 | 59 | Returns: 60 | x-redlock-auth JWT. 61 | ''' 62 | 63 | #Build request 64 | url = f'{self.api_url}/api/v1/authenticate' 65 | 66 | headers = { 67 | 'content-type': 'application/json; charset=UTF-8' 68 | } 69 | 70 | payload = { 71 | "username": self.uname, 72 | "password": self.passwd, 73 | } 74 | 75 | self.logger.debug('API - Generating CWP session token.') 76 | 77 | res = object() 78 | time_completed = 0 79 | try: 80 | start_time = time.time() 81 | self.logger.debug(url) 82 | res = requests.request("POST", url, headers=headers, json=payload, verify=self.verify, proxies=self.proxies) 83 | end_time = time.time() 84 | time_completed = round(end_time-start_time, 3) 85 | 86 | self.token_time_stamp = time.time() 87 | except: 88 | self.logger.error('Failed to connect to API.') 89 | self.logger.warning('Make sure any offending VPNs are disabled.') 90 | 91 | return [res, time_completed] 92 | 93 | def _expired_login(self) -> None: 94 | self.logger.error('FAILED') 95 | self.logger.warning('Invalid Login Credentials. JWT not generated.') 96 | self.logger.warning('Update your credentials file locally or in `~/.prismacloud`. Exiting...') 97 | # print('Invalid Creds. Exiting...') 98 | exit() -------------------------------------------------------------------------------- /src/pcpi/_saas_cwp_session.py: -------------------------------------------------------------------------------- 1 | #Installed 2 | import requests 3 | 4 | from urllib3.exceptions import InsecureRequestWarning 5 | requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) 6 | 7 | #Local 8 | from ._session_base import Session 9 | from ._cspm_session import CSPMSession 10 | 11 | #Python Library 12 | import time 13 | 14 | #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 15 | class SaaSCWPSession(Session): 16 | def __init__(self, tenant_name: str, a_key: str, s_key: str, api_url: str, verify: bool, proxies: dict, logger, cspm_session={}): 17 | """ 18 | Initializes a Prisma Cloud API session for a given tenant. 19 | 20 | Keyword Arguments: 21 | tenant_name -- Name of tenant associated with session 22 | a_key -- Tenant Access Key 23 | s_key -- Tenant Secret Key 24 | api_url -- API URL Tenant is hosted on 25 | """ 26 | 27 | super().__init__(logger) 28 | 29 | self.tenant = tenant_name 30 | self.a_key = a_key 31 | self.s_key = s_key 32 | self.api_url = api_url 33 | self.verify = verify 34 | self.proxies = proxies 35 | 36 | self.token_time_stamp = 0 37 | 38 | self.logger = logger 39 | 40 | self.cspm_session = {} 41 | self.cspm_token = '' 42 | 43 | if not cspm_session: 44 | self.__get_cspm_session() 45 | else: 46 | self.cspm_session = cspm_session 47 | self.cspm_token = self.cspm_session.token 48 | 49 | self.api_url = self.__cwpp_metadata(self.cspm_session) 50 | 51 | self.auth_key = 'Authorization' 52 | self.auth_style = 'Bearer ' 53 | 54 | self.token = '' 55 | 56 | self.headers = { 57 | 'content-type': 'application/json; charset=UTF-8', 58 | 'Authorization': 'Bearer ' 59 | } 60 | 61 | self._api_login_wrapper() 62 | 63 | #============================================================================== 64 | def __get_cspm_session(self): 65 | self.cspm_session = CSPMSession(self.tenant, self.a_key, self.s_key, self.api_url, self.verify, self.proxies, self.logger) 66 | self.cspm_token = self.cspm_session.token 67 | 68 | def __cspm_login(self): 69 | self.cspm_token = self.cspm_session._api_login_wrapper() 70 | 71 | def __cwpp_metadata(self, cspm_session): 72 | res = cspm_session.request('GET', 'meta_info') 73 | compute_url = res.json()['twistlockUrl'] 74 | 75 | return compute_url 76 | 77 | #============================================================================== 78 | def _api_login(self) -> object: 79 | ''' 80 | Calls the Prisma Cloud API to generate a x-redlock-auth JWT. 81 | 82 | Returns: 83 | x-redlock-auth JWT. 84 | ''' 85 | 86 | #Build request 87 | url = f'{self.api_url}/api/v1/authenticate' 88 | 89 | headers = { 90 | 'content-type': 'application/json; charset=UTF-8' 91 | } 92 | 93 | payload = { 94 | "username": None, 95 | "password": None, 96 | "token": self.cspm_token 97 | } 98 | 99 | self.logger.debug('API - Generating SaaS CWPP session token.') 100 | 101 | res = object() 102 | try: 103 | start_time = time.time() 104 | self.logger.debug(url) 105 | res = requests.request("POST", url, headers=headers, json=payload, verify=self.verify, proxies=self.proxies) 106 | 107 | end_time = time.time() 108 | time_completed = round(end_time-start_time, 3) 109 | 110 | self.token_time_stamp = time.time() 111 | except: 112 | self.logger.error('Failed to connect to API.') 113 | self.logger.warning('Make sure any offending VPNs are disabled.') 114 | 115 | return [res, time_completed] 116 | 117 | def _expired_login(self) -> None: 118 | self.logger.warning('CSPM session expired. Generating new session.') 119 | self.__cspm_login() 120 | 121 | def _api_refresh(self) -> None: 122 | self.logger.debug('API - Refreshing SaaS session token.') 123 | 124 | res = object() 125 | time_completed = 0 126 | try: 127 | res, time_completed = self.cspm_session._api_refresh() 128 | self.token_time_stamp = time.time() 129 | except: 130 | self.logger.error('Failed to connect to API.') 131 | self.logger.warning('Make sure any offending VPNs are disabled.') 132 | 133 | return [res, time_completed] -------------------------------------------------------------------------------- /src/pcpi/_session_base.py: -------------------------------------------------------------------------------- 1 | #Standard Library 2 | import time 3 | 4 | import json as json_lib 5 | 6 | #installed 7 | import requests 8 | 9 | from urllib3.exceptions import InsecureRequestWarning 10 | 11 | class Session: 12 | def __init__(self,logger): 13 | """ 14 | Initializes a Prisma Cloud API Session Manager. 15 | 16 | Keyword Arguments: 17 | logger - optional logger, either pylib logger or loguru 18 | """ 19 | self.retries = 20 20 | self.retry_statuses = [401, 425, 429, 500, 502, 503, 504] 21 | self.retry_delay_statuses = [429, 500, 502, 503, 504] 22 | self.success_status = [200,201,202,203,204,205,206] 23 | self.expired_code = 401 24 | self.retry_timer = 0 25 | self.retry_timer_max = 32 26 | self.token_time = 360 27 | 28 | self.empty_res = '' 29 | 30 | self.u_count = 1 31 | self.unknown_error_max = 5 32 | 33 | self.logger = logger 34 | 35 | #============================================================================== 36 | def _api_login_wrapper(self): 37 | if self.verify == False: 38 | requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) 39 | 40 | res = self.empty_res 41 | time_completed = 0 42 | while res == self.empty_res and self.u_count < self.unknown_error_max: 43 | try: 44 | res, time_completed = self._api_login() 45 | 46 | retries = 0 47 | encountered_401 = False 48 | while res.status_code in self.retry_statuses and retries < self.retries: 49 | 50 | if res.status_code == 500 and self.retry_timer >= 8: 51 | self._expired_login() 52 | 53 | if res.status_code in self.retry_delay_statuses: 54 | self.logger.warning(f'FAILED {res.status_code} - {time_completed} seconds') 55 | 56 | #Increase timer when ever encounted to slow script execution. 57 | if self.retry_timer == 0: 58 | self.retry_timer = 1 59 | else: 60 | self.retry_timer = self.retry_timer*2 61 | if self.retry_timer >= self.retry_timer_max: 62 | self.retry_timer = self.retry_timer_max 63 | 64 | self.logger.warning(f'Waiting {self.retry_timer} seconds') 65 | time.sleep(self.retry_timer) 66 | self.logger.warning('Increasing wait time.') 67 | 68 | elif res.status_code == self.expired_code: 69 | if encountered_401 == True: 70 | self.logger.error('ERROR - Can not perform API operations') 71 | self.logger.warning('Steps to troubleshoot: ') 72 | self.logger.warning('1) Ensure Access Key has proper permission.') 73 | self.logger.warning('2) Ensure Prisma Cloud license is valid.') 74 | self.logger.warning('3) If using a CWPP Project, ensure project name is valid in credential configurations.') 75 | # print('Repeat 401 Error. Exiting...') 76 | exit() 77 | 78 | self._expired_login() 79 | encountered_401 = True 80 | else: 81 | code = "NO_CODE" 82 | try: 83 | code = res.status_code 84 | except: 85 | pass 86 | self.logger.error(f'FAILED {code} - {time_completed} seconds') 87 | self.logger.error('ERROR Logging In. JWT not generated.') 88 | self.logger.warning('RESPONSE:') 89 | self.logger.info(res) 90 | self.logger.warning('RESPONSE URL:') 91 | self.logger.info(res.url) 92 | self.logger.warning('RESPONSE TEXT:') 93 | self.logger.info(res.text) 94 | 95 | 96 | res, time_completed = self._api_login() 97 | 98 | retries +=1 99 | 100 | if retries == self.retries: 101 | self.logger.error('ERROR. Max retires exceeded on API Login. Exiting...') 102 | # print('API Login Max Retries. Exiting...') 103 | exit() 104 | 105 | #Update token and headers 106 | token = res.json().get('token') 107 | self.token = token 108 | new_headers = self.headers 109 | new_headers[self.auth_key] = self.auth_style + token 110 | self.headers = new_headers 111 | 112 | try: 113 | self.logger.success(f'SUCCESS - {time_completed} seconds') 114 | except: 115 | self.logger.info(f'SUCCESS - {time_completed} seconds') 116 | 117 | self.u_count = 1 118 | return token 119 | 120 | except KeyboardInterrupt: 121 | self.logger.error('Keyboard Interrupt. Exiting...') 122 | # print('Interrupt signal. Exiting...') 123 | exit() 124 | except Exception as e: 125 | self.logger.error(e) 126 | self.logger.error(f'UNKNOWN ERROR - API login. Retrying... {self.u_count} of {self.unknown_error_max}') 127 | self.u_count += 1 128 | self.logger.warning('Steps to troubleshoot: ') 129 | self.logger.warning('1) Disable any VPNs.') 130 | self.logger.warning('2) Ensure API base URL is correct.') 131 | time.sleep(1) 132 | 133 | def _api_refresh_wrapper(self): 134 | res = self.empty_res 135 | 136 | refresh_func = getattr(self, "_api_refresh", None) 137 | time_completed = 0 138 | 139 | #FIXME API REFRESH NOT WORKING DUE TO EXTEND TOKEN ENDPOINT CHANGES 140 | if not callable(refresh_func): 141 | return self._api_login_wrapper() 142 | else: 143 | while res == self.empty_res and self.u_count < self.unknown_error_max: 144 | try: 145 | res, time_completed = self._api_refresh() 146 | 147 | retries = 0 148 | while res.status_code in self.retry_statuses and retries < self.retries: 149 | if res.status_code in self.retry_delay_statuses: 150 | self.logger.warning(f'CODE {res.status_code} - {time_completed} seconds') 151 | self.logger.warning(f'HEADERS: {res.headers}') 152 | #Increase timer when ever encounter to slow script execution. 153 | if self.retry_timer == 0: 154 | self.retry_timer = 1 155 | else: 156 | self.retry_timer = self.retry_timer*2 157 | if self.retry_timer >= self.retry_timer_max: 158 | self.retry_timer = self.retry_timer_max 159 | 160 | self.logger.warning(f'Waiting {self.retry_timer} seconds') 161 | time.sleep(self.retry_timer) 162 | self.logger.warning('Increasing wait time.') 163 | 164 | elif res.status_code == self.expired_code: 165 | self.logger.error(f'FAILED {self.expired_code} - {time_completed} seconds') 166 | self.logger.warning('Session expired. Generating new Token') 167 | self._api_login_wrapper() 168 | return 169 | else: 170 | code = "NO_CODE" 171 | try: 172 | code = res.status_code 173 | except: 174 | pass 175 | self.logger.error(f'FAILED {code} - {time_completed} seconds') 176 | self.logger.error('ERROR Refreshing Token.') 177 | self.logger.warning('RESPONSE:') 178 | self.logger.info(res) 179 | self.logger.warning('RESPONSE URL:') 180 | self.logger.info(res.url) 181 | self.logger.warning('RESPONSE TEXT:') 182 | self.logger.info(res.text) 183 | 184 | res, time_completed = self._api_refresh() 185 | 186 | retries +=1 187 | 188 | if retries == self.retries: 189 | self.logger.error('ERROR. Max retires exceeded on JWT refresh. Exiting...') 190 | # print('JWT Refresh max retry. Exiting...') 191 | exit() 192 | 193 | #Update token and headers 194 | token = res.json().get('token') 195 | self.token = token 196 | new_headers = self.headers 197 | new_headers[self.auth_key] = self.auth_style + token 198 | self.headers = new_headers 199 | 200 | try: 201 | self.logger.success(f'SUCCESS - {time_completed} seconds') 202 | except: 203 | self.logger.info(f'SUCCESS - {time_completed} seconds') 204 | 205 | self.u_count = 1 206 | 207 | return 208 | 209 | except KeyboardInterrupt: 210 | self.logger.error('Keyboard Interrupt. Exiting...') 211 | # print('Interrupt signal. Exiting...') 212 | exit() 213 | except Exception as e: 214 | self.logger.error(e) 215 | self.logger.error(f'UNKNOWN ERROR - API refresh. Retrying... {self.u_count} of {self.unknown_error_max}') 216 | self.u_count += 1 217 | self.logger.warning('Steps to troubleshoot: ') 218 | self.logger.warning('1) Disable any VPNs.') 219 | self.logger.warning('2) Ensure API base URL is correct.') 220 | time.sleep(1) 221 | 222 | 223 | #============================================================================== 224 | def __api_call_wrapper(self, method: str, url: str, json: dict=None, data: dict=None, params: dict=None, files: dict=None, verify=True, proxies=None, acceptCsv=False, redlock_ignore: list=None, status_ignore: list=[], custom_log='', custom_error_message=''): 225 | """ 226 | A wrapper around all API calls that handles token generation, retrying 227 | requests and API error console output logging. 228 | Keyword Arguments: 229 | method -- Request method/type. Ex: POST or GET 230 | url -- Full API request URL 231 | data -- Body of the request in a json compatible format 232 | params -- Queries for the API request 233 | Returns: 234 | Respose from API call. 235 | """ 236 | res = self.empty_res 237 | time_completed = 0 238 | while res == self.empty_res and self.u_count < self.unknown_error_max: 239 | try: 240 | if time.time() - self.token_time_stamp >= self.token_time: 241 | self.logger.warning('Session Refresh Timer - Generating new Token') 242 | self._api_refresh_wrapper() 243 | 244 | self.logger.debug(f'{url}') 245 | res,time_completed = self.__request_wrapper(method, url, headers=self.headers, json=json, data=data, params=params, files=files, verify=verify, proxies=proxies, acceptCsv=acceptCsv) 246 | 247 | if res.status_code in self.success_status or res.status_code in status_ignore: 248 | log_message = '' 249 | if custom_log: 250 | log_message = ' - ' + custom_log 251 | 252 | try: 253 | self.logger.success(f'SUCCESS - {time_completed} seconds{log_message}') 254 | except: 255 | self.logger.info(f'SUCCESS - {time_completed} seconds{log_message}') 256 | return res 257 | 258 | retries = 0 259 | encountered_401 = False 260 | while res.status_code in self.retry_statuses and retries < self.retries: 261 | #If we get a 429 code, sleep for a doubling amount of time. 262 | if res.status_code in self.retry_delay_statuses: 263 | error_msg = '' 264 | log_message = '' 265 | if custom_error_message: 266 | error_msg = " - " + custom_error_message 267 | if custom_log: 268 | log_message = ' - ' + custom_log 269 | 270 | self.logger.warning(f'FAILED {res.status_code} - {time_completed} seconds{log_message}{error_msg}') 271 | #Wait for retry timer 272 | if self.retry_timer > 0: 273 | self.logger.warning(f'Waiting {self.retry_timer} seconds') 274 | time.sleep(self.retry_timer) 275 | 276 | self.logger.warning('Increasing wait time') 277 | #Increase timer when ever wait status encounted to slow script execution. 278 | if self.retry_timer == 0: 279 | self.retry_timer = 1 280 | else: 281 | self.retry_timer = self.retry_timer*2 282 | if self.retry_timer >= self.retry_timer_max: 283 | self.retry_timer = self.retry_timer_max 284 | 285 | #If token expires, login again and get new token 286 | if res.status_code == self.expired_code: 287 | if encountered_401 == True: 288 | self.logger.error('ERROR - Can not perform API operations') 289 | self.logger.warning('Steps to troubleshoot: ') 290 | self.logger.warning('1) Ensure Access Key has proper permission.') 291 | self.logger.warning('2) Ensure Prisma Cloud license is valid.') 292 | self.logger.warning('3) If using a CWPP Project, ensure project name is valid in credential configurations.') 293 | # print('401 Error. Exiting...') 294 | exit() 295 | 296 | self.logger.error(f'FAILED {self.expired_code} - {time_completed} seconds') 297 | self.logger.warning('Session expired. Generating new Token and retrying') 298 | self._api_login_wrapper() 299 | encountered_401 = True 300 | 301 | self.logger.warning(f'Retrying request') 302 | self.logger.debug(f'{url}') 303 | 304 | res, time_completed = self.__request_wrapper(method, url, headers=self.headers, json=json, data=data, params=params, files=files, verify=verify, proxies=proxies, acceptCsv=acceptCsv) 305 | retries += 1 306 | 307 | if res.status_code in self.success_status or res.status_code in status_ignore: 308 | try: 309 | self.logger.success(f'SUCCESS - {time_completed} seconds') 310 | except: 311 | self.logger.info(f'SUCCESS - {time_completed} seconds') 312 | return res 313 | 314 | if retries >= self.retries: 315 | self.logger.error('ERROR. Max retires exceeded') 316 | 317 | #Some redlock errors need to be handled elsewhere and don't require this debugging output 318 | if 'x-redlock-status' in res.headers and redlock_ignore: 319 | for el in redlock_ignore: 320 | if el in res.headers['x-redlock-status']: 321 | return res 322 | 323 | code = "NO_CODE" 324 | try: 325 | code = res.status_code 326 | except: 327 | pass 328 | self.logger.error(f'FAILED {code} - {time_completed} seconds') 329 | self.logger.error('REQUEST DUMP:') 330 | self.logger.warning('REQUEST HEADERS:') 331 | self.logger.info(self.headers) 332 | self.logger.warning('REQUEST JSON:') 333 | self.logger.info(json) 334 | if data: 335 | self.logger.warning('REQUEST DATA:') 336 | self.logger.info(data) 337 | self.logger.warning('REQUEST PARAMS:') 338 | self.logger.info(params) 339 | self.logger.warning('RESPONSE:') 340 | self.logger.info(res) 341 | self.logger.warning('RESPONSE URL:') 342 | self.logger.info(res.url) 343 | self.logger.warning('RESPONSE HEADERS:') 344 | self.logger.info(res.headers) 345 | self.logger.warning('RESPONSE REQUEST BODY:') 346 | self.logger.info(res.request.body) 347 | self.logger.warning('RESPONSE STATUS:') 348 | if 'x-redlock-status' in res.headers: 349 | self.logger.info(res.headers['x-redlock-status']) 350 | self.logger.warning('RESPONSE TEXT:') 351 | self.logger.info(res.text) 352 | self.logger.warning('RESPONSE JSON:') 353 | if res.text != "": 354 | for json_data in res.json(): 355 | self.logger.info(json_data) 356 | 357 | self.u_count = 1 358 | return res 359 | except KeyboardInterrupt: 360 | self.logger.error('Keyboard Interrupt. Exiting...') 361 | # print('Interrupt Signal. Exiting...') 362 | exit() 363 | except Exception as e: 364 | #self.logger.error(e, f'- {time_completed} seconds') 365 | self.logger.error(e) 366 | if res == self.empty_res: 367 | self.logger.error(f'UNKNOWN ERROR - API Call Wrapper. Retrying... {self.u_count} of {self.unknown_error_max}') 368 | time.sleep(2) 369 | else: 370 | self.logger.error(f'UNKNOWN ERROR - API Call Wrapper. Continuing... {self.u_count} of {self.unknown_error_max}') 371 | self.u_count += 1 372 | 373 | #============================================================================== 374 | 375 | def request(self, method: str, endpoint_url: str, json: dict=None, data: dict=None, params: dict=None, files: dict=None, verify=None, proxies=None, acceptCsv=False, redlock_ignore: list=None, status_ignore: list=[], custom_log='', custom_error_message=''): 376 | ''' 377 | Function for calling the PC API using this session manager. Accepts the 378 | same arguments as 'requests.request' minus the headers argument as 379 | headers are supplied by the session manager. 380 | ''' 381 | if verify == None: 382 | verify = self.verify 383 | 384 | if proxies == None: 385 | proxies = self.proxies 386 | 387 | #If the CWP Session is a project, auto add project id query string 388 | try: 389 | if self.project_flag == True: 390 | if params: 391 | params.update({"project":self.tenant}) 392 | else: 393 | params = {"project":self.tenant} 394 | except: 395 | pass 396 | 397 | #Validate method 398 | method = method.upper() 399 | if method not in ['POST', 'PUT', 'GET', 'OPTIONS', 'DELETE', 'PATCH']: 400 | self.logger.warning('Invalid method.') 401 | 402 | #Build url 403 | if endpoint_url[0] != '/': 404 | endpoint_url = '/' + endpoint_url 405 | 406 | url = f'{self.api_url}{endpoint_url}' 407 | 408 | #Call wrapper 409 | return self.__api_call_wrapper(method, url, json=json, data=data, params=params, files=files,verify=verify, proxies=proxies, acceptCsv=acceptCsv, redlock_ignore=redlock_ignore, status_ignore=status_ignore, custom_log=custom_log, custom_error_message=custom_error_message) 410 | 411 | def config_search_request(self, json: dict, verify=None, proxies=None, redlock_ignore: list=None, status_ignore: list=[]): 412 | if verify == None: 413 | verify = self.verify 414 | 415 | if proxies == None: 416 | proxies = self.proxies 417 | 418 | 419 | limit = 2000#Max limit value is 100,000 420 | 421 | #Force best practices with HS 422 | json.update({"heuristicSearch": True, "limit": limit, "withResourceJson": True}) 423 | 424 | #initial API Call 425 | res = self.__api_call_wrapper('POST', f'{self.api_url}/search/config', json=json, verify=verify, proxies=proxies, redlock_ignore=redlock_ignore, status_ignore=status_ignore) 426 | 427 | total_rows = 0 428 | complete_res_list = [] 429 | complete_res_dict = res.json() 430 | 431 | #res_data var used for while loop 432 | res_data = res.json()['data'] 433 | 434 | complete_res_list.extend(res_data.get('items')) 435 | 436 | retrievedRows = res_data.get('totalRows') 437 | total_rows += retrievedRows 438 | 439 | counter = 0 440 | while 'nextPageToken' in res_data: 441 | #update payload 442 | json.update({'pageToken': res_data.get('nextPageToken')}) 443 | 444 | #call page endpoint 445 | res = self.__api_call_wrapper('POST', f'{self.api_url}/search/config/page', json=json, verify=verify, proxies=proxies, redlock_ignore=redlock_ignore, status_ignore=status_ignore) 446 | counter += 1 447 | 448 | #update res_data with the paginated response 449 | res_data = res.json() 450 | 451 | #Add results from each page API call 452 | complete_res_list.extend(res_data.get('items')) 453 | 454 | retrievedRows = res_data.get('totalRows') 455 | total_rows+= retrievedRows 456 | 457 | #Update res dict to be the some format as the 'data' object in a typical RQL res 458 | complete_res_dict['data'].update({'totalRows':total_rows, 'items': complete_res_list}) 459 | 460 | return complete_res_dict 461 | 462 | def config_search_request_function(self, json, function, verify=None, proxies=None, redlock_ignore: list=None, status_ignore: list=[]): 463 | if verify == None: 464 | verify = self.verify 465 | 466 | if proxies == None: 467 | proxies = self.proxies 468 | 469 | 470 | limit = 2000#Max limit value is 100,000 471 | 472 | #Force best practices with HS 473 | json.update({"heuristicSearch": True, "limit": limit, "withResourceJson": True}) 474 | 475 | #initial API Call 476 | res = self.__api_call_wrapper('POST', f'{self.api_url}/search/config', json=json, verify=verify, proxies=proxies, redlock_ignore=redlock_ignore, status_ignore=status_ignore) 477 | 478 | total_rows = res.json()['data']['totalRows'] 479 | 480 | res_details = res.json() 481 | res_details.pop('data') 482 | res_data = res.json()['data'] 483 | counter = 0 484 | 485 | function(res_details, res_data, counter, total_rows) 486 | 487 | while 'nextPageToken' in res_data: 488 | #update payload 489 | json.update({'pageToken': res_data.get('nextPageToken')}) 490 | 491 | #call page endpoint 492 | res = self.__api_call_wrapper('POST', f'{self.api_url}/search/config/page', json=json, verify=verify, proxies=proxies, redlock_ignore=redlock_ignore, status_ignore=status_ignore) 493 | counter += 1 494 | 495 | res_data = res.json() 496 | total_rows += res_data['totalRows'] 497 | 498 | function(res_details, res_data, counter, total_rows) 499 | 500 | return total_rows 501 | 502 | def __request_wrapper(self, method, url, headers, json, data, params, files, verify, proxies, acceptCsv): 503 | if acceptCsv == True: #CSPM Support Only 504 | headers.update({ 505 | 'Accept': 'text/csv' 506 | }) 507 | 508 | r = self.empty_res 509 | 510 | start_time = time.time() 511 | r = requests.request(method, url, headers=headers, json=json, data=data, params=params, files=files, verify=verify, proxies=proxies) 512 | end_time = time.time() 513 | time_completed = 0 514 | time_completed = round(end_time-start_time,3) 515 | 516 | while r == self.empty_res and self.u_count < self.unknown_error_max: 517 | try: 518 | start_time = time.time() 519 | r = requests.request(method, url, headers=headers, json=json, data=data, params=params, files=files, verify=verify, proxies=proxies) 520 | end_time = time.time() 521 | time_completed = round(end_time-start_time,3) 522 | 523 | self.u_count = 1 524 | return [r, time_completed] 525 | except KeyboardInterrupt: 526 | self.logger.error('Keyboard Interrupt. Exiting...') 527 | # print('Interrupt Signal. Exiting...') 528 | exit() 529 | except Exception as e: 530 | self.logger.error(e) 531 | self.logger.error(f'UNKNOWN ERROR - Request Wrapper. Retrying {self.u_count} of {self.unknown_error_max}') 532 | time.sleep(2) 533 | self.u_count += 1 534 | 535 | return [r, time_completed] -------------------------------------------------------------------------------- /src/pcpi/_session_types.py: -------------------------------------------------------------------------------- 1 | #Standard Library 2 | from ._cspm_session import CSPMSession 3 | from ._saas_cwp_session import SaaSCWPSession 4 | from ._onprem_cwp_session import CWPSession 5 | from ._cna_session import CNASession -------------------------------------------------------------------------------- /src/pcpi/onprem_session_manager.py: -------------------------------------------------------------------------------- 1 | #Local 2 | from ._session_types import CWPSession 3 | import logging 4 | 5 | logging.basicConfig() 6 | py_logger = logging.getLogger("pcpi") 7 | py_logger.setLevel(10) 8 | 9 | class CWPSessionManager: 10 | def __init__(self, tenant_name: str, api_url: str, uname: str, passwd: str, verify:bool=True, proxies:dict=None, project_flag:bool=False, logger:object=py_logger): 11 | """ 12 | Initializes a Prisma Cloud API Session Manager. 13 | 14 | Keyword Arguments: 15 | tenant_name -- Name of tenant associated with session 16 | a_key -- Tenant Access Key 17 | s_key -- Tenant Secret Key 18 | api_url -- API URL Tenant is hosted on 19 | """ 20 | self.logger = logger 21 | 22 | self.tenant = tenant_name 23 | self.uname = uname 24 | self.passwd = passwd 25 | self.api_url = api_url 26 | 27 | self.verify = verify 28 | self.proxies = proxies 29 | self.project_flag = project_flag 30 | 31 | self.cwp_session = {} 32 | 33 | 34 | #============================================================================== 35 | def create_cwp_session(self): 36 | session = CWPSession(self.tenant, self.api_url, self.uname, self.passwd, self.verify, self.proxies, self.project_flag, self.logger) 37 | self.cwp_session = session 38 | return session 39 | -------------------------------------------------------------------------------- /src/pcpi/saas_session_manager.py: -------------------------------------------------------------------------------- 1 | #Standard Library 2 | import time 3 | import logging 4 | 5 | logging.basicConfig() 6 | py_logger = logging.getLogger("pcpi") 7 | py_logger.setLevel(10) 8 | 9 | #Local 10 | from ._session_types import CSPMSession, SaaSCWPSession, CNASession 11 | 12 | class SaaSSessionManager: 13 | def __init__(self, tenant_name: str, a_key: str, s_key: str, api_url: str, verify=True, proxies:dict=None, logger=py_logger): 14 | """ 15 | Initializes a Prisma Cloud API Session Manager. 16 | 17 | Keyword Arguments: 18 | tenant_name -- Name of tenant associated with session 19 | a_key -- Tenant Access Key 20 | s_key -- Tenant Secret Key 21 | api_url -- API URL Tenant is hosted on 22 | """ 23 | self.logger = logger 24 | 25 | self.tenant = tenant_name 26 | self.a_key = a_key 27 | self.s_key = s_key 28 | self.api_url = api_url 29 | 30 | self.verify = verify 31 | self.proxies = proxies 32 | 33 | self.cspm_session = {} 34 | self.saas_cwp_session = {} 35 | self.cna_session = {} 36 | 37 | 38 | 39 | #============================================================================== 40 | def create_cspm_session(self): 41 | session = CSPMSession(self.tenant, self.a_key, self.s_key, self.api_url, self.verify, self.proxies, logger=self.logger) 42 | self.cspm_session = session 43 | return session 44 | 45 | def create_cwp_session(self): 46 | if self.cspm_session: 47 | session = SaaSCWPSession(self.tenant, self.a_key, self.s_key, self.api_url, self.verify, self.proxies, logger=self.logger, cspm_session=self.cspm_session) 48 | self.saas_cwp_session = session 49 | return session 50 | else: 51 | self.create_cspm_session() 52 | session = SaaSCWPSession(self.tenant, self.a_key, self.s_key, self.api_url, self.verify, self.proxies, logger=self.logger, cspm_session=self.cspm_session) 53 | self.saas_cwp_session = session 54 | return session 55 | 56 | def create_cna_session(self): 57 | if self.cspm_session: 58 | session = CNASession(self.tenant, self.a_key, self.s_key, self.api_url, self.verify, self.proxies, logger=self.logger, cspm_session=self.cspm_session) 59 | self.cna_session = session 60 | return session 61 | else: 62 | self.create_cspm_session() 63 | session = CNASession(self.tenant, self.a_key, self.s_key, self.api_url, self.verify, self.proxies, logger=self.logger, cspm_session=self.cspm_session) 64 | self.cna_session = session 65 | return session 66 | 67 | 68 | 69 | #============================================================================== -------------------------------------------------------------------------------- /src/pcpi/session_loader.py: -------------------------------------------------------------------------------- 1 | #Standard Library 2 | import os 3 | import re 4 | import logging 5 | import json 6 | 7 | #Installed 8 | import requests 9 | 10 | import getpass 11 | 12 | 13 | from urllib3.exceptions import InsecureRequestWarning 14 | requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) 15 | 16 | #Default Logger 17 | logging.basicConfig() 18 | py_logger = logging.getLogger("pcpi") 19 | py_logger.setLevel(10) 20 | 21 | #Local 22 | from .saas_session_manager import SaaSSessionManager 23 | from .onprem_session_manager import CWPSessionManager 24 | 25 | def __c_print(*args, **kwargs): 26 | ''' 27 | Uses ascii codes to enable colored print statements. Works on Mac, Linux and Windows terminals 28 | ''' 29 | 30 | #Magic that makes colors work on windows terminals 31 | os.system('') 32 | 33 | #Define Colors for more readable output 34 | c_gray = '\033[90m' 35 | c_red = '\033[91m' 36 | c_green = '\033[92m' 37 | c_yellow = '\033[93m' 38 | c_blue = '\033[94m' 39 | c_end = '\033[0m' 40 | 41 | color = c_end 42 | if 'color' in kwargs: 43 | c = kwargs['color'].lower() 44 | if c == 'gray' or c == 'grey': 45 | color = c_gray 46 | elif c == 'red': 47 | color = c_red 48 | elif c == 'green': 49 | color = c_green 50 | elif c == 'yellow': 51 | color = c_yellow 52 | elif c == 'blue': 53 | color = c_blue 54 | else: 55 | color = c_end 56 | 57 | _end = '\n' 58 | if 'end' in kwargs: 59 | _end = kwargs['end'] 60 | 61 | print(f'{color}', end='') 62 | for val in args: 63 | print(val, end='') 64 | print(f'{c_end}', end=_end) 65 | 66 | #============================================================================== 67 | 68 | def __validate_onprem_credentials(name, _url, uname, passwd, verify, proxies) -> bool: 69 | ''' 70 | This function creates a session with the supplied credentials to test 71 | if the user successfully entered valid credentials. 72 | ''' 73 | 74 | headers = { 75 | 'content-type': 'application/json; charset=UTF-8' 76 | } 77 | 78 | payload = { 79 | "username": uname, 80 | "password": passwd, 81 | } 82 | 83 | url = f'{_url}/api/v1/authenticate' 84 | 85 | try: 86 | __c_print('API - Validating credentials') 87 | res = requests.request("POST", url, headers=headers, json=payload, verify=verify, proxies=proxies) 88 | print(res.status_code) 89 | 90 | if res.status_code == 200: 91 | __c_print('SUCCESS', color='green') 92 | print() 93 | return True 94 | else: 95 | return False 96 | except: 97 | __c_print('ERROR', end=' ', color='red') 98 | print('Could not connect to Prisma Cloud API.') 99 | print() 100 | print('Steps to troubleshoot:') 101 | __c_print('1) Please disconnect from any incompatible VPN', color='blue') 102 | print() 103 | __c_print('2) Please ensure you have entered a valid Prisma Cloud URL.', color='blue') 104 | print() 105 | return False 106 | 107 | def __validate_credentials(a_key, s_key, url, verify, proxies) -> bool: 108 | ''' 109 | This function creates a session with the supplied credentials to test 110 | if the user successfully entered valid credentials. 111 | ''' 112 | 113 | headers = { 114 | 'content-type': 'application/json; charset=UTF-8' 115 | } 116 | 117 | payload = { 118 | "username": f"{a_key}", 119 | "password": f"{s_key}" 120 | } 121 | 122 | try: 123 | __c_print('API - Validating credentials') 124 | response = requests.request("POST", f'{url}/login', headers=headers, json=payload, verify=verify, proxies=proxies) 125 | 126 | if response.status_code == 200: 127 | __c_print('SUCCESS', color='green') 128 | print() 129 | return True 130 | else: 131 | return False 132 | except: 133 | __c_print('ERROR', end=' ', color='red') 134 | print('Could not connect to Prisma Cloud API.') 135 | print() 136 | print('Steps to troubleshoot:') 137 | __c_print('1) Please disconnect from any incompatible VPN', color='blue') 138 | print() 139 | __c_print('2) Please ensure you have entered a valid Prisma Cloud URL.', color='blue') 140 | print('EX: https://app.prismacloud.io or https://app2.eu.prismacloud.io') 141 | print() 142 | return False 143 | 144 | def __universal_validate_credentials(name, url, _id, secret, verify, proxies): 145 | if verify.lower() == 'true': 146 | verify = True 147 | else: 148 | verify = False 149 | if 'prismacloud.io' in url or 'prismacloud.cn' in url: 150 | return __validate_credentials(_id, secret, url, verify, proxies) 151 | else: 152 | return __validate_onprem_credentials(name, url, _id, secret, verify, proxies) 153 | 154 | #============================================================================== 155 | 156 | def __validate_url(url): 157 | if "prismacloud.io" not in (url): 158 | if 'https://' not in url and 'http://' not in url: 159 | url = 'https://' + url 160 | return url 161 | 162 | if len(url) >= 3: 163 | if 'https://' not in url: 164 | if url[:3] == 'app' or url[:3] == 'api': 165 | url = 'https://' + url 166 | 167 | url = url.replace('app', 'api') 168 | 169 | url = re.sub(r'prismacloud\.io\S*', 'prismacloud.io', url) 170 | 171 | return url 172 | 173 | #============================================================================== 174 | 175 | def __get_config(): 176 | __c_print('Enter Prisma URL. (SaaS EX: https://app.ca.prismacloud.io, On-Prem EX: https://yourdomain.com):', color='blue') 177 | url = input() 178 | print() 179 | new_url = __validate_url(url) 180 | if new_url != url: 181 | __c_print('Adjusted URL:',color='yellow') 182 | print(new_url) 183 | print() 184 | 185 | __c_print('Enter identity (Access Key or Username):', color='blue') 186 | _id = input() 187 | print() 188 | 189 | __c_print('Enter secret (Secret Key or Password):', color='blue') 190 | secret = getpass.getpass(prompt='') 191 | print() 192 | 193 | __c_print('Certificate verification: (True/False/ file)', color='blue') 194 | __c_print('Leave blank to use default value (True).', color='yellow') 195 | verify = input() 196 | print() 197 | 198 | __c_print('Proxy section. HTTP first, HTTPS second. Leave blank to not use a proxy.', color='yellow') 199 | 200 | __c_print('Enter HTTP proxy address.', color='blue') 201 | __c_print('If you are not using a proxy, leave blank.', color='yellow') 202 | http_proxy = input() 203 | print() 204 | 205 | __c_print('Enter HTTPS proxy address.', color='blue') 206 | __c_print('If you are not using a proxy, leave blank.', color='yellow') 207 | https_proxy = input() 208 | print() 209 | 210 | proxies = None 211 | if http_proxy: 212 | proxies = { 213 | 'http': http_proxy 214 | } 215 | if https_proxy: 216 | if proxies: 217 | proxies.update({'https': https_proxy}) 218 | else: 219 | proxies = { 220 | 'https': https_proxy 221 | } 222 | 223 | #If there is non-prisma URL, then ask if its a self hosted project 224 | project_flag = 'false' 225 | if 'prismacloud.io' not in new_url and 'prismacloud.cn' not in new_url: 226 | __c_print('CWP Project (True/False)', color='blue') 227 | __c_print('Leave blank to use default value (False).', color='yellow') 228 | project_flag = input() 229 | print() 230 | if project_flag.lower() == 'true': 231 | project_flag = 'true' 232 | else: 233 | project_flag = 'false' 234 | 235 | name = 'DEFAULT_NAME' 236 | if project_flag == 'true': 237 | __c_print('Enter project ID:', color='blue') 238 | name = input() 239 | else: 240 | __c_print('Enter tenant/console name (Optional):', color='blue') 241 | name = input() 242 | 243 | print() 244 | 245 | verify = verify.strip() 246 | 247 | if verify == '': 248 | verify = 'true' 249 | elif verify.lower() == 'true': 250 | verify = 'true' 251 | elif verify.lower() == 'false': 252 | verify = 'false' 253 | else: 254 | pass 255 | 256 | 257 | return name, _id, secret, new_url, verify, proxies, project_flag 258 | 259 | 260 | def __build_config_json(name, _id, secret, url, verify, proxies, project_flag): 261 | session_dict = { 262 | 'name': name, 263 | 'url': url, 264 | 'identity': _id, 265 | 'secret': secret, 266 | 'verify': verify, 267 | 'proxies': proxies, 268 | 'project_flag': project_flag 269 | } 270 | return session_dict 271 | 272 | #============================================================================== 273 | 274 | def __get_config_from_user(num_tenants, min_tenants): 275 | #Gets the source tenant credentials and ensures that are valid 276 | credentials = [] 277 | 278 | if num_tenants != -1 and min_tenants == -1: 279 | for i in range(num_tenants): 280 | valid = False 281 | while not valid: 282 | __c_print('Enter Prisma Cloud Credentials', color='blue') 283 | print() 284 | name, _id, secret, url, verify, proxies, project_flag = __get_config() 285 | 286 | valid = __universal_validate_credentials(name, url, _id, secret, verify, proxies) 287 | if valid == False: 288 | __c_print('FAILED', end=' ', color='red') 289 | print('Invalid credentials. Please re-enter your credentials') 290 | print() 291 | else: 292 | credentials.append(__build_config_json(name, _id, secret, url, verify, proxies, project_flag)) 293 | elif num_tenants == -1 and min_tenants != -1: 294 | tenant_count = 0 295 | while True: 296 | valid = False 297 | while not valid: 298 | __c_print('Enter Prisma Cloud Credentials', color='blue') 299 | print() 300 | name, _id, secret, url, verify, proxies, project_flag = __get_config() 301 | 302 | valid = __universal_validate_credentials(name, url, _id, secret, verify, proxies) 303 | if valid == False: 304 | __c_print('FAILED', end=' ', color='red') 305 | print('Invalid credentials. Please re-enter your credentials') 306 | print() 307 | else: 308 | credentials.append(__build_config_json(name, _id, secret, url, verify, proxies, project_flag)) 309 | tenant_count +=1 310 | 311 | if tenant_count >= min_tenants: 312 | __c_print('Would you like to add another Prisma Cloud credential? Y/N') 313 | choice = input().lower() 314 | if choice != 'yes' and choice != 'y': 315 | break 316 | else: 317 | while True: 318 | valid = False 319 | while not valid: 320 | __c_print('Enter Prisma Cloud Credentials', color='blue') 321 | print() 322 | name, _id, secret, url, verify, proxies, project_flag = __get_config() 323 | 324 | valid = __universal_validate_credentials(name, url, _id, secret, verify, proxies) 325 | if valid == False: 326 | __c_print('FAILED', end=' ', color='red') 327 | print('Invalid credentials. Please re-enter your credentials') 328 | print() 329 | else: 330 | credentials.append(__build_config_json(name, _id, secret, url, verify, proxies, project_flag)) 331 | 332 | __c_print('Would you like to add another Prisma Cloud credential? Y/N') 333 | choice = input().lower() 334 | 335 | if choice != 'yes' and choice != 'y': 336 | break 337 | 338 | return credentials 339 | 340 | def load_config(file_path='', num_tenants=-1, min_tenants=-1, logger=py_logger): 341 | if num_tenants != -1 and min_tenants != -1: 342 | logger.error('ERROR: Incompatible options. Exiting...') 343 | # print('Incompatible Options. Exiting...') 344 | exit() 345 | 346 | if file_path == '': 347 | config_dir = os.path.join(os.environ['HOME'], '.prismacloud') 348 | config_path = os.path.join(config_dir, 'credentials.json') 349 | if not os.path.exists(config_dir): 350 | os.mkdir(config_dir) 351 | else: 352 | config_path = file_path 353 | 354 | tenant_sessions = [] 355 | 356 | if not os.path.exists(config_path): 357 | config = __get_config_from_user(num_tenants, min_tenants) 358 | with open(config_path, 'w') as outfile: 359 | json.dump(config, outfile) 360 | 361 | config_data = {} 362 | with open(config_path, 'r') as infile: 363 | try: 364 | config_data = json.load(infile) 365 | except Exception as e: 366 | logger.error('Failed to load credentials file. Exiting...') 367 | logger.log(e) 368 | # print('Credential File Load Error. Exiting...') 369 | # print(e) 370 | exit() 371 | 372 | for blob in config_data: 373 | verify = blob['verify'] 374 | if verify.lower().strip() == 'false': 375 | verify = False 376 | elif verify.lower().strip() == 'true': 377 | verify = True 378 | else: 379 | verify = verify 380 | 381 | proxies = None 382 | try: 383 | proxies = blob['proxies'] 384 | except: 385 | pass 386 | 387 | if 'prismacloud.io' in blob['url'] or 'prismacloud.cn' in blob['url']: 388 | tenant_sessions.append(SaaSSessionManager(blob['name'], blob['identity'], blob['secret'], blob['url'], verify, proxies, logger=logger)) 389 | else: 390 | project_flag = False 391 | project_flag_in = blob.get('project_flag', None) 392 | if project_flag_in: 393 | if project_flag_in.lower().strip() == 'true': 394 | project_flag = True 395 | 396 | tenant_sessions.append(CWPSessionManager(blob['name'], blob['url'], blob['identity'], blob['secret'], verify, proxies, project_flag, logger=logger)) 397 | 398 | return tenant_sessions 399 | 400 | def load_config_user(num_tenants=-1, min_tenants=-1, logger=py_logger): 401 | if num_tenants != -1 and min_tenants != -1: 402 | logger.error('ERROR: Incompatible options. Exiting...') 403 | # print('Incompatible cmd arguments. Exiting...') 404 | exit() 405 | 406 | config = __get_config_from_user(num_tenants, min_tenants) 407 | 408 | tenant_sessions = [] 409 | 410 | for tenant in config: 411 | if 'prismacloud.io' in tenant['url'] or 'prismacloud.cn' in tenant['url']: 412 | tenant_sessions.append(SaaSSessionManager(tenant['name'], tenant['identity'], tenant['secret'], tenant['url'], tenant['verify'], logger=logger)) 413 | else: 414 | project_flag = False 415 | project_flag_in = tenant.get('project_flag', None) 416 | if project_flag_in: 417 | if project_flag_in.lower().strip() == 'true': 418 | project_flag = True 419 | tenant_sessions.append(CWPSessionManager(tenant['name'], tenant['url'], tenant['identity'], tenant['secret'], tenant['verify'], project_flag, logger=logger)) 420 | 421 | return tenant_sessions 422 | 423 | def load_config_env(prisma_name='PRISMA_PCPI_NAME', identifier_name='PRISMA_PCPI_ID', secret_name='PRISMA_PCPI_SECRET', api_url_name='PRISMA_PCPI_URL', verify_name='PRISMA_PCPI_VERIFY', http_name='PC_HTTP_PROXY', https_name='PC_HTTPS_PROXY', project_flag_name='PRISMA_PCPI_PROJECT_FLAG', logger=py_logger): 424 | error_exit = False 425 | 426 | name = 'Tenant' 427 | try: 428 | name = os.environ[prisma_name] 429 | except: 430 | logger.warning(f'Missing \'{prisma_name}\' environment variable. Using default name.') 431 | 432 | api_url = '' 433 | api = None 434 | try: 435 | api_url = os.environ[api_url_name] 436 | api = __validate_url(api_url) 437 | except: 438 | logger.error(f'Missing \'{api_url_name}\' environment variable.') 439 | error_exit = True 440 | 441 | a_key = None 442 | try: 443 | a_key = os.environ[identifier_name] 444 | except: 445 | logger.error(f'Missing \'{identifier_name}\' environment variable.') 446 | error_exit = True 447 | 448 | s_key = None 449 | try: 450 | s_key = os.environ[secret_name] 451 | except: 452 | logger.error(f'Missing \'{secret_name}\' environment variable.') 453 | error_exit = True 454 | 455 | verify = True 456 | try: 457 | verify = os.environ[verify_name] 458 | if verify.lower() == 'false': 459 | verify = False 460 | if verify.lower() == 'true': 461 | verify = True 462 | except: 463 | logger.warning(f'\'{verify_name}\' not set. Using default value...') 464 | 465 | proxies = None 466 | http_proxy = None 467 | https_proxy = None 468 | try: 469 | http_proxy = os.environ[http_name] 470 | except: 471 | logger.warning(f'\'{http_name}\' not set. No HTTP proxy will be used.') 472 | 473 | try: 474 | https_proxy = os.environ[https_name] 475 | except: 476 | logger.warning(f'\'{https_name}\' not set. No HTTPS proxy will be used.') 477 | 478 | if http_proxy or https_proxy: 479 | proxies = { 480 | 'http': http_proxy, 481 | 'https': https_proxy 482 | } 483 | 484 | project_flag = False 485 | try: 486 | project_flag = os.environ[project_flag_name] 487 | if project_flag.lower().strip() == 'true': 488 | project_flag = True 489 | except: 490 | logger.warning(f'\'{project_flag_name}\' not set. Using default value...') 491 | 492 | if error_exit: 493 | logger.info('Missing required environment variables. Exiting...') 494 | # print('Missing env variables. Exiting...') 495 | exit() 496 | 497 | if 'prismacloud.io' in api or 'prismacloud.cn' in api: 498 | return SaaSSessionManager(name, a_key, s_key, api, verify, proxies, logger=logger) 499 | else: 500 | return CWPSessionManager(name, api, a_key, s_key, verify, proxies, project_flag, logger=logger) 501 | 502 | 503 | # def __build_cwp_session_dict(name, url, uname, passwd, verify, proxies): 504 | # session_dict = { 505 | # name: { 506 | # 'url': url, 507 | # 'uname': uname, 508 | # 'passwd': passwd, 509 | # 'verify': verify, 510 | # 'proxies': proxies 511 | # } 512 | # } 513 | # return session_dict 514 | 515 | # def __build_session_dict(name, a_key, s_key, url, verify, proxies): 516 | # session_dict = { 517 | # name: { 518 | # 'access_key': a_key, 519 | # 'secret_key': s_key, 520 | # 'api_url': url, 521 | # 'verify': verify, 522 | # 'proxies': proxies 523 | # } 524 | # } 525 | # return session_dict 526 | 527 | # def __get_tenant_credentials(): 528 | 529 | # __c_print('Enter tenant name or any preferred identifier (optional):', color='blue') 530 | # name = input() 531 | 532 | # __c_print('Enter tenant url. (ex: https://app.ca.prismacloud.io):', color='blue') 533 | # url = input() 534 | # print() 535 | # new_url = __validate_url(url) 536 | # if new_url != url: 537 | # __c_print('Adjusted URL:',color='yellow') 538 | # print(new_url) 539 | # print() 540 | 541 | # __c_print('Enter tenant access key:', color='blue') 542 | # a_key = input() 543 | # print() 544 | 545 | # __c_print('Enter tenant secret key:', color='blue') 546 | # s_key = input() 547 | # print() 548 | 549 | # __c_print('Certificate verification: (True/False/)', color='blue') 550 | # __c_print('Leave blank to use default value.', color='yellow') 551 | # verify = input() 552 | # print() 553 | 554 | # __c_print('Proxy section. HTTP first, HTTPS second. Leave blank to not use a proxy.', color='blue') 555 | 556 | # __c_print('Enter HTTP proxy address.', color='blue') 557 | # __c_print('If you are not using a proxy, leave blank.', color='yellow') 558 | # http_proxy = input() 559 | # print() 560 | 561 | # __c_print('Enter HTTPS proxy address.', color='blue') 562 | # __c_print('If you are not using a proxy, leave blank.', color='yellow') 563 | # https_proxy = input() 564 | # print() 565 | 566 | # proxies = None 567 | # if http_proxy or https_proxy: 568 | # proxies = { 569 | # 'http': http_proxy, 570 | # 'https': https_proxy 571 | # } 572 | 573 | # if verify == '': 574 | # verify = True 575 | # elif verify.lower() == 'true': 576 | # verify = True 577 | # elif verify.lower() == 'false': 578 | # verify = False 579 | # else: 580 | # pass 581 | 582 | 583 | # return name, a_key, s_key, new_url, verify, proxies 584 | 585 | #============================================================================== 586 | 587 | # def __get_min_cwp_credentials_from_user(min_tenants): 588 | # credentials = [] 589 | # tenants_added = 0 590 | 591 | # while True: 592 | # valid = False 593 | # while not valid: 594 | # __c_print('Enter credentials for the console', color='blue') 595 | # print() 596 | # name, url, uname, passwd, verify, proxies = __get_cwp_tenant_credentials() 597 | 598 | # valid = __validate_cwp_credentials(name, url, uname, passwd, verify, proxies) 599 | # if valid == False: 600 | # __c_print('FAILED', end=' ', color='red') 601 | # print('Invalid credentials. Please re-enter your credentials') 602 | # print() 603 | # else: 604 | # credentials.append(__build_cwp_session_dict(name, url, uname, passwd, verify, proxies)) 605 | # tenants_added += 1 606 | 607 | # if tenants_added >= min_tenants: 608 | # __c_print('Would you like to add an other tenant? Y/N') 609 | # choice = input().lower() 610 | 611 | # if choice != 'yes' and choice != 'y': 612 | # break 613 | 614 | # return credentials 615 | 616 | # def __get_cwp_credentials_from_user(num_tenants): 617 | # #Gets the source tenant credentials and ensures that are valid 618 | # credentials = [] 619 | 620 | # if num_tenants != -1: 621 | # for i in range(num_tenants): 622 | # valid = False 623 | # while not valid: 624 | # __c_print('Enter credentials for the console', color='blue') 625 | # print() 626 | # name, url, uname, passwd, verify, proxies = __get_cwp_tenant_credentials() 627 | 628 | # valid = __validate_cwp_credentials(name, url, uname, passwd, verify, proxies) 629 | # if valid == False: 630 | # __c_print('FAILED', end=' ', color='red') 631 | # print('Invalid credentials. Please re-enter your credentials') 632 | # print() 633 | # else: 634 | # credentials.append(__build_cwp_session_dict(name, url, uname, passwd, verify, proxies)) 635 | 636 | # return credentials 637 | # else: 638 | # while True: 639 | # valid = False 640 | # while not valid: 641 | # __c_print('Enter credentials for the console', color='blue') 642 | # print() 643 | # name, url, uname, passwd, verify, proxies = __get_cwp_tenant_credentials() 644 | 645 | # valid = __validate_cwp_credentials(name, url, uname, passwd, verify, proxies) 646 | # if valid == False: 647 | # __c_print('FAILED', end=' ', color='red') 648 | # print('Invalid credentials. Please re-enter your credentials') 649 | # print() 650 | # else: 651 | # credentials.append(__build_cwp_session_dict(name, url, uname, passwd, verify, proxies)) 652 | 653 | # __c_print('Would you like to add an other tenant? Y/N') 654 | # choice = input().lower() 655 | 656 | # if choice != 'yes' and choice != 'y': 657 | # break 658 | 659 | # return credentials 660 | 661 | 662 | # def __get_min_credentials_from_user(min_tenants): 663 | # credentials = [] 664 | # tenants_added = 0 665 | # while True: 666 | # valid = False 667 | # while not valid: 668 | # __c_print('Enter credentials for the tenant', color='blue') 669 | # print() 670 | # src_name, src_a_key, src_s_key, src_url, verify, proxies = __get_tenant_credentials() 671 | 672 | # valid = __validate_credentials(src_a_key, src_s_key, src_url, verify, proxies) 673 | # if valid == False: 674 | # __c_print('FAILED', end=' ', color='red') 675 | # print('Invalid credentials. Please re-enter your credentials') 676 | # print() 677 | # else: 678 | # credentials.append(__build_session_dict(src_name, src_a_key, src_s_key, src_url, verify, proxies)) 679 | # tenants_added += 1 680 | 681 | # if tenants_added >= min_tenants: 682 | # __c_print('Would you like to add an other tenant? Y/N') 683 | # choice = input().lower() 684 | 685 | # if choice != 'yes' and choice != 'y': 686 | # break 687 | 688 | # return credentials 689 | 690 | # def __get_cwp_tenant_credentials(): 691 | 692 | # __c_print('Enter console name or any preferred identifier (optional):', color='blue') 693 | # name = input() 694 | 695 | # __c_print('Enter console base url with port number:', color='blue') 696 | # url = input() 697 | # print() 698 | 699 | # __c_print('Enter console username:', color='blue') 700 | # uname = input() 701 | # print() 702 | 703 | # __c_print('Enter console password:', color='blue') 704 | # passwd = input() 705 | # print() 706 | 707 | # __c_print('Certificate verification: (True/False/)', color='blue') 708 | # __c_print('Leave blank to use default value.', color='yellow') 709 | # verify = input() 710 | # print() 711 | 712 | # __c_print('Proxy section. HTTP first, HTTPS second. Leave blank to not use a proxy.', color='blue') 713 | 714 | # __c_print('Enter HTTP proxy address.', color='blue') 715 | # __c_print('If you are not using a proxy, leave blank.', color='yellow') 716 | # http_proxy = input() 717 | # print() 718 | 719 | # __c_print('Enter HTTPS proxy address.', color='blue') 720 | # __c_print('If you are not using a proxy, leave blank.', color='yellow') 721 | # https_proxy = input() 722 | # print() 723 | 724 | # proxies = None 725 | # if http_proxy or https_proxy: 726 | # proxies = { 727 | # 'http': http_proxy, 728 | # 'https': https_proxy 729 | # } 730 | 731 | # if verify == '': 732 | # verify = True 733 | # elif verify.lower() == 'true': 734 | # verify = True 735 | # elif verify.lower() == 'false': 736 | # verify = False 737 | # else: 738 | # pass 739 | 740 | # return name, url, uname, passwd, verify, proxies 741 | 742 | 743 | # def __get_credentials_from_user(num_tenants): 744 | # #Gets the source tenant credentials and ensures that are valid 745 | # credentials = [] 746 | 747 | # if num_tenants != -1: 748 | # for i in range(num_tenants): 749 | # valid = False 750 | # while not valid: 751 | # __c_print('Enter credentials for the tenant', color='blue') 752 | # print() 753 | # src_name, src_a_key, src_s_key, src_url, verify, proxies = __get_tenant_credentials() 754 | 755 | # valid = __validate_credentials(src_a_key, src_s_key, src_url, verify, proxies) 756 | # if valid == False: 757 | # __c_print('FAILED', end=' ', color='red') 758 | # print('Invalid credentials. Please re-enter your credentials') 759 | # print() 760 | # else: 761 | # credentials.append(__build_session_dict(src_name, src_a_key, src_s_key, src_url, verify, proxies)) 762 | 763 | # return credentials 764 | # else: 765 | # while True: 766 | # valid = False 767 | # while not valid: 768 | # __c_print('Enter credentials for the tenant', color='blue') 769 | # print() 770 | # src_name, src_a_key, src_s_key, src_url, verify, proxies = __get_tenant_credentials() 771 | 772 | # valid = __validate_credentials(src_a_key, src_s_key, src_url, verify, proxies) 773 | # if valid == False: 774 | # __c_print('FAILED', end=' ', color='red') 775 | # print('Invalid credentials. Please re-enter your credentials') 776 | # print() 777 | # else: 778 | # credentials.append(__build_session_dict(src_name, src_a_key, src_s_key, src_url, verify, proxies)) 779 | 780 | # __c_print('Would you like to add an other tenant? Y/N') 781 | # choice = input().lower() 782 | 783 | # if choice != 'yes' and choice != 'y': 784 | # break 785 | 786 | # return credentials 787 | 788 | # def __load_uuid_yaml(file_name, logger=py_logger): 789 | # with open(file_name, "r") as file: 790 | # cfg = yaml.load(file, Loader=yaml.BaseLoader) 791 | 792 | # credentials = cfg['credentials'] 793 | # entity_type = cfg['type'] 794 | # uuid = cfg['uuid'] 795 | # cmp_type = cfg['cmp_type'] 796 | 797 | # tenant_sessions = [] 798 | # for tenant in credentials: 799 | # tenant_name = '' 800 | # tenant_keys = tenant.keys() 801 | # for name in tenant_keys: 802 | # tenant_name = name 803 | 804 | # a_key = tenant[tenant_name]['access_key'] 805 | # s_key = tenant[tenant_name]['secret_key'] 806 | # api_url = tenant[tenant_name]['api_url'] 807 | 808 | # tenant_sessions.append(SaaSSessionManager(tenant_name, a_key, s_key, api_url, logger)) 809 | 810 | # return tenant_sessions, entity_type, uuid, cmp_type 811 | 812 | #============================================================================== 813 | 814 | # def onprem_load_from_env(logger=py_logger) -> object: 815 | # error_exit = False 816 | 817 | # name = 'Console' 818 | # try: 819 | # name = os.environ['PC_CONSOLE_NAME'] 820 | # except: 821 | # logger.warning('Missing \'PC_CONSOLE_NAME\' environment variable. Using default name.') 822 | 823 | # api_url = '' 824 | # api = None 825 | # try: 826 | # api_url = os.environ['PC_CONSOLE_URL'] 827 | # api = __validate_url(api_url) 828 | # except: 829 | # logger.error('Missing \'PC_CONSOLE_URL\' environment variable.') 830 | # error_exit = True 831 | 832 | # uname = None 833 | # try: 834 | # uname = os.environ['PC_CONSOLE_USERNAME'] 835 | # except: 836 | # logger.error('Missing \'PC_CONSOLE_USERNAME\' environment variable.') 837 | # error_exit = True 838 | 839 | # passwd = None 840 | # try: 841 | # passwd = os.environ['PC_CONSOLE_PASSWORD'] 842 | # except: 843 | # logger.error('Missing \'PC_CONSOLE_PASSWORD\' environment variable.') 844 | # error_exit = True 845 | 846 | # verify = True 847 | # try: 848 | # verify = os.environ['PC_API_VERIFY'] 849 | # if verify.lower() == 'false': 850 | # verify = False 851 | # if verify.lower() == 'true': 852 | # verify = True 853 | # except: 854 | # logger.warning('Missing \'PC_API_VERIFY\' environment variable. Using default value...') 855 | 856 | 857 | # if error_exit: 858 | # logger.info('Missing required environment variables. Exiting...') 859 | # # print('Missing Env Variables. Exiting...') 860 | # exit() 861 | 862 | # return CWPSessionManager(name, api_url, uname, passwd, verify, False, logger) 863 | 864 | # #============================================================================== 865 | # def onprem_load_min_from_file(min_tenants, file_path='console_credentials.yml', logger=py_logger): 866 | # ''' 867 | # Reads console_credentials.yml or specified file path to load 868 | # self hosted CWP console credentials to create a session. 869 | # Returns a CWP session object. 870 | # ''' 871 | # #Open and load config file 872 | # if not os.path.exists(file_path): 873 | # #Create credentials yml file 874 | # __c_print('No credentials file found. Generating...', color='yellow') 875 | # print() 876 | # tenants = __get_min_cwp_credentials_from_user(min_tenants) 877 | # with open(file_path, 'w') as yml_file: 878 | # for tenant in tenants: 879 | # yaml.dump(tenant, yml_file, default_flow_style=False) 880 | 881 | # cfg = {} 882 | # with open(file_path, "r") as file: 883 | # cfg = yaml.load(file, Loader=yaml.BaseLoader) 884 | 885 | # #Parse cfg for tenant names and create tokens for each tenant 886 | # tenant_sessions = [] 887 | # for tenant in cfg: 888 | # uname = cfg[tenant]['uname'] 889 | # passwd = cfg[tenant]['passwd'] 890 | # api_url = cfg[tenant]['url'] 891 | # verify = True 892 | # try: 893 | # verify = cfg[tenant]['verify'] 894 | # if verify.lower() == 'false': 895 | # verify = False 896 | # if verify.lower() == 'true': 897 | # verify = True 898 | # except: 899 | # pass 900 | 901 | # tenant_sessions.append(CWPSessionManager(tenant, api_url, uname=uname, passwd=passwd, verify=verify, project_flag=False, logger=logger)) 902 | 903 | # return tenant_sessions 904 | 905 | # def onprem_load_multi_from_file(file_path='console_credentials.yml', logger=py_logger, num_tenants=-1) -> list: 906 | # ''' 907 | # Reads console_credentials.yml or specified file path to load 908 | # self hosted CWP console credentials to create a session. 909 | # Returns a CWP session object. 910 | # ''' 911 | # #Open and load config file 912 | # if not os.path.exists(file_path): 913 | # #Create credentials yml file 914 | # __c_print('No credentials file found. Generating...', color='yellow') 915 | # print() 916 | # tenants = __get_cwp_credentials_from_user(num_tenants) 917 | # with open(file_path, 'w') as yml_file: 918 | # for tenant in tenants: 919 | # yaml.dump(tenant, yml_file, default_flow_style=False) 920 | 921 | # cfg = {} 922 | # with open(file_path, "r") as file: 923 | # cfg = yaml.load(file, Loader=yaml.BaseLoader) 924 | 925 | # #Parse cfg for tenant names and create tokens for each tenant 926 | # tenant_sessions = [] 927 | # for tenant in cfg: 928 | # uname = cfg[tenant]['uname'] 929 | # passwd = cfg[tenant]['passwd'] 930 | # api_url = cfg[tenant]['url'] 931 | # verify = True 932 | # try: 933 | # verify = cfg[tenant]['verify'] 934 | # if verify.lower() == 'false': 935 | # verify = False 936 | # if verify.lower() == 'true': 937 | # verify = True 938 | # except: 939 | # pass 940 | 941 | # tenant_sessions.append(CWPSessionManager(tenant, api_url, uname=uname, passwd=passwd, verify=verify, project_flag=False, logger=logger)) 942 | 943 | 944 | # return tenant_sessions 945 | 946 | # def onprem_load_from_file(file_path='console_credentials.yml', logger=py_logger) -> list: 947 | # ''' 948 | # Reads console_credentials.yml or specified file path to load 949 | # self hosted CWP console credentials to create a session. 950 | # Returns a CWP session object. 951 | # ''' 952 | # #Open and load config file 953 | # if not os.path.exists(file_path): 954 | # #Create credentials yml file 955 | # __c_print('No credentials file found. Generating...', color='yellow') 956 | # print() 957 | # tenants = __get_cwp_credentials_from_user(1) 958 | # with open(file_path, 'w') as yml_file: 959 | # for tenant in tenants: 960 | # yaml.dump(tenant, yml_file, default_flow_style=False) 961 | 962 | # cfg = {} 963 | # with open(file_path, "r") as file: 964 | # cfg = yaml.load(file, Loader=yaml.BaseLoader) 965 | 966 | # #Parse cfg for tenant names and create tokens for each tenant 967 | # tenant_sessions = [] 968 | # for tenant in cfg: 969 | # uname = cfg[tenant]['uname'] 970 | # passwd = cfg[tenant]['passwd'] 971 | # api_url = cfg[tenant]['url'] 972 | # verify = True 973 | # try: 974 | # verify = cfg[tenant]['verify'] 975 | # if verify.lower() == 'false': 976 | # verify = False 977 | # if verify.lower() == 'true': 978 | # verify = True 979 | # except: 980 | # pass 981 | 982 | # tenant_sessions.append(CWPSessionManager(tenant, api_url, uname=uname, passwd=passwd, verify=verify, project_flag=False, logger=logger)) 983 | 984 | # try: 985 | # return tenant_sessions[0] 986 | # except: 987 | # logger.error('Error - No credentials found. Exiting...') 988 | # # print('Missing Credentials. Exiting...') 989 | # exit() 990 | 991 | # def load_min_from_file(min_tenants, file_path='tenant_credentials.yml', logger=py_logger) -> list: 992 | # ''' 993 | # Reads config.yml and generates a Session object for the tenant 994 | # Returns: 995 | # Tenant Session object 996 | # ''' 997 | # #Open and load config file 998 | # if not os.path.exists(file_path): 999 | # #Create credentials yml file 1000 | # __c_print('No credentials file found. Generating...', color='yellow') 1001 | # print() 1002 | # tenants = __get_min_credentials_from_user(min_tenants) 1003 | # with open(file_path, 'w') as yml_file: 1004 | # for tenant in tenants: 1005 | # yaml.dump(tenant, yml_file, default_flow_style=False) 1006 | 1007 | # with open(file_path, "r") as file: 1008 | # cfg = yaml.load(file, Loader=yaml.BaseLoader) 1009 | 1010 | # #Parse cfg for tenant names and create tokens for each tenant 1011 | # tenant_sessions = [] 1012 | # for tenant in cfg: 1013 | # a_key = cfg[tenant]['access_key'] 1014 | # s_key = cfg[tenant]['secret_key'] 1015 | # api_url = cfg[tenant]['api_url'] 1016 | # verify = True 1017 | # try: 1018 | # verify = cfg[tenant]['verify'] 1019 | # if verify.lower() == 'false': 1020 | # verify = False 1021 | # if verify.lower() == 'true': 1022 | # verify = True 1023 | # except: 1024 | # pass 1025 | 1026 | # tenant_sessions.append(SaaSSessionManager(tenant, a_key, s_key, api_url, verify, logger)) 1027 | 1028 | # return tenant_sessions 1029 | 1030 | # def load_multi_from_file(file_path='tenant_credentials.yml', logger=py_logger, num_tenants=-1) -> list: 1031 | # ''' 1032 | # Reads config.yml and generates a Session object for the tenant 1033 | # Returns: 1034 | # Tenant Session object 1035 | # ''' 1036 | # #Open and load config file 1037 | # if not os.path.exists(file_path): 1038 | # #Create credentials yml file 1039 | # __c_print('No credentials file found. Generating...', color='yellow') 1040 | # print() 1041 | # tenants = __get_credentials_from_user(num_tenants) 1042 | # with open(file_path, 'w') as yml_file: 1043 | # for tenant in tenants: 1044 | # yaml.dump(tenant, yml_file, default_flow_style=False) 1045 | 1046 | # with open(file_path, "r") as file: 1047 | # cfg = yaml.load(file, Loader=yaml.BaseLoader) 1048 | 1049 | # #Parse cfg for tenant names and create tokens for each tenant 1050 | # tenant_sessions = [] 1051 | # for tenant in cfg: 1052 | # a_key = cfg[tenant]['access_key'] 1053 | # s_key = cfg[tenant]['secret_key'] 1054 | # api_url = cfg[tenant]['api_url'] 1055 | # verify = True 1056 | # try: 1057 | # verify = cfg[tenant]['verify'] 1058 | # if verify.lower() == 'false': 1059 | # verify = False 1060 | # if verify.lower() == 'true': 1061 | # verify = True 1062 | # except: 1063 | # pass 1064 | 1065 | # tenant_sessions.append(SaaSSessionManager(tenant, a_key, s_key, api_url, verify, logger)) 1066 | 1067 | 1068 | # return tenant_sessions 1069 | 1070 | # def load_from_file(file_path='tenant_credentials.yml', logger=py_logger) -> list: 1071 | # ''' 1072 | # Reads config.yml and generates a Session object for the tenant 1073 | # Returns: 1074 | # Tenant Session object 1075 | # ''' 1076 | # #Open and load config file 1077 | # if not os.path.exists(file_path): 1078 | # #Create credentials yml file 1079 | # __c_print('No credentials file found. Generating...', color='yellow') 1080 | # print() 1081 | # tenants = __get_credentials_from_user(1) 1082 | # with open(file_path, 'w') as yml_file: 1083 | # for tenant in tenants: 1084 | # yaml.dump(tenant, yml_file, default_flow_style=False) 1085 | 1086 | # with open(file_path, "r") as file: 1087 | # cfg = yaml.load(file, Loader=yaml.BaseLoader) 1088 | 1089 | # #Parse cfg for tenant names and create tokens for each tenant 1090 | # tenant_sessions = [] 1091 | # for tenant in cfg: 1092 | # a_key = cfg[tenant]['access_key'] 1093 | # s_key = cfg[tenant]['secret_key'] 1094 | # api_url = cfg[tenant]['api_url'] 1095 | # verify = True 1096 | # try: 1097 | # verify = cfg[tenant]['verify'] 1098 | # if verify.lower() == 'false': 1099 | # verify = False 1100 | # if verify.lower() == 'true': 1101 | # verify = True 1102 | # except: 1103 | # pass 1104 | 1105 | # tenant_sessions.append(SaaSSessionManager(tenant, a_key, s_key, api_url, verify, logger)) 1106 | 1107 | # try: 1108 | # return tenant_sessions[0] 1109 | # except: 1110 | # logger.error('Error - No credentials found. Exiting...') 1111 | # # print('No Creds Found. Exiting...') 1112 | # exit() 1113 | 1114 | # def load_from_env(logger=py_logger) -> object: 1115 | # error_exit = False 1116 | 1117 | # name = 'Tenant' 1118 | # try: 1119 | # name = os.environ['PC_TENANT_NAME'] 1120 | # except: 1121 | # logger.warning('Missing \'PC_TENANT_NAME\' environment variable. Using default name.') 1122 | 1123 | # api_url = '' 1124 | # api = None 1125 | # try: 1126 | # api_url = os.environ['PC_TENANT_API'] 1127 | # api = __validate_url(api_url) 1128 | # except: 1129 | # logger.error('Missing \'PC_TENANT_API\' environment variable.') 1130 | # error_exit = True 1131 | 1132 | # a_key = None 1133 | # try: 1134 | # a_key = os.environ['PC_TENANT_A_KEY'] 1135 | # except: 1136 | # logger.error('Missing \'PC_TENANT_A_KEY\' environment variable.') 1137 | # error_exit = True 1138 | 1139 | # s_key = None 1140 | # try: 1141 | # s_key = os.environ['PC_TENANT_S_KEY'] 1142 | # except: 1143 | # logger.error('Missing \'PC_TENANT_S_KEY\' environment variable.') 1144 | # error_exit = True 1145 | 1146 | # verify = True 1147 | # try: 1148 | # verify = os.environ['PC_API_VERIFY'] 1149 | # if verify.lower() == 'false': 1150 | # verify = False 1151 | # if verify.lower() == 'true': 1152 | # verify = True 1153 | # except: 1154 | # logger.warning('Missing \'PC_API_VERIFY\' environment variable. Using default value...') 1155 | 1156 | # if error_exit: 1157 | # logger.info('Missing required environment variables. Exiting...') 1158 | # # print('Missing Environment variables. Exiting...') 1159 | # exit() 1160 | 1161 | # return SaaSSessionManager(name, a_key, s_key, api_url, verify, logger) 1162 | 1163 | # def load_min_from_user(min_tenants, logger=py_logger): 1164 | # tenant_sessions = [] 1165 | # tenants = __get_min_credentials_from_user(min_tenants) 1166 | # for tenant in tenants: 1167 | # for key in tenant: 1168 | # name = key 1169 | # verify = True 1170 | # try: 1171 | # verify = tenant[name]['verify'] 1172 | # if verify.lower() == 'false': 1173 | # verify = False 1174 | # if verify.lower() == 'true': 1175 | # verify = True 1176 | # except: 1177 | # pass 1178 | 1179 | # tenant_sessions.append(SaaSSessionManager(name, tenant[name]['access_key'], tenant[name]['secret_key'], tenant[name]['api_url'], verify, logger)) 1180 | 1181 | # return tenant_sessions 1182 | 1183 | # def load_multi_from_user(logger=py_logger, num_tenants=-1) -> list: 1184 | # tenant_sessions = [] 1185 | # tenants = __get_credentials_from_user(num_tenants) 1186 | # for tenant in tenants: 1187 | # for key in tenant: 1188 | # name = key 1189 | # verify = True 1190 | # try: 1191 | # verify = tenant[name]['verify'] 1192 | # if verify.lower() == 'false': 1193 | # verify = False 1194 | # if verify.lower() == 'true': 1195 | # verify = True 1196 | # except: 1197 | # pass 1198 | 1199 | # tenant_sessions.append(SaaSSessionManager(name, tenant[name]['access_key'], tenant[name]['secret_key'], tenant[name]['api_url'], verify, logger)) 1200 | 1201 | # return tenant_sessions 1202 | 1203 | # def load_from_user(logger=py_logger) -> list: 1204 | # tenant_sessions = [] 1205 | # tenants = __get_credentials_from_user(1) 1206 | # for tenant in tenants: 1207 | # for key in tenant: 1208 | # name = key 1209 | # verify = True 1210 | # try: 1211 | # verify = tenant[name]['verify'] 1212 | # if verify.lower() == 'false': 1213 | # verify = False 1214 | # if verify.lower() == 'true': 1215 | # verify = True 1216 | # except: 1217 | # pass 1218 | 1219 | # tenant_sessions.append(SaaSSessionManager(name, tenant[name]['access_key'], tenant[name]['secret_key'], tenant[name]['api_url'], verify, logger)) 1220 | 1221 | # return tenant_sessions[0] 1222 | 1223 | -------------------------------------------------------------------------------- /ssl_gen.py: -------------------------------------------------------------------------------- 1 | """ Utility to create a CA bundle including GlobalProtect certificates """ 2 | #Install pythons openssl library 3 | #pip3 install pyopenssl 4 | 5 | import argparse 6 | import socket 7 | import OpenSSL 8 | 9 | import certifi 10 | 11 | # --Description-- # 12 | 13 | # Prisma Cloud SSL Helper (requires 'pip install certifi pyopenssl') 14 | 15 | # --Configuration-- # 16 | 17 | pc_arg_parser = argparse.ArgumentParser() 18 | pc_arg_parser.add_argument( 19 | '--api', 20 | default='api.prismacloud.io', 21 | type=str, 22 | help='(Optional) - Prisma Cloud API/UI Base URL') 23 | pc_arg_parser.add_argument( 24 | '--api_port', 25 | default=443, 26 | type=int, 27 | help='(Optional) - Prisma Cloud API/UI Port.') 28 | pc_arg_parser.add_argument( 29 | '--ca_bundle', 30 | default='globalprotect_certifi.txt', 31 | type=str, 32 | help='(Optional) - Custom CA (bundle) file to create') 33 | args = pc_arg_parser.parse_args() 34 | 35 | src_ca_file = certifi.where() 36 | dst_ca_file = args.ca_bundle 37 | host_name = args.api 38 | port = args.api_port 39 | panw_subjects = [ 40 | '/C=US/ST=CA/O=paloalto networks/OU=IT/CN=decrypt.paloaltonetworks.com', 41 | '/DC=local/DC=paloaltonetworks/CN=Palo Alto Networks Inc Domain CA', 42 | '/C=US/O=Palo Alto Networks Inc/CN=Palo Alto Networks Inc Root CA' 43 | ] 44 | ssl_context_method = OpenSSL.SSL.TLSv1_2_METHOD # SSL.SSLv23_METHOD 45 | 46 | # --Main-- # 47 | 48 | with open(src_ca_file, 'r') as root_ca_file: 49 | root_certificates = root_ca_file.read() 50 | 51 | with open(dst_ca_file, 'w') as custom_ca_file: 52 | custom_ca_file.write(root_certificates) 53 | context = OpenSSL.SSL.Context(method=ssl_context_method) 54 | context.load_verify_locations(cafile=src_ca_file) 55 | conn = OpenSSL.SSL.Connection(context, socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)) 56 | conn.settimeout(5) 57 | conn.connect((host_name, port)) 58 | conn.setblocking(1) 59 | conn.do_handshake() 60 | conn.set_tlsext_host_name(host_name.encode()) 61 | for (idx, certificate) in enumerate(conn.get_peer_cert_chain()): 62 | subject = ''.join("/{0:s}={1:s}".format(name.decode(), value.decode()) for name, value in certificate.get_subject().get_components()) 63 | issuer = ''.join("/{0:s}={1:s}".format(name.decode(), value.decode()) for name, value in certificate.get_issuer().get_components()) 64 | if subject in panw_subjects: 65 | subject_string = '# Subject: %s' % subject 66 | issuer_string = '# Issuer: %s' % issuer 67 | certificate_string = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, certificate).decode('utf-8') 68 | custom_ca_file.write("\n") 69 | custom_ca_file.write(subject_string) 70 | custom_ca_file.write("\n") 71 | custom_ca_file.write(issuer_string) 72 | custom_ca_file.write("\n") 73 | custom_ca_file.write(certificate_string) 74 | conn.close() 75 | 76 | print('CA bundle file saved as: %s' % dst_ca_file) 77 | print("Use it with these scripts by specifying '--verify %s' on the command line or in your config file" % dst_ca_file) --------------------------------------------------------------------------------