From 70b696baaa532dfb9ba0c3b05c5671480f2aabe1 Mon Sep 17 00:00:00 2001 From: Michael Kubacki Date: Wed, 6 Sep 2023 12:03:00 -0400 Subject: [PATCH] Add CodeQL helper functions Adds a new file that contains functions are intended to ease repo integration with CodeQL, ensure consistent command-line usage across repos, and define standard scopes that other plugins and tools can depend on for CodeQL operations. Signed-off-by: Michael Kubacki --- docs/user/features/codeql.md | 110 +++++++++++++++++++++++++++++++++++ edk2toolext/codeql.py | 84 ++++++++++++++++++++++++++ mkdocs.yml | 1 + tests.unit/test_codeql.py | 78 +++++++++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 docs/user/features/codeql.md create mode 100644 edk2toolext/codeql.py create mode 100644 tests.unit/test_codeql.py diff --git a/docs/user/features/codeql.md b/docs/user/features/codeql.md new file mode 100644 index 000000000..89770c5f0 --- /dev/null +++ b/docs/user/features/codeql.md @@ -0,0 +1,110 @@ +# CodeQL + +## About CodeQL + +CodeQL is open source and free for open-source projects. It is maintained by GitHub and naturally has excellent +integration with GitHub projects. CodeQL generates a "database" during the firmware build process that enables queries +to run against that database. Many open-source queries are officially supported and comprise the vulnerability analysis +performed against the database. These queries are maintained here - [github/codeql](https://github.com/github/codeql). + +Queries are written in an object-oriented query language called QL. CodeQL provides: + +1. A [command-line (CLI) interface](https://codeql.github.com/docs/codeql-cli/#codeql-cli) +2. A [VS Code extension](https://codeql.github.com/docs/codeql-for-visual-studio-code/#codeql-for-visual-studio-code) + to help write queries and run queries +3. [GitHub action](https://github.com/github/codeql-action) support for repo integration via + [code scanning](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning) +4. In addition to other features described in the [CodeQL overview](https://codeql.github.com/docs/codeql-overview/) + +## CodeQL Usage + +CodeQL provides the capability to debug the actual queries and for our (Tianocore) community to write our own queries +and even contribute back to the upstream repo when appropriate. In other cases, we might choose to keep our own queries +in a separate TianoCore repo or within a directory in the edk2 code tree. + +This is all part of CodeQL Scanning. [This page](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning) +has information concerning how to configure CodeQL scanning within a GitHub project such as edk2. Information on the +topic of running additional custom queries is documented [here](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#running-additional-queries) +in that page. + +In addition, CodeQL offers the flexibility to: + +- Build databases locally +- Retrieve databases from server builds +- Relatively quickly test queries locally against a database for a fast feedback loop +- Suppress false positives +- Customize the files and queries used in the edk2 project and quickly keep this list in sync between the server and + local execution + +## EDK2 PyTool Extensions CodeQL Module + +A Python module located in `edk2toolext/codeql.py` provides helper functions that platform and CI build files can use +to support CodeQL enabled builds with minimal effort. + +The following functions are available. The expected actions to be taken per scope are included as well. + +- `add_command_line_option()` - Adds the CodeQL command (`--codeql`) to the platform command line options. +- `get_scopes()` - Returns the active CodeQL scopes for this build. + - Host OS neutral scopes: + - `codeql-build` - Build the CodeQL database during firmware build. + - `codeql-analyze` - Analyze the CodeQL database after firmware build (post-build). + - Linux scope: + - `codeql-linux-ext-dep` - Download the Linux CodeQL CLI external dependency. + - Windows scope: + - `codeql-windows-ext-dep` - Download the Windows CodeQL CLI external dependency. +- `is_codeql_enabled_on_command_line` - Returns whether CodeQL was enabled (via `--codeql`) on the command line. +- `set_audit_only_mode` - Configures the CodeQL plugin to run in audit only mode. + +These functions are intended to ease repo integration with CodeQL, ensure consistent command-line usage across repos, +and define standard scopes that other plugins and tools can depend on for CodeQL operations. + +### Integration Examples + +This section provides examples of how to use the functions available in `edk2toolext/codeql.py`. + +#### add_common_line_option() + +Call when the command-line options are registered for the build: + +```python + import edk2toolext.codeql as codeql + + def AddCommandLineOptions(self, parserObj): + codeql_helpers.add_command_line_option(parserObj) +``` + +#### get_scopes() + +Call when scopes are returned for the build file: + +```python + def GetActiveScopes(self): + scopes = ("cibuild", "edk2-build", "host-based-test") + + scopes += codeql_helpers.get_scopes(self.codeql) + return scopes +``` + +#### is_codeql_enabled_on_command_line() + +Call when code needs to know if the CodeQL argument was given on the command line: + +```python + def RetrieveCommandLineOptions(self, args): + super().RetrieveCommandLineOptions(args) + + try: + self.codeql = codeql_helpers.is_codeql_enabled_on_command_line(args) +``` + +In the above example, a `Settings` class is creating an instance variable that can be referred to later based on the +value returned from the function. + +#### set_audit_only_mode() + +Call when a CI or platform build would like to enable audit mode for the current build invocation. + +```python + def GetActiveScopes(self): + set_audit_only_mode() +``` diff --git a/edk2toolext/codeql.py b/edk2toolext/codeql.py new file mode 100644 index 000000000..d749f25ab --- /dev/null +++ b/edk2toolext/codeql.py @@ -0,0 +1,84 @@ +# @file codeql.py +# +# Exports functions commonly needed for Stuart-based platforms to easily +# enable CodeQL in their platform build. +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## +"""CodeQL Helper Functions. + +Contains functions are intended to ease repo integration with CodeQL, ensure +consistent command-line usage across repos, and define standard scopes that +other plugins and tools can depend on for CodeQL operations. +""" +from argparse import ArgumentParser, Namespace +from typing import Tuple + +from edk2toollib.utility_functions import GetHostInfo + +from edk2toolext.environment.uefi_build import UefiBuilder + + +def add_command_line_option(parser: ArgumentParser) -> None: + """Add the CodeQL command to the platform command line options. + + Args: + parser (ArgumentParser): The argument parser used in this build. + + """ + parser.add_argument( + '--codeql', + dest='codeql', + action='store_true', + default=False, + help="Optional - Produces CodeQL results from the build.") + + +def get_scopes(codeql_enabled: bool) -> Tuple[str]: + """Return the active CodeQL scopes for this build. + + Args: + codeql_enabled (bool): Whether CodeQL is enabled. + + Returns: + Tuple[str]: A tuple of strings containing scopes that enable the + CodeQL plugin. + """ + active_scopes = () + + if codeql_enabled: + if GetHostInfo().os == "Linux": + active_scopes += ("codeql-linux-ext-dep",) + else: + active_scopes += ("codeql-windows-ext-dep",) + active_scopes += ("codeql-build", "codeql-analyze") + + return active_scopes + + +def is_codeql_enabled_on_command_line(args: Namespace) -> bool: + """Return whether CodeQL was enabled on the command line. + + Args: + args (Namespace): Object holding a string representation of command + line arguments. + + Returns: + bool: True if CodeQL is enabled on the command line. Otherwise, false. + """ + return args.codeql + + +def set_audit_only_mode(uefi_builder: UefiBuilder) -> None: + """Configure the CodeQL plugin to run in audit only mode. + + Args: + uefi_builder (UefiBuilder): The UefiBuilder object for this platform + build. + + """ + uefi_builder.env.SetValue( + "STUART_CODEQL_AUDIT_ONLY", + "true", + "Platform Defined") diff --git a/mkdocs.yml b/mkdocs.yml index bb23c1505..27b1247cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,6 +85,7 @@ nav: - Self Describing Environment: features/sde.md - Settings Manager: features/settings_manager.md - Logging: features/logging.md + - CodeQL: features/codeql.md - WSL: features/using_linux.md - API Reference: - ... | flat | api/**/*.md diff --git a/tests.unit/test_codeql.py b/tests.unit/test_codeql.py new file mode 100644 index 000000000..5ad0b6ea1 --- /dev/null +++ b/tests.unit/test_codeql.py @@ -0,0 +1,78 @@ +# @file utility_functions_test.py +# unit test for utility_functions module. +# +# +# Copyright (c) Microsoft Corporation +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +## +"""CodeQL module unit tests.""" + +import unittest +from argparse import ArgumentParser, Namespace +from unittest.mock import Mock, patch + +import edk2toolext.codeql as codeql + + +class CodeQlTests(unittest.TestCase): + """CodeQL unit tests.""" + + def test_codeql_option(self): + """Tests that common line options are added as expected.""" + parser = ArgumentParser() + codeql.add_command_line_option(parser) + + # Test that the default value is False + args = parser.parse_args([]) + self.assertFalse(args.codeql) + + # Test that the option sets the value to True + args_with_option = parser.parse_args(['--codeql']) + self.assertTrue(args_with_option.codeql) + + @patch('edk2toolext.codeql.GetHostInfo') + def test_codeql_enabled_linux(self, mock_host_info): + """Tests that the proper scope is returned on a Linux host.""" + mock_host_info.return_value.os = "Linux" + result = codeql.get_scopes(codeql_enabled=True) + expected_result = ("codeql-linux-ext-dep", "codeql-build", + "codeql-analyze") + self.assertEqual(result, expected_result) + + @patch('edk2toolext.codeql.GetHostInfo') + def test_codeql_enabled_windows(self, mock_host_info): + """Tests that the proper scope is returned on a Windows host.""" + mock_host_info.return_value.os = "Windows" + result = codeql.get_scopes(codeql_enabled=True) + expected_result = ("codeql-windows-ext-dep", "codeql-build", + "codeql-analyze") + self.assertEqual(result, expected_result) + + @patch('edk2toolext.codeql.GetHostInfo') + def test_codeql_disabled(self, mock_host_info): + """Tests that the proper scopes are returned if CodeQL is disabled.""" + result = codeql.get_scopes(codeql_enabled=False) + expected_result = () + self.assertEqual(result, expected_result) + + def test_codeql_enabled(self): + """Tests that CodeQL is recognized on the command-line properly.""" + mock_args = Namespace(codeql=True) + result = codeql.is_codeql_enabled_on_command_line(mock_args) + self.assertTrue(result) + + def test_codeql_not_enabled(self): + """Tests that CodeQL is recognized on the command-line properly.""" + mock_args = Namespace(codeql=False) + result = codeql.is_codeql_enabled_on_command_line(mock_args) + self.assertFalse(result) + + def test_set_audit_only_mode(self): + """Tests that CodeQL audit mode is enabled as expected.""" + mock_uefi_builder = Mock() + codeql.set_audit_only_mode(mock_uefi_builder) + mock_uefi_builder.env.SetValue.assert_called_once_with( + "STUART_CODEQL_AUDIT_ONLY", + "true", + "Platform Defined")