diff --git a/check-vulnerabilities/.safety-ignore.yml b/check-vulnerabilities/.safety-ignore.yml new file mode 100644 index 000000000..c4c874fca --- /dev/null +++ b/check-vulnerabilities/.safety-ignore.yml @@ -0,0 +1,34 @@ +# Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Safety ignore file for vulnerability checks +# This file contains vulnerability IDs that are accepted/ignored +# Format: YAML configuration for Safety CLI +# Documentation: https://docs.pyup.io/docs/safety-20-policy-file + +security: + ignore-vulnerabilities: + # List of vulnerability IDs to ignore + # Example: + # 52495: + # reason: "Accepted vulnerability" + # expires: null diff --git a/check-vulnerabilities/README.md b/check-vulnerabilities/README.md new file mode 100644 index 000000000..08212f452 --- /dev/null +++ b/check-vulnerabilities/README.md @@ -0,0 +1,12 @@ +# IMPORTANT: migration from `ignored-safety.txt` to `.safety-ignore.yml` + +We have migrated from using `ignored-safety.txt` to `.safety-ignore.yml` for managing ignored +vulnerabilities. Especially for `ansys/actions` maintainers, make sure that whenever a new +vulnerability is added to `.safety-ignore.yml`, it is also added to `ignored-safety.txt` until +the migration is complete. This ensures that the CI checks continue to function correctly +during the transition period. + +> [!IMPORTANT] +> The `ignored-safety.txt` file is still required for the consumers of this action to work properly. Old action +> versions will continue to use `ignored-safety.txt` until repository maintainers upgrade to the latest +> version of the action that supports `.safety-ignore.yml`. diff --git a/check-vulnerabilities/action.yml b/check-vulnerabilities/action.yml index 515746885..32f731a23 100644 --- a/check-vulnerabilities/action.yml +++ b/check-vulnerabilities/action.yml @@ -101,8 +101,21 @@ inputs: bandit-configfile: description: | Optional config file to use for selecting plugins, overriding defaults, - and customizing checks performed by bandit. + and customizing checks performed by bandit. Path location should be relative + to the repository root. If the provided file does not exist, the action will + fail with an error. + default: '' + required: false + type: string + safety-configfile: + description: | + Path to a custom .safety-ignore.yml policy file to use with safety. + If not provided, the action will use its default policy file. Path + location should be relative to the repository root. For instance, if the policy + file is located in the ``/config`` folder of the repository and is named + ``my-safety-policy.yml``, the input should be set to ``config/my-safety-policy.yml``. + If the provided file does not exist, the action will fail with an error. default: '' required: false type: string @@ -461,25 +474,82 @@ runs: python -m pip install -r "${GITHUB_ACTION_PATH}/requirements.txt" fi - - name: "Install wget on Windows" - if: runner.os == 'Windows' - shell: pwsh + - name: "Validate configuration files" + id: validate-config-files + shell: bash + env: + BANDIT_CONFIGFILE: ${{ inputs.bandit-configfile }} + SAFETY_CONFIGFILE: ${{ inputs.safety-configfile }} run: | - # Check if wget is installed - if not, install it - if (-not (Get-Command wget -ErrorAction SilentlyContinue)) { - Write-Host "wget is not installed. Installing using Chocolatey..." - # Install wget using Chocolatey - choco install wget -y - } else { - Write-Host "wget is already installed." - } - - - name: "Download the list of ignored safety vulnerabilities" + # Initialize validation results + SAFETY_CONFIG_ERROR="false" + BANDIT_CONFIG_ERROR="false" + + # Check if provided safety policy file exists + if [[ "${SAFETY_CONFIGFILE}" != "" ]]; then + if [[ ! -f "${GITHUB_WORKSPACE}/${SAFETY_CONFIGFILE}" ]]; then + SAFETY_CONFIG_ERROR="true" + fi + fi + + # Check if provided bandit config file exists + if [[ "${BANDIT_CONFIGFILE}" != "" ]]; then + if [[ ! -f "${GITHUB_WORKSPACE}/${BANDIT_CONFIGFILE}" ]]; then + BANDIT_CONFIG_ERROR="true" + fi + fi + + # Set outputs + echo "SAFETY_CONFIG_ERROR=${SAFETY_CONFIG_ERROR}" >> ${GITHUB_OUTPUT} + echo "BANDIT_CONFIG_ERROR=${BANDIT_CONFIG_ERROR}" >> ${GITHUB_OUTPUT} + + - uses: ansys/actions/_logging@main + if: ${{ steps.validate-config-files.outputs.SAFETY_CONFIG_ERROR == 'true' }} + with: + level: "ERROR" + message: > + The provided safety policy file '${{ inputs.safety-configfile }}' does not exist in the repository. + + - uses: ansys/actions/_logging@main + if: ${{ steps.validate-config-files.outputs.BANDIT_CONFIG_ERROR == 'true' }} + with: + level: "ERROR" + message: > + The provided bandit config file '${{ inputs.bandit-configfile }}' does not exist in the repository. + + - uses: ansys/actions/_logging@main + with: + level: "INFO" + message: > + Running safety vulnerability checks. + + - name: "Run safety checks" shell: bash + continue-on-error: true + env: + ACTIVATE_VENV_BANDIT_SAFETY: ${{ steps.virtual-environment-activation-command.outputs.ACTIVATE_VENV_BANDIT_SAFETY }} + SAFETY_CONFIGFILE: ${{ inputs.safety-configfile }} run: | - wget https://raw.githubusercontent.com/ansys/actions/main/check-vulnerabilities/ignored-safety.txt + ${ACTIVATE_VENV_BANDIT_SAFETY} + # Set the safety policy file to use + if [[ "${SAFETY_CONFIGFILE}" == "" ]]; then + SAFETY_POLICY_FILE="${GITHUB_ACTION_PATH}/.safety-ignore.yml" + echo "Using default safety policy file: ${SAFETY_POLICY_FILE}" + else + SAFETY_POLICY_FILE="${GITHUB_WORKSPACE}/${SAFETY_CONFIGFILE}" + echo "Using custom safety policy file: ${SAFETY_POLICY_FILE}" + fi + + # Run safety vulnerability checks + safety check -o bare --save-json info_safety.json --continue-on-error --policy-file "${SAFETY_POLICY_FILE}" -r "${GITHUB_ACTION_PATH}/requirements-for-safety.txt" + + - uses: ansys/actions/_logging@main + with: + level: "INFO" + message: > + Running bandit security checks. - - name: "Run safety and bandit" + - name: "Run bandit checks" shell: bash continue-on-error: true env: @@ -488,21 +558,16 @@ runs: SOURCE_DIRECTORY: ${{ inputs.source-directory }} run: | ${ACTIVATE_VENV_BANDIT_SAFETY} - # Load accepted safety vulnerabilities - mapfile ignored_safety_vulnerabilities < ignored-safety.txt - ignored_vulnerabilities='' - for pckg in ${ignored_safety_vulnerabilities[*]}; do ignored_vulnerabilities+="-i $pckg "; done - ignored_safety_vulnerabilities=${ignored_safety_vulnerabilities::-1} - echo "Ignored safety vulnerabilities: $ignored_vulnerabilities" - - # Run security tools - safety check -o bare --save-json info_safety.json --continue-on-error $ignored_vulnerabilities -r "${GITHUB_ACTION_PATH}/requirements-for-safety.txt" - + # Set the bandit config file to use if [[ "${BANDIT_CONFIGFILE}" == "" ]]; then CONFIGFILE="" + echo "Using default bandit configuration" else - CONFIGFILE="-c ${BANDIT_CONFIGFILE}" + CONFIGFILE="-c ${GITHUB_WORKSPACE}/${BANDIT_CONFIGFILE}" + echo "Using custom bandit config file: ${GITHUB_WORKSPACE}/${BANDIT_CONFIGFILE}" fi + + # Run bandit security checks bandit ${CONFIGFILE} -r "${SOURCE_DIRECTORY}" -o info_bandit.json -f json --exit-zero - name: "Run advisory checks" diff --git a/doc/source/changelog/1215.added.md b/doc/source/changelog/1215.added.md new file mode 100644 index 000000000..7f546a8ec --- /dev/null +++ b/doc/source/changelog/1215.added.md @@ -0,0 +1 @@ +Migrate to using \`\`.safety-ignore.yml\`\` file diff --git a/doc/source/conf.py b/doc/source/conf.py index c1d9c3eb7..29e392a4e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -18,7 +18,7 @@ ACTIONS_INPUTS_FIELDS = ("description", "required", "type", "default") ACCEPTED_LICENSES = BASE_DIR / "check-licenses" / "accepted-licenses.txt" IGNORED_PACKAGES = BASE_DIR / "check-licenses" / "ignored-packages.txt" -IGNORED_SAFETY = BASE_DIR / "check-vulnerabilities" / "ignored-safety.txt" +IGNORED_SAFETY = BASE_DIR / "check-vulnerabilities" / ".safety-ignore.yml" # Project information project = "Ansys Actions" @@ -312,8 +312,27 @@ def get_example_file_title(example_file): ] -# Dynamically load the file contents for accepted licenses and ignored packages -def load_file_lines_as_list(file_path): +def load_safety_ignore_vulnerabilities(file_path: pathlib.Path): + """Loads the vulnerability IDs from the safety ignore YAML file. + + Parameters + ---------- + file_path : ~pathlib.Path + The ``Path`` instance representing the YAML file location. + + Returns + ------- + list[str] + A list of vulnerability ID strings. + + """ + with file_path.open() as safety_ignore_file: + data = yaml.safe_load(safety_ignore_file) + vulnerabilities = data.get("security", {}).get("ignore-vulnerabilities", {}) + return list(vulnerabilities.keys()) if vulnerabilities else [] + + +def load_file_lines_as_list(file_path: pathlib.Path): """Loads the lines of a file in the form of a Python list. Parameters @@ -331,7 +350,7 @@ def load_file_lines_as_list(file_path): This function is expected to be used for loading the contents of TXT files. """ - with open(file_path) as accepted_licenses_file: + with file_path.open() as accepted_licenses_file: return list(accepted_licenses_file.read().split("\n")) @@ -342,8 +361,8 @@ def load_file_lines_as_list(file_path): jinja_contexts["check-licenses"][var] = load_file_lines_as_list(file) # Check vulnerabilities -jinja_contexts["check-vulnerabilities"]["ignored_safety"] = load_file_lines_as_list( - IGNORED_SAFETY +jinja_contexts["check-vulnerabilities"]["ignored_safety"] = ( + load_safety_ignore_vulnerabilities(IGNORED_SAFETY) )