diff --git a/doc/tool_usage_guide.md b/doc/tool_usage_guide.md index 1d96035f132c..8a41fcee7135 100644 --- a/doc/tool_usage_guide.md +++ b/doc/tool_usage_guide.md @@ -22,6 +22,7 @@ This repo is currently migrating all checks from a slower `tox`-based framework, |`black`| Runs `black` checks. | `azpysdk black .` | |`verifytypes`| Runs `verifytypes` checks. | `azpysdk verifytypes .` | |`ruff`| Runs `ruff` checks. | `azpysdk ruff .` | +|`bandit`| Runs `bandit` checks, which detect common security issues. | `azpysdk bandit .` | |`verifywhl`| Verifies that the root directory in whl is azure, and verifies manifest so that all directories in source are included in sdist. | `azpysdk verifywhl .` | |`verifysdist`| Verify directories included in sdist and contents in manifest file. Also ensures that py.typed configuration is correct within the setup.py. | `azpysdk verifysdist .` | |`verify_keywords`| Verify that the keyword 'azure sdk' is present in the targeted package's keywords. | `azpysdk verify_keywords .` | diff --git a/eng/tools/azure-sdk-tools/azpysdk/bandit.py b/eng/tools/azure-sdk-tools/azpysdk/bandit.py new file mode 100644 index 000000000000..132d640c29cd --- /dev/null +++ b/eng/tools/azure-sdk-tools/azpysdk/bandit.py @@ -0,0 +1,80 @@ +import argparse +import os +import sys +from typing import Optional, List +import subprocess +from subprocess import check_call, CalledProcessError + +from .Check import Check +from ci_tools.environment_exclusions import is_check_enabled +from ci_tools.variables import in_ci, set_envvar_defaults +from ci_tools.logging import logger +from ci_tools.functions import install_into_venv, get_pip_command + + +class bandit(Check): + def __init__(self) -> None: + super().__init__() + + def register( + self, subparsers: "argparse._SubParsersAction", parent_parsers: Optional[List[argparse.ArgumentParser]] = None + ) -> None: + """Register the bandit check. The bandit check installs bandit and runs bandit against the target package to find common security issues.""" + parents = parent_parsers or [] + p = subparsers.add_parser( + "bandit", parents=parents, help="Run the bandit check to find common security issues for a package" + ) + p.set_defaults(func=self.run) + + def run(self, args: argparse.Namespace) -> int: + """Run the bandit check command.""" + logger.info("Running bandit check...") + + set_envvar_defaults() + targeted = self.get_targeted_directories(args) + + results: List[int] = [] + + for parsed in targeted: + package_dir = parsed.folder + package_name = parsed.name + executable, staging_directory = self.get_executable(args.isolate, args.command, sys.executable, package_dir) + logger.info(f"Processing {package_name} for bandit check") + + self.install_dev_reqs(executable, args, package_dir) + + try: + install_into_venv(executable, ["bandit"], package_dir) + except CalledProcessError as e: + logger.error(f"Failed to install bandit: {e}") + return e.returncode + + # debug a pip freeze result + cmd = get_pip_command(executable) + ["freeze"] + freeze_result = subprocess.run( + cmd, cwd=package_dir, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + logger.debug(f"Running pip freeze with {cmd}") + logger.debug(freeze_result.stdout) + + if in_ci(): + if not is_check_enabled(package_dir, "bandit"): + logger.warning(f"Bandit is disabled for {package_name}. Skipping...") + continue + + try: + check_call( + [ + executable, + "-m", + "bandit", + "-r", + os.path.join(package_dir, "azure"), + "-ll", + ] + ) + except CalledProcessError as e: + logger.error(f"{package_name} exited with error {e.returncode}") + results.append(e.returncode) + + return max(results) if results else 0 diff --git a/eng/tools/azure-sdk-tools/azpysdk/main.py b/eng/tools/azure-sdk-tools/azpysdk/main.py index 652e4ac71c54..03665a993bb0 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/main.py +++ b/eng/tools/azure-sdk-tools/azpysdk/main.py @@ -27,6 +27,7 @@ from .verifytypes import verifytypes from .verify_whl import verify_whl from .verify_sdist import verify_sdist +from .bandit import bandit from .verify_keywords import verify_keywords from ci_tools.logging import configure_logging, logger @@ -86,6 +87,7 @@ def build_parser() -> argparse.ArgumentParser: verifytypes().register(subparsers, [common]) verify_sdist().register(subparsers, [common]) verify_whl().register(subparsers, [common]) + bandit().register(subparsers, [common]) verify_keywords().register(subparsers, [common]) return parser