diff --git a/prowler/__main__.py b/prowler/__main__.py index 08ed2c16d3..30dd639b33 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -99,7 +99,7 @@ from prowler.providers.common.provider import Provider from prowler.providers.common.quick_inventory import run_provider_quick_inventory from prowler.providers.gcp.models import GCPOutputOptions from prowler.providers.github.models import GithubOutputOptions -from prowler.providers.github_action.models import GithubActionOutputOptions +from prowler.providers.github_actions.models import GithubActionsOutputOptions from prowler.providers.iac.models import IACOutputOptions from prowler.providers.kubernetes.models import KubernetesOutputOptions from prowler.providers.m365.models import M365OutputOptions @@ -153,7 +153,8 @@ def prowler(): if compliance_framework: args.output_formats.extend(compliance_framework) # If no input compliance framework, set all, unless a specific service or check is input - elif default_execution: + # Skip for IAC, GitHub Actions, and Pipeline providers that don't use compliance frameworks + elif default_execution and provider not in ["iac", "github_actions", "pipeline"]: args.output_formats.extend(get_available_compliance_frameworks(provider)) # Set Logger configuration @@ -180,7 +181,7 @@ def prowler(): logger.debug("Loading compliance frameworks from .json files") # Skip compliance frameworks for IAC, GitHub Action, and Pipeline providers - if provider not in ["iac", "github_action", "pipeline"]: + if provider not in ["iac", "github_actions", "pipeline"]: bulk_compliance_frameworks = Compliance.get_bulk(provider) # Complete checks metadata with the compliance framework specification bulk_checks_metadata = update_checks_metadata_with_compliance( @@ -308,8 +309,8 @@ def prowler(): ) elif provider == "iac": output_options = IACOutputOptions(args, bulk_checks_metadata) - elif provider == "github_action": - output_options = GithubActionOutputOptions(args, bulk_checks_metadata) + elif provider == "github_actions": + output_options = GithubActionsOutputOptions(args, bulk_checks_metadata) elif provider == "pipeline": output_options = PipelineOutputOptions(args, bulk_checks_metadata) @@ -321,7 +322,7 @@ def prowler(): # Execute checks findings = [] - if provider in ["iac", "github_action", "pipeline"]: + if provider in ["iac", "github_actions", "pipeline"]: # For IAC, GitHub Action, and Pipeline providers, run the scan directly findings = global_provider.run() elif len(checks_to_execute): diff --git a/prowler/lib/check/checks_loader.py b/prowler/lib/check/checks_loader.py index d772f7c0cc..a281d8f1aa 100644 --- a/prowler/lib/check/checks_loader.py +++ b/prowler/lib/check/checks_loader.py @@ -20,8 +20,8 @@ def load_checks_to_execute( ) -> set: """Generate the list of checks to execute based on the cloud provider and the input arguments given""" try: - # Bypass check loading for IAC, GitHub Action, and Pipeline providers since they use external tools directly - if provider in ["iac", "github_action", "pipeline"]: + # Bypass check loading for IAC, GitHub Actions, and Pipeline providers since they use external tools directly + if provider in ["iac", "github_actions", "pipeline"]: return set() # Local subsets diff --git a/prowler/lib/check/utils.py b/prowler/lib/check/utils.py index b4af410c1a..7eefcb53ad 100644 --- a/prowler/lib/check/utils.py +++ b/prowler/lib/check/utils.py @@ -14,8 +14,8 @@ def recover_checks_from_provider( Returns a list of tuples with the following format (check_name, check_path) """ try: - # Bypass check loading for IAC, GitHub Action, and Pipeline providers since they use external tools directly - if provider in ["iac", "github_action", "pipeline"]: + # Bypass check loading for IAC, GitHub Actions, and Pipeline providers since they use external tools directly + if provider in ["iac", "github_actions", "pipeline"]: return [] checks = [] @@ -63,8 +63,8 @@ def recover_checks_from_service(service_list: list, provider: str) -> set: Returns a set of checks from the given services """ try: - # Bypass check loading for IAC, GitHub Action, and Pipeline providers since they use external tools directly - if provider in ["iac", "github_action", "pipeline"]: + # Bypass check loading for IAC, GitHub Actions, and Pipeline providers since they use external tools directly + if provider in ["iac", "github_actions", "pipeline"]: return set() checks = set() diff --git a/prowler/lib/cli/parser.py b/prowler/lib/cli/parser.py index 47b1796473..40cb5228bd 100644 --- a/prowler/lib/cli/parser.py +++ b/prowler/lib/cli/parser.py @@ -26,10 +26,10 @@ class ProwlerArgumentParser: self.parser = argparse.ArgumentParser( prog="prowler", formatter_class=RawTextHelpFormatter, - usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,dashboard,iac,github_action,pipeline} ...", + usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,dashboard,iac,github_actions,pipeline} ...", epilog=""" Available Cloud Providers: - {aws,azure,gcp,kubernetes,m365,github,iac,nhn,github_action,pipeline} + {aws,azure,gcp,kubernetes,m365,github,iac,nhn,github_actions,pipeline} aws AWS Provider azure Azure Provider gcp GCP Provider @@ -38,7 +38,7 @@ Available Cloud Providers: github GitHub Provider iac IaC Provider (Preview) nhn NHN Provider (Unofficial) - github_action GitHub Actions Security Provider + github_actions GitHub Actions Security Provider pipeline CI/CD Pipeline Security Provider Available components: diff --git a/prowler/lib/outputs/finding.py b/prowler/lib/outputs/finding.py index 122360b61d..deb702b845 100644 --- a/prowler/lib/outputs/finding.py +++ b/prowler/lib/outputs/finding.py @@ -302,10 +302,10 @@ class Finding(BaseModel): output_data["region"] = check_output.resource_line_range output_data["resource_line_range"] = check_output.resource_line_range output_data["framework"] = check_output.check_metadata.ServiceName - elif provider.type == "github_action": + elif provider.type == "github_actions": output_data["auth_method"] = provider.auth_method - output_data["account_uid"] = "github-actions" - output_data["account_name"] = "github-actions" + output_data["account_uid"] = "github_actions" + output_data["account_name"] = "github_actions" output_data["resource_name"] = check_output.resource_name output_data["resource_uid"] = check_output.resource_name output_data["region"] = check_output.resource_line_range diff --git a/prowler/lib/outputs/summary_table.py b/prowler/lib/outputs/summary_table.py index 2770551254..16af7290c0 100644 --- a/prowler/lib/outputs/summary_table.py +++ b/prowler/lib/outputs/summary_table.py @@ -61,7 +61,7 @@ def display_summary_table( else: entity_type = "Directory" audited_entities = provider.scan_path - elif provider.type == "github_action": + elif provider.type == "github_actions": if provider.repository_url: entity_type = "Repository" audited_entities = provider.repository_url diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index 48422df85f..27c42e9415 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -150,8 +150,8 @@ class Provider(ABC): f"{providers_path}.{arguments.provider}.{arguments.provider}_provider" ) # Special handling for certain providers - if arguments.provider == "github_action": - provider_class_name = "GithubActionProvider" + if arguments.provider == "github_actions": + provider_class_name = "GithubActionsProvider" elif arguments.provider == "pipeline": provider_class_name = "PipelineProvider" else: @@ -254,7 +254,7 @@ class Provider(ABC): config_path=getattr(arguments, "config_file", None), fixer_config=fixer_config, ) - elif "githubaction" in provider_class_name.lower(): + elif "githubactions" in provider_class_name.lower(): provider_class( workflow_path=getattr(arguments, "workflow_path", "."), repository_url=getattr(arguments, "repository_url", None), diff --git a/prowler/providers/github_action/__init__.py b/prowler/providers/github_actions/__init__.py similarity index 100% rename from prowler/providers/github_action/__init__.py rename to prowler/providers/github_actions/__init__.py diff --git a/prowler/providers/github_action/github_action_provider.py b/prowler/providers/github_actions/github_actions_provider.py similarity index 97% rename from prowler/providers/github_action/github_action_provider.py rename to prowler/providers/github_actions/github_actions_provider.py index db324275d0..25a8e4bdb1 100644 --- a/prowler/providers/github_action/github_action_provider.py +++ b/prowler/providers/github_actions/github_actions_provider.py @@ -21,8 +21,8 @@ from prowler.providers.common.models import Audit_Metadata from prowler.providers.common.provider import Provider -class GithubActionProvider(Provider): - _type: str = "github_action" +class GithubActionsProvider(Provider): + _type: str = "github_actions" audit_metadata: Audit_Metadata def __init__( @@ -37,13 +37,13 @@ class GithubActionProvider(Provider): personal_access_token: str = None, oauth_app_token: str = None, ): - logger.info("Instantiating GitHub Action Provider...") + logger.info("Instantiating GitHub Actions Provider...") self.workflow_path = workflow_path self.repository_url = repository_url self.exclude_workflows = exclude_workflows self.region = "global" - self.audited_account = "github-actions" + self.audited_account = "github_actions" self._session = None self._identity = "prowler" self._auth_method = "No auth" @@ -94,7 +94,7 @@ class GithubActionProvider(Provider): self.audit_metadata = Audit_Metadata( provider=self._type, account_id=self.audited_account, - account_name="github_action", + account_name="github_actions", region=self.region, services_scanned=0, # GitHub Actions doesn't use services expected_checks=[], # GitHub Actions doesn't use checks @@ -129,7 +129,7 @@ class GithubActionProvider(Provider): return self._fixer_config def setup_session(self): - """GitHub Action provider doesn't need a session since it uses zizmor directly""" + """GitHub Actions provider doesn't need a session since it uses zizmor directly""" return None def _extract_workflow_file_from_location(self, location: dict) -> str: @@ -210,18 +210,18 @@ class GithubActionProvider(Provider): # Create CheckReport finding_id = ( - f"githubaction_{finding.get('ident', 'unknown').replace('-', '_')}" + f"githubactions_{finding.get('ident', 'unknown').replace('-', '_')}" ) # Prepare metadata dict metadata = { - "Provider": "github_action", + "Provider": "github_actions", "CheckID": finding_id, "CheckTitle": finding.get( "desc", "Unknown GitHub Actions Security Issue" ), "CheckType": ["Security"], - "ServiceName": "githubaction", + "ServiceName": "githubactions", "SubServiceName": "", "ResourceIdTemplate": "", "Severity": prowler_severity, diff --git a/prowler/providers/github_action/lib/__init__.py b/prowler/providers/github_actions/lib/__init__.py similarity index 100% rename from prowler/providers/github_action/lib/__init__.py rename to prowler/providers/github_actions/lib/__init__.py diff --git a/prowler/providers/github_action/lib/arguments/__init__.py b/prowler/providers/github_actions/lib/arguments/__init__.py similarity index 100% rename from prowler/providers/github_action/lib/arguments/__init__.py rename to prowler/providers/github_actions/lib/arguments/__init__.py diff --git a/prowler/providers/github_action/lib/arguments/arguments.py b/prowler/providers/github_actions/lib/arguments/arguments.py similarity index 71% rename from prowler/providers/github_action/lib/arguments/arguments.py rename to prowler/providers/github_actions/lib/arguments/arguments.py index ff88a90a3c..f128281832 100644 --- a/prowler/providers/github_action/lib/arguments/arguments.py +++ b/prowler/providers/github_actions/lib/arguments/arguments.py @@ -2,56 +2,56 @@ from prowler.lib.logger import logger def init_parser(self): - """Initialize the GitHub Action Provider parser to add all the arguments and flags. - + """Initialize the GitHub Actions Provider parser to add all the arguments and flags. + Receives a ProwlerArgumentParser object and fills it. """ - github_action_parser = self.subparsers.add_parser( - "github_action", + github_actions_parser = self.subparsers.add_parser( + "github_actions", parents=[self.common_providers_parser], - help="GitHub Action provider for scanning GitHub Actions workflows security", + help="GitHub Actions provider for scanning GitHub Actions workflows security", ) - github_action_auth_subparser = github_action_parser.add_argument_group( + github_actions_auth_subparser = github_actions_parser.add_argument_group( "Authentication" ) - github_action_auth_subparser.add_argument( + github_actions_auth_subparser.add_argument( "--github-username", nargs="?", default=None, help="GitHub username for authentication when cloning private repositories", ) - github_action_auth_subparser.add_argument( + github_actions_auth_subparser.add_argument( "--personal-access-token", nargs="?", default=None, help="GitHub personal access token for authentication when cloning private repositories", ) - github_action_auth_subparser.add_argument( + github_actions_auth_subparser.add_argument( "--oauth-app-token", nargs="?", default=None, help="GitHub OAuth App token for authentication when cloning private repositories", ) - github_action_scan_subparser = github_action_parser.add_argument_group( + github_actions_scan_subparser = github_actions_parser.add_argument_group( "Scan Configuration" ) - github_action_scan_subparser.add_argument( + github_actions_scan_subparser.add_argument( "--workflow-path", "--scan-path", nargs="?", default=".", help="Path to the directory containing GitHub Actions workflow files (default: current directory)", ) - github_action_scan_subparser.add_argument( + github_actions_scan_subparser.add_argument( "--repository-url", "--scan-repository-url", nargs="?", default=None, help="URL of the GitHub repository to scan (e.g., https://github.com/user/repo)", ) - github_action_scan_subparser.add_argument( + github_actions_scan_subparser.add_argument( "--exclude-workflows", "--exclude-path", nargs="+", @@ -61,8 +61,8 @@ def init_parser(self): def validate_arguments(arguments): - """Validate the arguments for the GitHub Action provider.""" - + """Validate the arguments for the GitHub Actions provider.""" + # Check if both local path and repository URL are provided if hasattr(arguments, "workflow_path") and hasattr(arguments, "repository_url"): if arguments.repository_url and arguments.workflow_path != ".": @@ -70,22 +70,24 @@ def validate_arguments(arguments): False, "Cannot specify both --workflow-path and --repository-url. Please choose one.", ) - + # Check authentication when using repository URL if hasattr(arguments, "repository_url") and arguments.repository_url: has_github_auth = False - - if hasattr(arguments, "github_username") and hasattr(arguments, "personal_access_token"): + + if hasattr(arguments, "github_username") and hasattr( + arguments, "personal_access_token" + ): if arguments.github_username and arguments.personal_access_token: has_github_auth = True - + if hasattr(arguments, "oauth_app_token") and arguments.oauth_app_token: has_github_auth = True - + # Note: Authentication is optional for public repositories if not has_github_auth: logger.info( "No GitHub authentication provided. Only public repositories will be accessible." ) - - return (True, "") \ No newline at end of file + + return (True, "") diff --git a/prowler/providers/github_action/models.py b/prowler/providers/github_actions/models.py similarity index 64% rename from prowler/providers/github_action/models.py rename to prowler/providers/github_actions/models.py index ca4356d93b..e579702ad6 100644 --- a/prowler/providers/github_action/models.py +++ b/prowler/providers/github_actions/models.py @@ -2,10 +2,10 @@ from prowler.config.config import output_file_timestamp from prowler.providers.common.models import ProviderOutputOptions -class GithubActionOutputOptions(ProviderOutputOptions): +class GithubActionsOutputOptions(ProviderOutputOptions): """ - GithubActionOutputOptions overrides ProviderOutputOptions for GitHub Action-specific output logic. - For example, generating a filename that includes github-action identifier. + GithubActionsOutputOptions overrides ProviderOutputOptions for GitHub Actions-specific output logic. + For example, generating a filename that includes github_actions identifier. Attributes inherited from ProviderOutputOptions: - output_filename (str): The base filename used for generated reports. @@ -13,7 +13,7 @@ class GithubActionOutputOptions(ProviderOutputOptions): - ... see ProviderOutputOptions for more details. Methods: - - __init__: Customizes the output filename logic for GitHub Action. + - __init__: Customizes the output filename logic for GitHub Actions. """ def __init__(self, arguments, bulk_checks_metadata): @@ -21,7 +21,9 @@ class GithubActionOutputOptions(ProviderOutputOptions): # If --output-filename is not specified, build a default name. if not getattr(arguments, "output_filename", None): - self.output_filename = f"prowler-output-github-action-{output_file_timestamp}" + self.output_filename = ( + f"prowler-output-github_actions-{output_file_timestamp}" + ) # If --output-filename was explicitly given, respect that else: - self.output_filename = arguments.output_filename \ No newline at end of file + self.output_filename = arguments.output_filename diff --git a/tests/providers/github_action/__init__.py b/tests/providers/github_actions/__init__.py similarity index 100% rename from tests/providers/github_action/__init__.py rename to tests/providers/github_actions/__init__.py diff --git a/tests/providers/github_action/test_github_action_provider.py b/tests/providers/github_actions/test_github_actions_provider.py similarity index 83% rename from tests/providers/github_action/test_github_action_provider.py rename to tests/providers/github_actions/test_github_actions_provider.py index da48eb99c0..b2827120c5 100644 --- a/tests/providers/github_action/test_github_action_provider.py +++ b/tests/providers/github_actions/test_github_actions_provider.py @@ -1,5 +1,4 @@ import json -from unittest import mock from unittest.mock import MagicMock, patch import pytest @@ -14,7 +13,7 @@ class TestGithubActionProvider: """Test provider initialization with default values""" with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider() - + assert provider.type == "github_action" assert provider.workflow_path == "." assert provider.repository_url is None @@ -29,9 +28,9 @@ class TestGithubActionProvider: provider = GithubActionProvider( repository_url="https://github.com/test/repo", github_username="testuser", - personal_access_token="token123" + personal_access_token="token123", ) - + assert provider.repository_url == "https://github.com/test/repo" assert provider.github_username == "testuser" assert provider.personal_access_token == "token123" @@ -42,9 +41,9 @@ class TestGithubActionProvider: with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider( repository_url="https://github.com/test/repo", - oauth_app_token="oauth_token123" + oauth_app_token="oauth_token123", ) - + assert provider.oauth_app_token == "oauth_token123" assert provider.auth_method == "OAuth App Token" assert provider.github_username is None @@ -54,7 +53,7 @@ class TestGithubActionProvider: """Test processing a zizmor finding""" with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider() - + # Sample zizmor finding finding = { "id": "template-injection", @@ -62,22 +61,21 @@ class TestGithubActionProvider: "level": "HIGH", "description": "Potential code injection in workflow", "documentation_url": "https://example.com/docs", - "location": { - "line": 10, - "column": 5 - }, + "location": {"line": 10, "column": 5}, "risk": "High risk of code execution", - "remediation": "Use environment variables instead" + "remediation": "Use environment variables instead", } - + report = provider._process_finding(finding, ".github/workflows/test.yml") - + assert report.resource_name == ".github/workflows/test.yml" assert report.status == "FAIL" assert "line 10, column 5" in report.status_extended - - # Check metadata - assert report.check_metadata.CheckID == "githubaction_template_injection" # Prefixed with githubaction_ + + # Check metadata + assert ( + report.check_metadata.CheckID == "githubaction_template_injection" + ) # Prefixed with githubaction_ assert report.check_metadata.Severity == "high" assert report.check_metadata.Provider == "github_action" @@ -85,24 +83,26 @@ class TestGithubActionProvider: """Test scanning when no issues are found""" with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider() - + # Mock subprocess to return empty findings mock_process = MagicMock() mock_process.stdout = '{"findings": {}}' mock_process.stderr = "" mock_process.returncode = 0 - + with patch("subprocess.run", return_value=mock_process): - with patch("prowler.providers.github_action.github_action_provider.alive_bar"): + with patch( + "prowler.providers.github_action.github_action_provider.alive_bar" + ): reports = provider.run_scan(".", []) - + assert reports == [] def test_run_scan_with_findings(self): """Test scanning with security findings""" with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider() - + # Mock subprocess to return findings mock_output = { "findings": { @@ -113,21 +113,23 @@ class TestGithubActionProvider: "level": "MEDIUM", "description": "Workflow has write-all permissions", "documentation_url": "https://docs.example.com", - "location": {"line": 5} + "location": {"line": 5}, } ] } } - + mock_process = MagicMock() mock_process.stdout = json.dumps(mock_output) mock_process.stderr = "" mock_process.returncode = 0 - + with patch("subprocess.run", return_value=mock_process): - with patch("prowler.providers.github_action.github_action_provider.alive_bar"): + with patch( + "prowler.providers.github_action.github_action_provider.alive_bar" + ): reports = provider.run_scan(".", []) - + assert len(reports) == 1 assert reports[0].resource_name == ".github/workflows/ci.yml" @@ -135,9 +137,14 @@ class TestGithubActionProvider: """Test error handling when zizmor is not installed""" with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider() - - with patch("subprocess.run", side_effect=FileNotFoundError("No such file or directory: 'zizmor'")): - with patch("prowler.providers.github_action.github_action_provider.alive_bar"): + + with patch( + "subprocess.run", + side_effect=FileNotFoundError("No such file or directory: 'zizmor'"), + ): + with patch( + "prowler.providers.github_action.github_action_provider.alive_bar" + ): with pytest.raises(SystemExit): provider.run_scan(".", []) @@ -145,26 +152,28 @@ class TestGithubActionProvider: """Test cloning repository with personal access token""" with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider() - + with patch("tempfile.mkdtemp", return_value="/tmp/test"): with patch("dulwich.porcelain.clone"): - with patch("prowler.providers.github_action.github_action_provider.alive_bar"): + with patch( + "prowler.providers.github_action.github_action_provider.alive_bar" + ): temp_dir = provider._clone_repository( "https://github.com/test/repo", github_username="user", - personal_access_token="token" + personal_access_token="token", ) - + assert temp_dir == "/tmp/test" def test_print_credentials_local_scan(self): """Test printing credentials for local scan""" with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider(workflow_path="/path/to/workflows") - + with patch("prowler.lib.utils.utils.print_boxes") as mock_print: provider.print_credentials() - + # Verify print_boxes was called with expected content mock_print.assert_called_once() args = mock_print.call_args[0] @@ -175,14 +184,14 @@ class TestGithubActionProvider: with patch.object(GithubActionProvider, "setup_session", return_value=None): provider = GithubActionProvider( repository_url="https://github.com/test/repo", - exclude_workflows=["test*.yml"] + exclude_workflows=["test*.yml"], ) - + with patch("prowler.lib.utils.utils.print_boxes") as mock_print: provider.print_credentials() - + # Verify print_boxes was called with expected content mock_print.assert_called_once() args = mock_print.call_args[0] assert "https://github.com/test/repo" in str(args[0]) - assert "test*.yml" in str(args[0]) \ No newline at end of file + assert "test*.yml" in str(args[0])