diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index ebe6491e3..2c0dc355e 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -228,3 +228,25 @@ jobs: id: check-workflows if: ${{ !startsWith(github.head_ref || github.ref_name, 'release/prepare-') }} run: poetry run -- nox -s workflow:check -- all + audit-workflows: + name: Audit Workflows + runs-on: "ubuntu-24.04" + permissions: + contents: read + steps: + - name: Check out Repository + id: check-out-repository + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Set up Python & Poetry Environment + id: set-up-python-and-poetry-environment + uses: exasol/python-toolbox/.github/actions/python-environment@v9 + with: + python-version: "3.10" + poetry-version: "2.3.0" + + - name: Audit Workflows + id: audit-workflows + run: poetry run -- nox -s workflow:audit diff --git a/.github/workflows/fast-tests-extension.yml b/.github/workflows/fast-tests-extension.yml index 0c8dfca7f..88f645cdc 100644 --- a/.github/workflows/fast-tests-extension.yml +++ b/.github/workflows/fast-tests-extension.yml @@ -25,22 +25,4 @@ jobs: - name: Lint Imports id: lint-imports - run: poetry run -- nox -s lint:import - - # This will be moved to a standard check in the checks.yml in: - # https://github.com/exasol/python-toolbox/issues/811 - lint-github-actions: - name: Lint GitHub Actions - runs-on: ubuntu-24.04 - steps: - - name: Check out Repository - id: check-out-repository - uses: actions/checkout@v6 - with: - persist-credentials: false - - - name: Lint GitHub actions with Zizmor - id: lint-github-actions - uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 - with: - advanced-security: false + run: poetry run -- nox -s lint:import \ No newline at end of file diff --git a/.github/zizmor.yml b/.zizmor.yml similarity index 100% rename from .github/zizmor.yml rename to .zizmor.yml diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index dcc829b04..4fcaebf47 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -9,9 +9,14 @@ so ITDE-related test flows use the configured Exasol baseline and unit-test help * #744: Updated nox DB-version handling to use `BaseConfig.minimum_exasol_version` instead hardcoded `7.1.9` +## Feature + +* #878: Added Nox session `workflow:audit` which uses `zizmor` and added it in `checks.yml` + ## Refactoring * #744: Extracted shared minimum-version selection logic into `minimum_declared_version()` + ## Security * #867: Fixed zizmor linting results diff --git a/doc/user_guide/configuration.rst b/doc/user_guide/configuration.rst index d2d33ea43..ffc5e4395 100644 --- a/doc/user_guide/configuration.rst +++ b/doc/user_guide/configuration.rst @@ -16,4 +16,5 @@ features: features/metrics/sonar Formatting + Zizmor features/github_workflows/github_project_configuration diff --git a/doc/user_guide/features/index.rst b/doc/user_guide/features/index.rst index bd98ae29e..373fb643b 100644 --- a/doc/user_guide/features/index.rst +++ b/doc/user_guide/features/index.rst @@ -10,7 +10,7 @@ Features github_workflows/index documentation/index creating_a_release - managing_dependencies + managing_dependencies/index git_hooks/index metrics/collecting_metrics diff --git a/doc/user_guide/features/managing_dependencies.rst b/doc/user_guide/features/managing_dependencies.rst deleted file mode 100644 index cc4a349a7..000000000 --- a/doc/user_guide/features/managing_dependencies.rst +++ /dev/null @@ -1,17 +0,0 @@ -Managing Dependencies and Vulnerabilities -========================================= - -+------------------------------+----------------+-------------------------------------+ -| Nox session | CI Usage | Action | -+==============================+================+=====================================+ -| ``dependency:licenses`` | ``report.yml`` | Uses ``pip-licenses`` to return | -| | | packages with their licenses | -+------------------------------+----------------+-------------------------------------+ -| ``dependency:audit`` | No | Uses ``pip-audit`` to report active | -| | | vulnerabilities in our dependencies | -+------------------------------+----------------+-------------------------------------+ -| ``vulnerabilities:resolved`` | No | Uses ``pip-audit`` to report known | -| | | vulnerabilities in dependencies | -| | | that have been resolved in | -| | | comparison to the last release. | -+------------------------------+----------------+-------------------------------------+ diff --git a/doc/user_guide/features/managing_dependencies/index.rst b/doc/user_guide/features/managing_dependencies/index.rst new file mode 100644 index 000000000..2502d6920 --- /dev/null +++ b/doc/user_guide/features/managing_dependencies/index.rst @@ -0,0 +1,31 @@ +.. _managing_dependencies: + +Managing Dependencies and Vulnerabilities +========================================= + +.. toctree:: + :maxdepth: 1 + + zizmor_configuration + +.. list-table:: + :widths: 25 20 55 + :header-rows: 1 + + * - Nox session + - CI Usage + - Action + * - ``dependency:licenses`` + - ``report.yml`` + - Uses ``pip-licenses`` to return packages with their licenses. + * - ``dependency:audit`` + - ``dependency-update.yml`` + - Uses ``pip-audit`` to report active vulnerabilities in our dependencies. + * - ``vulnerabilities:resolved`` + - No + - Uses ``pip-audit`` to report known vulnerabilities in dependencies that + have been resolved in comparison to the last release. + * - ``workflow:audit`` + - ``checks.yml`` + - Uses ``zizmor`` to audit GitHub actions and workflows for security issues + and accepts extra zizmor arguments. See :ref:`zizmor_configuration`. diff --git a/doc/user_guide/features/managing_dependencies/zizmor_configuration.rst b/doc/user_guide/features/managing_dependencies/zizmor_configuration.rst new file mode 100644 index 000000000..38e7240f8 --- /dev/null +++ b/doc/user_guide/features/managing_dependencies/zizmor_configuration.rst @@ -0,0 +1,15 @@ +.. _zizmor_configuration: + +Configuring Zizmor +================== + +``workflow:audit`` uses `zizmor `__ to audit GitHub +Actions and workflows. Zizmor reads its project configuration from a file named +``.zizmor.yml`` in the repository root. + +As a starting point, copy the template shipped with the PTB: + +.. literalinclude:: ../../../../exasol/toolbox/templates/github/zizmor.yml + :language: yaml + +For troubleshooting help, see :ref:`handle_zizmor_findings`. diff --git a/doc/user_guide/troubleshooting/handle_zizmor_findings.rst b/doc/user_guide/troubleshooting/handle_zizmor_findings.rst new file mode 100644 index 000000000..69916e4fe --- /dev/null +++ b/doc/user_guide/troubleshooting/handle_zizmor_findings.rst @@ -0,0 +1,39 @@ +.. _handle_zizmor_findings: + +Handling Zizmor Findings +======================== + +Fixing the Issues +----------------- + +`zizmor `__ can automatically fix some findings. The +``--fix`` flag accepts three modes: + +* ``--fix=safe`` applies only safe fixes. This is the default. +* ``--fix=unsafe-only`` applies only unsafe fixes, meaning fixes that may be + correct but require human review because they can affect semantics. +* ``--fix=all`` applies both safe and unsafe fixes. + +If a finding does not have a known auto-fix, check the relevant audit +documentation in the `zizmor documentation `__. +That usually makes it clearer whether the issue needs a code change, a +configuration change, or an accepted exception. + +Ignoring Accepted Issues +------------------------ + +When you are first enabling ``workflow:audit``, it can be practical to start with +a broader ``.zizmor.yml`` configuration and then tighten it over time. +However, once a finding is understood, prefer ignoring the specific line that +triggers it instead of adding a broader suppressing rule to ``.zizmor.yml``. +That keeps the exception local and visible during review. + +A typical line-level ignore looks like this: + +.. code-block:: yaml + + secrets: inherit # zizmor: ignore[secrets-inherit] - PTB cannot customize inherited secrets here yet. + +Use configuration rules in ``.zizmor.yml`` only when the finding is genuinely +project-wide. If you add a temporary rule while working through a batch of +findings, remove it again once the repository is clean. diff --git a/doc/user_guide/troubleshooting/index.rst b/doc/user_guide/troubleshooting/index.rst index 38cc5b545..b963e1973 100644 --- a/doc/user_guide/troubleshooting/index.rst +++ b/doc/user_guide/troubleshooting/index.rst @@ -17,6 +17,7 @@ proposed mitigations, some potentially specific to the related tool. format_check_reports_unmodified_files formatting_disable "F401 unused import" (reported by Ruff) + Handling Zizmor Findings ../features/metrics/ignore_findings debug_github_workflows diff --git a/exasol/toolbox/config.py b/exasol/toolbox/config.py index 126a44587..4e7211342 100644 --- a/exasol/toolbox/config.py +++ b/exasol/toolbox/config.py @@ -351,3 +351,11 @@ def github_workflow_patcher_yaml(self) -> Path | None: if workflow_patcher_yaml.is_file(): return workflow_patcher_yaml return None + + @computed_field # type: ignore[misc] + @property + def zizmor_config_path(self) -> Path: + """ + Path where the zizmor configuration file for the project is stored. + """ + return self.root_path / ".zizmor.yml" diff --git a/exasol/toolbox/nox/_workflow.py b/exasol/toolbox/nox/_workflow.py index 2c07c0093..bde901ba5 100644 --- a/exasol/toolbox/nox/_workflow.py +++ b/exasol/toolbox/nox/_workflow.py @@ -64,3 +64,23 @@ def generate_workflow(session: Session) -> None: workflow_choice=args.workflow_choice, config=PROJECT_CONFIG, ).generate_workflows() + + +@nox.session(name="workflow:audit", python=False) +def audit_workflows(session: Session) -> None: + """ + Audit GitHub actions & workflows with zizmor. + This Nox session passes through additional arguments that zizmor supports. + In some cases, zizmor findings can be automatically fixed with `--fix=safe`. + """ + config_path = PROJECT_CONFIG.zizmor_config_path + if not config_path.is_file(): + raise FileNotFoundError(f"{config_path} must exist.") + + session.run( + "zizmor", + "--config", + config_path, + *session.posargs, + PROJECT_CONFIG.root_path, + ) diff --git a/exasol/toolbox/templates/github/workflows/checks.yml b/exasol/toolbox/templates/github/workflows/checks.yml index 24103ae69..ac3109f0b 100644 --- a/exasol/toolbox/templates/github/workflows/checks.yml +++ b/exasol/toolbox/templates/github/workflows/checks.yml @@ -226,3 +226,26 @@ jobs: - name: Check Workflows id: check-workflows run: poetry run -- nox -s workflow:check -- all + + audit-workflows: + name: Audit Workflows + runs-on: "(( os_version ))" + permissions: + contents: read + steps: + - name: Check out Repository + id: check-out-repository + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Set up Python & Poetry Environment + id: set-up-python-and-poetry-environment + uses: exasol/python-toolbox/.github/actions/python-environment@v9 + with: + python-version: "(( minimum_python_version ))" + poetry-version: "(( dependency_manager_version ))" + + - name: Audit Workflows + id: audit-workflows + run: poetry run -- nox -s workflow:audit diff --git a/test/unit/config_test.py b/test/unit/config_test.py index d87c0a97b..3115d0632 100644 --- a/test/unit/config_test.py +++ b/test/unit/config_test.py @@ -77,6 +77,7 @@ def test_works_as_defined(tmp_path, test_project_config_factory): "root_path": root_path, "sonar_code_path": Path("exasol/test"), "source_code_path": root_path / "exasol" / "test", + "zizmor_config_path": root_path / ".zizmor.yml", } @staticmethod diff --git a/test/unit/nox/_workflow_test.py b/test/unit/nox/_workflow_test.py index 55ec74c10..fad8a1013 100644 --- a/test/unit/nox/_workflow_test.py +++ b/test/unit/nox/_workflow_test.py @@ -6,6 +6,7 @@ from exasol.toolbox.config import BaseConfig from exasol.toolbox.nox._workflow import ( + audit_workflows, check_workflow, generate_workflow, ) @@ -32,7 +33,8 @@ def github_workflow_patcher_yaml(self) -> None: @pytest.fixture def nox_session_runner_posargs(request): - return [request.param] + value = request.param + return list(value) if isinstance(value, (list, tuple)) else [value] class TestGenerateWorkflow: @@ -144,3 +146,37 @@ def test_raises_session_quit_when_workflows_are_out_of_date( "- report\n" "- slow-checks" ) + + +class TestAuditWorkflows: + @staticmethod + @pytest.mark.parametrize( + "nox_session_runner_posargs", + [["--version"]], + indirect=["nox_session_runner_posargs"], + ) + def test_passes_through_extra_arguments( + nox_session, + project_config_without_patcher, + nox_session_runner_posargs, + ): + config_path = project_config_without_patcher.root_path / ".zizmor.yml" + config_path.parent.mkdir(parents=True, exist_ok=True) + config_path.write_text("rules: []\n") + + with ( + patch( + "exasol.toolbox.nox._workflow.PROJECT_CONFIG", + new=project_config_without_patcher, + ), + patch("nox.sessions.Session.run") as run_mock, + ): + audit_workflows(nox_session) + + run_mock.assert_called_once_with( + "zizmor", + "--config", + config_path, + "--version", + project_config_without_patcher.root_path, + )