refactor(github_actions): rename to github_actions

This commit is contained in:
Andoni A.
2025-11-06 15:35:41 +01:00
parent aadcebfa0e
commit 3fdfa7a12f
15 changed files with 113 additions and 99 deletions
+7 -6
View File
@@ -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):
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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()
+3 -3
View File
@@ -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:
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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),
@@ -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,
@@ -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, "")
return (True, "")
@@ -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
self.output_filename = arguments.output_filename
@@ -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])
assert "test*.yml" in str(args[0])