├── .github └── workflows │ ├── main.yml │ ├── policyscan.yml │ ├── publish-to-pypi.yml │ ├── sbom.yml │ └── sca.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── docs ├── analytics.md ├── api.md ├── apicreds.md ├── applications.md ├── businessunits.md ├── collections.md ├── dast.md ├── docs.md ├── dynamic.md ├── findings.md ├── healthcheck.md ├── jitdefaults.md ├── policy.md ├── roles.md ├── sca.md ├── static.md ├── teams.md ├── users.md └── xml.md ├── license ├── pyproject.toml ├── requirements.txt ├── samples ├── dast_sample.py ├── dynamic_sample.py ├── getself.py ├── reportingapi_audit_sample.py ├── reportingapi_deleted_sample.py ├── reportingapi_sample.py ├── scansbom.py ├── static_scan_sample.py ├── user_api_creds_create.py └── xml_sandbox.py ├── setup.cfg ├── setup.py └── veracode_api_py ├── __init__.py ├── analytics.py ├── api.py ├── apihelper.py ├── applications.py ├── collections.py ├── constants.py ├── dast.py ├── dynamic.py ├── exceptions.py ├── findings.py ├── healthcheck.py ├── identity.py ├── log.py ├── policy.py ├── sca.py ├── static.py └── xmlapi.py /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This workflow will initiate a Veracode Static Analysis Pipeline scan, return a results.json and convert to SARIF for upload as a code scanning alert 2 | 3 | name: Veracode Static Analysis Pipeline Scan 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - '**' 12 | pull_request: 13 | branches: 14 | - master 15 | - main 16 | 17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 18 | jobs: 19 | # This workflow contains a job to build and submit pipeline scan, you will need to customize the build process accordingly and make sure the artifact you build is used as the file input to the pipeline scan file parameter 20 | build: 21 | # The type of runner that the job will run on 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | # zip the project and move it to a staging directory 27 | - name: Zip Project 28 | run: zip -R project.zip '*.py' '*.html' '*.htm' '*.js' '*.php' 'requirements.txt' '*.json' '*.lock' '*.ts' '*.pl' '*.pm' '*.plx' '*.pl5' '*.cgi' '*.go' '*.sum' '*.mod' 29 | env: 30 | build-name: project.zip 31 | 32 | - name: Archive package 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: CodePackage 36 | path: project.zip 37 | 38 | pipeline-scan: 39 | needs: build 40 | runs-on: ubuntu-latest 41 | container: 42 | image: veracode/pipeline-scan:latest 43 | options: --user root # our normal luser doesn't have privs to write to github directories 44 | 45 | steps: 46 | - name: Retrieve artifact 47 | uses: actions/download-artifact@v4 48 | with: 49 | name: CodePackage 50 | path: /github/home 51 | 52 | # Submit project to pipeline scan 53 | - name: Pipeline Scan 54 | run: | 55 | cd /github/home 56 | java -jar /opt/veracode/pipeline-scan.jar --veracode_api_id="${{secrets.VERACODE_API_ID}}" --veracode_api_key="${{secrets.VERACODE_API_KEY}}" --fail_on_severity="Very High, High" --file="project.zip" --app_id="${{secrets.VERACODE_APP_ID}}" --json_output_file="results.json" 57 | continue-on-error: true 58 | 59 | - uses: actions/upload-artifact@v4 60 | with: 61 | name: ScanResults 62 | path: /github/home/results.json 63 | 64 | # Convert pipeline scan output to SARIF format 65 | process-results: 66 | needs: pipeline-scan 67 | runs-on: ubuntu-latest 68 | 69 | permissions: 70 | id-token: write 71 | actions: read 72 | contents: read 73 | pull-requests: read 74 | security-events: write 75 | 76 | steps: 77 | 78 | - name: Retrieve results 79 | uses: actions/download-artifact@v4 80 | with: 81 | name: ScanResults 82 | 83 | - name: convert 84 | uses: veracode/veracode-pipeline-scan-results-to-sarif@master 85 | with: 86 | pipeline-results-json: results.json 87 | output-results-sarif: veracode-results.sarif 88 | finding-rule-level: "4:3:0" 89 | - uses: github/codeql-action/upload-sarif@v3 90 | with: 91 | # Path to SARIF file relative to the root of the repository 92 | sarif_file: veracode-results.sarif 93 | -------------------------------------------------------------------------------- /.github/workflows/policyscan.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Veracode Policy Scan 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | workflow_dispatch: 9 | release: 10 | types: [created] 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | # This workflow contains a single job called "build" 15 | build-and-policy-scan: 16 | # The type of runner that the job will run on 17 | runs-on: ubuntu-latest 18 | 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 22 | - uses: actions/checkout@v4 23 | - uses: actions/setup-java@v2 # Make java accessible on path so the uploadandscan action can run. 24 | with: 25 | java-version: '11' 26 | distribution: 'zulu' 27 | 28 | # zip the project and move it to a staging directory 29 | - name: Zip Project 30 | run: zip -R project.zip '*.py' '*.js' '*.php' '*.ts' 31 | env: 32 | build-name: project.zip 33 | - uses: actions/upload-artifact@v4 # Copy files from repository to docker container so the next uploadandscan action can access them. 34 | with: 35 | path: project.zip # Wildcards can be used to filter the files copied into the container. See: https://github.com/actions/upload-artifact 36 | - uses: veracode/veracode-uploadandscan-action@master # Run the uploadandscan action. Inputs are described above. 37 | with: 38 | appname: 'veracode-api-py' 39 | filepath: 'project.zip' 40 | vid: '${{ secrets.VERACODE_API_ID }}' 41 | vkey: '${{ secrets.VERACODE_API_KEY }}' 42 | scantimeout: 15 43 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | build-n-publish: 10 | name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Set up Python 3.10 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: "3.10" 19 | 20 | - name: Install pypa/build 21 | run: >- 22 | python -m 23 | pip install 24 | build 25 | --user 26 | - name: Build a binary wheel and a source tarball 27 | run: >- 28 | python -m 29 | build 30 | --sdist 31 | --wheel 32 | --outdir dist/ 33 | . 34 | 35 | - name: Publish distribution 📦 to Test PyPI 36 | uses: pypa/gh-action-pypi-publish@release/v1 37 | with: 38 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 39 | repository_url: https://test.pypi.org/legacy/ 40 | - name: Publish distribution 📦 to PyPI 41 | if: startsWith(github.ref, 'refs/tags') 42 | uses: pypa/gh-action-pypi-publish@release/v1 43 | with: 44 | password: ${{ secrets.PYPI_API_TOKEN }} 45 | -------------------------------------------------------------------------------- /.github/workflows/sbom.yml: -------------------------------------------------------------------------------- 1 | # This workflow retrieves the SBOM for the application being scanned and saves it as an artifact in the pipeline. 2 | # Required secrets (also used by the pipelinescan-*.yml workflows): 3 | # VERACODE_API_ID, VERACODE_API_KEY: API credentials for a Veracode user 4 | # VERACODE_APP_ID: Numeric application ID for the application profile for this project 5 | 6 | --- 7 | name: SBOM 8 | 9 | on: 10 | workflow_dispatch: 11 | release: 12 | 13 | jobs: 14 | sbom: 15 | name: setup 16 | runs-on: ubuntu-latest 17 | container: 18 | image: veracode/api-signing:latest 19 | env: 20 | VERACODE_API_KEY_ID: ${{ secrets.VERACODE_API_ID }} 21 | VERACODE_API_KEY_SECRET : ${{ secrets.VERACODE_API_KEY }} 22 | 23 | steps: 24 | - name: generate-sbom 25 | run: | 26 | cd /tmp 27 | export LEGACYID=${{ secrets.VERACODE_APP_ID }} 28 | echo LEGACYID: ${LEGACYID} 29 | appguid=$(http --auth-type=veracode_hmac GET "https://api.veracode.com/appsec/v1/applications?legacy_id=${LEGACYID}" | jq -r '._embedded.applications[0].guid') 30 | echo GUID: ${appguid} 31 | http --auth-type=veracode_hmac GET "https://api.veracode.com/srcclr/sbom/v1/targets/${appguid}/cyclonedx?type=application" > sbom.json 32 | ls -l 33 | - name: save sbom file 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: sbom 37 | path: /tmp/sbom.json 38 | -------------------------------------------------------------------------------- /.github/workflows/sca.yml: -------------------------------------------------------------------------------- 1 | # This workflow will initiate a Veracode SCA Scan. Requires the following secrets: 2 | # SRCCLR_API_TOKEN - generated when creating a new integration under the Agents tab 3 | # SCM_GITHUB - https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token 4 | # USER_EMAIL - email address, used in pull requests 5 | # USER_NAME - username, used in pull requests 6 | 7 | name: Veracode SCA Scan 8 | 9 | # Controls when the action will run. Triggers the workflow on push or pull request 10 | # events but only for the master branch 11 | on: 12 | workflow_dispatch: 13 | push: 14 | branches: 15 | - '**' 16 | pull_request: 17 | branches: 18 | - master 19 | - main 20 | 21 | jobs: 22 | opensource-scan: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: SCA Agent scan 27 | env: # Set the secret as an input 28 | SRCCLR_API_TOKEN: ${{ secrets.SRCCLR_API_TOKEN }} 29 | SRCCLR_SCM_TYPE: GITHUB 30 | SRCCLR_SCM_TOKEN: ${{ secrets.SCM_GITHUB }} 31 | SRCCLR_PR_ON: methods 32 | SRCCLR_NO_BREAKING_UPDATES: true 33 | SRCCLR_IGNORE_CLOSED_PRS: true 34 | SRCCLR_SCM_URL: https://github.com/$GITHUB_REPOSITORY 35 | EXTRA_ARGS: '--update-advisor --pull-request --unmatched' 36 | run: | 37 | git config --global user.email "${{ secrets.USER_EMAIL }}" 38 | git config --global user.name "${{ secrets.USER_NAME }}" 39 | curl -sSL https://download.sourceclear.com/ci.sh | sh -s -- scan $EXTRA_ARGS 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.git 2 | /.vscode 3 | /.idea 4 | 5 | play.py 6 | *.xml 7 | *.log 8 | veracode-plugin.conf 9 | *.json 10 | 11 | # https://github.com/github/gitignore/blob/master/Python.gitignore 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | helpers/__pycache__/ 18 | 19 | # C extensions 20 | *.so 21 | 22 | # Distribution / packaging 23 | .Python 24 | build/ 25 | develop-eggs/ 26 | dist/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | lib/ 31 | lib64/ 32 | parts/ 33 | sdist/ 34 | var/ 35 | wheels/ 36 | pip-wheel-metadata/ 37 | share/python-wheels/ 38 | *.egg-info/ 39 | .installed.cfg 40 | *.egg 41 | MANIFEST 42 | 43 | # PyInstaller 44 | # Usually these files are written by a python script from a template 45 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 46 | *.manifest 47 | *.spec 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | htmlcov/ 55 | .tox/ 56 | .nox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | *.py,cover 64 | .hypothesis/ 65 | .pytest_cache/ 66 | cover/ 67 | 68 | # Translations 69 | *.mo 70 | *.pot 71 | 72 | # Django stuff: 73 | *.log 74 | local_settings.py 75 | db.sqlite3 76 | db.sqlite3-journal 77 | 78 | # Flask stuff: 79 | instance/ 80 | .webassets-cache 81 | 82 | # Scrapy stuff: 83 | .scrapy 84 | 85 | # Sphinx documentation 86 | docs/_build/ 87 | 88 | # PyBuilder 89 | .pybuilder/ 90 | target/ 91 | 92 | # Jupyter Notebook 93 | .ipynb_checkpoints 94 | 95 | # IPython 96 | profile_default/ 97 | ipython_config.py 98 | 99 | # pyenv 100 | # For a library or package, you might want to ignore these files since the code is 101 | # intended to run in multiple environments; otherwise, check them in: 102 | # .python-version 103 | 104 | # pipenv 105 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 106 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 107 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 108 | # install all needed dependencies. 109 | #Pipfile.lock 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .env 123 | .venv 124 | env/ 125 | venv/ 126 | ENV/ 127 | env.bak/ 128 | venv.bak/ 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # Cython debug symbols 152 | cython_debug/ 153 | .DS_Store 154 | helpers/__pycache__/api.cpython-37.pyc 155 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Please follow the instructions below to make a contribution. 4 | 5 | ## Table of Contents 6 | 7 | - [Making a Change](#making-a-change) 8 | - [Contributors](#contributors) 9 | 10 | ## Making a Change 11 | 12 | - Changes should be submitted as pull requests. 13 | - Ensure that your change has an issue associated with it. 14 | - If you add or change a method, please ensure that you update the README as part of your pull request. 15 | 16 | ## Contributors 17 | 18 | - [Tim Jarrett](https://github.com/tjarrettveracode) 19 | - [Mastermargie](https://github.com/mastermargie) 20 | - [Billy Tinnes](https://github.com/DaYuM) 21 | - [Dennis Medeiros](https://github.com/dennismedeiros) 22 | - [Nazareno Furchì](https://github.com/nazafur) 23 | - [Andrzej Szaryk](https://github.com/aszaryk) 24 | - [Gabriel Marquet](https://github.com/Gby56) 25 | - [Aaron Butler](https://github.com/AaronButler-Veracode) 26 | - [Henry Post](https://github.com/HenryFBP) 27 | - [Bnreplah](https://github.com/bnreplah) 28 | - [Ashton Syed](https://github.com/ashtonsyed) 29 | - [Jbrule](https://github.com/jbrule) 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tim Jarrett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # MANIFEST.in 2 | graft veracode_api_py 3 | graft samples 4 | 5 | include README.md 6 | include requirements.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Veracode API Python 2 | 3 | Python helper library for working with the Veracode APIs. Handles retries, pagination, and other features of the modern Veracode REST APIs. 4 | 5 | Not an official Veracode product. Heavily based on original work by [CTCampbell](https://github.com/ctcampbell). 6 | 7 | ## Setup 8 | 9 | Install from pypi: 10 | 11 | pip install veracode-api-py 12 | 13 | ### Authenticating from a developer machine 14 | 15 | Save Veracode API credentials in `~/.veracode/credentials` 16 | 17 | [default] 18 | veracode_api_key_id = 19 | veracode_api_key_secret = 20 | 21 | ### Authenticating from a pipeline 22 | 23 | Set Veracode API credentials as environment variables. 24 | 25 | export VERACODE_API_KEY_ID= 26 | export VERACODE_API_KEY_SECRET= 27 | 28 | ### Authenticating through a proxy 29 | 30 | To use this library (or a script based on it) with a proxy server, set environment variables with the address of the proxy: 31 | 32 | export HTTP_PROXY='http://10.10.10.10:8000' 33 | export HTTPS_PROXY='http://10.10.10.10:1212' 34 | 35 | ## Use in your applications 36 | 37 | Import VeracodeAPI or one of the individual API classes into your code and call the methods. Most methods return JSON or XML depending on the underlying API. 38 | 39 | You can find sample scripts for some APIs in the [Samples folder](https://github.com/veracode/veracode-api-py/tree/main/samples). 40 | 41 | ## Docs 42 | 43 | For detailed documentation on the available methods, please see the [veracode-api-py docs](https://github.com/veracode/veracode-api-py/blob/main/docs/docs.md). 44 | 45 | ## Notes 46 | 47 | 1. Different API calls require different roles or permissions. Consult the [Veracode Docs](https://docs.veracode.com/r/c_role_permissions). 48 | 2. This library does not include a complete set of Veracode API methods. In particular, it only provides a handful of XML API methods. 49 | 3. Contributions are welcome. See the [Contributions guidelines](CONTRIBUTING.md). 50 | -------------------------------------------------------------------------------- /docs/analytics.md: -------------------------------------------------------------------------------- 1 | # Analytics 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | **Notes**: 6 | 7 | 1. The Reporting API is available to Veracode customers by request. More information about the API is available in the [Veracode Docs](https://docs.veracode.com/r/Reporting_REST_API). 8 | 9 | - `Analytics().create_report(report_type ('findings'),last_updated_start_date(opt), last_updated_end_date (opt), scan_type (opt), finding_status(opt), passed_policy(opt), policy_sandbox(opt), application_id(opt), rawjson(opt), deletion_start_date(opt), deletion_end_date(opt), start_date(opt), end_date(opt), audit_action(opt), target_user_id(opt), modifier_user_id(opt))`: set up a request for a report. By default this command returns the GUID of the report request; specify `rawjson=True` to get the full response. Dates should be specified as `YYYY-MM-DD HH:MM:SS` with the timestamp optional. Options include: 10 | - `report_type`: required, currently supports `findings`, `scans`, `deletedscans`, and `audit`. 11 | - `last_updated_start_date`: required for `findings` report type, beginning of date range for new or changed findings or scans 12 | - `last_updated_end_date`: optional, end of date range for new or changed findings or scans 13 | - `scan_type`: optional, one or more of 'Static Analysis', 'Dynamic Analysis', 'Manual', 'Software Composition Analysis', 'SCA'. `SCA` is only supported for the `findings` report type. 14 | - `finding_status`: optional, 'Open' or 'Closed'. Applies only to the `findings` report. 15 | - `passed_policy`: optional, boolean. Applies only to the `findings` report. 16 | - `policy_sandbox`: optional, 'Policy' or 'Sandbox' 17 | - `application_id`: optional, application ID for which to return results 18 | - `rawjson`: optional, defaults to False. Returns full response if True, the GUID of the request if false 19 | - `deletion_start_date`: required for `deletedscans` report type, beginning of date range for deleted scans. 20 | - `deletion_end_date`: optional, end of date range for deleted scans. 21 | - `sandbox_ids`: optional, array of sandbox IDs (integers) for which to return results 22 | - `start_date`: required for `audit` report type, beginning of date range for audit events 23 | - `end_date`: optional for `audit` report type, end of date range for audit events 24 | - `audit_action`: optional for `audit` report type. An array of audit actions to include in the report. Examples include `Login`, `Login Account`, `Auth`, `Create`, `Delete`, `Update` 25 | - `target_user_id`: optional for `audit` report type. The numeric user id of the user for which you want to retrieve audit events that happened to the user. 26 | - `modifier_user_id`: optional for `audit` report type. The numeric user id of the user for which you want to retrieve audit events that were caused by the user. 27 | 28 | - `Analytics().create_findings_report(last_updated_start_date(opt), last_updated_end_date (opt), scan_type (opt), finding_status(opt), passed_policy(opt), policy_sandbox(opt), application_id(opt), rawjson(opt))`: set up a request for a findings report. By default this command returns the GUID of the report request; specify `rawjson=True` to get the full response. Dates should be specified as `YYYY-MM-DD HH:MM:SS` with the timestamp optional. Options include: 29 | - `start_date`: required, beginning of date range for findings 30 | - `end_date`: optional, end of date range for findings 31 | - `scan_type`: optional, one or more of 'Static Analysis', 'Dynamic Analysis', 'Manual', 'Software Composition Analysis', 'SCA'. 32 | - `finding_status`: optional, 'Open' or 'Closed'. Applies only to the `findings` report. 33 | - `passed_policy`: optional, boolean. Applies only to the `findings` report. 34 | - `policy_sandbox`: optional, 'Policy' or 'Sandbox' 35 | - `application_id`: optional. The numeric application id of an application whose findings you want to include in the report. 36 | - `rawjson`: optional, defaults to False. Returns full response if True, the GUID of the request if false 37 | 38 | - `Analytics().create_scans_report(start_date, end_date(opt), scan_type(opt), policy_sandbox(opt), application_id(opt), rawjson(opt))`: set up a request for a scans report. By default this command returns the GUID of the report request; specify `rawjson=True` to get the full response. Dates should be specified as `YYYY-MM-DD HH:MM:SS` with the timestamp optional. Options include: 39 | - `start_date`: required, beginning of date range for scans 40 | - `end_date`: optional, end of date range for scans 41 | - `scan_type`: optional, one or more of 'Static Analysis', 'Dynamic Analysis', 'Manual'. 42 | - `policy_sandbox`: optional, 'Policy' or 'Sandbox' 43 | - `application_id`: optional. The numeric application id of an application whose scans you want to include in the report. 44 | - `rawjson`: optional, defaults to False. Returns full response if True, the GUID of the request if false 45 | 46 | - `Analytics().create_deleted_scans_report(start_date, end_date(opt), application_id(opt),rawjson(opt))`: set up a request for a deleted scans report. By default this command returns the GUID of the report request; specify `rawjson=True` to get the full response. Dates should be specified as `YYYY-MM-DD HH:MM:SS` with the timestamp optional. Options include: 47 | - `start_date`: required, beginning of date range for deleted scans 48 | - `end_date`: optional, end of date range for deleted scans 49 | - `application_id`: optional. The numeric application id of an application whose deleted scans you want to include in the report. 50 | - `rawjson`: optional, defaults to False. Returns full response if True, the GUID of the request if false 51 | 52 | - `Analytics().create_audit_report(start_date, end_date(opt), audit_action(opt), target_user_id(opt), modifier_user_id(opt),rawjson(opt))`: set up a request for an audit log report. By default this command returns the GUID of the report request; specify `rawjson=True` to get the full response. Dates should be specified as `YYYY-MM-DD HH:MM:SS` with the timestamp optional. Options include: 53 | - `start_date`: required, beginning of date range for audit events 54 | - `end_date`: optional, end of date range for audit events 55 | - `audit_action`: optional. An array of audit actions to include in the report. Examples include `Success`, `Logged out`, `Create`, `Delete`, `Update` 56 | - `target_user_id`: optional. The numeric user id of the user for which you want to retrieve audit events that happened to the user. 57 | - `modifier_user_id`: optional. The numeric user id of the user for which you want to retrieve audit events that were caused by the user. 58 | - `rawjson`: optional, defaults to False. Returns full response if True, the GUID of the request if false 59 | 60 | - `Analytics().get(guid, report_type(findings))`: check the status of the report request and return the report contents when ready. Note that this method returns a tuple of `status` (string) and `results` (list); when `status` is `COMPLETED`, the `results` list will populate with results. Also, you need to specify the type of data expected by the GUID with `report_type`; this defaults to `findings`. 61 | 62 | - `Analytics().get_findings(guid)`: check the status of a findings report request specified by `guid` and return the report contents when ready. Note that this method returns a tuple of `status` (string) and `results` (list); when `status` is `COMPLETED`, the `results` list will populate with results. 63 | 64 | - `Analytics().get_scans(guid)`: check the status of a scans report request specified by `guid` and return the report contents when ready. Note that this method returns a tuple of `status` (string) and `results` (list); when `status` is `COMPLETED`, the `results` list will populate with results. 65 | 66 | - `Analytics().get_deletedscans(guid)`: check the status of a deleted scans report request specified by `guid` and return the report contents when ready. Note that this method returns a tuple of `status` (string) and `results` (list); when `status` is `COMPLETED`, the `results` list will populate with results. 67 | 68 | [All docs](docs.md) 69 | -------------------------------------------------------------------------------- /docs/apicreds.md: -------------------------------------------------------------------------------- 1 | # API Credentials 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | **Notes**: 6 | 7 | 1. You can also access these methods from the `APICredentials` class. 8 | 1. Calling these methods requires a user with the [Administator, Team Admin,](https://docs.veracode.com/r/c_role_permissions) or [Admin API](https://docs.veracode.com/r/c_API_roles_details) user role. 9 | 10 | - `APICredentials().get(api_id)`: get credentials information (API ID and expiration date) for the user specified by `api_id`. 11 | - `APICredentials().get_self()`: get credentials information for the user calling the API. 12 | - `APICredentials().create(user_guid)`: create or renew API credentials for the API service account specified by `user_guid`. 13 | - `APICredentials().renew()`: renew credentials for the current user. NOTE: you must note the return from this call as the API key cannot be viewed again. 14 | - `APICredentials().revoke (api_id)`: revoke immediately the API credentials identified by `api_id`. 15 | 16 | [All docs](docs.md) 17 | -------------------------------------------------------------------------------- /docs/applications.md: -------------------------------------------------------------------------------- 1 | # Applications and Sandboxes 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | ## Applications 6 | 7 | - `Applications().get_all(policy_check_after(opt))` : get a list of Veracode applications (JSON format). If provided, returns only applications that have a policy check date on or after `policy_check_after` (format is `yyyy-mm-dd`). 8 | - `Applications().get(guid(opt),legacy_id(opt))`: get information for a single Veracode application using either the `guid` or the `legacy_id` (integer). 9 | - `Applications().get_by_name(name)`: get list of applications whose names contain the search string `name`. 10 | - `Applications().get_by_repo(git_repo_url)`: get list of applications associated with the `git_repo_url`. 11 | - `Applications().create(app_name, business_criticality, description(opt), business_unit(opt), teams(opt), policy_guid(opt), custom_fields(opt array), bus_owner_name(opt), bus_owner_email(opt),git_repo_url(opt),custom_kms_alias(opt))`: create an application profile. 12 | - `business_criticality`: one of "VERY HIGH", "HIGH", "MEDIUM", "LOW", "VERY LOW" 13 | - `description`: extended description of the application. 14 | - `business_unit`: the GUID of the business unit to which the application should be assigned 15 | - `teams`: a list of the GUIDs of the teams to which the application should be assigned 16 | - `policy_guid`: the GUID of the policy to set for this application. 17 | - `custom_fields`: an array of custom field values for the application 18 | - `bus_owner_name`: the name of the business owner of the application 19 | - `bus_owner_email`: the email address of the business owner of the application 20 | - `git_repo_url`: the URL to the git repository containing the code for the application 21 | - `custom_kms_alias`: the alias for the Customer Managed Encryption Key (CMK), which will be used to encrypt/decrypt customer provided data. Note: The Customer Managed Encrytion Key feature must be activated and configured for your organization before attempting to set this value. 22 | - `Applications().update(guid, app_name, business_criticality, description(opt),business_unit(opt), teams(opt), policy_guid(opt), custom_fields(opt array), bus_owner_name(opt), bus_owner_email(opt),git_repo_url(opt), custom_kms_alias(opt))`: update an application profile. Note that partial updates are NOT supported, so you need to provide all values including those that aren't changing. 23 | - `Applications().delete(guid)`: delete the application identified by `guid`. This is not a reversible action. 24 | 25 | ## Custom Fields 26 | - `CustomFields().get_all()`: get a list of app profile custom fields available for your organization. 27 | 28 | ## Sandboxes 29 | 30 | - `Sandboxes().get_all(guid)`: get the sandboxes associated with the application identified by `guid`. 31 | - `Sandboxes().create(app,name,auto_recreate(opt),custom_fields(opt))`: create a sandbox in the application identified by `app`. Custom fields must be specified as a list of dictionaries of `name`/`value` pairs, e.g. [{'name': 'Custom 1','value': 'foo'}]. 32 | - `Sandboxes().update(app,sandbox,name,auto_recreate(opt),custom_fields(opt))`: update the `sandbox` (guid) in `app` (guid) with the provided values. Note that partial updates are NOT supported, so you need to provide all values including those you don't wish to change. 33 | - `Sandboxes().delete(app,sandbox)`: delete `sandbox` (guid) in `app` (guid). 34 | 35 | [All docs](docs.md) 36 | -------------------------------------------------------------------------------- /docs/businessunits.md: -------------------------------------------------------------------------------- 1 | # Business Units 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | - `BusinessUnits().get_all()`: get the list of business units in the organization. 6 | - `BusinessUnits().get(guid)`: get the business unit identified by `guid`. 7 | - `BusinessUnits().create(name,teams)`: create a business unit. `teams` is a list of `team_id` GUIDs. 8 | - `BusinessUnits().update(guid,name,teams)`: update the business unit identified by `guid`. 9 | - `BusinessUnits().delete(guid)`: delete the business unit identified by `guid`. 10 | 11 | [All docs](docs.md) 12 | -------------------------------------------------------------------------------- /docs/collections.md: -------------------------------------------------------------------------------- 1 | # Collections 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | *Accessing*: The Collections feature is available only to Veracode customers in the Collections Early Adopter program. As the Collections feature is not generally available yet, the functionality of the feature will change over time. 6 | 7 | - `Collections().get_all()`: get all collections for the organization. 8 | - `Collections().get_by_name(collection_name)`: get all collections with a name that partially matches `collection_name`. 9 | - `Collections().get_by_business_unit(business_unit_name)`: get all collections associated with `business_unit_name` (exact match). 10 | - `Collections().get_statistics()`: get summary counts of collections by policy status. 11 | - `Collections().get(guid)`: get detailed information for the collection identified by `guid`. 12 | - `Collections().get_assets(guid)`: get a list of assets and detailed policy information for the collection identified by `guid`. 13 | - `Collections().create(name, description(opt), tags(opt), business_unit_guid(opt),custom_fields(opt list),assets(opt list of application guids))`: create a collection with the provided settings. 14 | - `Collections().update(guid, name, description(opt), tags(opt), business_unit_guid(opt),custom_fields(opt list),assets(opt list of application guids))`: update the collection identified by `guid` with the provided settings. 15 | - `Collections().delete(guid)`: delete the collection identified by `guid`. 16 | 17 | [All docs](docs.md) 18 | -------------------------------------------------------------------------------- /docs/dast.md: -------------------------------------------------------------------------------- 1 | # DAST 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | ## Targets 6 | 7 | Manage information and settings related to scan targets. 8 | 9 | - `DASTTargets().get_all()`: get a list of DAST targets to which you have access. 10 | - `DASTTargets().get(target_id)`: get the DAST target identified by `target_id`. 11 | - `DASTTargets().get_by_name(target_name)`: get a list of DAST targets whose name contains `target_name`. 12 | - `DASTTargets().search(target_name(opt), url(opt), search_term(opt), target_type)`: get a list of DAST targets to which you have access based on the search terms provided: 13 | - `target_name`: finds targets whose name contains `target_name` 14 | - `url`: finds targets whose url contains `url` 15 | - `search_term`: finds targets whose name or URL contains `search_term` 16 | - `target_type`: use to restrict the search to a `WEB_APP` or an `API` 17 | - `DASTTargets().create(name, description, protocol, url, api_specification_file_url, target_type, scan_type,is_sec_lead_only,teams(opt))`: create a DAST target. Note that this will also create an analysis profile for the target. Arguments include: 18 | - `name`: the name of the target. 19 | - `description`: the long description of the target. 20 | - `protocol`: the protocol of the main URL for the target (`HTTP`, `HTTPS`). 21 | - `url`: the main URL for the target. Must be specified for `target_type` = `WEB_APP`. 22 | - `api_specification_file_url`: the API specification URL for the target. Must be specified for `target_type` = `API`. 23 | - `target_type`: use to specify that the target is a `WEB_APP` or an `API`. 24 | - `scan_type`: use to specify the type of scan (`QUICK` or `FULL`). 25 | - `is_sec_lead_only`: set to `False` if the target should be accessed only by one or more `teams`. 26 | - `teams` (opt): an array of team GUIDs for whom access to the target should be restricted. 27 | - `DASTTargets().update(target_id, name, description, protocol, url, api_specification_file_url, target_type, scan_type,is_sec_lead_only,teams(opt))`: update the DAST target identified by `target_id`. 28 | - `DASTTargets().delete(target_id)`: delete the DAST target identified by `target_id`. 29 | 30 | 31 | ## Analysis Profiles 32 | 33 | Configure analysis options for a scan. 34 | 35 | - `DASTAnalysisProfiles().get_all(target_id(opt),type(opt))`: Retrieve the analysis profiles for the account, optionally filtered by `target_id` or `type`. 36 | - `target_id`: Retrieve the analysis profiles associated with the target identified by `target_id`. Note that this returns an array, though currently DAST Essentials only supports a single analysis profile per target. 37 | - `type`: One of `TARGET` or `SYSTEM`. 38 | - `DASTAnalysisProfiles().get(analysis_profile_id)`: Retrieve the details for the analysis profile identified by `analysis_profile_id`. 39 | - `DASTAnalysisProfiles().update(self, analysis_profile_id, allowed_urls(opt),denied_urls(opt), seed_urls(opt), grouped_urls(opt), crawler_mode(opt), rate_limit(opt), max_duration(opt), max_crawl_duration(opt))`: Update the analysis profile identified by `analysis_profile_id` with one or more settings: 40 | - `allowed_urls`: an array of the URLs the scanner is allowed to scan. 41 | - `denied_urls`: an array of the URLs the scanner is not allowed to scan. 42 | - `seed_urls`: an array of [seed URLs](https://docs.veracode.com/r/advanced-scan-configuration#seed-urls) that the scanner can use as starting points to crawl the target. Use this to include URLs that are not linked from the application but should be scanned. 43 | - `grouped_urls`: an array of [grouped URLs](https://docs.veracode.com/r/advanced-scan-configuration#grouped-urls). Define this parameter to improve scanning speed on sites that have a large number of similar pages. 44 | - `crawler_mode`: one of `SMART`, `EXHAUSTIVE`. 45 | - `rate_limit`: an integer that limits the number of attacks the crawler makes in an interval. 46 | - `max_duration`: an integer that specifies the maximum duration for the scan. 47 | - `max_crawl_duration`: an integer that specifies the maximum duration for crawling the target. 48 | - `DASTAnalysisProfiles().update_parent(analysis_profile_id, parent_analysis_profile_id)`: identifies a new parent analysis profile for the analysis profile identified by `analysis_profile_id`. This allows inheriting analysis profile settings from the parent. 49 | - `DASTAnalysisProfiles().get_authentications(analysis_profile_id)`: Retrieve the authentication options for the analysis profile identified by `analysis_profile_id`. 50 | - `DASTAnalysisProfiles().update_system_auth(analysis_profile_id, username, password)`: Set the username and password used for basic (HTTP) authentication for the analysis profile identified by `analysis_profile_id`. 51 | - `DASTAnalysisProfiles().update_app_auth(analysis_profile_id, username, password, login_url)`: Set the username and password used for application authentication on the login page at `login_url`, for the analysis profile identified by `analysis_profile_id`. 52 | - `DASTAnalysisProfiles().update_parameter_auth(analysis_profile_id, id, title, type, key, value)`: Set the options for paraemeter authentication for the analysis profile identified by `analysis_profile_id`. 53 | - `DASTAnalysisProfiles().get_scanners(analysis_profile_id)`: get the scanners associated with the analysis profile identified by `analysis_profile_id`. 54 | - `DASTAnalysisProfiles().update_scanners(analysis_profile_id, scanner_id, scanner_value)`: For the analysis profile identified by `analysis_profile_id`, enable or disable the scanner identified by `scanner_id`. Allowed values include: [ 'fingerprinting', 'ssl', 'http_header', 'portscan', 'fuzzer', 'sql_injection', 'xss', 'file_inclusion', 'deserialization', 'xxe', 'command_injection', 'csrf', 'ldap_injection'] 55 | - `DASTAnalysisProfiles().get_schedules(analysis_profile_id)`: Get the schedules associated with the application profile identified by `analysis_profile_id`. 56 | - `DASTAnalysisProfiles().get_schedule(analysis_profile_id, schedule_id)`: Get the schedule identified by `schedule_id` and associated with the application profile identified by `analysis_profile_id`. 57 | - `DASTAnalysisProfiles().create_schedule(analysis_profile_id, frequency, day=1, weekday=1, timezone='America/New York',time="00:00"))`: Create a schedule for the application profile identified by `analysis_profile_id`. Options include: 58 | - `frequency`: one of [`daily`, `weekly`, `monthly`] 59 | - `day`: integer identifying the day of the month to perform a scan with monthly frequency 60 | - `weekday`: integer identifying the day of the week to perform a scan with weekly frequency 61 | - `timezone`: time zone identifier for scheduling the scan 62 | - `time`: timestamp at which to start the scan 63 | - `DASTAnalysisProfiles().update_schedule(analysis_profile_id, schedule_id, frequency, day=1, weekday=1, timezone='America/New York',time="00:00"))`: Update the schedule identified by `schedule_id` for the application profile identified by `analysis_profile_id`. 64 | - `DASTAnalysisProfiles().delete_schedule(analysis_profile_id, schedule_id))`: Delete the schedule identified by `schedule_id` for the application profile identified by `analysis_profile_id`. 65 | 66 | ## Analysis Runs 67 | 68 | Begin or check the status of an analysis run. 69 | 70 | - `DASTAnalysisRuns().start(target_id)`: start an analysis run for the target identified by `target_id`. 71 | - `DASTAnalysisRuns().get(target_id)`: get the PDF report for the target identified by `target_id`. Returns a 400 if the scanning report is not ready. Save the response to a file to use. 72 | 73 | [All docs](docs.md) 74 | -------------------------------------------------------------------------------- /docs/docs.md: -------------------------------------------------------------------------------- 1 | # veracode-api-py docs 2 | 3 | See the topics below for more information on how to use this library. 4 | 5 | `veracode-api-py` provides two ways to access each method in the library. The library provides individual objects for key resource types (e.g. Applications, Users, Workspaces) on which indvidual methods can be called. Alternately, there is a single "API object" that lists all the methods in the library. 6 | 7 | ## Scans, Findings, Applications and Policy 8 | 9 | * [XML APIs](xml.md) - work with Veracode legacy XML APIs to access report data for individual scans and to perform static scans. 10 | * [Healthcheck and Status](healthcheck.md) - access information about the status of Veracode services. 11 | * [Applications and Sandboxes](applications.md) - create, update, access, and delete application profiles and sandboxes. 12 | * [Policy](policy.md) - create, update, access, and delete policy definitions. 13 | * [Findings, Annotations, Summary Reports, and CWE and Category Metadata](findings.md) - retrieve findings and propose, accept, and reject mitigations. Get summary reports for applications. Get CWE and category metadata. 14 | * [Collections](collections.md) - (EARLY ACCESS) create, update, access, and delete collections. 15 | * [SCA Agent](sca.md) - access information about SCA workspaces, projects, issues, vulnerabilities, libraries, and licenses. 16 | * [Dynamic Analysis](dynamic.md) - configure, schedule and start dynamic analyses (use with the Veracode Dynamic Analysis product). 17 | * [DAST](dast.md) - configure, schedule, and run DAST Essentials scans (use with the Veracode DAST Essentials product). 18 | * [Analytics](analytics.md) - request and retrieve reports across your Veracode organization. 19 | 20 | ## Administration 21 | 22 | * [Users](users.md) - create, update, access, and delete users. 23 | * [Teams](teams.md) - create, update, access, and delete teams. 24 | * [Business Units](businessunits.md) - create, update, access, and delete business units. 25 | * [API Credentials](apicreds.md) - create, access, renew, and revoke API credentials. 26 | * [Roles and Permissions](roles.md) - access system roles and permissions; create, update, access, and delete custom roles. 27 | * [JIT Default Settings](jitdefaults.md) - create and update default Just-In-Time Provisioning settings. 28 | 29 | ## API Object 30 | 31 | You can use the library without importing individual methods by using the `API()` object. 32 | 33 | * [API](api.md) - use this object to access all methods in the `veracode-api-py` library. -------------------------------------------------------------------------------- /docs/dynamic.md: -------------------------------------------------------------------------------- 1 | # Dynamic Analysis 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | ## Analyses 6 | 7 | _Note_: You can also access these methods from the `Analyses` class. 8 | 9 | - `Analyses().get_all()`: get a list of dynamic analyses to which you have access. 10 | - `Analyses().get_by_name(name)`: get a list of dynamic analyses matching `name`. 11 | - `Analyses().get_by_target_url(url)`: get a list of dynamic analyses containing `url`. 12 | - `Analyses().get_by_search_term(search_term)`: get a list of dynamic analyses matching `search_term`. 13 | - `Analyses().get(analysis_id)`: get the analysis identified by `analysis_id` (guid). 14 | - `Analyses().get_audits(analysis_id)`: get the audits for the analysis identified by `analysis_id` (guid). 15 | - `Analyses().get_scans(analysis_id)`: get the scans for the analysis identified by `analysis_id` (guid). 16 | - `Analyses().get_scanner_variables(analysis_id)`: get the scanner variables for the analysis identified by `analysis_id` (guid). 17 | - `Analyses().create(name,scans,schedule_frequency='ONCE',start_scan (opt),business_unit_guid (opt),email (opt),owner (opt))`: create an analysis with the provided settings. Use `setup_scan` and related functions to construct the list of scans. 18 | - `Analyses().update(guid,name,scans,schedule_frequency='ONCE',start_scan (opt),business_unit_guid (opt),email (opt),owner (opt))`: update the analysis identified by `guid` with the provided settings. 19 | - `Analyses().update_scanner_variable(analysis_guid,scanner_variable_guid,reference_key,value,description)`: update the scanner variable identified by the `scanner_variable_guid` for the analysis identified by `analysis_guid`. 20 | - `Analyses().delete_scanner_variable(analysis_guid,scanner_variable_guid)`: delete the scanner variable identified by the `scanner_variable_guid` for the analysis identified by `analysis_guid`. 21 | - `Analyses().delete(analysis_guid)`: delete the analysis identified by `analysis_guid`. 22 | 23 | ## Scans 24 | 25 | - `Scans().get(scan_guid)`: get the scan identified by `scan_guid`. Get `scan_guid` from `get_analysis_scans()`. 26 | - `Scans().get_audits(scan_guid)`: get the audits for the scan identified by `scan_guid`. 27 | - `Scans().get_scan_config(scan_guid)`: get the scan config for the scan identified by `scan_guid`. 28 | - `Scans().update(scan_guid,scan)`: update the scan identified by `scan_guid`. Prepare `scan` with `dyn_setup_scan()`. 29 | - `Scans().delete(scan_guid)`: delete the scan identified by `scan_guid`. 30 | - `Scans().get_scanner_variables(scan_id)`: get the scanner variables for the scan identified by `scan_guid`. 31 | - `Scans().update_scanner_variable(scan_guid,scanner_variable_guid,reference_key,value,description)`: update the scanner variable identified by the `scanner_variable_guid` for the scan identified by `scan_guid`. 32 | - `Scans().delete_scanner_variable(scan_guid,scanner_variable_guid)`: delete the scanner variable identified by the `scanner_variable_guid` for the scan identified by `scan_guid`. 33 | 34 | ## Analysis Occurrences 35 | 36 | - `Occurrences().get_all()`: get all dynamic analysis occurrences. 37 | - `Occurrences().get(occurrence_guid)`: get the dynamic analysis occurrence identified by `occurrence_guid`. 38 | - `Occurrences().stop(occurrence_guid,save_or_delete)`: stop the dynamic analysis occurrence identified by `occurrence_guid`. Analysis results identified so far are processed according to `save_or_delete`. 39 | - `Occurrences().get_scan_occurrences(occurrence_guid)`: get the scan occurrences for the dynamic analysis occurrence identified by `occurrence_guid`. 40 | 41 | ## Scan Occurrences 42 | 43 | - `ScanOccurrences().get(scan_occ_guid)`: get the scan occurrence identified by `scan_occ_guid`. 44 | - `ScanOccurrences().stop(scan_occ_guid,save_or_delete)`: stop the scan occurrence identified by `scan_occ_guid`. Scan results identified so far are processed according to `save_or_delete`. 45 | - `ScanOccurrences().get_configuration(scan_occ_guid)`: get the configuration of the scan occurrence identified by `scan_occ_guid`. 46 | - `ScanOccurrences().get_verification_report(scan_occ_guid)`: get the verification report of the scan occurrence identified by `scan_occ_guid`. 47 | - `ScanOccurrences().get_notes_report(scan_occ_guid)`: get the scan notes report of the scan occurrence identified by `scan_occ_guid`. 48 | - `ScanOccurrences().get_screenshots(scan_occ_guid)`: get the screenshots of the scan occurrence identified by `scan_occ_guid`. 49 | 50 | ## Global settings 51 | 52 | - `CodeGroups().get_all()`: get the allowable code values for all code groups for Dynamic Analysis. 53 | - `CodeGroups().get(name)`: get the allowable code values for the Dynamic Analysis code group identified by `name`. 54 | - `Configuration().get()`: get the default Dynamic Analysis configuration. 55 | - `ScanCapacitySummary().get()`: get the Dynamic Analysis scan capacity summary. 56 | - `ScannerVariables().get_all()`: get the list of global Dynamic Analysis scanner variables. 57 | - `ScannerVariables().get(guid)`: get the Dynamic Analysis global scanner variable identified by `guid`. 58 | - `ScannerVariables().create(reference_key,value,description)`: create a global Dynamic Analysis scanner variable. 59 | - `ScannerVariables().update(guid,reference_key,value,description)`: update the global Dynamic Analysis scanner variable identified by `guid`. 60 | - `ScannerVariables().delete(guid)`: delete the global Dynamic Analysis scanner variable identified by `guid`. 61 | 62 | ## Utilities 63 | 64 | _Note_: You can also access these methods from the `DynUtils` class. 65 | 66 | - `DynUtils().setup_user_agent(custom_header,type)`: set up the payload to specify the user agent for a dynamic scan. 67 | - `DynUtils().setup_custom_host(host_name,ip_address)`: set up the payload to specify the custom host for a dynamic scan. 68 | - `DynUtils().setup_blocklist( urls:List)`: set up the payload to specify the blocklist for a dynamic scan. 69 | - `DynUtils().setup_url(url,directory_restriction_type='DIRECTORY_AND_SUBDIRECTORY',http_and_https=True)`: set up the payload to specify a URL object for a dynamic scan. This payload can be used in other setup calls that require a `url`. 70 | - `DynUtils().setup_scan_setting(blocklist_configs:list,custom_hosts:List, user_agent(opt))`: set up the payload to specify a scan setting for a dynamic scan. 71 | - `DynUtils().setup_scan_contact_info(email,first_and_last_name,telephone)`: set up the payload to specify contact information for a dynamic scan. 72 | - `DynUtils().setup_crawl_script(script_body,script_type='SELENIUM')`: set up the payload to specify crawl script information for a dynamic scan. 73 | - `DynUtils().setup_crawl_configuration(scripts:List,disabled=False)`: set up the payload to specify crawl configuration for a dynamic scan. 74 | - `DynUtils().setup_login_logout_script(script_body,script_type='SELENIUM')`: set up the payload to specify login/logout script information for a dynamic scan. 75 | - `DynUtils().setup_auth(authtype,username,password,domain(opt),base64_pkcs12(opt),cert_name(opt), login_script_data(opt), logout_script_data(opt))`: set up the payload to specify authentication information for a specific authtype for a dynamic scan. The following parameters are required: 76 | - `AUTO`: `username`, `password` 77 | - `BASIC`: `username`, `password`, `domain` (opt) 78 | - `CERT`: `base64_pkcs12`, `cert_name`, `password` 79 | - `FORM`: `login_script_data`, `logout_script_data` 80 | - `DynUtils().setup_auth_config(authentication_node:dict)`: set up the payload to specify authentication information for a dynamic scan. Set up `authentication_node` with `dyn_setup_auth`. 81 | - `DynUtils().setup_scan_config_request( url, allowed_hosts:List, auth_config(opt), crawl_config(opt), scan_setting(opt))`: set up the payload to specify the scan config request for a dynamic scan. `url` and `allowed_hosts` are set up using `dyn_setup_url()`. `crawl_config` is setup using `dyn_setup_crawl_configuration()`. `scan_setting` is setup using `dyn_setup_scan_setting()`. 82 | - `DynUtils().setup_scan( scan_config_request, scan_contact_info(opt), linked_app_guid(opt))`: set up the payload to specify the scan for a Dynamic Analysis. `scan_config_request` is setup using `dyn_setup_scan_config_request()` and `scan_contact_info` is set up using `dyn_setup_scan_contact_info()`. Specify `linked_app_guid` (using `get_apps()` or `get_app()`) to link the scan results to an application profile. 83 | - `DynUtils().start_scan(length,unit)`: start a dynamic analysis and set duration. 84 | 85 | [All docs](docs.md) 86 | -------------------------------------------------------------------------------- /docs/findings.md: -------------------------------------------------------------------------------- 1 | # Findings, Annotations, Summary Reports, and CWE and Category Metadata 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | ## Findings and Annotations 6 | 7 | - `Findings().get_findings(app,scantype(opt),annot(opt),request_params(opt),sandbox(opt))`: get the findings for `app` (guid). 8 | - `scantype`: Defaults to STATIC findings, but can be STATIC, DYNAMIC, MANUAL, SCA, or ALL (static, dynamic, manual). You can also pass a comma-delimited string of valid scantype options. 9 | - `annot`: Defaults to TRUE but can be FALSE 10 | - `sandbox`: The guid of the sandbox in `app` for which you want findings. (Use the Sandboxes APIs to get the sandbox guid.) 11 | - `request_params`: Dictionary of additional query parameters. See the full [Findings API specification](https://help.veracode.com/r/c_findings_v2_intro) for some of the other options available. 12 | - `Findings().get_static_flaw_info(app,issueid,sandbox(opt))`: get the static flaw information, including data paths, for the finding identified by `issueid` in `app` (guid) or its `sandbox` (guid). 13 | - `Findings().get_dynamic_flaw_info(app,issueid)`: get the dynamic flaw information, including request/response data, for the finding identified by `issueid` in `app` (guid). 14 | - `Findings().add_annotation(app,issue_list,comment,action,sandbox(opt))`: add an annotation (comment, mitigation proposal/acceptance/rejection) to the findings in `issue_list` for `app` (guid) (or optionally `sandbox` (guid)). Note that you must have the Mitigation Approver role (regular user) to use the ACCEPTED or REJECTED action, or the Mitigation and Comments API role for an API service account to use this call. 15 | - `issue_list`: must be passed as a Python list of `issue_id`s 16 | - `action`: must be one of COMMENT, POTENTIAL_FALSE_POSITIVE, APP_BY_DESIGN, OS_ENV, NET_ENV, LIBRARY, ACCEPT_RISK, ACCEPTED, REJECTED 17 | - `Findings().match_findings(origin_finding,potential_matches,approved_findings_only(opt),allow_fuzzy_match(opt))`: return a matching finding from `potential_matches` for the `origin_finding`, based on the finding type. 18 | - `approved_findings_only`: limits matches to findings with approved mitigations. 19 | - `allow_fuzzy_match`: look for matches within a range of source lines around the origin finding. This allows for code movement but can result in flaws being mismatched; use sparingly. 20 | 21 | ## Summary Report 22 | 23 | - `SummaryReport().get_summary_report(app,sandbox(opt), build_id(opt))`: get the summary report for `app` (guid) or its `sandbox` (guid). Optionally specify a `build_id` to get a summary report for an older scan. 24 | 25 | ## Manual Testing 26 | 27 | - `ManualScans().get_for_app(appid)`: get the manual scans for `appid` (guid). 28 | - `ManualScans().get(scanid)`: get the manual scan information for `scanid` (int), returned by `get_for_app()`. 29 | - `ManualScans().get_findings(scanid,include_artifacts(opt))`: get the manual findings detail for `scanid` (int). 30 | - `include_artifacts`: if `True`, includes screenshots and code samples associated with the findings. 31 | 32 | ## CWEs and Category Metadata 33 | 34 | - `CWEs().get_all()`: get metadata for all CWEs. 35 | - `CWEs().get(cwe_id)`: get metadata for the CWE identified by `cwe_id` (int). 36 | - `CWECategories().get_all()`: get metadata for all CWE categories. 37 | - `CWECategories().get(category_id)`: get metadata for the CWE category identified by `category_id` (int). 38 | 39 | [All docs](docs.md) 40 | -------------------------------------------------------------------------------- /docs/healthcheck.md: -------------------------------------------------------------------------------- 1 | # Healthcheck and Status 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | - `Healthcheck().healthcheck()`: returns an empty response with HTTP 200 if authentication succeeds. 6 | - `Healthcheck().status()`: returns detailed status of Veracode services, mirroring [status.veracode.com](https://status.veracode.com). 7 | 8 | [All docs](docs.md) 9 | -------------------------------------------------------------------------------- /docs/jitdefaults.md: -------------------------------------------------------------------------------- 1 | # Just In Time Provisioning Default Settings 2 | 3 | The following methods call Veracode REST APIs and return JSON. More information about the JIT settings is available in the [Veracode Docs](https://docs.veracode.com/r/Configure_SAML_Self_Registration). 4 | 5 | - `JITDefaultSettings().get()` - retrieve the current Just In Time (JIT) default settings. 6 | - `JITDefaultSettings().create(ip_restricted(opt),prefer_veracode_data(opt), allowed_ip_addresses(opt), use_csv_for_roles_claim(opt), use_csv_for_teams_claim(opt), use_csv_for_teams_managed_claim(opt), use_csv_for_ip_address_claim(opt),teams(opt),roles(opt))` - create new Just In Time (JIT) default settings. Settings include: 7 | - `ip_restricted`: set to `True` to apply IP restrictions (defined in `allowed_ip_addresses`) for a JIT user. 8 | - `prefer_veracode_data`: set to `True` to allow an administrator to manage roles, teams, and other settings for users in the Veracode administrative console after user creation. If set to `False`, the SAML assertion sent from the customer's Identity Provider must contain these values. 9 | - `allowed_ip_addresses`: an array of IP addresses. See the [Veracode Docs](https://docs.veracode.com/r/admin_ip) for more information. 10 | - `use_csv_for_roles_claim`: set to `True` if your IDP will send a comma separated list of roles (instead of an array). 11 | - `use_csv_for_teams_claim`: set to `True` if your IDP will send a comma separated list of teams (instead of an array). 12 | - `use_csv_for_teams_managed_claim`: set to `True` if your IDP will send a comma separated list of teams managed by a team admin (instead of an array). 13 | - `use_csv_for_ip_address_claim`: set to `True` if your IDP will send a comma separated list of IP address restrictions (instead of an array). 14 | - `teams`: an array of team IDs (UUIDs) that should be assigned to a JIT user by default. 15 | - `roles`: an array of role IDs (UUIDs) that should be assigned to a JIT user by default. 16 | - `JITDefaultSettings().update(jit_default_id, ip_restricted(opt),prefer_veracode_data(opt), allowed_ip_addresses(opt), use_csv_for_roles_claim(opt), use_csv_for_teams_claim(opt), use_csv_for_teams_managed_claim(opt), use_csv_for_ip_address_claim(opt),teams(opt),roles(opt))` - update existing Just In Time (JIT) default settings identified by `jit_default_id`. 17 | - `JITDefaultSettings().delete(jit_default_id)` - delete the Just In Time (JIT) default settings identified by `jit_default_id`. 18 | 19 | [All docs](docs.md) 20 | -------------------------------------------------------------------------------- /docs/policy.md: -------------------------------------------------------------------------------- 1 | # Policy 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | _Note_: You can also access these methods from the `Policies` class. 6 | 7 | - `Policies().get_all()`: Get a list of available policies. 8 | - `Policies().get(guid)`: get information for the policy corresponding to `guid`. 9 | - `Policies().create(name,description,vendor_policy(opt),finding_rules(opt),scan_frequency_rules(opt),grace_period_rules(opt))`: create a policy 10 | - `Policies().update(guid,name,description,vendor_policy(opt),finding_rules(opt),scan_frequency_rules(opt),grace_period_rules(opt))`: edit a policy 11 | - `Policies().delete(guid)`: delete a policy 12 | - `Policies().format_finding_rule(rule_type,scan_types=[],rule_value='')`: formatting utility to create a valid finding rule based on the input. For more information about finding rules, see the [finding rules documentation](https://docs.veracode.com/r/Policy_API_Rules_Properties): 13 | - `rule_type`: one of `FAIL_ALL`, `CWE`, `CATEGORY`, `MAX_SEVERITY`, `CVSS`, `CVE`, `BLACKLIST`, `MIN_SCORE`, `SECURITY_STANDARD`, `LICENSE_RISK` 14 | - `scan_types`: an array of scan types to which the rule applies. Valid values are `STATIC`, `DYNAMIC`, `MANUAL`, `SCA` 15 | - `rule_value`: a string representing the value for the rule 16 | - `Policies().format_scan_frequency_rule(scan_type,frequency)`: formatting utility to create a valid scan frequency rule based on the input: 17 | - `scan_type`: valid values are `STATIC`, `DYNAMIC`, `MANUAL`, `SCA` 18 | - `frequency`: valid values are `NOT_REQUIRED`, `ONCE`, `WEEKLY`, `MONTHLY`, `QUARTERLY`, `SEMI_ANNUALLY`, `ANNUALLY`, `EVERY_18_MONTHS`, `EVERY_2_YEARS`, `EVERY_3_YEARS` 19 | - `Policies().format_grace_periods(sev5: int, sev4: int, sev3: int, sev2: int, sev1: int, sev0: int, score: int, sca_blocklist: int)`: formatting utility to create a valid grace period rule. Each argument represents a number of days in the grace period for the findings of the given severity or for the Veracode score (see [grace period documentation](https://docs.veracode.com/r/c_policy_grace_period) for more info). 20 | 21 | [All docs](docs.md) 22 | -------------------------------------------------------------------------------- /docs/roles.md: -------------------------------------------------------------------------------- 1 | # Roles and Permissions 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | - `Roles().get_all()`: get the list of roles for the organization. 6 | - `Roles().get(role_guid)`: get the role definition for a given role. 7 | - `Roles().create(role_name,role_description,is_api (opt),jit_assignable(opt),jit_assignable_default (opt),permissions (opt),child_roles (opt))`: create a role named `role_name`. You must specify either `permissions` or `child_roles`, or both. Arguments include: 8 | - `role_description`: The human readable description of the role. 9 | - `is_api`: Set to `True` to create a role for an API user. Defaults to `False`. 10 | - `jit_assignable`: Set to `True` to allow the role to be assigned by a SAML assertion using just-in-time provisioning. Defaults to `True`. 11 | - `jit_assignable_default`: Set to `True` to allow the role to be assigned by default during just-in-time provisioning. Defaults to `True`. 12 | - `permissions`: An array of permission names. Use `Permissions().get_all()` to see the list of assignable permissions. 13 | - `child_roles`: An array of role names. Adding a child role to a custom role gives the user all the permissions contained in the child role, in addition to any permissions defined in `permissinos`. You can add more than one child role. 14 | - `Roles().update(role_name,role_description,role_guid,is_api (opt),jit_assignable(opt),jit_assignable_default (opt),permissions (opt),child_roles (opt))`: update the role identified by `role_guid` with the provided information. 15 | - `Roles().delete(role_guid)`: delete the role identified by `role_guid`. Note: You can only delete custom roles. 16 | - `Permissions().get_all()`: get the list of permissions that can be part of custom roles. 17 | - `Permissions().get(permission_guid)`: get the permission definition for a given permission. 18 | 19 | 20 | [All docs](docs.md) 21 | -------------------------------------------------------------------------------- /docs/sca.md: -------------------------------------------------------------------------------- 1 | # SCA Agent 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | _Note_: SCA APIs must be called with a human user, since the SCA Agent APIs do not currently support being called by an API Service User. 6 | 7 | ## Workspaces, Projects, Issues, and Agents 8 | 9 | - `Workspaces().get_all(include_metrics(opt))`: get a list of SCA Agent workspaces for the organization. The `include_metrics` parameter can add counts of issues and other attributes in the response, at some cost to performance, and defaults to `False`. 10 | - `Workspaces().get_by_name(name)`: get a list of SCA Agent workspaces whose name partially matches `name`. 11 | - `Workspaces().create(name)`: create an SCA Agent workspace named `name`. Returns the GUID for the workspace. 12 | - `Workspaces().add_team(workspace_guid,team_id)`: add the team identified by `team_id` (int) to the workspace identified by `workspace_guid`. 13 | - `Workspaces().get_teams(workspace_guid(opt))`: get a list of teams. If no `workspace_guid` is provided, return all available teams. 14 | - `Workspaces().remove_team(workspace_guid,team_id)`: remove the team identified by `team_id` (int) from the workspace identified by `workspace_guid`. 15 | - `Workspaces().delete(workspace_guid)`: delete the workspace identified by `workspace_guid`. 16 | - `Workspaces().get_projects(workspace_guid,project_name(opt))`: get a list of projects for the workspace identified by `workspace_guid`. 17 | - `Workspaces().get_agents(workspace_guid)`: get a list of agents for the workspace identified by `workspace_guid`. 18 | - `Workspaces().get_agent(workspace_guid,agent_guid)`: get the agent identified by `agent_guid` in the workspace identified by `workspace_guid`. 19 | - `Workspaces().create_agent(workspace_guid,name,agent_type(opt))`: create an agent in the workspace identified by `workspace_guid`. Default for `agent_type` is `CLI`. 20 | - `Workspaces().delete_agent(workspace_guid,agent_guid)`: delete the agent identified by `agent_guid`. 21 | - `Workspaces().get_agent_tokens(workspace_guid, agent_guid)`: get token IDs for the agent identified by `agent_guid` in the workspace identified by `workspace_guid`. 22 | - `Workspaces().get_agent_token(workspace_guid, agent_guid, token_id)`: get the token ID identified by `token_id`. 23 | - `Workspaces().regenerate_agent_token(workspace_guid, agent_guid)`: regenerate the token for the agent identified by `agent_id`. 24 | - `Workspaces().revoke_agent_token(workspace_guid, agent_guid, token_id)`: revoke the token identified by `token_id`. 25 | - `Workspaces().get_issues(workspace_guid, branch(opt), direct(opt), created_after(opt), ignored(opt), vuln_method(opt), project_id (opt array))`: get the list of issues for the workspace identified by `workspace_guid`. 26 | - `Workspaces().get_issue(issue_id)`: get the issue identified by `issue_id`. 27 | - `Workspaces().get_scan(scan_id)`: get the scan identified by `scan_id` (returned in `get_issue`). 28 | - `Workspaces().get_libraries(workspace_guid,unmatched(bool,opt))`: get the libraries associated with the workspace identified by `workspace_guid`. 29 | - `Workspaces().get_library(library_id)`: get the library identified by `library_id` (e.g. "maven:commons-fileupload:commons-fileupload:1.3.2:") 30 | - `Workspaces().get_vulnerability(vulnerability_id)`: get the vulnerability identified by `vulnerability_id` (an integer value, visible in the output of `get_issues`). 31 | - `Workspaces().get_license(license_id)`: get the license identified by `license_id` (a string, e.g. "GPL30"). 32 | - `Workspaces().get_events(date_gte,event_group,event_type)`: get the audit events for the arguments passed. Be careful with the arguments for this and try to limit by date as it will fetch all pages of data, which might be a lot. 33 | 34 | ## Component Activity 35 | 36 | - `ComponentActivity().get(component_id)`: get the activity for the component identified by `component_id` (similar to `library_id` above, e.g. "maven:net.minidev:json-smart:1.3.1"). 37 | 38 | ## SBOM 39 | 40 | - `SBOM().get(app_guid,format(opt),linked(opt),vulnerability(opt),dependency(opt))`: generate an SBOM in either CycloneDX (default) or SPDX format for the application represented by `app_guid`. Get the `app_guid` from the Applications API. The following options are available: 41 | - `linked` (CycloneDX only): if `True`, returns an SBOM based on the linked SCA Agent project. Defaults to `False`. 42 | - `vulnerability`: if `True`, returns an SBOM containing vulnerability information. Defaults to `True`. 43 | - `dependency` (SPDX only): if `True`, returns an SBOM that includes dependency information. Defaults to `True`. 44 | - `SBOM().get_for_project(project_guid,format(opt),vulnerability(opt))`: generate an SBOM in CycloneDX (default) or SPDX format for the SCA Agent project represented by `project_guid`. Get the `project_guid` from the SCA Agent API (e.g. `get_projects(workspace_guid)`). The following options are available: 45 | - `vulnerability`: if `True`, returns an SBOM containing vulnerability information. Defaults to `True`. 46 | - `dependency` (SPDX only): if `True`, returns an SBOM that includes dependency information. Defaults to `True`. 47 | - `SBOM().scan(sbom)`: (EXPERIMENTAL) Scan an SBOM (pass the filename, including absolute path, as the `sbom` parameter) and return an updated SBOM with additional vulnerability information from the Veracode SCA Database. 48 | 49 | ## Application Info 50 | 51 | _Note_: You can also access these methods from the `SCAApplications` class. 52 | 53 | - `SCAApplications().get_projects(app_guid)`: get the list of linked SCA projects for an application. (This API call is also available on the SCAApplications object as `SCAApplications().get_projects()`.) 54 | - `SCAApplications().link_project(app_guid, project_guid)`: link the application to the project. (This API call is also available on the SCAApplications object as `SCAApplications().link_project()`.) 55 | - `SCAApplications().unlink_project(app_guid, project_guid)`: unlink the application from the project. (This API call is also available on the SCAApplications object as `SCAApplications().unlink_project()`.) 56 | - `SCAApplications().get_annotations(app_guid, annotation_type, annotation_reason(opt), annotation_status(opt),cve_name(opt), cwe_id(opt), severities(opt array), license_name(opt), license_risk(opt))`: get the list of annotations (mitigations and comments) for an application. (This API call is also available on the SCAApplications object as `SCAApplications().get_annotations()`.) 57 | - `SCAApplications().add_annotation(app_guid, action, comment, annotation_type, component_id, cve_name (required for VULNERABILITY type), license_id (required for LICENSE type))`: add an annotation (mitigation or comment) to an SCA vulnerability or license finding. Note that ability to APPROVE or REJECT requires the mitigation approver role. (This API call is also available on the SCAApplications object as `SCAApplications().add_annotation()`.) 58 | 59 | [All docs](docs.md) 60 | -------------------------------------------------------------------------------- /docs/static.md: -------------------------------------------------------------------------------- 1 | # Static 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | ## Static CLI 6 | 7 | Commands to work with the Static CLI scans, aka Pipeline Scan. The workflow to run a scan is as follows: 8 | - Create a scan 9 | - Add one or more segments 10 | - Start the scan 11 | - Get findings 12 | 13 | - `StaticCLI().Scans().create(binary_name, binary_size, binary_hash, app_id(opt), project_name(opt), project_uri(opt), project_ref(opt), commit_hash(opt), dev_stage(opt), scan_timeout (opt))` - Set up a scan 14 | - `StaticCLI().Scans().get(scan_id)` - Get scan details for the scan represented by `scan_id`. 15 | - `StaticCLI().Scans().Segments().add(scan_id,segment_id,file)` - Upload a segment of the scanned file. 16 | - `StaticCLI().Scans().start(scan_id)` - Start the scan represented by `scan_id`. 17 | - `StaticCLI().Scans().cancel(scan_id)` - Cancel the scan represented by `scan_id`. 18 | - `StaticCLI().Scans().Findings().get(scan_id)` - Get the findings for the scan represented by `scan_id`. 19 | -------------------------------------------------------------------------------- /docs/teams.md: -------------------------------------------------------------------------------- 1 | # Teams 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | - `Teams().get_all(all_for_org)`: get the list of teams for the user, or (if `all_for_org` is `True`) all teams in the organization. 6 | - `Teams().get(team_uuid)`: get the details for a given team uuid, including members 7 | - `Teams().create(team_name,business_unit,members)`: create a team named `team_name`. Optionally pass the business unit guid and/or a list of user names to add to the team. 8 | - `Teams().update(team_guid,team_name(opt),business_unit(opt),members(opt))`: update the team identified by `team_guid` with the provided information. 9 | - `Teams().delete(team_guid)`: delete the team identified by `team_guid`. 10 | 11 | [All docs](docs.md) 12 | -------------------------------------------------------------------------------- /docs/users.md: -------------------------------------------------------------------------------- 1 | # Users 2 | 3 | The following methods call Veracode REST APIs and return JSON. 4 | 5 | - `Users().get_all()`: get a list of users for the organization. 6 | - `Users().get_self()`: get user information for the current user. 7 | - `Users().get(user_guid)`: get information for an individual user based on `user_guid`. 8 | - `Users().get_by_name(username)`: look up info for an individual user based on their user_name. 9 | - `Users().get_user_search(search_term, api_id, role_id, login_status, saml_user, team_id, detailed, user_type, request_params)`: search for users based on parameters below (all optional): 10 | - `search_term`: string 11 | - `api_id`: search by customer api id 12 | - `role_id`: search by role_id (see `get_roles`) 13 | - `login_status`: search by login status (e.g. `active`) 14 | - `saml_user`: search by saml user ID 15 | - `team_id`: serach by team ID (see `get_teams`) 16 | - `detailed`: returns additional attributes in summary list of users 17 | - `user_type`: search by user type (e.g. `user` or `api`) 18 | - `request_params`: optionally pass a dictionary of additional query parameters. See [Identity API specification](https://app.swaggerhub.com/apis/Veracode/veracode-identity_api/1.0#/user/getUsersBySearchUsingGET) 19 | - `Users().create(email,firstname,lastname,type(opt),username(opt),roles(opt),mfa(opt),ipRestricted(opt),allowedIpAddresses(opt),samlUser(opt),samlSubject(opt))`: create a user based on the provided information. 20 | - `type`: `HUMAN` or `API`. Defaults to `HUMAN`. If `API` specified, must also provide `username`. 21 | - `roles`: list of role names (specified in the Veracode Help Center, for both [human](https://help.veracode.com/go/c_identity_create_human) and [API service account](https://help.veracode.com/go/c_identity_create_api) users). Provide the role names from `get_roles()`. 22 | - `mfa`: set to `TRUE` to require the user to configure multifactor authentication on first sign in. 23 | - `ipRestricted`: set to `TRUE` and provide an array of allowed IP addresses to restrict the IP addresses from which the user can log in. See [Veracode Docs](https://docs.veracode.com/r/admin_ip) for more info. 24 | - `samlUser`: set to `TRUE` and provide a `samlSubject` to require the user to log in via SAML Single Sign-On. See [Veracode Docs](https://docs.veracode.com/r/about_saml#configure-a-user-for-saml-access) for more info. 25 | - `Users().update_roles(user_guid, roles)`: update the user identified by `user_guid` with the list of roles passed in `roles`. Because the Identity API does not support adding a single role, the list should be the entire list of existing roles for the user plus whatever new roles. See [veracode-user-bulk-role-assign](https://github.com/tjarrettveracode/veracode-user-bulk-role-assign) for an example. 26 | - `Users().update(user_guid,changes)`: update a user based upon the provided information. 27 | - `user_guid`: the unique identifier of the user to be updated. 28 | - `changes`: the attributes of the user to be changed. Must be JSON whose format follows the [Identity API specification](https://app.swaggerhub.com/apis/Veracode/veracode-identity_api/1.0#/ResourceOfUserResource) of a valid user object. 29 | - `Users().update_email_address(user_guid,email_address,ignore_verification(opt))`: updates the email address of the specified user. 30 | - `ignore_verification`: Boolean. Defaults to `False`. If `True`, immediately updates email address without requiring the user to verify the change via email. If the user has not yet activated the account, this must be set to `True`. 31 | - `Users().reset_password(user_legacy_id)`: sends a password reset email to the specified user. If the user has yet to activate their account, sends a new activation email instead. 32 | - NOTE: this function uses the `user_legacy_id` value (as opposed to the `user_guid` value), which can be obtained via a call to `get_user()`. 33 | - `Users().disable(user_guid)`: set the `Active` flag the user identified by `user_guid` to `False`. 34 | - `Users().delete(user_guid)`: delete the user identified by `user_guid`. This is not a reversible action. 35 | - `Roles().get_all()`: get a list of available roles to assign to users. 36 | 37 | [All docs](docs.md) 38 | -------------------------------------------------------------------------------- /docs/xml.md: -------------------------------------------------------------------------------- 1 | # XML APIs 2 | 3 | The following methods call Veracode XML APIs and return XML output. For a more detailed reference on the underlying API calls, see the [Veracode docs](https://docs.veracode.com/r/c_api_main). 4 | 5 | - `XMLAPI().get_app_list()` : get a list of Veracode applications (XML format) 6 | - `XMLAPI().get_app_info(app_id)` : get application info for the `app_id` (integer) passed. 7 | - `XMLAPI().get_sandbox_list(app_id)` : get list of sandboxes for the `app_id` (integer) passed. 8 | - `XMLAPI().get_build_list(app_id, sandbox_id(opt))`: get list of builds for the `app_id` (integer) passed. If `sandbox_id` (integer) passed, returns a list of builds in the sandbox. 9 | - `XMLAPI().get_build_info(app_id, build_id, sandbox_id(opt))`: get build info for the `build_id` (integer) and `app_id` (integer) passed. If `sandbox_id` (integer) passed, returns information for the `build_id` in the sandbox. 10 | - `XMLAPI().get_detailed_report(build_id)`: get detailed report XML for the `build_id` (integer) passed. 11 | - `XMLAPI().set_mitigation_info(build_id,flaw_id_list,action,comment)`: create a mitigation of type `action` with comment `comment` for the flaws in `flaw_id_list` (comma separated list of integers) of build `build_id` (integer). Supported values for `action`: 'Mitigate by Design', 'Mitigate by Network Environment', 'Mitigate by OS Environment', 'Approve Mitigation', 'Reject Mitigation', 'Potential False Positive', 'Reported to Library Maintainer'. Any other value passed to `action` is interpreted as a comment. 12 | - `XMLAPI().generate_archer(payload)`: generate an Archer report based on the comma separated list of parameters provided. Possible parameters include `period` (`yesterday`, `last_week`, `last_month`; all time if omitted), `from_date` (mm-dd-yyyy format), `to_date` (mm-dd-yyyy format), `scan_type` (one of `static`, `dynamic`, `manual`). Returns a payload that contains a token to download an Archer report. 13 | - `XMLAPI().download_archer(token(opt))`: get Archer report corresponding to the token passed. If no token passed, retrieves the latest Archer report generated. 14 | - `XMLAPI().upload_file(app_id, file, sandbox_id(opt), save_as(opt))`: Uploads a file to an existing build or creates a build. 15 | - `XMLAPI().begin_prescan(app_id, sandbox_id(opt), auto_scan(opt), scan_all_nonfatal_top_level_modules(opt)`: begin a static prescan on the application and/or sandbox specified. 16 | - `XMLAPI().begin_scan(app_id, modules(opt), scan_all_top_level_modules(opt),scan_selected_modules(opt),scan_previously_selected_modules(opt),sandbox_id(opt))`: begin a static scan on the application and/or sandbox specified. 17 | - `XMLAPI().get_prescan_results(app_id, build_id(opt), sandbox_id(opt))`: get the prescan results for the application, sandbox and/or scan specified. 18 | - `XMLAPI().get_file_list(app_id, build_id(opt), sandbox_id(opt))`: get the list of files uploaded for the application, sandbox, and/or scan specified. 19 | - `XMLAPI().remove_file(app_id, file_id, sandbox_id(opt))`: delete a file previously uploaded for the application and/or sandbox specified. 20 | - `XMLAPI().delete_build(app_id, sandbox_id(opt))`: delete the last build uploaded for the application and/or sandbox specified. 21 | 22 | [All docs](docs.md) 23 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tim Jarrett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'veracode_api_py' 3 | version = '0.9.62' 4 | authors = [ {name = "Tim Jarrett", email="tjarrett@veracode.com"} ] 5 | description = 'Python helper library for working with the Veracode APIs. Handles retries, pagination, and other features of the modern Veracode REST APIs.' 6 | readme = 'README.md' 7 | license = { text = "MIT" } 8 | requires-python = ">=3.7" 9 | classifiers=[ 10 | 'Development Status :: 4 - Beta', # Chose either "3 - Alpha", "4 - Beta" or "5 - Production/Stable" as the current state of your package 11 | 'Intended Audience :: Developers', 12 | 'Topic :: Software Development :: Build Tools', 13 | 'License :: OSI Approved :: MIT License', 14 | 'Programming Language :: Python :: 3' 15 | ] 16 | keywords = ['veracode', 'veracode-api'] 17 | dynamic = ["dependencies"] 18 | 19 | [tool.setuptools.dynamic] 20 | dependencies = {file = ["requirements.txt"]} 21 | 22 | [project.urls] 23 | "Homepage" = "https://github.com/veracode/veracode-api-py" 24 | "Bug Tracker" = "https://github.com/veracode/veracode-api-py/issues" 25 | "Download" = "https://github.com/veracode/veracode-api-py/archive/v_0962.tar.gz" 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.32.0 2 | veracode-api-signing>=24.11.0 3 | Pygments>= 2.9.0 4 | idna>=3.7 5 | certifi>=2024.7.4 -------------------------------------------------------------------------------- /samples/dast_sample.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import requests 4 | from veracode_api_py import DASTAnalysisProfiles, DASTAnalysisRuns, DASTTargets, VeracodeAPIError 5 | 6 | # The most simple workflow to set up a DAST Essentials scan is as follows: 7 | # 1. Create the target 8 | # 2. Configure the analysis profile 9 | # 3. Run the scan 10 | # 4. Get results 11 | # See the Veracode Docs (https://docs.veracode.com/r/DAST_Essentials_REST_API) for more info. 12 | 13 | #Update the values in this example for your own application 14 | target = DASTTargets().create(name='Example', description='Example Target', protocol='HTTP', 15 | url='www.example.com', target_type='WEB_APP', scan_type='QUICK', is_sec_lead_only=True) 16 | 17 | #optionally, assign the target to a team during the create 18 | 19 | analysisprofile = DASTAnalysisProfiles().get_all(target_id=target['target_id'])[0] 20 | 21 | # update with actual credentials and login info using other authentication methods 22 | DASTAnalysisProfiles().update_system_auth(analysis_profile_id=analysisprofile['analysis_profile_id'], 23 | username='admin',password='smithy') 24 | 25 | DASTAnalysisProfiles().update(analysis_profile_id=analysisprofile['analysis_profile_id'], allowed_urls=[], 26 | denied_urls=[], seed_urls=[], grouped_urls=[], crawler_mode='SMART') 27 | 28 | scan = DASTAnalysisRuns().start(target_id=target['target_id']) 29 | 30 | print('Scan id {} started on url {} at {}'.format(scan['id'], scan['url'], scan['started_at'])) 31 | 32 | # To fetch the report afterwards, use the following and save to a file. 33 | done = False 34 | 35 | while not done: 36 | try: 37 | report = DASTAnalysisRuns().get(target_id=target['target_id']) 38 | done = True 39 | except requests.exceptions.RequestException as re: 40 | data = re.response.json() 41 | if data['status']==400 and data['errors'][0]['code']=='INVALID_TARGET_STATE': 42 | print('Report for target {} is not ready. Checking again in 10 seconds…'.format(target['target_id'])) 43 | time.sleep(10) 44 | continue 45 | else: 46 | print("An error occurred: HTTP Error {}, {}, {}".format(data['status'],data['detail'],data['errors'])) 47 | except Exception as e: 48 | ex_type, ex_value, ex_traceback = sys.exc_info() 49 | print('A general exception occurred:{} {}'.format(ex_type.__name__, ex_value)) 50 | done=True 51 | 52 | print('Got the report') 53 | print(report) # save the report to a file to actually do something with it! -------------------------------------------------------------------------------- /samples/dynamic_sample.py: -------------------------------------------------------------------------------- 1 | from veracode_api_py.dynamic import Analyses, Scans, ScanCapacitySummary, ScanOccurrences, ScannerVariables, DynUtils, Occurrences 2 | 3 | url = DynUtils().setup_url('http://www.example.com','DIRECTORY_AND_SUBDIRECTORY',False) 4 | 5 | allowed_hosts = [url] 6 | 7 | auth = DynUtils().setup_auth('AUTO','admin','smithy') 8 | 9 | auth_config = DynUtils().setup_auth_config(auth) 10 | 11 | crawl_config = DynUtils().setup_crawl_configuration([],False) 12 | 13 | scan_setting = DynUtils().setup_scan_setting(blocklist_configs=[],custom_hosts=[],user_agent=None) 14 | 15 | scan_config_request = DynUtils().setup_scan_config_request(url, allowed_hosts,auth_config, crawl_config, scan_setting) 16 | 17 | scan_contact_info = DynUtils().setup_scan_contact_info('tjarrett@example.com','Alan Smithee','800-555-1212') 18 | 19 | scan = DynUtils().setup_scan(scan_config_request,scan_contact_info) 20 | 21 | start_scan = DynUtils().start_scan(12, "HOUR") 22 | 23 | print(scan) 24 | 25 | analysis = Analyses().create('My API Analysis 4',scans=[scan],owner='Tim Jarrett',email='tjarrett@example.com', start_scan=start_scan) 26 | 27 | print(analysis) -------------------------------------------------------------------------------- /samples/getself.py: -------------------------------------------------------------------------------- 1 | # a simple sample to get attributes from the user's login information 2 | from veracode_api_py import Users 3 | 4 | me = Users().get_self() 5 | 6 | print("You are {}".format(me['user_name'])) -------------------------------------------------------------------------------- /samples/reportingapi_audit_sample.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import json 4 | import datetime 5 | from veracode_api_py import Analytics 6 | 7 | wait_seconds = 15 8 | 9 | print('Generating audit report...') 10 | theguid = Analytics().create_report(report_type="audit", start_date='2025-05-01',end_date='2025-05-05') 11 | 12 | print('Checking status for report {}...'.format(theguid)) 13 | thestatus,theaudits=Analytics().get_audits(theguid) 14 | 15 | while thestatus != 'COMPLETED': 16 | print('Waiting {} seconds before we try again...'.format(wait_seconds)) 17 | time.sleep(wait_seconds) 18 | print('Checking status for report {}...'.format(theguid)) 19 | thestatus,theaudits=Analytics().get_audits(theguid) 20 | 21 | recordcount = len(theaudits) 22 | 23 | print('Retrieved {} audit records'.format(recordcount)) 24 | 25 | if recordcount > 0: 26 | now = datetime.datetime.now().astimezone() 27 | filename = 'report-{}'.format(now) 28 | with open('{}.json'.format(filename), 'w') as outfile: 29 | json.dump(theaudits,outfile) 30 | outfile.close() 31 | 32 | print('Wrote {} audit records to {}.json'.format(recordcount,filename)) -------------------------------------------------------------------------------- /samples/reportingapi_deleted_sample.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import json 4 | import datetime 5 | from veracode_api_py import Analytics 6 | 7 | wait_seconds = 15 8 | 9 | print('Generating deleted scans report...') 10 | theguid = Analytics().create_deleted_scans_report(start_date='2024-07-01',end_date='2024-12-31') 11 | 12 | print('Checking status for report {}...'.format(theguid)) 13 | thestatus,thescans=Analytics().get_deleted_scans(theguid) 14 | 15 | while thestatus != 'COMPLETED': 16 | print('Waiting {} seconds before we try again...'.format(wait_seconds)) 17 | time.sleep(wait_seconds) 18 | print('Checking status for report {}...'.format(theguid)) 19 | thestatus,thescans=Analytics().get_deleted_scans(theguid) 20 | 21 | recordcount = len(thescans) 22 | 23 | print('Retrieved {} deleted scans'.format(recordcount)) 24 | 25 | if recordcount > 0: 26 | now = datetime.datetime.now().astimezone() 27 | filename = 'report-{}'.format(now) 28 | with open('{}.json'.format(filename), 'w') as outfile: 29 | json.dump(thescans,outfile) 30 | outfile.close() 31 | 32 | print('Wrote {} deleted scan records to {}.json'.format(recordcount,filename)) -------------------------------------------------------------------------------- /samples/reportingapi_sample.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import json 4 | import datetime 5 | from veracode_api_py import Analytics 6 | 7 | wait_seconds = 15 8 | 9 | print('Generating report...') 10 | theguid = Analytics().create_findings_report(start_date="2023-07-01 00:00:00") 11 | 12 | print('Checking status for report {}...'.format(theguid)) 13 | thestatus,thefindings=Analytics().get(theguid) 14 | 15 | while thestatus != 'COMPLETED': 16 | print('Waiting {} seconds before we try again...'.format(wait_seconds)) 17 | time.sleep(wait_seconds) 18 | print('Checking status for report {}...'.format(theguid)) 19 | thestatus,thefindings=Analytics().get(theguid) 20 | 21 | recordcount = len(thefindings) 22 | 23 | print('Retrieved {} findings'.format(recordcount)) 24 | 25 | if recordcount > 0: 26 | now = datetime.datetime.now().astimezone() 27 | filename = 'report-{}'.format(now) 28 | with open('{}.json'.format(filename), 'w') as outfile: 29 | json.dump(thefindings,outfile) 30 | outfile.close() 31 | 32 | print('Wrote {} findings to {}.json'.format(recordcount,filename)) -------------------------------------------------------------------------------- /samples/scansbom.py: -------------------------------------------------------------------------------- 1 | from veracode_api_py import SBOM 2 | from requests.exceptions import RequestException 3 | import json 4 | 5 | # provide a file name that is either in the same directory, or a fully qualified path 6 | # to generate a small sample, try: 7 | # veracode sbom --type image --source alpine:latest --format cyclonedx-json 8 | file = 'api-signing-sbom.json' 9 | 10 | try: 11 | f = open (file) 12 | except IOError: 13 | print('File is not valid: {}'.format(file)) 14 | 15 | try: 16 | updated_sbom = SBOM().scan(sbom=file) 17 | if updated_sbom is not None: 18 | # do something useful 19 | print(updated_sbom['components'][17]) 20 | 21 | except RequestException as e: 22 | print(type(e)) 23 | print(e) 24 | -------------------------------------------------------------------------------- /samples/static_scan_sample.py: -------------------------------------------------------------------------------- 1 | # a sample to show how to use veracode-api-py to access static scanning functions 2 | from veracode_api_py import StaticCLI 3 | 4 | #you can get a scan id from the output of the veracode static scan CLI command 5 | sample_scan_id = 'a5565dfa-6cad-491c-b680-e32f501c1b5a' 6 | 7 | scan = StaticCLI().Scans().get(scan_id=sample_scan_id) 8 | 9 | status = scan['scan_status'] 10 | segments = scan['binary_segments_expected'] 11 | 12 | print ('Scan status is {}'.format(status)) 13 | 14 | if status == 'SUCCESS': 15 | findings = StaticCLI().Scans().Findings().get(scan_id=sample_scan_id) 16 | print ('Findings: {}'.format(len(findings.get("findings")))) 17 | -------------------------------------------------------------------------------- /samples/user_api_creds_create.py: -------------------------------------------------------------------------------- 1 | from veracode_api_py import Users, APICredentials 2 | 3 | # This script creates an API user and then initializes their API credentials 4 | 5 | the_teams = ['46cc22ff-xxxx-xxxx-xxxx-xxxxxxxxxxx'] # fill in with the appropriate team GUIDs from Teams().get_all() 6 | the_username = 'sample_username' # fill in with the appropriate username 7 | the_email = 'veracode123@example.com' # fill in with the appropriate email 8 | the_roles = ["resultsapi"] # fill in with appropriate roles. Roles list is provided by Roles().get_all() 9 | 10 | print('Creating API service account with username {}...'.format(the_username)) 11 | theuser = Users().create(email=the_email,lastname='demo',firstname='demo',username=the_username, 12 | type='API',roles=the_roles,teams=the_teams) 13 | 14 | theguid = theuser['user_id'] 15 | 16 | thecreds = APICredentials().create(user_guid=theguid) 17 | 18 | print('') 19 | print('[{}]'.format(the_username)) 20 | print('veracode_api_key_id={}'.format(thecreds['api_id'])) 21 | print('veracode_api_key_secret={}'.format(thecreds['api_secret'])) 22 | print('') 23 | print('Please clear your terminal and scrollback buffer once you have copied the credentials!') 24 | -------------------------------------------------------------------------------- /samples/xml_sandbox.py: -------------------------------------------------------------------------------- 1 | # a simple sample to get the sandbox id 2 | from veracode_api_py import XMLAPI 3 | import xml.etree.ElementTree as ET 4 | 5 | xmlApi = XMLAPI() 6 | appListXml = xmlApi.get_app_list() 7 | appList = ET.fromstring(appListXml) 8 | 9 | # CHANGE BELOW TO SEARCH FOR OTHER APP NAMES / SANDBOXES 10 | searchAppName = "VeraDemo" 11 | searchSandboxName = "POJOs" 12 | 13 | foundAppId = 0 14 | for app in appList: 15 | appName = app.get("app_name") 16 | appId = app.get("app_id") 17 | if searchAppName == appName: 18 | foundAppId = appId 19 | break 20 | 21 | if foundAppId == 0: 22 | raise Exception("Did not find app id!") 23 | 24 | sandboxListXml = xmlApi.get_sandbox_list(foundAppId) 25 | sandboxList = ET.fromstring(sandboxListXml) 26 | 27 | foundSandboxId = 0 28 | for sandbox in sandboxList: 29 | sandboxName = sandbox.get("sandbox_name") 30 | sandboxId = sandbox.get("sandbox_id") 31 | if searchSandboxName == sandboxName: 32 | foundSandboxId = sandboxId 33 | break 34 | 35 | if foundSandboxId == 0: 36 | raise Exception("Did not find sandbox id!") 37 | 38 | print("App '" + searchAppName + "' ID: " + foundAppId + "\nSandbox '" + searchSandboxName + "' ID: " + foundSandboxId) -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | from distutils.core import setup 3 | 4 | with open("README.md", "r", encoding="utf-8") as fh: 5 | long_description = fh.read() 6 | 7 | setup( 8 | name = 'veracode_api_py', 9 | packages = ['veracode_api_py'], 10 | version = '0.9.62', 11 | license='MIT', 12 | description = 'Python helper library for working with the Veracode APIs. Handles retries, pagination, and other features of the modern Veracode REST APIs.', 13 | long_description = long_description, 14 | long_description_content_type="text/markdown", 15 | author = 'Tim Jarrett', 16 | author_email = 'tjarrett@veracode.com', 17 | url = 'https://github.com/tjarrettveracode', 18 | download_url = 'https://github.com/veracode/veracode-api-py/archive/v_0962.tar.gz', 19 | keywords = ['veracode', 'veracode-api'], 20 | install_requires=[ 21 | 'veracode-api-signing' 22 | ], 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', # Chose either "3 - Alpha", "4 - Beta" or "5 - Production/Stable" as the current state of your package 25 | 'Intended Audience :: Developers', 26 | 'Topic :: Software Development :: Build Tools', 27 | 'License :: OSI Approved :: MIT License', 28 | 'Programming Language :: Python :: 3' 29 | ], 30 | python_requires=">3.7" 31 | ) 32 | -------------------------------------------------------------------------------- /veracode_api_py/__init__.py: -------------------------------------------------------------------------------- 1 | from veracode_api_py.api import VeracodeAPI 2 | from veracode_api_py.policy import Policies 3 | from veracode_api_py.applications import Applications, Sandboxes, CustomFields 4 | from veracode_api_py.collections import Collections 5 | from veracode_api_py.dynamic import Analyses, Scans, CodeGroups, Configuration, ScannerVariables, ScanCapacitySummary, Occurrences, DynUtils 6 | from veracode_api_py.exceptions import VeracodeAPIError, VeracodeError 7 | from veracode_api_py.findings import Findings, SummaryReport, ManualScans, CWEs, CWECategories 8 | from veracode_api_py.healthcheck import Healthcheck 9 | from veracode_api_py.identity import Users, Teams, BusinessUnits, APICredentials, Roles 10 | from veracode_api_py.sca import Workspaces, ComponentActivity, SBOM, SCAApplications 11 | from veracode_api_py.exceptions import VeracodeError, VeracodeAPIError 12 | from veracode_api_py.xmlapi import XMLAPI 13 | from veracode_api_py.analytics import Analytics 14 | from veracode_api_py.static import StaticCLI 15 | from veracode_api_py.dast import DASTTargets, DASTAnalysisProfiles, DASTAnalysisRuns -------------------------------------------------------------------------------- /veracode_api_py/analytics.py: -------------------------------------------------------------------------------- 1 | #analytics.py - API class for Analytics Reporting API calls 2 | 3 | import json 4 | from uuid import UUID 5 | 6 | from .apihelper import APIHelper 7 | 8 | class Analytics(): 9 | report_types = [ "findings", "scans", "deletedscans", "audit" ] 10 | 11 | findings_scan_types = ["Static Analysis", "Dynamic Analysis", "Manual", "SCA", "Software Composition Analysis" ] 12 | scan_scan_types = ["Static Analysis", "Dynamic Analysis", "Manual" ] 13 | 14 | base_url = 'appsec/v1/analytics/report' 15 | 16 | #public methods 17 | def create_report(self,report_type,last_updated_start_date=None,last_updated_end_date=None, 18 | scan_type:list = [], finding_status=None,passed_policy=None, 19 | policy_sandbox=None,application_id=None,rawjson=False, deletion_start_date=None, 20 | deletion_end_date=None, sandbox_ids:list = [], start_date=None, end_date=None, 21 | audit_action:list = [], target_user_id:int = None, modifier_user_id:int = None): 22 | 23 | if report_type not in self.report_types: 24 | raise ValueError("{} is not in the list of valid report types ({})".format(report_type,self.report_types)) 25 | 26 | report_def = { 'report_type': report_type } 27 | 28 | if report_type in ['audit']: 29 | if start_date: 30 | report_def['start_date'] = start_date 31 | else: 32 | raise ValueError("{} report type requires a start date.").format(report_type) 33 | 34 | if end_date: 35 | report_def['end_date'] = end_date 36 | 37 | if report_type in ['findings','scans']: 38 | if last_updated_start_date: 39 | report_def['last_updated_start_date'] = last_updated_start_date 40 | else: 41 | raise ValueError("{} report type requires a last updated start date.").format(report_type) 42 | 43 | if last_updated_end_date: 44 | report_def['last_updated_end_date'] = last_updated_end_date 45 | 46 | if report_type == 'deletedscans': 47 | if deletion_start_date: 48 | report_def['deletion_start_date'] = deletion_start_date 49 | else: 50 | raise ValueError("{} report type requires a deletion start date.").format(report_type) 51 | 52 | if deletion_end_date: 53 | report_def['deletion_end_date'] = deletion_end_date 54 | 55 | # clean this part up, make it object oriented, probably switch report creation by report type and create sub methods 56 | 57 | if len(scan_type) > 0: 58 | if report_type == 'findings': 59 | valid_scan_types = self.findings_scan_types 60 | elif report_type in [ 'scans', 'deletedscans' ]: 61 | valid_scan_types = self.scan_scan_types 62 | if not(self._case_insensitive_list_compare(scan_type,valid_scan_types)): 63 | raise ValueError("{} is not in the list of valid scan types ({})".format(scan_type,valid_scan_types)) 64 | report_def['scan_type'] = scan_type 65 | 66 | if finding_status: 67 | report_def['finding_status'] = finding_status 68 | 69 | if passed_policy: 70 | report_def['passed_policy'] = passed_policy 71 | 72 | if policy_sandbox: 73 | report_def['policy_sandbox'] = policy_sandbox 74 | 75 | if application_id: 76 | report_def['application_id'] = application_id 77 | 78 | if sandbox_ids: 79 | report_def['sandbox_ids'] = sandbox_ids 80 | 81 | if len(audit_action) > 0: 82 | report_def['audit_action'] = audit_action 83 | 84 | if target_user_id: 85 | report_def['target_user_id'] = target_user_id 86 | 87 | if modifier_user_id: 88 | report_def['modifier_user_id'] = modifier_user_id 89 | 90 | payload = json.dumps(report_def) 91 | response = APIHelper()._rest_request(url=self.base_url,method="POST",body=payload) 92 | 93 | if rawjson: 94 | return response 95 | else: 96 | return response['_embedded']['id'] #we will usually just need the guid so we can come back and fetch the report 97 | 98 | def create_findings_report(self, start_date, end_date=None, 99 | scan_type:list = [], finding_status=None, passed_policy=None, 100 | policy_sandbox=None, application_id=None,rawjson=False): 101 | return self.create_report(report_type='findings', last_updated_start_date=start_date, 102 | last_updated_end_date=end_date,scan_type=scan_type, 103 | finding_status=finding_status, passed_policy=passed_policy, 104 | policy_sandbox=policy_sandbox, application_id=application_id,rawjson=rawjson) 105 | 106 | def create_scans_report(self, start_date, end_date=None, scan_type:list = [], 107 | policy_sandbox=None, application_id=None, rawjson=False): 108 | return self.create_report(report_type='scans', last_updated_start_date=start_date, 109 | last_updated_end_date=end_date, scan_type=scan_type, 110 | policy_sandbox=policy_sandbox, application_id=application_id,rawjson=rawjson) 111 | 112 | def create_deleted_scans_report(self, start_date, end_date=None, application_id=None, 113 | rawjson=False): 114 | return self.create_report(report_type='deleted_scans', deletion_start_date=start_date, 115 | deletion_end_date=end_date, application_id=application_id, 116 | rawjson=rawjson) 117 | 118 | def create_audit_report(self, start_date, end_date=None, audit_action:list=[], target_user_id:int=None, 119 | modifier_user_id:int=None): 120 | return self.create_report(report_type='audit', start_date=start_date, end_date=end_date, audit_action=audit_action, 121 | target_user_id=target_user_id, modifier_user_id=modifier_user_id) 122 | 123 | def get_findings(self, guid: UUID): 124 | thestatus, thefindings = self.get(guid=guid,report_type='findings') 125 | return thestatus, thefindings 126 | 127 | def get_scans(self, guid: UUID): 128 | thestatus, thescans = self.get(guid=guid,report_type='scans') 129 | return thestatus, thescans 130 | 131 | def get_deleted_scans(self, guid: UUID): 132 | thestatus, thescans = self.get(guid=guid,report_type='deletedscans') 133 | return thestatus, thescans 134 | 135 | def get_audits(self, guid: UUID): 136 | thestatus, theaudits = self.get(guid=guid, report_type='audit_logs') 137 | return thestatus, theaudits 138 | 139 | def get(self,guid: UUID,report_type='findings'): 140 | # handle multiple scan types 141 | uri = "{}/{}".format(self.base_url,guid) 142 | theresponse = APIHelper()._rest_paged_request(uri,"GET",report_type,{},fullresponse=True) 143 | thestatus = theresponse.get('_embedded',{}).get('status','') 144 | thebody = theresponse.get('_embedded',{}).get(report_type,{}) 145 | return thestatus, thebody 146 | 147 | #helper methods 148 | def _case_insensitive_list_compare(self,input_list:list, target_list:list): 149 | input_set = self._lowercase_set_from_list(input_list) 150 | target_set = self._lowercase_set_from_list(target_list) 151 | return target_set.issuperset(input_set) 152 | 153 | def _lowercase_set_from_list(self,thelist:list): 154 | return set([x.lower() for x in thelist]) -------------------------------------------------------------------------------- /veracode_api_py/apihelper.py: -------------------------------------------------------------------------------- 1 | # apihelper.py - API class for making network calls 2 | 3 | import requests 4 | import logging 5 | import json 6 | import time 7 | from requests.adapters import HTTPAdapter 8 | 9 | from veracode_api_signing.exceptions import VeracodeAPISigningException 10 | from veracode_api_signing.plugin_requests import RequestsAuthPluginVeracodeHMAC 11 | from veracode_api_signing.credentials import get_credentials 12 | from veracode_api_signing.regions import get_region_for_api_credential 13 | 14 | from .exceptions import VeracodeAPIError 15 | from .log import VeracodeLog as vlog 16 | from .constants import Constants 17 | 18 | logger = logging.getLogger(__name__) 19 | 20 | 21 | class APIHelper(): 22 | api_key_id = None 23 | api_key_secret = None 24 | region = None 25 | 26 | def __init__(self, debug=False): 27 | self.baseurl = self._get_baseurl() 28 | self.base_rest_url = self._get_baseresturl() 29 | self.retry_seconds = 120 30 | self.connect_error_msg = "Connection Error" 31 | # vlog.setup_logging(self,debug=debug) 32 | 33 | # helper functions 34 | 35 | def _get_baseurl(self): 36 | return self._get_region_url('xml') 37 | 38 | def _get_baseresturl(self): 39 | return self._get_region_url('rest') 40 | 41 | def _get_region_url(self, type): 42 | if self.api_key_id is None or self.api_key_secret is None: 43 | self.api_key_id, self.api_key_secret = get_credentials() 44 | if self.region is None: 45 | self.region = get_region_for_api_credential(self.api_key_id) 46 | 47 | if type == 'xml': 48 | return Constants().REGIONS[self.region]['base_xml_url'] 49 | elif type == 'rest': 50 | return Constants().REGIONS[self.region]['base_rest_url'] 51 | 52 | def _check_for_errors(self,theresponse, *args, **kwargs): 53 | if theresponse.status_code in (429, 502, 503, 504): 54 | # retry by populating new prepared request from the request in the response object 55 | # and recalculating auth 56 | logger.debug("Retrying request, error code {} received".format(theresponse.status_code)) 57 | session = requests.Session() 58 | oldreq = theresponse.request 59 | oldheaders = oldreq.headers 60 | del oldheaders['authorization'] 61 | newreq = requests.Request(oldreq.method,oldreq.url,auth=RequestsAuthPluginVeracodeHMAC(), 62 | headers=oldheaders) 63 | return session.send(newreq.prepare()) 64 | 65 | def _prepare_headers(self,method,apifamily,files=False): 66 | headers = {"User-Agent": "api.py"} 67 | if method in ["POST", "PUT"] and apifamily=='json' and not(files): 68 | headers.update({'Content-type': 'application/json'}) 69 | return headers 70 | 71 | def _rest_request(self, url, method, params=None, body=None, fullresponse=False, use_base_url=True, files=None): 72 | # base request method for a REST request 73 | if files is None: 74 | myheaders = self._prepare_headers(method,'json') 75 | else: 76 | myheaders = self._prepare_headers(method,'json',files=True) 77 | 78 | session = requests.Session() 79 | 80 | if use_base_url: 81 | url = self.base_rest_url + url 82 | 83 | try: 84 | if method == "GET": 85 | request = requests.Request(method, url, params=params, auth=RequestsAuthPluginVeracodeHMAC(), 86 | headers=myheaders, 87 | hooks={'response': self._check_for_errors}) 88 | prepared_request = request.prepare() 89 | r = session.send(prepared_request) 90 | elif method == "POST": 91 | if files is None: 92 | r = requests.post(url, params=params, auth=RequestsAuthPluginVeracodeHMAC(), headers=myheaders, 93 | data=body) 94 | else: 95 | r = requests.post(url, params=params, auth=RequestsAuthPluginVeracodeHMAC(), headers=myheaders, 96 | data=body, files=files) 97 | elif method == "PUT": 98 | r = requests.put(url, params=params, auth=RequestsAuthPluginVeracodeHMAC(), headers=myheaders, 99 | data=body) 100 | elif method == "DELETE": 101 | r = requests.delete(url, params=params, auth=RequestsAuthPluginVeracodeHMAC(), headers=myheaders) 102 | else: 103 | raise VeracodeAPIError("Unsupported HTTP method") 104 | except requests.exceptions.RequestException as e: 105 | logger.exception("Error: {}".format(self.connect_error_msg)) 106 | raise VeracodeAPIError(e.text) from e 107 | 108 | if r.status_code != requests.codes.ok: 109 | logger.debug("API call returned non-200 HTTP status code: {}".format(r.status_code)) 110 | 111 | if not (r.ok): 112 | conv_id = r.headers['x-conversation-id'] 113 | logger.debug("Error retrieving data. HTTP status code: {}, conversation id {}".format(r.status_code,conv_id)) 114 | if r.status_code == 401: 115 | logger.exception( 116 | "Error [{}]: {} for request {}. Check that your Veracode API account credentials are correct.".format( 117 | r.status_code, 118 | r.text, r.request.url)) 119 | else: 120 | logger.exception("Error [{}]: {} for request {}". 121 | format(r.status_code, r.text, r.request.url)) 122 | re = requests.exceptions.RequestException() 123 | re.response = r 124 | re.errno = r.status_code 125 | re.request = r.request 126 | raise re 127 | 128 | if fullresponse: 129 | return r 130 | elif r.text != "": 131 | return r.json() 132 | else: 133 | return "" 134 | 135 | def _rest_paged_request(self, uri, method, element, params=None,fullresponse=False): 136 | all_data = [] 137 | page = 0 138 | more_pages = True 139 | 140 | while more_pages: 141 | params['page'] = page 142 | page_data = self._rest_request(uri, method, params) 143 | total_pages = page_data.get('page', {}).get('total_pages', 0) 144 | data_page = page_data.get('_embedded', {}).get(element, []) 145 | all_data += data_page 146 | 147 | page += 1 148 | more_pages = page < total_pages 149 | if page==1 and fullresponse: 150 | return page_data 151 | else: 152 | return all_data 153 | 154 | def _xml_request(self, url, method, params=None, files=None): 155 | # base request method for XML APIs, handles what little error handling there is around these APIs 156 | if method not in ["GET", "POST"]: 157 | raise VeracodeAPIError("Unsupported HTTP method") 158 | 159 | try: 160 | session = requests.Session() 161 | session.mount(self.baseurl, HTTPAdapter(max_retries=3)) 162 | request = requests.Request(method, url, params=params, files=files, 163 | auth=RequestsAuthPluginVeracodeHMAC(), headers=self._prepare_headers(method,'xml')) 164 | prepared_request = request.prepare() 165 | r = session.send(prepared_request) 166 | if 200 <= r.status_code <= 299: 167 | if r.status_code == 204: 168 | # retry after wait 169 | time.sleep(self.retry_seconds) 170 | return self._xml_request(url,method,params) 171 | elif r.content is None: 172 | logger.debug("HTTP response body empty:\r\n{}\r\n{}\r\n{}\r\n\r\n{}\r\n{}\r\n{}\r\n" 173 | .format(r.request.url, r.request.headers, r.request.body, r.status_code, r.headers, 174 | r.content)) 175 | raise VeracodeAPIError("HTTP response body is empty") 176 | else: 177 | return r.content 178 | else: 179 | logger.debug("HTTP error for request:\r\n{}\r\n{}\r\n{}\r\n\r\n{}\r\n{}\r\n{}\r\n" 180 | .format(r.request.url, r.request.headers, r.request.body, r.status_code, r.headers, 181 | r.content)) 182 | raise VeracodeAPIError("HTTP error: {}".format(r.status_code)) 183 | except requests.exceptions.RequestException as e: 184 | logger.exception("Connection error") 185 | raise VeracodeAPIError(e) 186 | -------------------------------------------------------------------------------- /veracode_api_py/applications.py: -------------------------------------------------------------------------------- 1 | #applications.py - API class for Applications API calls 2 | 3 | import json 4 | from urllib import parse 5 | from uuid import UUID 6 | 7 | from .apihelper import APIHelper 8 | from .constants import Constants 9 | 10 | class Applications(): 11 | def get_all(self,policy_check_after=None): 12 | if policy_check_after == None: 13 | params={} 14 | else: 15 | params={"policy_compliance_checked_after": policy_check_after} 16 | 17 | return APIHelper()._rest_paged_request('appsec/v1/applications',"GET", params=params, 18 | element="applications") 19 | 20 | def get (self,guid: UUID=None,legacy_id: int=None): 21 | """Gets a single applications in the current customer account using the Veracode Application API.""" 22 | if legacy_id == None: 23 | apps_base_uri = "appsec/v1/applications" + "/{}" 24 | uri = apps_base_uri.format(guid) 25 | else: 26 | apps_base_uri = "appsec/v1/applications?legacy_id={}" 27 | uri = apps_base_uri.format(legacy_id) 28 | 29 | return APIHelper()._rest_request(uri,"GET") 30 | 31 | def get_by_name (self,appname: str): 32 | """Gets a list of applications having a name that matches appname, using the Veracode Applications API.""" 33 | params = {"name": parse.quote(appname)} 34 | return APIHelper()._rest_paged_request(uri="appsec/v1/applications",method="GET",element="applications",params=params) 35 | 36 | def get_by_repo (self,git_repo_url: str): 37 | """Gets a list of applications having a name that matches appname, using the Veracode Applications API.""" 38 | params = {"git_repo_url": parse.quote(git_repo_url)} 39 | return APIHelper()._rest_paged_request(uri="appsec/v1/applications",method="GET",element="applications",params=params) 40 | 41 | def create(self,app_name:str ,business_criticality, description: str=None, business_unit: UUID=None, teams=[], policy_guid:UUID=None, 42 | custom_fields=[], bus_owner_name=None, bus_owner_email=None, git_repo_url=None, custom_kms_alias: str=None, tags=None): 43 | return self._create_or_update("CREATE",app_name=app_name,business_criticality=business_criticality, 44 | description=description,business_unit=business_unit,teams=teams, policy_guid=policy_guid, 45 | custom_fields=custom_fields, tags=tags, bus_owner_name=bus_owner_name, 46 | bus_owner_email=bus_owner_email, git_repo_url=git_repo_url, custom_kms_alias=custom_kms_alias) 47 | 48 | def update(self,guid: UUID,app_name:str, business_criticality, description: str=None, business_unit: UUID=None, 49 | teams=[], policy_guid:UUID=None, custom_fields=[], 50 | bus_owner_name=None,bus_owner_email=None, git_repo_url=None, custom_kms_alias: str=None, tags=None): 51 | return self._create_or_update("UPDATE",app_name=app_name,business_criticality=business_criticality, 52 | description=description,business_unit=business_unit,teams=teams,guid=guid, 53 | policy_guid=policy_guid, custom_fields=custom_fields, tags=tags, 54 | bus_owner_name=bus_owner_name,bus_owner_email=bus_owner_email, 55 | git_repo_url=git_repo_url, custom_kms_alias=custom_kms_alias) 56 | 57 | def delete(self,guid: UUID): 58 | uri = 'appsec/v1/applications/{}'.format(guid) 59 | return APIHelper()._rest_request(uri,'DELETE') 60 | 61 | def _create_or_update(self,method,app_name: str, business_criticality, description: str=None, business_unit: UUID=None, 62 | teams=[],guid=None,policy_guid:UUID=None, custom_fields=[], 63 | bus_owner_name=None,bus_owner_email=None,git_repo_url=None,custom_kms_alias:str=None, tags=None): 64 | if method == 'CREATE': 65 | uri = 'appsec/v1/applications' 66 | httpmethod = 'POST' 67 | elif method == 'UPDATE': 68 | uri = 'appsec/v1/applications/{}'.format(guid) 69 | httpmethod = 'PUT' 70 | else: 71 | return 72 | 73 | if business_criticality not in Constants().BUSINESS_CRITICALITY: 74 | raise ValueError("{} is not in the list of valid business criticalities ({})".format(business_criticality,Constants().BUSINESS_CRITICALITY)) 75 | business_criticality = business_criticality.replace(" ", "_") 76 | 77 | app_def = {'name':app_name, 'business_criticality':business_criticality} 78 | 79 | if (description != None): 80 | desc = { 'description': description} 81 | app_def.update(desc) 82 | 83 | if (tags != None): 84 | app_def.update({ 'tags': tags }) 85 | 86 | if policy_guid: 87 | app_def.update({"policies": [{'guid': str(policy_guid)}]}) 88 | 89 | if len(teams) > 0: 90 | # optionally pass a list of teams to add to the application profile 91 | team_list = [] 92 | for team in teams: 93 | team_list.append({'guid': team}) 94 | app_def.update({'teams': team_list}) 95 | 96 | if business_unit != None: 97 | bu = {'business_unit': {'guid': str(business_unit)}} 98 | app_def.update(bu) 99 | 100 | if (custom_fields != None): 101 | app_def.update({"custom_fields": custom_fields}) 102 | 103 | if (bus_owner_email != None) & (bus_owner_name != None): 104 | bus_owner = {'business_owners':[ {'email': bus_owner_email, 'name': bus_owner_name } ] } 105 | app_def.update(bus_owner) 106 | 107 | if (git_repo_url != None): 108 | gru = { 'git_repo_url': git_repo_url} 109 | app_def.update(gru) 110 | 111 | if (custom_kms_alias != None): 112 | app_def.update({"custom_kms_alias": custom_kms_alias}) 113 | 114 | payload = json.dumps({"profile": app_def}) 115 | return APIHelper()._rest_request(uri,httpmethod,body=payload) 116 | 117 | class Sandboxes (): 118 | def get_all(self,guid: UUID): 119 | request_params = {} 120 | uri = 'appsec/v1/applications/{}/sandboxes'.format(guid) 121 | return APIHelper()._rest_paged_request(uri,'GET','sandboxes',request_params) 122 | 123 | def create(self, app: UUID, name: str, auto_recreate=False, custom_fields=[]): 124 | uri = 'appsec/v1/applications/{}/sandboxes'.format(app) 125 | sandbox_def = {'name': name, 'auto_recreate': auto_recreate} 126 | 127 | if len(custom_fields) > 0: 128 | sandbox_def.update({"custom_fields": custom_fields}) 129 | 130 | payload = json.dumps(sandbox_def) 131 | return APIHelper()._rest_request(uri,'POST',body=payload) 132 | 133 | def update(self, app: UUID, sandbox: UUID, name: str, auto_recreate=False, custom_fields=[]): 134 | uri = 'appsec/v1/applications/{}/sandboxes/{}'.format(app,sandbox) 135 | sandbox_def = {'name': name, 'auto_recreate': auto_recreate} 136 | 137 | if len(custom_fields) > 0: 138 | sandbox_def.update({"custom_fields": custom_fields}) 139 | 140 | payload = json.dumps(sandbox_def) 141 | return APIHelper()._rest_request(uri,'PUT',body=payload) 142 | 143 | def delete(self, app: UUID, sandbox: UUID): 144 | uri = 'appsec/v1/applications/{}/sandboxes/{}'.format(app,sandbox) 145 | return APIHelper()._rest_request(uri,'DELETE') 146 | 147 | class CustomFields(): 148 | def get_all (self): 149 | return APIHelper()._rest_request('appsec/v1/custom_fields','GET') 150 | -------------------------------------------------------------------------------- /veracode_api_py/collections.py: -------------------------------------------------------------------------------- 1 | #collections.py - API class for Collections API calls 2 | 3 | import json 4 | from urllib import parse 5 | from uuid import UUID 6 | 7 | from .apihelper import APIHelper 8 | 9 | class Collections(): 10 | compliance_titles = { 11 | 'DID_NOT_PASS': 'Did Not Pass', 12 | 'PASSED': 'Passed', 13 | 'CONDITIONAL_PASS': 'Conditional Pass', 14 | 'NOT_ASSESSED': 'Not Assessed', 15 | 'NOT_EVALUATED': 'Not Evaluated', 16 | 'out_of_compliance': 'Out of Compliance', 17 | 'within_grace_period': 'Within Grace Period', 18 | 'compliant': 'Compliant' 19 | } 20 | 21 | #public methods 22 | def get_all(self): 23 | request_params = {} 24 | return self._get_collections(request_params) 25 | 26 | def get_by_name(self,collection_name: str): 27 | params = {"name": parse.quote(collection_name)} 28 | return self._get_collections(params) 29 | 30 | def get_by_business_unit(self,business_unit_name: str): 31 | params = {"business_unit": parse.quote(business_unit_name)} 32 | return self._get_collections(params) 33 | 34 | def get_statistics(self): 35 | return APIHelper()._rest_request("appsec/v1/collections/statistics","GET") 36 | 37 | def get(self,guid: UUID): 38 | uri = "appsec/v1/collections/{}".format(guid) 39 | return APIHelper()._rest_request(uri,"GET") 40 | 41 | def get_assets(self,guid: UUID): 42 | uri = "appsec/v1/collections/{}/assets".format(guid) 43 | return APIHelper()._rest_paged_request(uri,"GET","assets",params={}) 44 | 45 | def create(self,name: str,description: str="",tags='',business_unit_guid: UUID=None,custom_fields=[],assets=[]): 46 | return self._create_or_update(method="CREATE",name=name,description=description, 47 | tags=tags,business_unit_guid=business_unit_guid,custom_fields=custom_fields,assets=assets) 48 | 49 | def update(self,guid: UUID,name: str,description: str="",tags: str="",business_unit_guid: UUID=None,custom_fields=[],assets=[]): 50 | return self._create_or_update(method="UPDATE",name=name,description=description, 51 | tags=tags,business_unit_guid=business_unit_guid,custom_fields=custom_fields,assets=assets,guid=guid) 52 | 53 | def delete(self,guid: UUID): 54 | uri = "appsec/v1/collections/{}".format(guid) 55 | return APIHelper()._rest_request(uri,"DELETE") 56 | 57 | #private methods 58 | 59 | def _get_collections(self,params): 60 | return APIHelper()._rest_paged_request("appsec/v1/collections","GET","collections",params=params) 61 | 62 | def _create_or_update(self,method,name: str,description: str="",tags: str="",business_unit_guid: UUID=None,custom_fields=[],assets=[],guid: UUID=None): 63 | if method == 'CREATE': 64 | uri = 'appsec/v1/collections' 65 | httpmethod = 'POST' 66 | elif method == 'UPDATE': 67 | uri = 'appsec/v1/collections/{}'.format(guid) 68 | httpmethod = 'PUT' 69 | else: 70 | return 71 | 72 | payload = {"name": name, "description": description} 73 | if tags != '': 74 | t = {'tags': tags} 75 | payload.update(t) 76 | if business_unit_guid != None: 77 | bu = {'business_unit': {'guid': business_unit_guid} } 78 | payload.update(bu) 79 | if len(custom_fields) > 0: 80 | cf = {'custom_fields': custom_fields} 81 | payload.update(cf) 82 | if len(assets) > 0: 83 | asset_list = [] 84 | for asset in assets: 85 | this_asset = {'guid':asset,'type': 'APPLICATION'} 86 | asset_list.append(this_asset) 87 | al = {'asset_infos': asset_list} 88 | payload.update(al) 89 | return APIHelper()._rest_request(uri,httpmethod,params={},body=json.dumps(payload)) -------------------------------------------------------------------------------- /veracode_api_py/constants.py: -------------------------------------------------------------------------------- 1 | #constants.py - contains constant values for lookups 2 | 3 | class Constants(): 4 | # translate between annotation types in Findings API v2 and in Mitigations API (XML) 5 | ANNOT_TYPE = {"APPDESIGN":"appdesign",\ 6 | "NETENV": "netenv",\ 7 | "OSENV":"osenv",\ 8 | "APPROVED":"accepted",\ 9 | "REJECTED":"rejected",\ 10 | "FP":"fp", \ 11 | "LIBRARY":"library", \ 12 | "ACCEPTRISK": 'acceptrisk'} 13 | 14 | SCA_ANNOTATION_TYPE = ["VULNERABILITY", "LICENSE"] 15 | 16 | SCA_ANNOT_ACTION = ["BYENV", "BYDESIGN", "FP", "ACCEPTRISK", "LEGAL", "COMMERCIAL", "EXPERIMENTAL", "INTERNAL", "APPROVE", "REJECT", "COMMENT"] 17 | 18 | SCA_ANNOT_STATUS = ["PROPOSED", "ACCEPTED", "REJECTED"] 19 | 20 | SCA_LICENSE_RISK = [ "HIGH", "MEDIUM", "LOW", "NON_OSS", "UNRECOGNIZED"] 21 | 22 | AGENT_TYPE = ["CLI", "MAVEN", "GRADLE", "JENKINS", "BAMBOO", "CIRCLECI", "CODESHIP", "PIPELINES", "TRAVIS", "WINDOWSCI"] 23 | 24 | SCA_EVENT_GROUP = [ 'WORKSPACE', 'AGENT', 'SCAN', 'PROJECT', 'RULES'] 25 | 26 | SEVERITIES = [ "VERY_HIGH", "HIGH", "MEDIUM", "LOW", "VERY_LOW", "INFORMATIONAL"] 27 | 28 | REGIONS = { 29 | 'global': { 30 | 'base_xml_url': 'https://analysiscenter.veracode.com/api', 31 | 'base_rest_url': 'https://api.veracode.com/' 32 | }, 33 | 'eu': { 34 | 'base_xml_url': 'https://analysiscenter.veracode.com/eu', 35 | 'base_rest_url': 'https://api.veracode.eu/' 36 | }, 37 | 'fedramp': { 38 | 'base_xml_url': 'https://analysiscenter.veracode.us/api', 39 | 'base_rest_url': 'https://api.veracode.us/' 40 | } 41 | } 42 | 43 | DEV_STAGE = [ 'DEVELOPMENT', 'TESTING', 'RELEASE'] 44 | 45 | BUSINESS_CRITICALITY = [ 'VERY HIGH', 'HIGH', 'MEDIUM', 'LOW', 'VERY LOW', 'VERY_HIGH', 'VERY_LOW'] 46 | 47 | DAST_TARGET_TYPE = [ 'WEB_APP', 'API'] 48 | 49 | DAST_CRAWLER_MODE = [ 'SMART', 'EXHAUSTIVE' ] 50 | 51 | DAST_PROTOCOL = [ 'HTTP', 'HTTPS'] 52 | 53 | DAST_SCAN_TYPE = [ 'QUICK', 'FULL'] 54 | 55 | DAST_SCANNERS = [ 'fingerprinting', 'ssl', 'http_header', 'portscan', 'fuzzer', 'sql_injection', 56 | 'xss', 'file_inclusion', 'deserialization', 'xxe', 'command_injection', 57 | 'csrf', 'ldap_injection'] 58 | -------------------------------------------------------------------------------- /veracode_api_py/dast.py: -------------------------------------------------------------------------------- 1 | #collections.py - API class for Collections API calls 2 | 3 | import json 4 | from urllib import parse 5 | from typing import List 6 | from uuid import UUID 7 | from requests import RequestException 8 | 9 | from .apihelper import APIHelper 10 | from .constants import Constants 11 | from .exceptions import VeracodeAPIError, VeracodeError 12 | 13 | ROOT_URL = 'dae/api/tcs-api/api/v1' 14 | 15 | class DASTTargets(): 16 | base_url = ROOT_URL + '/targets' 17 | 18 | def get_all(self): 19 | return APIHelper()._rest_paged_request(self.base_url, 'GET', element='targets',params={}) 20 | 21 | def get(self, target_id: UUID): 22 | uri = self.base_url + '/{}'.format(target_id) 23 | return APIHelper()._rest_request(uri, 'GET') 24 | 25 | def get_by_name(self, target_name): 26 | return self.search(target_name=target_name) 27 | 28 | def search(self, target_name=None, url=None, search_term=None, target_type=None): 29 | # do null checks and construct the parameters 30 | params = {} 31 | 32 | if target_name != None: 33 | params['name'] = target_name 34 | 35 | if url != None: 36 | params['url'] = url 37 | 38 | if search_term != None: 39 | params['search_term'] = search_term 40 | 41 | if target_type != None: 42 | if target_type not in Constants().DAST_TARGET_TYPE: 43 | raise ValueError("{} is not in the list of valid target types ({})".format(target_type,Constants().DAST_TARGET_TYPE)) 44 | params['target_type'] = target_type 45 | 46 | if params == {}: 47 | return {} 48 | 49 | return APIHelper()._rest_paged_request(self.base_url, 'GET', element='targets',params=params) 50 | 51 | def create(self, name, description, protocol, url='', api_specification_file_url='', target_type='WEB_APP', 52 | scan_type='QUICK',is_sec_lead_only=False,teams=[]): 53 | 54 | return self._create_or_update(method='CREATE', name=name, description=description, 55 | protocol=protocol,url=url, 56 | api_specification_file_url=api_specification_file_url, 57 | target_type=target_type,scan_type=scan_type, 58 | is_sec_lead_only=is_sec_lead_only,teams=teams) 59 | 60 | def update(self, target_id, name, description, protocol, url, api_specification_file_url, target_type='WEB_APP', 61 | scan_type='QUICK',is_sec_lead_only=False,teams=[]): 62 | 63 | return self._create_or_update(method='UPDATE', name=name, description=description, 64 | protocol=protocol,url=url, 65 | api_specification_file_url=api_specification_file_url, 66 | target_type=target_type,scan_type=scan_type, 67 | is_sec_lead_only=is_sec_lead_only,teams=teams, target_id=target_id) 68 | 69 | def delete(self, target_id: UUID): 70 | uri = self.base_url + '/{}'.format(target_id) 71 | return APIHelper()._rest_request(uri, 'DELETE') 72 | 73 | def _create_or_update(self, method, name, description, protocol, url, api_specification_file_url, target_type='WEB_APP', 74 | scan_type='QUICK',is_sec_lead_only=False,teams=[], target_id: UUID=None): 75 | 76 | if protocol not in Constants().DAST_PROTOCOL: 77 | raise ValueError("{} is not in the list of valid protocols ({})".format(protocol,Constants().DAST_PROTOCOL)) 78 | 79 | if target_type not in Constants().DAST_TARGET_TYPE: 80 | raise ValueError("{} is not in the list of valid target types ({})".format(target_type,Constants().DAST_TARGET_TYPE)) 81 | 82 | if scan_type not in Constants().DAST_SCAN_TYPE: 83 | raise ValueError("{} is not in the list of valid scan types ({})".format(scan_type,Constants().DAST_SCAN_TYPE)) 84 | 85 | body = { 'name': name, 'description': description, 'protocol': protocol, 'url': url, 86 | 'api_specification_file_url': api_specification_file_url, 'target_type': target_type, 87 | 'scan_type': scan_type, 'is_sec_lead_only': is_sec_lead_only, 'teams': teams} 88 | 89 | if method == "CREATE": 90 | httpmethod = 'POST' 91 | elif method == "UPDATE": 92 | httpmethod = 'PUT' 93 | body['target_id'] = target_id 94 | else: 95 | return 96 | 97 | payload = json.dumps(body) 98 | 99 | return APIHelper()._rest_request(self.base_url,httpmethod,body=payload) 100 | 101 | class DASTAnalysisProfiles(): 102 | base_url = ROOT_URL + '/analysis_profiles' 103 | 104 | def get_all(self, target_id: UUID=None, type=None): 105 | params = {} 106 | 107 | if type != None: 108 | if type not in ['TARGET','SYSTEM']: 109 | raise ValueError("{} is not in the list of valid types ({})".format(type,['TARGET','SYSTEM'])) 110 | params['type'] = type 111 | 112 | if target_id != None: 113 | params['target_id'] = target_id 114 | 115 | return APIHelper()._rest_paged_request(self.base_url,"GET",'analysis_profiles',params=params) 116 | 117 | def get(self,analysis_profile_id: UUID): 118 | uri = '{}/{}'.format(self.base_url,analysis_profile_id) 119 | return APIHelper()._rest_request(uri,"GET") 120 | 121 | def update(self, analysis_profile_id: UUID, allowed_urls=[],denied_urls=[],seed_urls=[], 122 | grouped_urls=[],crawler_mode=None,rate_limit=None,max_duration=None, 123 | max_crawl_duration=None): 124 | uri = '{}/{}'.format(self.base_url,analysis_profile_id) 125 | body = {} 126 | 127 | if len(allowed_urls) > 0: 128 | body['allowed_urls'] = allowed_urls 129 | 130 | if len(denied_urls) > 0: 131 | body['denied_urls'] = denied_urls 132 | 133 | if len(seed_urls) > 0: 134 | body['seed_urls'] = seed_urls 135 | 136 | if len(grouped_urls) > 0: 137 | body['grouped_urls'] = grouped_urls 138 | 139 | if crawler_mode != None: 140 | if crawler_mode not in Constants().DAST_CRAWLER_MODE: 141 | raise ValueError("{} is not in the list of valid crawler modes ({})".format(crawler_mode,Constants().DAST_CRAWLER_MODE)) 142 | 143 | body['crawler_mode'] = crawler_mode 144 | 145 | if rate_limit != None: 146 | body['rate_limit'] = rate_limit 147 | 148 | if max_duration != None: 149 | body['max_duration'] = max_duration 150 | 151 | if max_crawl_duration != None: 152 | body['max_crawl_duration'] = max_crawl_duration 153 | 154 | payload = json.dumps(body) 155 | return APIHelper()._rest_request(uri,'PUT',body=payload) 156 | 157 | def update_parent (self, analysis_profile_id: UUID, parent_analysis_profile_id: UUID): 158 | uri = '{}/{}'.format(self.base_url,analysis_profile_id) 159 | body = { 'parent_analysis_profile_id': parent_analysis_profile_id } 160 | payload = json.dumps(body) 161 | 162 | return APIHelper()._rest_request(uri,"PUT",body=payload) 163 | 164 | def get_authentications ( self, analysis_profile_id: UUID): 165 | self.authentications = self.Authentications(analysis_profile_id=analysis_profile_id, 166 | base_url=self.base_url) 167 | return self.authentications.get() 168 | 169 | def update_system_auth ( self, analysis_profile_id: UUID, username, password): 170 | self.authentications = self.Authentications(analysis_profile_id=analysis_profile_id, 171 | base_url=self.base_url) 172 | return self.authentications.update_system_auth(username=username,password=password) 173 | 174 | def update_app_auth ( self, analysis_profile_id: UUID, username, password, login_url): 175 | self.authentications = self.Authentications(analysis_profile_id=analysis_profile_id, 176 | base_url=self.base_url) 177 | return self.authentications.update_app_auth(username=username,password=password,login_url=login_url) 178 | 179 | def update_parameter_auth ( self, analysis_profile_id: UUID, id, title, type, key, value): 180 | self.authentications = self.Authentications(analysis_profile_id=analysis_profile_id, 181 | base_url=self.base_url) 182 | return self.authentications.update_parameter_auth(id=id, title=title, type=type, 183 | key=key, value=value) 184 | 185 | def get_scanners(self, analysis_profile_id: UUID): 186 | self.scanners = self.Scanners(analysis_profile_id=analysis_profile_id, base_url=self.base_url) 187 | return self.scanners.get() 188 | 189 | def update_scanners(self, analysis_profile_id: UUID, scanner_id, scanner_value=True): 190 | self.scanners = self.Scanners(analysis_profile_id=analysis_profile_id, base_url=self.base_url) 191 | return self.scanners.update(scanner_id=scanner_id, scanner_value=scanner_value) 192 | 193 | def get_schedules(self, analysis_profile_id: UUID): 194 | self.schedules = self.Schedules(analysis_profile_id=analysis_profile_id, base_url=self.base_url) 195 | return self.schedules.get_all() 196 | 197 | def get_schedule(self, analysis_profile_id: UUID, schedule_id: UUID): 198 | self.schedules = self.Schedules(analysis_profile_id=analysis_profile_id, base_url=self.base_url) 199 | return self.schedules.get(schedule_id=schedule_id) 200 | 201 | def create_schedule(self,analysis_profile_id: UUID, frequency,day=1,weekday=1,timezone='America/New York',time="00:00"): 202 | self.schedules = self.Schedules(analysis_profile_id=analysis_profile_id, base_url=self.base_url) 203 | return self.schedules.create(frequency=frequency, day=day, weekday=weekday, timezone=timezone, time=time) 204 | 205 | def update_schedule(self, schedule_id: UUID, analysis_profile_id: UUID, frequency,day=1,weekday=1,timezone='America/New York',time="00:00"): 206 | self.schedules = self.Schedules(analysis_profile_id=analysis_profile_id, base_url=self.base_url) 207 | return self.schedules.update(schedule_id=schedule_id, frequency=frequency, day=day, 208 | weekday=weekday, timezone=timezone, time=time) 209 | 210 | def delete_schedule(self, analysis_profile_id: UUID, schedule_id: UUID): 211 | self.schedules = self.Schedules(analysis_profile_id=analysis_profile_id, base_url=self.base_url) 212 | return self.schedules.delete(schedule_id=schedule_id) 213 | 214 | class Authentications(): 215 | def __init__(self, analysis_profile_id: UUID, base_url): 216 | self.analysis_profile_id = analysis_profile_id 217 | self.base_url = '{}/{}'.format(base_url,self.analysis_profile_id) 218 | 219 | def get(self): 220 | uri = '{}/authentications'.format(self.base_url) 221 | return APIHelper()._rest_request(uri,"GET") 222 | 223 | def update_system_auth(self, username, password): 224 | uri = '{}/system_authentication'.format(self.base_url) 225 | body = { 'username': username, 'password': password } 226 | payload = json.dumps(body) 227 | 228 | return APIHelper()._rest_request(uri,"PUT",body=payload) 229 | 230 | def update_app_auth(self, username, password, login_url): 231 | uri = '{}/application_authentication'.format(self.base_url) 232 | body = { 'login_url': login_url, 'username': username, 'password': password } 233 | payload = json.dumps(body) 234 | 235 | return APIHelper()._rest_request(uri,"PUT",body=payload) 236 | 237 | def update_parameter_auth(self, id, title, type, key, value): 238 | uri = '{}/parameter_authentication'.format(self.base_url) 239 | body = { id: id, title: title, type: type, key: key, value: value } 240 | payload = json.dumps(body) 241 | 242 | return APIHelper()._rest_request(uri,"PUT",body=payload) 243 | 244 | class Scanners(): 245 | def __init__(self, analysis_profile_id: UUID, base_url): 246 | self.analysis_profile_id = analysis_profile_id 247 | self.base_url = '{}/{}/scanners'.format(base_url,self.analysis_profile_id) 248 | 249 | def get(self): 250 | return APIHelper()._rest_request(self.base_url,"GET") 251 | 252 | def update(self, scanner_id, scanner_value=True): 253 | if scanner_id not in Constants().DAST_SCANNERS: 254 | raise ValueError("{} is not in the list of valid scanners ({})".format(scanner_id,Constants().DAST_SCANNERS)) 255 | 256 | body = { 'id': scanner_id, 'value': scanner_value} 257 | payload = json.dumps(body) 258 | return APIHelper()._rest_request(self.base_url,"PUT",body=payload) 259 | 260 | class Schedules(): 261 | def __init__(self, analysis_profile_id: UUID, base_url): 262 | self.analysis_profile_id = analysis_profile_id 263 | self.base_url = '{}/{}/schedules'.format(base_url,self.analysis_profile_id) 264 | 265 | def get_all(self): 266 | return APIHelper()._rest_request(self.base_url,"GET") 267 | 268 | def get(self,schedule_id:UUID): 269 | uri = '{}/{}'.format(self.base_url,schedule_id) 270 | return APIHelper()._rest_request(uri,"GET") 271 | 272 | def create(self,frequency,day=1,weekday=1,timezone='America/New York',time="00:00"): 273 | return self._create_or_update("CREATE",frequency=frequency,day=day,weekday=weekday, 274 | timezone=timezone,time=time) 275 | 276 | def update(self, schedule_id: UUID, frequency,day=1,weekday=1,timezone='America/New York',time="00:00"): 277 | return self._create_or_update("UPDATE",frequency=frequency,day=day,weekday=weekday, 278 | timezone=timezone,time=time,schedule_id=schedule_id) 279 | 280 | def delete(self, schedule_id: UUID): 281 | uri = '{}/{}'.format(self.base_url,schedule_id) 282 | return APIHelper()._rest_request(uri, "DELETE") 283 | 284 | def _create_or_update(self,method,frequency,day=1,weekday=1,timezone='America/New York', 285 | time="00:00",schedule_id: UUID=None): 286 | if method=='CREATE': 287 | httpmethod = "POST" 288 | uri = self.base_url 289 | elif method=='UPDATE': 290 | httpmethod = "PUT" 291 | uri = '{}/{}'.format(self.base_url,schedule_id) 292 | else: 293 | return 294 | 295 | body = { 'frequency': frequency, 'day': day, 'weekday': weekday, 'timezone': timezone, 'time': time } 296 | payload = json.dumps(body) 297 | return APIHelper()._rest_request(uri, httpmethod, body = payload) 298 | 299 | class DASTAnalysisRuns(): 300 | base_url = ROOT_URL + '/analysis_run' 301 | 302 | def start(self, target_id: UUID): 303 | body = {'id': target_id} 304 | payload = json.dumps(body) 305 | return APIHelper()._rest_request(self.base_url,"POST",body=payload) 306 | 307 | def get(self, target_id: UUID): 308 | # this returns a PDF report when called. Save the report as a file 309 | uri = '{}/report/{}'.format(self.base_url,target_id) 310 | report = APIHelper()._rest_request(uri,"GET",fullresponse=True) 311 | return report 312 | -------------------------------------------------------------------------------- /veracode_api_py/dynamic.py: -------------------------------------------------------------------------------- 1 | #collections.py - API class for Collections API calls 2 | 3 | import json 4 | from urllib import parse 5 | from typing import List 6 | from uuid import UUID 7 | 8 | from .apihelper import APIHelper 9 | 10 | ROOT_URL = 'was/configservice/v1' 11 | 12 | class Analyses(): 13 | base_url = ROOT_URL + '/analyses' 14 | 15 | #public methods 16 | def get_all(self): 17 | request_params = {} 18 | return self._get_analyses(request_params) 19 | 20 | def get_by_name(self,analysis_name: str): 21 | params = {"name": parse.quote(analysis_name)} 22 | return self._get_analyses(params) 23 | 24 | def get_by_target_url(self,target_url): 25 | params = {"target_url": target_url} 26 | return self._get_analyses(params) 27 | 28 | def get_by_search_term(self,search_term: str): 29 | params = {"search_term": parse.quote(search_term)} 30 | return self._get_analyses(params) 31 | 32 | def get(self,guid: UUID): 33 | uri = self.base_url + "/{}".format(guid) 34 | return APIHelper()._rest_request(uri,"GET") 35 | 36 | def get_audits(self,guid: UUID): 37 | uri = self.base_url + "/{}/audits".format(guid) 38 | return APIHelper()._rest_paged_request(uri,"GET",'analysis_audits',{'page':0}) 39 | 40 | def create_scan(self,guid: UUID): 41 | uri = self.base_url + '/{}/scans'.format(guid) 42 | payload = {} #TODO add code for all the scan values 43 | return APIHelper()._rest_request(uri,"POST",json.dumps(payload)) 44 | 45 | def get_scans(self,guid: UUID): 46 | uri = self.base_url + "/{}/scans".format(guid) 47 | return APIHelper()._rest_paged_request(uri,"GET",'scans',{'page': 0}) 48 | 49 | def create(self,name: str,scans,start_scan=None,business_unit_guid: UUID=None,email=None,owner: str=None): 50 | # basic create that adds only metadata. Use Scans().setup() to create a Scans object 51 | return self._create_or_update(method="CREATE",name=name,scans=scans, 52 | business_unit_guid=business_unit_guid,email=email,owner=owner,start_scan=start_scan) 53 | 54 | def update(self,guid: UUID,name: str,scans,start_scan=None,business_unit_guid: UUID=None,email=None,owner: str=None): 55 | return self._create_or_update(method="UPDATE",name=name,scans=scans, 56 | business_unit_guid=business_unit_guid,email=email,owner=owner,guid=guid,start_scan=start_scan) 57 | 58 | def get_scanner_variables(self,guid: UUID): 59 | uri = self.base_url + "/{}/scanner_variables".format(guid) 60 | return APIHelper()._rest_paged_request(uri,"GET", 'scanner_variables', {'page': 0}) 61 | 62 | def update_scanner_variable(self,analysis_guid: UUID,scanner_variable_guid: UUID,reference_key: str,value: str,description: str): 63 | uri = self.base_url + '/{}/scanner_variables/{}'.format(analysis_guid,scanner_variable_guid) 64 | body = { 'reference_key': reference_key, 'value': value, 'description': description } 65 | return APIHelper()._rest_request(uri,"PUT",body) 66 | 67 | def delete_scanner_variable(self,analysis_guid: UUID,scanner_variable_guid: UUID): 68 | uri = self.base_url + '/{}/scanner_variables/{}'.format(analysis_guid,scanner_variable_guid) 69 | return APIHelper()._rest_request(uri,'DELETE') 70 | 71 | def delete(self,guid: UUID): 72 | uri = self.base_url + "/{}".format(guid) 73 | return APIHelper()._rest_request(uri,"DELETE") 74 | 75 | #private methods 76 | 77 | def _get_analyses(self,params): 78 | return APIHelper()._rest_paged_request(self.base_url,"GET","analyses",params=params) 79 | 80 | def _create_or_update(self,method,name: str,scans,start_scan=None,business_unit_guid: UUID=None,email=None,owner: str=None,guid: UUID=None): 81 | if method == 'CREATE': 82 | uri = self.base_url 83 | httpmethod = 'POST' 84 | elif method == 'UPDATE': 85 | uri = self.base_url + '/{}'.format(guid) 86 | httpmethod = 'PUT' 87 | else: 88 | return 89 | 90 | payload = {"name": name, "scans": scans} 91 | 92 | org_info = {} 93 | if business_unit_guid != None: 94 | org_info.update({'business_unit_id': business_unit_guid }) 95 | if email != None: 96 | org_info.update({'email': email}) 97 | if owner != None: 98 | org_info.update({'owner': owner}) 99 | payload.update({'org_info': org_info}) 100 | if start_scan != None: 101 | payload.update(start_scan) 102 | payload.update({"visibility": {"setup_type": "SEC_LEADS_ONLY", "team_identifiers": []}}) 103 | return APIHelper()._rest_request(uri,httpmethod,params={},body=json.dumps(payload)) 104 | 105 | class Scans(): 106 | base_url = ROOT_URL + '/scans' 107 | 108 | def get(self, guid: UUID): 109 | uri = self.base_url + "/{}".format(guid) 110 | return APIHelper()._rest_request(uri,"GET") 111 | 112 | def get_audits(self, guid: UUID): 113 | uri = self.base_url + "/{}/audits".format(guid) 114 | return APIHelper()._rest_paged_request(uri,"GET",'scan_audits',{'page':0}) 115 | 116 | def get_configuration(self,guid: UUID): 117 | uri = self.base_url + "/{}/configuration".format(guid) 118 | return APIHelper()._rest_request(uri,"GET") 119 | 120 | def delete(self,guid: UUID): 121 | uri = self.base_url + '/{}'.format(guid) 122 | return APIHelper()._rest_request(uri, 'DELETE') 123 | 124 | def update(self,guid: UUID,scan): 125 | # use DynUtils().setup_scan() to create the scans parameter 126 | uri = self.base_url + '/{}'.format(guid) 127 | return APIHelper()._rest_request(uri,'PUT',body=json.dumps(scan)) 128 | 129 | def get_scanner_variables(self,guid: UUID): 130 | uri = self.base_url + "/{}/scanner_variables".format(guid) 131 | return APIHelper()._rest_paged_request(uri,"GET",'scanner_variables',{'page': 0}) 132 | 133 | def update_scanner_variable(self,scan_guid: UUID,scanner_variable_guid: UUID,reference_key: str,value: str,description: str): 134 | uri = self.base_url + '/{}/scanner_variables/{}'.format(scan_guid,scanner_variable_guid) 135 | body = { 'reference_key': reference_key, 'value': value, 'description': description } 136 | return APIHelper()._rest_request(uri,"PUT",body) 137 | 138 | def delete_scanner_variable(self,scan_guid: UUID,scanner_variable_guid: UUID): 139 | uri = self.base_url + '/{}/scanner_variables/{}'.format(scan_guid,scanner_variable_guid) 140 | return APIHelper()._rest_request(uri,'DELETE') 141 | 142 | class Occurrences(): 143 | base_url = ROOT_URL + '/analysis_occurrences' 144 | 145 | def get_all(self): 146 | return APIHelper()._rest_paged_request(self.base_url,'GET','analysis_occurrences',{'page':0}) 147 | 148 | def get(self,guid: UUID): 149 | uri = self.base_url + '/{}'.format(guid) 150 | return APIHelper()._rest_request(uri, 'GET') 151 | 152 | def stop(self, guid: UUID, save_or_delete): 153 | actions = {'SAVE': 'STOP_SAVE', 'DELETE': 'STOP_DELETE'} 154 | uri = self.base_url + '/{}'.format(guid) 155 | params = { 'action' : actions[save_or_delete] } 156 | return APIHelper()._rest_request(uri, 'PUT', json.dumps(params)) 157 | 158 | def get_scan_occurrences(self,guid: UUID): 159 | uri = self.base_url + '/{}/scan_occurrences'.format(guid) 160 | return APIHelper()._rest_paged_request(uri, 'GET', 'scan_occurrences',{'page':0}) 161 | 162 | class ScanOccurrences(): 163 | base_url = ROOT_URL + '/scan_occurrences' 164 | 165 | def get(self, guid: UUID): 166 | uri = self.base_url + '/{}'.format(guid) 167 | return APIHelper()._rest_request(uri,'GET') 168 | 169 | def stop(self,guid: UUID,save_or_delete): 170 | actions = {'SAVE': 'STOP_SAVE', 'DELETE': 'STOP_DELETE'} 171 | uri = self.base_url + '/{}'.format(guid) 172 | params = { 'action': actions[save_or_delete]} 173 | return APIHelper()._rest_request(uri,'PUT', json.dumps(params)) 174 | 175 | def get_configuration(self,guid: UUID): 176 | uri = self.base_url + '/{}/configuration'.format(guid) 177 | return APIHelper()._rest_request(uri,'GET') 178 | 179 | def get_verification_report(self,guid: UUID): 180 | uri = self.base_url + '/{}/verification_report'.format(guid) 181 | return APIHelper()._rest_request(uri,'GET') 182 | 183 | def get_scan_notes_report(self,guid: UUID): 184 | uri = self.base_url + '/{}/scan_notes_report'.format(guid) 185 | return APIHelper()._rest_request(uri,'GET') 186 | 187 | def get_scan_screenshots(self,guid: UUID): 188 | uri = self.base_url + '/{}/scan_screenshots'.format(guid) 189 | return APIHelper()._rest_request(uri,'GET') 190 | 191 | class CodeGroups(): 192 | base_url = ROOT_URL + '/code_groups' 193 | 194 | def get_all(self): 195 | return APIHelper()._rest_request(self.base_url,'GET') 196 | 197 | def get(self,name: str): 198 | uri = self.base_url + '/{}'.format(name) 199 | return APIHelper()._rest_request(uri,'GET') 200 | 201 | class Configuration(): 202 | base_url = ROOT_URL + '/configuration' 203 | 204 | def get(self): 205 | return APIHelper()._rest_request(self.base_url,'GET') 206 | 207 | class ScannerVariables(): 208 | base_url = ROOT_URL + '/scanner_variables' 209 | 210 | def get_all(self): 211 | return APIHelper()._rest_paged_request(self.base_url,"GET",'scanner_variables',{'page': 0}) 212 | 213 | def create(self,reference_key: str,value: str,description: str): 214 | payload = {'reference_key':reference_key, 'value': value, 'description': description} 215 | return APIHelper()._rest_request(self.base_url,'POST',body=json.dumps(payload)) 216 | 217 | def get(self,guid: UUID): 218 | uri = self.base_url + '/{}'.format(guid) 219 | return APIHelper()._rest_request(uri,"GET") 220 | 221 | def update(self,guid: UUID,reference_key: str,value: str,description: str): 222 | uri = self.base_url + '/{}'.format(guid) + "?method=PATCH" 223 | body = { 'reference_key': reference_key, 'value': value, 'description': description } 224 | return APIHelper()._rest_request(uri,"PUT",body=json.dumps(body)) 225 | 226 | def delete(self,guid: UUID): 227 | uri = self.base_url + '/{}'.format(guid) 228 | return APIHelper()._rest_request(uri,'DELETE') 229 | 230 | class ScanCapacitySummary(): 231 | 232 | def get(self): 233 | return APIHelper()._rest_request(ROOT_URL + '/scan_capacity_summary', 'GET') 234 | 235 | class DynUtils(): 236 | 237 | def setup_user_agent(self,custom_header: str,type): 238 | return { "custom_header": custom_header, "type": type} 239 | 240 | def setup_custom_host(self,host_name,ip_address): 241 | return { 'host_name': host_name, 'ip_address': ip_address} 242 | 243 | def setup_blocklist(self, urls:List): 244 | return { 'black_list': urls} 245 | 246 | def setup_url(self,url,directory_restriction_type='DIRECTORY_AND_SUBDIRECTORY',http_and_https=True): 247 | # use to format any URL being made 248 | return { 'url': url, 'directory_restriction_type': directory_restriction_type, 'http_and_https': http_and_https} 249 | 250 | def setup_scan_setting(self,blocklist_configs:list,custom_hosts:List, user_agent: str=None): 251 | payload = {} 252 | 253 | if len(blocklist_configs) > 0: 254 | payload.update({'blacklist_configuration': blocklist_configs}) 255 | 256 | if len(custom_hosts) > 0: 257 | payload.update({'custom_hosts': custom_hosts}) 258 | 259 | if user_agent != None: 260 | payload.update({'user_agent':user_agent}) 261 | 262 | return { 'scan_setting': payload } 263 | 264 | def setup_scan_contact_info(self,email,first_and_last_name: str,telephone): 265 | return {'scan_contact_info': {'email': email, 'first_and_last_name': first_and_last_name, 'telephone': telephone}} 266 | 267 | def setup_crawl_script(self,script_body: str,script_type='SELENIUM'): 268 | return { 'crawl_script_data': { 'script_body': script_body, 'script_type': script_type}} 269 | 270 | def setup_crawl_configuration(self,scripts:List,disabled=False): 271 | return { 'crawl_configuration': { 'disabled': disabled, 'scripts': scripts}} 272 | 273 | def setup_login_logout_script(self,script_body: str,script_type='SELENIUM'): 274 | return { 'script_body': script_body, 'script_type': script_type} 275 | 276 | def setup_auth(self,authtype,username: str,password: str,domain=None,base64_pkcs12=None,cert_name: str=None, login_script_data: str=None, logout_script_data: str=None): 277 | payload = {} 278 | if authtype == 'AUTO': 279 | payload.update({'AUTO': {'authtype': authtype, 'username': username, 'password': password}}) 280 | elif authtype == 'BASIC': 281 | payload.update({'BASIC': {'authtype': authtype, 'username': username, 'password': password, 'domain': domain}}) 282 | elif authtype == 'CERT': 283 | payload.update({'CERT': {'authtype': authtype, 'password': password, 'base64_pkcs12': base64_pkcs12, 'cert_name': cert_name}}) 284 | elif authtype == 'FORM': 285 | payload.update({'FORM': {'authtype': authtype, 'login_script_data': login_script_data, 'logout_script_data': logout_script_data}}) 286 | return payload 287 | 288 | def setup_auth_config(self,authentication_node:dict): 289 | return { 'auth_configuration': { 'authentications': authentication_node}} 290 | 291 | def setup_scan_config_request(self, url, allowed_hosts:List, auth_config=None, crawl_config=None, scan_setting=None): 292 | payload = {'target_url': url } 293 | 294 | if len(allowed_hosts) > 0: 295 | payload.update({'allowed_hosts': allowed_hosts}) 296 | if scan_setting != None: 297 | payload.update(scan_setting) 298 | if auth_config != None: 299 | payload.update(auth_config) 300 | if crawl_config != None: 301 | payload.update(crawl_config) 302 | return { 'scan_config_request': payload } 303 | 304 | def setup_scan(self, scan_config_request, scan_contact_info=None, linked_app_guid: UUID=None): 305 | payload = {} 306 | payload.update( scan_config_request ) 307 | if scan_contact_info is None: 308 | scan_contact_info = self.setup_scan_contact_info("", "", "") 309 | payload.update(scan_contact_info) 310 | if linked_app_guid != None: 311 | payload.update({'linked_platform_app_uuid': linked_app_guid}) 312 | return payload 313 | def start_scan(self, length, unit): 314 | return { 'schedule': {'now': True, 'duration':{'length': length,'unit': unit }} } 315 | -------------------------------------------------------------------------------- /veracode_api_py/exceptions.py: -------------------------------------------------------------------------------- 1 | # Purpose: Exceptions 2 | 3 | 4 | class VeracodeError(Exception): 5 | def __init__(self, message): 6 | super().__init__(message) 7 | """Raised when something goes wrong""" 8 | pass 9 | 10 | 11 | class VeracodeAPIError(Exception): 12 | def __init__(self, message): 13 | super().__init__(message) 14 | """Raised when something goes wrong with talking to the Veracode API""" 15 | pass 16 | -------------------------------------------------------------------------------- /veracode_api_py/findings.py: -------------------------------------------------------------------------------- 1 | #findings.py - API class for Findings API and related calls 2 | 3 | import json 4 | from uuid import UUID 5 | 6 | from .apihelper import APIHelper 7 | 8 | LINE_NUMBER_SLOP = 3 #adjust to allow for line number movement 9 | 10 | class Findings(): 11 | 12 | def get_findings(self,app: UUID,scantype='STATIC',annot='TRUE',request_params=None,sandbox: UUID=None): 13 | #Gets a list of findings for app using the Veracode Findings API 14 | if request_params == None: 15 | request_params = {} 16 | 17 | scantypes = "" 18 | scantype = scantype.split(',') 19 | for st in scantype: 20 | if st in ['STATIC', 'DYNAMIC', 'MANUAL','SCA']: 21 | if len(scantypes) > 0: 22 | scantypes += "," 23 | scantypes += st 24 | if len(scantypes) > 0: 25 | request_params['scan_type'] = scantypes 26 | #note that scantype='ALL' will result in no scan_type parameter as in API 27 | 28 | request_params['include_annot'] = annot 29 | 30 | if sandbox != None: 31 | request_params['context'] = sandbox 32 | 33 | uri = "appsec/v2/applications/{}/findings".format(app) 34 | return APIHelper()._rest_paged_request(uri,"GET","findings",request_params) 35 | 36 | def get_static_flaw_info(self,app: UUID,issueid: int,sandbox: UUID=None): 37 | if sandbox != None: 38 | uri = "appsec/v2/applications/{}/findings/{}/static_flaw_info?context={}".format(app,issueid,sandbox) 39 | else: 40 | uri = "appsec/v2/applications/{}/findings/{}/static_flaw_info".format(app,issueid) 41 | 42 | return APIHelper()._rest_request(uri,"GET") 43 | 44 | def get_dynamic_flaw_info(self,app: UUID,issueid: int): 45 | uri = "appsec/v2/applications/{}/findings/{}/dynamic_flaw_info".format(app,issueid) 46 | return APIHelper()._rest_request(uri,"GET") 47 | 48 | def add_annotation(self,app: UUID,issue_list,comment: str,action,sandbox: UUID=None): 49 | #pass issue_list as a list of issue ids 50 | uri = "appsec/v2/applications/{}/annotations".format(app) 51 | 52 | if sandbox != None: 53 | params = {'context': sandbox} 54 | else: 55 | params = None 56 | 57 | annotation_def = {'comment': comment, 'action': action} 58 | 59 | converted_list = [str(element) for element in issue_list] 60 | issue_list_string = ','.join(converted_list) 61 | annotation_def['issue_list'] = issue_list_string 62 | 63 | payload = json.dumps(annotation_def) 64 | return APIHelper()._rest_request(uri,"POST",body=payload,params=params) 65 | 66 | def match(self,origin_finding,potential_matches,approved_matches_only=True,allow_fuzzy_match=False): 67 | # match a finding against an array of potential matches 68 | match = None 69 | 70 | if approved_matches_only: 71 | potential_matches = self._filter_approved(potential_matches) 72 | 73 | #flatten findings arrays to make processing easier 74 | scan_type = origin_finding['scan_type'] 75 | of = self._create_match_format_policy(policy_findings=[origin_finding],finding_type=scan_type) 76 | pm = self._create_match_format_policy(policy_findings=potential_matches,finding_type=scan_type) 77 | 78 | if scan_type == 'STATIC': 79 | match = self._match_static (of[0], pm, allow_fuzzy_match) 80 | elif scan_type == 'DYNAMIC': 81 | match = self._match_dynamic (of[0], pm) 82 | return match 83 | 84 | def format_file_path(self,file_path): 85 | # special case - omit prefix for teamcity work directories, which look like this: 86 | # teamcity/buildagent/work/d2a72efd0db7f7d7 87 | 88 | if file_path is None: 89 | return '' 90 | 91 | suffix_length = len(file_path) 92 | 93 | buildagent_loc = file_path.find('teamcity/buildagent/work/') 94 | 95 | if buildagent_loc > 0: 96 | #strip everything starting with this prefix plus the 17 characters after 97 | # (25 characters for find string, 16 character random hash value, plus / ) 98 | formatted_file_path = file_path[(buildagent_loc + 42):suffix_length] 99 | else: 100 | formatted_file_path = file_path 101 | 102 | return formatted_file_path 103 | 104 | def _match_static(self,origin_finding,potential_matches,allow_fuzzy_match=False): 105 | match = None 106 | if origin_finding['source_file'] not in ('', None): 107 | #attempt precise match first 108 | match = next((pf for pf in potential_matches if ((origin_finding['cwe'] == int(pf['cwe'])) & 109 | (origin_finding['source_file'].find(pf['source_file']) > -1 ) & 110 | (origin_finding['line'] == pf['line'] ))), None) 111 | 112 | if match is None and allow_fuzzy_match: 113 | #then fall to fuzzy match 114 | match = next((pf for pf in potential_matches if ((origin_finding['cwe'] == int(pf['cwe'])) & 115 | (origin_finding['source_file'].find(pf['source_file']) > -1 ) & 116 | ((origin_finding['line'] - LINE_NUMBER_SLOP) <= pf['line'] <= (origin_finding['line'] + LINE_NUMBER_SLOP)))), None) 117 | 118 | if match is None: 119 | #then fall to nondebug as a last resort 120 | match = self._get_matched_static_finding_nondebug(origin_finding,potential_matches) 121 | else: 122 | # if we don't have source file info try matching on procedure and relative location 123 | match = self._get_matched_static_finding_nondebug(origin_finding,potential_matches) 124 | 125 | return match 126 | 127 | def _get_matched_static_finding_nondebug(self,origin_finding, potential_findings): 128 | match = None 129 | 130 | match = next((pf for pf in potential_findings if ((origin_finding['cwe'] == int(pf['cwe'])) & 131 | (origin_finding['procedure'].find(pf['procedure']) > -1 ) & 132 | (origin_finding['relative_location'] == pf['relative_location'] ))), None) 133 | return match 134 | 135 | def _match_dynamic (self, origin_finding, potential_matches): 136 | match = None 137 | 138 | match = next((pf for pf in potential_matches if ((origin_finding['cwe'] == int(pf['cwe'])) & 139 | (origin_finding['path'] == pf['path']) & 140 | (origin_finding['vulnerable_parameter'] == pf['vulnerable_parameter']))), None) 141 | 142 | return match 143 | 144 | def _filter_approved(self,findings): 145 | return [f for f in findings if (f['finding_status']['resolution_status'] == 'APPROVED')] 146 | 147 | def _create_match_format_policy(self, policy_findings, finding_type): 148 | findings = [] 149 | 150 | if finding_type == 'STATIC': 151 | thesefindings = [{'id': pf['issue_id'], 152 | 'resolution': pf['finding_status']['resolution'], 153 | 'cwe': pf['finding_details']['cwe']['id'], 154 | 'procedure': pf['finding_details'].get('procedure'), 155 | 'relative_location': pf['finding_details'].get('relative_location'), 156 | 'source_file': self.format_file_path(pf['finding_details'].get('file_path')), 157 | 'line': pf['finding_details'].get('file_line_number'), 158 | 'finding': pf} for pf in policy_findings] 159 | findings.extend(thesefindings) 160 | elif finding_type == 'DYNAMIC': 161 | thesefindings = [{'id': pf['issue_id'], 162 | 'resolution': pf['finding_status']['resolution'], 163 | 'cwe': pf['finding_details']['cwe']['id'], 164 | 'path': pf['finding_details']['path'], 165 | 'vulnerable_parameter': pf['finding_details'].get('vulnerable_parameter',''), # vulnerable_parameter may not be populated for some info leak findings 166 | 'finding': pf} for pf in policy_findings] 167 | findings.extend(thesefindings) 168 | return findings 169 | 170 | class SummaryReport(): 171 | def get_summary_report(self,app: UUID,sandbox: UUID=None, build_id: int=None): 172 | uri = "appsec/v2/applications/{}/summary_report".format(app) 173 | 174 | params = {} 175 | if sandbox != None: 176 | params['context'] = sandbox 177 | 178 | if build_id != None: 179 | params['build_id'] = build_id 180 | 181 | return APIHelper()._rest_request(uri,"GET", params=params) 182 | 183 | class ManualScans(): 184 | def get_for_app(self,appid: UUID): 185 | params = {} 186 | params['application'] = appid 187 | uri = 'mpt/v1/scans' 188 | return APIHelper()._rest_paged_request(uri,"GET","scans",params=params) 189 | 190 | def get(self,scanid: int): 191 | uri = "mpt/v1/scans/{}".format(scanid) 192 | return APIHelper()._rest_request(uri,"GET") 193 | 194 | def get_findings(self,scanid: int, include_artifacts=False): 195 | uri = "mpt/v1/scans/{}/findings".format(scanid) 196 | params = {} 197 | params['include_artifacts'] = include_artifacts 198 | return APIHelper()._rest_paged_request(uri,"GET","findings",params=params) 199 | 200 | class CWEs(): 201 | base_uri = 'appsec/v1/cwes' 202 | def get_all(self): 203 | params = {} 204 | return APIHelper()._rest_paged_request(self.base_uri,"GET","cwes", params=params) 205 | 206 | def get(self,cwe_id: int): 207 | uri = '{}/{}'.format(self.base_uri, cwe_id) 208 | return APIHelper()._rest_request(uri,"GET") 209 | 210 | class CWECategories(): 211 | base_uri = 'appsec/v1/categories' 212 | def get_all(self): 213 | params = {} 214 | return APIHelper()._rest_paged_request(self.base_uri,"GET", "categories", params=params) 215 | 216 | def get(self,category_id: int): 217 | uri = '{}/{}'.format(self.base_uri, category_id) 218 | return APIHelper()._rest_request(uri,"GET") -------------------------------------------------------------------------------- /veracode_api_py/healthcheck.py: -------------------------------------------------------------------------------- 1 | #healthcheck.py - API class for Healthcheck API calls 2 | 3 | from .apihelper import APIHelper 4 | 5 | class Healthcheck(): 6 | 7 | def healthcheck(self): 8 | uri = 'healthcheck/status' 9 | return APIHelper()._rest_request(uri,"GET") 10 | 11 | def status(self): 12 | uri = 'https://api.status.veracode.com/status' 13 | return APIHelper()._rest_request(uri,"GET",use_base_url=False) -------------------------------------------------------------------------------- /veracode_api_py/identity.py: -------------------------------------------------------------------------------- 1 | #identity.py - API classes for Identity API calls 2 | 3 | import json 4 | import logging 5 | from urllib import parse 6 | from uuid import UUID 7 | 8 | from .apihelper import APIHelper 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | class Users(): 13 | USER_URI = "api/authn/v2/users" 14 | 15 | def get_all(self): 16 | #Gets a list of users using the Veracode Identity API 17 | request_params = {'page': 0} #initialize the page request 18 | return APIHelper()._rest_paged_request(self.USER_URI,"GET","users",request_params) 19 | 20 | def get_self (self): 21 | #Gets the user info for the current user, using the Veracode Identity API 22 | return APIHelper()._rest_request(self.USER_URI + "/self","GET") 23 | 24 | def get(self,user_guid: UUID): 25 | #Gets an individual user provided their GUID, using the Veracode Identity API 26 | uri = self.USER_URI + "/{}".format(user_guid) 27 | return APIHelper()._rest_request(uri,"GET") 28 | 29 | def get_by_name(self,username): 30 | #Gets all the users who match the provided email address, using the Veracode Identity API 31 | request_params = {'user_name': parse.quote(username)} #initialize the page request 32 | return APIHelper()._rest_paged_request(self.USER_URI,"GET","users",request_params) 33 | 34 | def get_user_search(self,search_term: str=None, api_id: UUID=None, role_id: UUID=None, login_status=None, saml_user=None, team_id: UUID=None, detailed=False, user_type=None, request_params=None): 35 | if request_params == None: 36 | request_params = {'detailed': detailed} 37 | 38 | if search_term != None: 39 | request_params['search_term'] = parse.quote(search_term) 40 | 41 | if api_id != None: 42 | request_params['api_id'] = api_id 43 | 44 | if role_id != None: 45 | request_params['role_id'] = role_id 46 | 47 | if login_status != None: 48 | request_params['login_status'] = login_status 49 | 50 | if saml_user != None: 51 | request_params['saml_user'] = saml_user 52 | 53 | if team_id != None: 54 | request_params['team_id'] = team_id 55 | 56 | if user_type != None: 57 | request_params['user_type'] = user_type 58 | 59 | return APIHelper()._rest_paged_request(self.USER_URI + "/search","GET","users",request_params) 60 | 61 | def create(self,email,firstname: str,lastname: str,username: str=None,type="HUMAN",roles=[],teams=[],mfa=False, 62 | ipRestricted=False, allowedIpAddresses=[], 63 | samlUser=False, samlSubject=""): 64 | user_def = { "email_address": email, "first_name": firstname, "last_name": lastname, "active": True } 65 | 66 | rolelist = [] 67 | if len(roles) > 0: 68 | for role in roles: 69 | rolelist.append({"role_name": role}) 70 | user_def.update({"roles":rolelist}) 71 | 72 | if type == "API": 73 | user_def.update({"user_name": username}) 74 | user_def.update({"permissions": [{"permission_name": "apiUser"}]}) 75 | if len(roles) == 0: 76 | rolelist.append({"role_name": "uploadapi"}) 77 | rolelist.append({"role_name":"apisubmitanyscan"}) 78 | else: 79 | if len(roles) == 0: 80 | rolelist.append({"role_name":"submitter"}) 81 | 82 | if username is not None: 83 | user_def.update({"user_name": username}) 84 | else: 85 | user_def.update({"user_name": email}) 86 | 87 | teamlist = [] 88 | if len(teams) > 0: 89 | for team in teams: 90 | teamlist.append({"team_id": team}) 91 | user_def.update({"teams": teamlist}) 92 | 93 | user_def.update({"roles": rolelist}) 94 | 95 | if mfa: 96 | user_def.update({"pin_required":True}) 97 | 98 | if ipRestricted & len(allowedIpAddresses)>0: 99 | user_def.update({"ipRestricted":True}) 100 | user_def.update({"allowedIpAddresses": allowedIpAddresses}) 101 | 102 | if samlUser & len(samlSubject)>0: 103 | user_def.update({"samlUser":True}) 104 | user_def.update({"samlSubject": samlSubject}) 105 | 106 | payload = json.dumps(user_def) 107 | return APIHelper()._rest_request(self.USER_URI,'POST',body=payload) 108 | 109 | def update_roles(self,user_guid: UUID,roles: list): 110 | request_params = {'partial': 'TRUE',"incremental": 'FALSE'} 111 | uri = self.USER_URI + "/{}".format(user_guid) 112 | 113 | rolelist = [] 114 | for role in roles: 115 | rolelist.append({"role_name": role}) 116 | 117 | payload = json.dumps({"roles": rolelist}) 118 | return APIHelper()._rest_request(uri,"PUT",request_params,body=payload) 119 | 120 | def update(self,user_guid: UUID,changes): 121 | request_params = {'partial':'TRUE',"incremental": 'TRUE'} 122 | uri = self.USER_URI + "/{}".format(user_guid) 123 | payload = json.dumps(changes) 124 | return APIHelper()._rest_request(uri,"PUT",request_params,body=payload) 125 | 126 | def update_email_address(self,user_guid: UUID,email_address,ignore_verification=False): 127 | request_params = {'partial':'TRUE',"incremental": 'FALSE'} 128 | if ignore_verification: 129 | request_params['adminNoVerificationEmail'] = 'TRUE' 130 | uri = self.USER_URI + "/{}".format(user_guid) 131 | user_def = {'email_address': email_address} 132 | payload = json.dumps(user_def) 133 | return APIHelper()._rest_request(uri,"PUT",request_params,body=payload) 134 | 135 | def reset_password(self,user_legacy_id): 136 | # Sends a password reset email for the specified user 137 | # If user has not yet activated, re-sends activation email instead 138 | uri = self.USER_URI + "/{}/resetPassword".format(user_legacy_id) 139 | return APIHelper()._rest_request(uri,"POST") 140 | 141 | def disable(self,user_guid: UUID): 142 | request_params = {'partial':'TRUE'} 143 | uri = self.USER_URI + '/{}'.format(user_guid) 144 | payload = json.dumps({'active': False}) 145 | return APIHelper()._rest_request(uri,"PUT",request_params,payload) 146 | 147 | def delete(self,user_guid: UUID): 148 | uri = self.USER_URI + '/{}'.format(user_guid) 149 | return APIHelper()._rest_request(uri,"DELETE") 150 | 151 | class Teams(): 152 | def get_all(self, all_for_org=False): 153 | #Gets a list of teams using the Veracode Identity API 154 | if all_for_org: 155 | request_params = {'all_for_org': True} 156 | else: 157 | request_params = {'page': 0} #initialize the page request 158 | return APIHelper()._rest_paged_request("api/authn/v2/teams","GET","teams",request_params) 159 | 160 | def get(self, team_id): 161 | uri = "api/authn/v2/teams/{}".format(team_id) 162 | return APIHelper()._rest_request(uri,"GET") 163 | 164 | def create(self, team_name: str, business_unit=None, members=[]): 165 | team_def = {'team_name': team_name} 166 | 167 | if len(members) > 0: 168 | # optionally pass a list of usernames to add as team members 169 | users = [] 170 | for member in members: 171 | users.append({'user_name': member}) 172 | team_def.update({'users': users}) 173 | 174 | if business_unit != None: 175 | bu = {'bu_id': business_unit} 176 | team_def.update(bu) 177 | 178 | payload = json.dumps(team_def) 179 | return APIHelper()._rest_request('api/authn/v2/teams','POST',body=payload) 180 | 181 | def update(self, team_guid: UUID, team_name: str="", business_unit: UUID=None, members=[], incremental=True, partial=True): 182 | requestbody = {} 183 | 184 | if team_name != "": 185 | requestbody.update({"team_name": team_name}) 186 | 187 | if business_unit != None: 188 | requestbody.update({"business_unit": {"bu_id": business_unit}}) 189 | 190 | if len(members) > 0: 191 | users = [] 192 | for member in members: 193 | users.append({"user_name": member}) 194 | requestbody.update({"users": users}) 195 | 196 | if requestbody == {}: 197 | logging.error("No update specified for team {}".format(team_guid)) 198 | 199 | payload = json.dumps(requestbody) 200 | params = {"partial": partial, "incremental": incremental} 201 | uri = 'api/authn/v2/teams/{}'.format(team_guid) 202 | return APIHelper()._rest_request(uri,'PUT',body=payload,params=params) 203 | 204 | def delete(self, team_guid: UUID): 205 | uri = 'api/authn/v2/teams/{}'.format(team_guid) 206 | return APIHelper()._rest_request(uri,"DELETE") 207 | 208 | class BusinessUnits(): 209 | base_uri = "api/authn/v2/business_units" 210 | 211 | def get_all(self): 212 | request_params = {'page': 0} 213 | return APIHelper()._rest_paged_request(self.base_uri,"GET","business_units",request_params) 214 | 215 | def get(self,guid: UUID): 216 | return APIHelper()._rest_request(self.base_uri + "/{}".format(guid),"GET") 217 | 218 | def create(self,name: str,teams=[]): 219 | payload = {"bu_name":name} 220 | 221 | if len(teams)>0: 222 | team_list = [] 223 | for team in teams: 224 | team_list.append({"team_id": team}) 225 | payload["teams"] = team_list 226 | 227 | return APIHelper()._rest_request(self.base_uri,"POST",body=json.dumps(payload)) 228 | 229 | def update(self,guid: UUID,name: str="",teams=[]): 230 | payload = {} 231 | 232 | if name != "": 233 | payload["bu_name"] = name 234 | 235 | if len(teams)>0: 236 | team_list = [] 237 | for team in teams: 238 | team_list.append({"team_id": team}) 239 | payload["teams"] = team_list 240 | 241 | return APIHelper()._rest_request(self.base_uri + "/{}".format(guid),"PUT",body=json.dumps(payload),params={"partial":True, "incremental":True}) 242 | 243 | def delete(self,guid: UUID): 244 | return APIHelper()._rest_request(self.base_uri + "/{}".format(guid),"DELETE") 245 | 246 | class APICredentials(): 247 | base_uri = "api/authn/v2/api_credentials" 248 | def get_self (self): 249 | return APIHelper()._rest_request(self.base_uri,"GET") 250 | 251 | def get (self, api_id): 252 | return APIHelper()._rest_request(self.base_uri + '/{}'.format(api_id),"GET") 253 | 254 | def create (self, user_guid: UUID): 255 | return APIHelper()._rest_request("{}/user_id/{}".format(self.base_uri,user_guid),"POST",body=json.dumps({})) 256 | 257 | def renew (self): 258 | return APIHelper()._rest_request(self.base_uri,"POST",body=json.dumps({})) 259 | 260 | def revoke (self, api_id): 261 | return APIHelper()._rest_request(self.base_uri + '/{}'.format(api_id), "DELETE") 262 | 263 | class Roles(): 264 | base_uri = "api/authn/v2/roles" 265 | def get_all(self): 266 | return APIHelper()._rest_paged_request(self.base_uri,"GET","roles",{'page':0}) 267 | 268 | def get(self, role_guid: UUID): 269 | return APIHelper()._rest_request("{}/{}".format(self.base_uri,role_guid),"GET") 270 | 271 | def create(self, role_name, role_description, is_api=False, jit_assignable=True, 272 | jit_assignable_default=True, permissions=[], child_roles=[]): 273 | return self._create_or_update("CREATE", role_name=role_name, role_description=role_description, 274 | is_api=is_api, jit_assignable=jit_assignable, 275 | jit_assignable_default=jit_assignable_default, 276 | permissions=permissions, child_roles=child_roles) 277 | 278 | def update(self, role_name, role_description, role_guid: UUID, is_api=False, 279 | jit_assignable=True, jit_assignable_default=True, 280 | permissions=[], child_roles=[]): 281 | # TODO handle partial and incremental 282 | return self._create_or_update("UPDATE", role_name=role_name, role_description=role_description, 283 | role_guid=role_guid, is_api=is_api, jit_assignable=jit_assignable, 284 | jit_assignable_default=jit_assignable_default, 285 | permissions=permissions, child_roles=child_roles) 286 | 287 | def delete(self, role_guid: UUID): 288 | return APIHelper()._rest_request("{}/{}".format(self.base_uri,role_guid),"DELETE") 289 | 290 | def _create_or_update(self, method, role_name, role_description, role_guid: UUID=None, is_api=False, 291 | jit_assignable=True,jit_assignable_default=True, 292 | permissions=[], child_roles=[]): 293 | uri = self.base_uri 294 | if method == 'CREATE': 295 | httpmethod = 'POST' 296 | elif method == 'UPDATE': 297 | uri = uri + '/{}'.format(role_guid) 298 | httpmethod = 'PUT' 299 | else: 300 | return 301 | 302 | role_def = { 'role_name': role_name, 'role_description': role_description, 'is_api': is_api, 303 | 'jit_assignable': jit_assignable, 'jit_assignable_default': jit_assignable_default} 304 | 305 | if len(permissions) > 0: 306 | role_def['permissions'] = permissions 307 | 308 | if len(child_roles) > 0: 309 | role_def['child_roles'] = child_roles 310 | 311 | payload = json.dumps(role_def) 312 | return APIHelper()._rest_request(uri,httpmethod,body=payload) 313 | 314 | class Permissions(): 315 | base_uri = "api/authn/v2/permissions" 316 | def get_all(self): 317 | return APIHelper()._rest_paged_request( self.base_uri,"GET","permissions",{'page':0}) 318 | 319 | def get(self, permission_guid: UUID): 320 | return APIHelper()._rest_request("{}/{}".format(self.base_uri,permission_guid),"GET") 321 | 322 | class JITDefaultSettings(): 323 | base_uri = "api/authn/v2/jit_default_settings" 324 | 325 | def get(self): 326 | return APIHelper()._rest_request( self.base_uri, "GET") 327 | 328 | def create(self, ip_restricted=False,prefer_veracode_data=True, allowed_ip_addresses=[], 329 | use_csv_for_roles_claim=False, use_csv_for_teams_claim=False, use_csv_for_teams_managed_claim=False, 330 | use_csv_for_ip_address_claim=True,teams=[],roles=[]): 331 | return self._create_or_update("CREATE", ip_restricted=ip_restricted, prefer_veracode_data=prefer_veracode_data, 332 | allowed_ip_addresses=allowed_ip_addresses, use_csv_for_roles_claim=use_csv_for_roles_claim, 333 | use_csv_for_teams_claim=use_csv_for_teams_claim, 334 | use_csv_for_teams_managed_claim=use_csv_for_teams_managed_claim, 335 | use_csv_for_ip_address_claim=use_csv_for_ip_address_claim, teams=teams, roles=roles) 336 | 337 | def update(self, jit_default_id: UUID, ip_restricted=False,prefer_veracode_data=True, allowed_ip_addresses=[], 338 | use_csv_for_roles_claim=False, use_csv_for_teams_claim=False, use_csv_for_teams_managed_claim=False, 339 | use_csv_for_ip_address_claim=True,teams=[],roles=[]): 340 | return self._create_or_update("UPDATE", jit_default_id = jit_default_id, ip_restricted=ip_restricted, 341 | prefer_veracode_data=prefer_veracode_data,allowed_ip_addresses=allowed_ip_addresses, 342 | use_csv_for_roles_claim=use_csv_for_roles_claim, 343 | use_csv_for_teams_claim=use_csv_for_teams_claim, 344 | use_csv_for_teams_managed_claim=use_csv_for_teams_managed_claim, 345 | use_csv_for_ip_address_claim=use_csv_for_ip_address_claim, teams=teams, roles=roles) 346 | 347 | def _create_or_update(self, method, jit_default_id: UUID=None, ip_restricted=False,prefer_veracode_data=True, allowed_ip_addresses=[], 348 | use_csv_for_roles_claim=False, use_csv_for_teams_claim=False, use_csv_for_teams_managed_claim=False, 349 | use_csv_for_ip_address_claim=True,teams=[],roles=[]): 350 | 351 | if method == "CREATE": 352 | uri = self.base_uri 353 | httpmethod = "POST" 354 | elif method == "UPDATE": 355 | uri = '{}/{}'.format(self.base_uri, jit_default_id) 356 | httpmethod = "PUT" 357 | else: 358 | return 359 | 360 | params = { 'ip_restricted': ip_restricted, 'prefer_veracode_data': prefer_veracode_data, 'allowed_ip_addresses': allowed_ip_addresses, 361 | 'use_csv_for_roles_claim': use_csv_for_roles_claim, 'use_csv_for_teams_claim': use_csv_for_teams_claim, 362 | 'use_csv_for_teams_managed_claim': use_csv_for_teams_managed_claim, 'use_csv_for_ip_address_claim': use_csv_for_ip_address_claim, 363 | 'teams': teams, 'roles': roles} 364 | 365 | body = json.dumps(params) 366 | 367 | return APIHelper()._rest_request(url=uri, method=httpmethod, params=body) 368 | 369 | def delete(self, jit_default_id: UUID): 370 | uri = '{}/{}'.format(self.base_uri, jit_default_id) 371 | return APIHelper()._rest_request( uri, "DELETE") -------------------------------------------------------------------------------- /veracode_api_py/log.py: -------------------------------------------------------------------------------- 1 | # Purpose: Log utilities 2 | 3 | import logging 4 | import time 5 | from datetime import datetime 6 | 7 | class VeracodeLog(): 8 | def setup_logging(self,debug=False): 9 | now = datetime.utcnow().strftime("%Y-%m-%d-%H%M%S") 10 | format_string = "%(asctime)s %(levelname)s %(message)s" 11 | datetime_format = '%Y-%m-%d %H:%M:%S %Z' 12 | 13 | logging.captureWarnings(True) 14 | logging.Formatter.converter = time.gmtime 15 | 16 | if debug: 17 | logging.basicConfig(format=format_string, datefmt=datetime_format, filename="{}-debug.log".format(now), level=logging.DEBUG) 18 | else: 19 | logging.basicConfig(format=format_string, datefmt=datetime_format, filename="{}.log".format(now), level=logging.INFO) 20 | requests_logger = logging.getLogger("requests") 21 | requests_logger.setLevel(logging.WARNING) 22 | -------------------------------------------------------------------------------- /veracode_api_py/policy.py: -------------------------------------------------------------------------------- 1 | #policy.py - API class for Policy API calls 2 | 3 | import json 4 | from uuid import UUID 5 | 6 | from .apihelper import APIHelper 7 | 8 | class Policies(): 9 | 10 | def get_all (self): 11 | return APIHelper()._rest_paged_request("appsec/v1/policies","GET","policy_versions",{"page": 0}) 12 | 13 | def get (self,guid: UUID): 14 | uri = "appsec/v1/policies/{}".format(guid) 15 | return APIHelper()._rest_request(uri,"GET") 16 | 17 | def delete (self,guid: UUID): 18 | uri = "appsec/v1/policies/{}".format(guid) 19 | return APIHelper()._rest_request(uri,"DELETE") 20 | 21 | def create (self, name: str, description: str, vendor_policy=False, finding_rules=[], scan_frequency_rules=[], grace_periods=None): 22 | if grace_periods == None: 23 | grace_periods = {} 24 | return self._create_or_update("CREATE",name,description,vendor_policy,finding_rules,scan_frequency_rules,grace_periods) 25 | 26 | def update(self,guid: UUID, name: str, description: str, vendor_policy=False, finding_rules=[], scan_frequency_rules=[], grace_periods=None): 27 | if grace_periods == None: 28 | grace_periods = {} 29 | return self._create_or_update("UPDATE",name,description,vendor_policy,finding_rules,scan_frequency_rules,grace_periods,guid) 30 | 31 | def format_finding_rule(self,rule_type,scan_types=[],rule_value=''): 32 | finding_rule = {} 33 | finding_rule['type'] = rule_type 34 | finding_rule['scan_types'] = scan_types 35 | finding_rule['value'] = rule_value 36 | return finding_rule 37 | 38 | def format_scan_frequency_rule(self,scan_type,frequency): 39 | scan_frequency_rule = {} 40 | scan_frequency_rule['scan_type'] = scan_type 41 | scan_frequency_rule['frequency'] = frequency 42 | return scan_frequency_rule 43 | 44 | def format_grace_periods(self, sev5: int, sev4: int, sev3: int, sev2: int, sev1: int, sev0: int, score: int, sca_blocklist: int): 45 | grace_periods = {} 46 | grace_periods["sev5_grace_period"] = sev5 47 | grace_periods["sev4_grace_period"] = sev4 48 | grace_periods["sev3_grace_period"] = sev3 49 | grace_periods["sev2_grace_period"] = sev2 50 | grace_periods["sev1_grace_period"] = sev1 51 | grace_periods["sev0_grace_period"] = sev0 52 | grace_periods["score_grace_period"] = score 53 | grace_periods["sca_blacklist_grace_period"] = sca_blocklist 54 | return grace_periods 55 | 56 | def _create_or_update(self, method, name: str, description: str, vendor_policy=False, finding_rules=[], scan_frequency_rules=[], grace_periods=None, guid: UUID=None): 57 | if grace_periods == None: 58 | grace_periods = {} 59 | if method == 'CREATE': 60 | uri = 'appsec/v1/policies' 61 | httpmethod = 'POST' 62 | elif method == 'UPDATE': 63 | uri = 'appsec/v1/policies/{}'.format(guid) 64 | httpmethod = 'PUT' 65 | else: 66 | return 67 | 68 | policy_def = {"name": name, "description": description, "vendor_policy": vendor_policy} 69 | policy_def["finding_rules"] = finding_rules 70 | policy_def["scan_frequency_rules"] = scan_frequency_rules 71 | policy_def.update(grace_periods) 72 | 73 | return APIHelper()._rest_request(uri,httpmethod,body=json.dumps(policy_def)) 74 | -------------------------------------------------------------------------------- /veracode_api_py/sca.py: -------------------------------------------------------------------------------- 1 | #sca.py - API class for SCA API calls 2 | 3 | import json 4 | from urllib import parse 5 | from uuid import UUID 6 | 7 | from .apihelper import APIHelper 8 | from .constants import Constants 9 | 10 | class Workspaces(): 11 | sca_base_url = "srcclr/v3/workspaces" 12 | sca_issues_url = "srcclr/v3/issues" 13 | 14 | def get_all(self, include_metrics=False): 15 | #Gets existing workspaces 16 | request_params = {'include_metrics': include_metrics} 17 | return APIHelper()._rest_paged_request(self.sca_base_url,"GET",params=request_params,element="workspaces") 18 | 19 | def get_by_name(self,name: str): 20 | #Does a name filter on the workspaces list. Note that this is a partial match. Only returns the first match 21 | name = parse.quote(name) #urlencode any spaces or special characters 22 | request_params = {'filter[workspace]': name} 23 | return APIHelper()._rest_paged_request(self.sca_base_url,"GET",params=request_params,element="workspaces") 24 | 25 | def create(self,name: str): 26 | #pass payload with name, return guid to workspace 27 | payload = json.dumps({"name": name}) 28 | r = APIHelper()._rest_request(self.sca_base_url,"POST",body=payload,fullresponse=True) 29 | loc = r.headers.get('location','') 30 | return loc.split("/")[-1] 31 | 32 | def add_team(self,workspace_guid: UUID,team_id: UUID): 33 | return APIHelper()._rest_request(self.sca_base_url + "/{}/teams/{}".format(workspace_guid,team_id),"PUT") 34 | 35 | def remove_team(self,workspace_guid: UUID,team_id: UUID): 36 | return APIHelper()._rest_request(self.sca_base_url + "/{}/teams/{}".format(workspace_guid,team_id),"DELETE") 37 | 38 | def delete(self,workspace_guid: UUID): 39 | return APIHelper()._rest_request(self.sca_base_url + "/{}".format(workspace_guid),"DELETE") 40 | 41 | def get_teams(self, workspace_guid: UUID=None): 42 | if workspace_guid: 43 | return APIHelper()._rest_request(self.sca_base_url + "/{}/teams".format(workspace_guid),"GET") 44 | else: 45 | return APIHelper()._rest_paged_request("srcclr/v3/teams","GET","teams",{}) 46 | 47 | def get_projects(self,workspace_guid: UUID,project_name=""): 48 | if project_name != "": 49 | params = { 'search': project_name } 50 | else: 51 | params = {} 52 | return APIHelper()._rest_paged_request(self.sca_base_url + '/{}/projects'.format(workspace_guid),"GET","projects",params) 53 | 54 | def get_project(self,workspace_guid: UUID,project_guid:UUID ): 55 | uri = self.sca_base_url + '/{}/projects/{}'.format(workspace_guid,project_guid) 56 | return APIHelper()._rest_request(uri,"GET") 57 | 58 | def get_project_issues(self,workspace_guid: UUID,project_guid: UUID, params={}): 59 | uri = self.sca_base_url + '/{}/projects/{}/issues'.format(workspace_guid,project_guid) 60 | return APIHelper()._rest_paged_request(uri,"GET","issues", params) 61 | 62 | def get_project_libraries(self,workspace_guid: UUID,project_guid: UUID): 63 | uri = self.sca_base_url + '/{}/projects/{}/libraries'.format(workspace_guid,project_guid) 64 | return APIHelper()._rest_paged_request(uri,"GET","libraries",{}) 65 | 66 | def get_agents(self,workspace_guid: UUID): 67 | return APIHelper()._rest_paged_request(self.sca_base_url + '/{}/agents'.format(workspace_guid),"GET","agents",{}) 68 | 69 | def get_agent(self,workspace_guid: UUID,agent_guid: UUID): 70 | uri = self.sca_base_url + '/{}/agents/{}'.format(workspace_guid,agent_guid) 71 | return APIHelper()._rest_request(uri,"GET") 72 | 73 | def create_agent(self,workspace_guid: UUID,name: str,agent_type='CLI'): 74 | if agent_type not in Constants().AGENT_TYPE: 75 | raise ValueError("{} is not in the list of valid agent types ({})".format(agent_type,Constants().AGENT_TYPE)) 76 | uri = self.sca_base_url + '/{}/agents'.format(workspace_guid) 77 | body = {'agent_type': agent_type, 'name': name} 78 | return APIHelper()._rest_request(uri,"POST",body=json.dumps(body)) 79 | 80 | def delete_agent(self,workspace_guid: UUID,agent_guid: UUID): 81 | uri = self.sca_base_url + '/{}/agents/{}'.format(workspace_guid,agent_guid) 82 | return APIHelper()._rest_request(uri,"DELETE") 83 | 84 | def get_agent_tokens(self,workspace_guid: UUID,agent_guid: UUID): 85 | uri = self.sca_base_url + '/{}/agents/{}/tokens'.format(workspace_guid,agent_guid) 86 | return APIHelper()._rest_paged_request(uri, "GET", "tokens" ) 87 | 88 | def get_agent_token(self,workspace_guid: UUID,agent_guid: UUID,token_id: UUID): 89 | uri = self.sca_base_url + '/{}/agents/{}/tokens/{}'.format(workspace_guid,agent_guid,token_id) 90 | return APIHelper()._rest_request(uri, "GET" ) 91 | 92 | def regenerate_agent_token(self,workspace_guid: UUID, agent_guid: UUID): 93 | uri = self.sca_base_url + '/{}/agents/{}/token:regenerate'.format(workspace_guid,agent_guid) 94 | return APIHelper()._rest_request(uri,"POST") 95 | 96 | def revoke_agent_token(self,workspace_guid: UUID, agent_guid: UUID, token_id: UUID): 97 | uri = self.sca_base_url + '/{}/agents/{}/tokens/{}'.format(workspace_guid,agent_guid,token_id) 98 | return APIHelper()._rest_request(uri,"DELETE") 99 | 100 | def get_issues(self,workspace_guid: UUID, branch=None, created_after=None,direct=None, ignored=None, vuln_methods=None, project_id=None): 101 | params = {} 102 | if branch: 103 | params["branch"] = branch 104 | if created_after: 105 | params["created_after"] = created_after 106 | if direct: 107 | params["direct"] = direct 108 | if ignored: 109 | params["ignored"] = ignored 110 | if vuln_methods: 111 | params["vuln_methods"] = vuln_methods 112 | if project_id: 113 | params["project_id"] = project_id 114 | uri = self.sca_base_url + '/{}/issues'.format(workspace_guid) 115 | return APIHelper()._rest_paged_request(uri,"GET","issues",params) 116 | 117 | def get_issue(self,issue_id: UUID): 118 | uri = self.sca_issues_url + '/{}'.format(issue_id) 119 | return APIHelper()._rest_request(uri,"GET") 120 | 121 | def get_libraries(self,workspace_guid: UUID,unmatched: bool): 122 | if unmatched: 123 | uri = self.sca_base_url + '/{}/libraries/unmatched'.format(workspace_guid) 124 | else: 125 | uri = self.sca_base_url + '/{}/libraries'.format(workspace_guid) 126 | return APIHelper()._rest_paged_request(uri,"GET",'libraries',{}) 127 | 128 | def get_library(self,library_id: str): 129 | uri = "srcclr/v3/libraries/{}".format(library_id) 130 | return APIHelper()._rest_request(uri,"GET") 131 | 132 | def get_vulnerability(self,vulnerability_id: int): 133 | uri = "srcclr/v3/vulnerabilities/{}".format(vulnerability_id) 134 | return APIHelper()._rest_request(uri,"GET") 135 | 136 | def get_license(self,license_id: str): 137 | uri = "srcclr/v3/licenses/{}".format(license_id) 138 | return APIHelper()._rest_request(uri,"GET") 139 | 140 | def get_scan(self,scan_id: UUID): 141 | return APIHelper()._rest_request("srcclr/v3/scans/{}".format(scan_id),"GET") 142 | 143 | def get_events(self, date_gte=None, event_group=None, event_type=None): 144 | baseuri = "srcclr/v3/events" 145 | params = {} 146 | if event_group != None: 147 | if event_group not in Constants().SCA_EVENT_GROUP: 148 | raise ValueError("{} is not in the valid list of SCA event groups ({})".format(event_group,Constants().SCA_EVENT_GROUP)) 149 | params["group"] = event_group 150 | 151 | if event_type != None: 152 | params["type"] = event_type 153 | 154 | if date_gte != None: 155 | params["date_gte"] = date_gte 156 | 157 | return APIHelper()._rest_paged_request(baseuri,"GET","events",params) 158 | 159 | class ComponentActivity(): 160 | component_base_uri = "srcclr/v3/component-activity" 161 | 162 | def get(self,component_id: str): 163 | return APIHelper()._rest_request(self.component_base_uri+"/{}".format(component_id),"GET") 164 | 165 | class SBOM(): 166 | entity_base_uri = "srcclr/sbom/v1" 167 | valid_formats = ['cyclonedx','spdx'] 168 | 169 | def get(self,app_guid: UUID, format='cyclonedx',linked=False,vulnerability=True,dependency=True): 170 | return self._get_sbom(guid=app_guid,format=format,sbom_type='application',linked=linked,vulnerability=vulnerability,dependency=dependency) 171 | 172 | def get_for_project(self,project_guid: UUID, format='cyclonedx', vulnerability=True,dependency=True): 173 | return self._get_sbom(guid=project_guid,format=format,sbom_type='agent',linked=False,vulnerability=vulnerability,dependency=dependency) 174 | 175 | def scan(self,sbom: str): 176 | try: 177 | files = { 'sbom-file': open(sbom)} 178 | except IOError: 179 | print("Could not read file {}".format(sbom)) 180 | return 181 | 182 | return APIHelper()._rest_request(self.entity_base_uri+"/manage/scan","POST",params=None,files=files) 183 | 184 | def _get_sbom(self,guid: UUID,format,sbom_type,linked,vulnerability,dependency): 185 | if format not in self.valid_formats: 186 | return 187 | params={"type":sbom_type,"vulnerability": vulnerability} 188 | if linked: 189 | params["linked"] = linked 190 | if format=='spdx': #currently only supported for SPDX SBOMs 191 | params["dependency"] = dependency 192 | return APIHelper()._rest_request(self.entity_base_uri+"/targets/{}/{}".format(guid,format),"GET",params=params) 193 | 194 | class SCAApplications(): 195 | entity_base_uri = "srcclr/v3/applications" 196 | 197 | def get_projects(self, app_guid: UUID): 198 | return APIHelper()._rest_request(self.entity_base_uri+"/{}/projects".format(app_guid),"GET") 199 | 200 | def link_project(self, app_guid: UUID, project_guid: UUID): 201 | return APIHelper()._rest_request(self.entity_base_uri+"/{}/projects/{}".format(app_guid,project_guid),"PUT") 202 | 203 | def unlink_project(self, app_guid: UUID, project_guid: UUID): 204 | return APIHelper()._rest_request(self.entity_base_uri+"/{}/projects/{}".format(app_guid,project_guid),"DELETE") 205 | 206 | def get_annotations(self, app_guid: UUID, annotation_type: str, annotation_reason: str=None, 207 | annotation_status: str=None, cve_name: str=None, cwe_id: str=None, severities=None, 208 | license_name: str=None, license_risk: str=None): 209 | if annotation_type not in Constants().SCA_ANNOTATION_TYPE: 210 | raise ValueError("{} is not in the list of valid annotation types ({})".format(annotation_type,Constants().SCA_ANNOTATION_TYPE)) 211 | params={"annotation_type":annotation_type} 212 | 213 | if annotation_reason: 214 | if annotation_reason not in Constants().SCA_ANNOT_ACTION: 215 | raise ValueError("{} is not in the list of valid annotation reasons ({})".format(annotation_reason,Constants().SCA_ANNOT_ACTION)) 216 | params["annotation_reason"] = annotation_reason 217 | 218 | if annotation_status: 219 | if annotation_status not in Constants().SCA_ANNOT_STATUS: 220 | raise ValueError("{} is not in the list of valid annotation statuses ({})".format(annotation_status,Constants().SCA_ANNOT_STATUS)) 221 | params["annotation_status"] = annotation_status 222 | 223 | if cve_name: 224 | params["cve_name"] = cve_name 225 | 226 | if cwe_id: 227 | params["cwe_id"] = cwe_id 228 | 229 | if severities: 230 | params["severities"] = severities #check against Constants().SEVERITIES 231 | 232 | if license_name: 233 | params["license_name"] = license_name 234 | 235 | if license_risk: 236 | if license_risk not in Constants().SCA_LICENSE_RISK: 237 | raise ValueError("{} is not in the list of valid license risks ({})".format(license_risk,Constants().SCA_LICENSE_RISK)) 238 | params["license_risk"] = license_risk 239 | 240 | return APIHelper()._rest_request(self.entity_base_uri+"/{}/sca_annotations".format(app_guid),"GET",params=params) 241 | 242 | def add_annotation(self, app_guid: UUID, action: str, comment: str, annotation_type: str, 243 | component_id: UUID, cve_name: str=None, license_id: str=None): 244 | 245 | if action not in Constants().SCA_ANNOT_ACTION: 246 | raise ValueError("{} is not in the list of valid actions ({})".format(action,Constants().SCA_ANNOT_ACTION)) 247 | 248 | if annotation_type == "VULNERABILITY": 249 | if not cve_name: 250 | raise ValueError("You must provide a cve_name for a VULNERABILITY annotation.") 251 | annotation = {"component_id": component_id, "cve_name": cve_name} 252 | elif annotation_type == "LICENSE": 253 | if not license_id: 254 | raise ValueError("You must provide a license_id for a LICENSE annotation.") 255 | annotation = {"component_id": component_id, "license_id": license_id} 256 | else: 257 | raise ValueError("{} is not in the list of valid annotation types ({})".format(annotation_type,Constants().SCA_ANNOTATION_TYPE)) 258 | 259 | payload = { "action": action, "comment": comment, "annotation_type": annotation_type, "annotations": [ annotation ]} 260 | 261 | payload_json = json.dumps(payload) 262 | 263 | return APIHelper()._rest_request(self.entity_base_uri+"/{}/sca_annotations".format(app_guid),"POST",body=payload_json) 264 | -------------------------------------------------------------------------------- /veracode_api_py/static.py: -------------------------------------------------------------------------------- 1 | #static.py - API class for Static REST API calls 2 | 3 | from .apihelper import APIHelper 4 | from uuid import UUID 5 | from .constants import Constants 6 | import json 7 | 8 | class StaticCLI(): 9 | 10 | class Scans(): 11 | baseuri = 'pipeline_scan/v1/scans' 12 | 13 | def create(self, binary_name: str, binary_size: int, binary_hash, app_id: int=None, 14 | project_name: str=None, project_uri: str=None, project_ref: str=None, 15 | commit_hash=None, dev_stage: str=None, scan_timeout: int=None): 16 | 17 | scan_def = { 'binary_name': binary_name, 'binary_size': binary_size, 'binary_hash': binary_hash } 18 | 19 | if app_id: 20 | scan_def.update({'app_id':app_id}) 21 | 22 | if project_name: 23 | scan_def.update({'project_name': project_name}) 24 | 25 | if project_uri: 26 | scan_def.update({'project_uri': project_uri}) 27 | 28 | if project_ref: 29 | scan_def.update({'project_ref': project_ref}) 30 | 31 | if commit_hash: 32 | scan_def.update({'commit_hash':commit_hash}) 33 | 34 | if dev_stage: 35 | if dev_stage not in Constants().DEV_STAGE: 36 | raise ValueError("{} is not in the list of valid development stages: ({})". 37 | format(dev_stage,Constants().DEV_STAGE)) 38 | else: 39 | scan_def.update({'dev_stage': dev_stage}) 40 | 41 | if scan_timeout: 42 | if scan_timeout > 60: 43 | raise ValueError("scan_timeout: {} too large (must be between 0 and 60)".format(scan_timeout)) 44 | else: 45 | scan_def.update({'scan_timeout': scan_timeout}) 46 | 47 | payload = json.dumps(scan_def) 48 | 49 | return APIHelper()._rest_request(self.baseuri,"POST",body=payload) 50 | 51 | def get(self, scan_id: UUID): 52 | uri = self.baseuri + '/{}'.format(scan_id) 53 | return APIHelper()._rest_request(uri,"GET") 54 | 55 | def start(self, scan_id: UUID): 56 | return self()._start_or_cancel(scan_id = scan_id, action='STARTED') 57 | 58 | def cancel(self, scan_id: UUID): 59 | return self()._start_or_cancel(scan_id = scan_id, action='CANCELLED') 60 | 61 | def _start_or_cancel(self, scan_id: UUID, action: str): 62 | uri = self.baseuri + '/{}'.format(scan_id) 63 | return APIHelper()._rest_request(uri,"PUT",body={'scan_status': action}) 64 | 65 | class Segments(): 66 | baseuri = 'pipeline_scan/v1/scans/{}/segments/{}' 67 | 68 | def add(self, scan_id: UUID, segment_id: int, segment_file): 69 | uri = self.baseuri.format(scan_id, segment_id) 70 | 71 | try: 72 | files = { 'file': open(segment_file)} 73 | except IOError: 74 | print("Could not read file {}".format(segment_file)) 75 | return 76 | return APIHelper()._rest_request(uri,"PUT",params=None,files=files) 77 | 78 | class Findings(): 79 | baseuri = 'pipeline_scan/v1/scans/{}/findings' 80 | 81 | def get(self, scan_id: UUID): 82 | uri = self.baseuri.format(scan_id) 83 | return APIHelper()._rest_request(uri,"GET") -------------------------------------------------------------------------------- /veracode_api_py/xmlapi.py: -------------------------------------------------------------------------------- 1 | # xmlapi.py - API class for legacy XML API calls 2 | 3 | from .apihelper import APIHelper 4 | from .constants import Constants 5 | 6 | 7 | class XMLAPI(): 8 | baseurl = "https://analysiscenter.veracode.com/api" 9 | 10 | # Upload XML APIs 11 | def get_app_list(self): 12 | """Returns all application profiles.""" 13 | return APIHelper()._xml_request(self.baseurl + "/4.0/getapplist.do", "GET") 14 | 15 | def get_app_info(self, app_id: int): 16 | """Returns application profile info for a given app ID.""" 17 | return APIHelper()._xml_request(self.baseurl + "/5.0/getappinfo.do", "GET", params={"app_id": app_id}) 18 | 19 | def get_sandbox_list(self, app_id: int): 20 | """Returns a list of sandboxes for a given app ID""" 21 | return APIHelper()._xml_request(self.baseurl + "/5.0/getsandboxlist.do", "GET", params={"app_id": app_id}) 22 | 23 | def get_build_list(self, app_id: int, sandbox_id: int = None): 24 | """Returns all builds for a given app ID.""" 25 | if sandbox_id is None: 26 | params = {"app_id": app_id} 27 | else: 28 | params = {"app_id": app_id, "sandbox_id": sandbox_id} 29 | return APIHelper()._xml_request(self.baseurl + "/4.0/getbuildlist.do", "GET", params=params) 30 | 31 | def get_build_info(self, app_id: int, build_id: int = None, sandbox_id: int = None): 32 | """Returns build info for a given build ID.""" 33 | params = {"app_id": app_id} 34 | if sandbox_id != None: 35 | params["sandbox_id"] = sandbox_id 36 | if build_id != None: 37 | params["build_id"] = build_id 38 | return APIHelper()._xml_request(self.baseurl + "/5.0/getbuildinfo.do", "GET", params=params) 39 | 40 | 41 | def delete_build(self, app_id: int, sandbox_id: int = None): 42 | """Deletes the last build in an application profile or sandbox.""" 43 | if sandbox_id is None: 44 | params = {"app_id": app_id} 45 | else: 46 | params = {"app_id": app_id, "sandbox_id": sandbox_id} 47 | return APIHelper()._xml_request(self.baseurl + "/5.0/deletebuild.do", "GET", params=params) 48 | 49 | 50 | def upload_file(self, app_id: int, file: str, sandbox_id=None, save_as=None): 51 | """Uploads a file to an existing build or creates a build.""" 52 | params = {'app_id': app_id} 53 | if sandbox_id: 54 | params['sandbox_id'] = sandbox_id 55 | if save_as: 56 | params['save_as'] = save_as 57 | files = {'file': open(file, 'rb')} 58 | return APIHelper()._xml_request(self.baseurl + "/5.0/uploadfile.do", "POST", params=params, files=files) 59 | 60 | def begin_prescan(self, app_id: int, sandbox_id=None, auto_scan=None,scan_all_nonfatal_top_level_modules=None): 61 | """Runs a static prescan for an application.""" 62 | params = {'app_id': app_id} 63 | if sandbox_id: 64 | params['sandbox_id'] = sandbox_id 65 | if auto_scan: 66 | params['auto_scan'] = auto_scan 67 | if scan_all_nonfatal_top_level_modules: 68 | params['scan_all_nonfatal_top_level_modules'] = scan_all_nonfatal_top_level_modules 69 | return APIHelper()._xml_request(self.baseurl + "/5.0/beginprescan.do", "POST", params=params) 70 | 71 | def get_prescan_results(self,app_id: int, build_id=None, sandbox_id=None): 72 | """Gets the prescan results for an application.""" 73 | params = {'app_id': app_id} 74 | if build_id: 75 | params['build_id'] = build_id 76 | if sandbox_id: 77 | params['sandbox_id'] = sandbox_id 78 | return APIHelper()._xml_request(self.baseurl + "/5.0/getprescanresults.do", "GET", params=params) 79 | 80 | def get_file_list(self,app_id: int, build_id=None, sandbox_id=None): 81 | """Gets the list of uploaded files for an application.""" 82 | params = {'app_id': app_id} 83 | if build_id: 84 | params['build_id'] = build_id 85 | if sandbox_id: 86 | params['sandbox_id'] = sandbox_id 87 | return APIHelper()._xml_request(self.baseurl + "/5.0/getfilelist.do", "GET", params=params) 88 | 89 | def remove_file(self,app_id: int, file_id: int, sandbox_id=None): 90 | """Deletes a file from an existing application scan.""" 91 | params = {'app_id': app_id, 'file_id': file_id} 92 | if sandbox_id: 93 | params['sandbox_id'] = sandbox_id 94 | return APIHelper()._xml_request(self.baseurl + "/5.0/removefile.do", "GET", params=params) 95 | 96 | def begin_scan(self, app_id: int, modules=None, scan_all_top_level_modules=None,scan_selected_modules=None,scan_previously_selected_modules=None,sandbox_id=None): 97 | """Runs a static scan for an application. Must specify one of: modules, scan_all_top_level_modules, scan_selected_modules, scan_previously_selected_modules""" 98 | params = {'app_id': app_id} 99 | if sandbox_id: 100 | params['sandbox_id'] = sandbox_id 101 | if modules: 102 | params['modules'] = modules 103 | if scan_all_top_level_modules: 104 | params['scan_all_top_level_modules'] = scan_all_top_level_modules 105 | if scan_selected_modules: 106 | params['scan_selected_modules'] = scan_selected_modules 107 | if scan_previously_selected_modules: 108 | params['scan_previously_selected_modules'] = scan_previously_selected_modules 109 | return APIHelper()._xml_request(self.baseurl + "/5.0/beginscan.do", "POST", params=params) 110 | 111 | 112 | # Results XML APIs 113 | def get_detailed_report(self, build_id: int): 114 | """Returns a detailed report for a given build ID.""" 115 | return APIHelper()._xml_request(self.baseurl + "/5.0/detailedreport.do", "GET", params={"build_id": build_id}) 116 | 117 | def generate_archer(self, payload): 118 | return APIHelper()._xml_request(self.baseurl + "/3.0/generatearcherreport.do", "GET", params=payload) 119 | 120 | def download_archer(self, token=None): 121 | if token is None: 122 | payload = None 123 | else: 124 | payload = {'token': token} 125 | 126 | return APIHelper()._xml_request(self.baseurl + "/3.0/downloadarcherreport.do", "GET", params=payload) 127 | 128 | # Mitigation and Comments XML APIs 129 | def set_mitigation_info(self, build_id: int, flaw_id_list, action, comment: str): 130 | """Adds a new mitigation proposal, acceptance, rejection, or comment for a set of flaws for an application. 131 | Mitigation action types: ['appdesign','comment','fp','osenv','netenv','library','accepted','rejected','acceptrisk'] 132 | """ 133 | actiontype = Constants.ANNOT_TYPE.get(action, action) 134 | payload = {'build_id': build_id, 'flaw_id_list': flaw_id_list, 'action': actiontype, 'comment': comment} 135 | return APIHelper()._xml_request(self.baseurl + "/updatemitigationinfo.do", "POST", params=payload) 136 | --------------------------------------------------------------------------------