diff --git a/docs/docs.json b/docs/docs.json index 54402e5d8a..2327a0b381 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -221,6 +221,13 @@ "user-guide/providers/iac/authentication" ] }, + { + "group": "GitHub Actions", + "pages": [ + "user-guide/providers/github-actions/getting-started-github-actions", + "user-guide/providers/github-actions/authentication" + ] + }, { "group": "MongoDB Atlas", "pages": [ diff --git a/docs/user-guide/providers/github-actions/authentication.mdx b/docs/user-guide/providers/github-actions/authentication.mdx new file mode 100644 index 0000000000..994c6e5de0 --- /dev/null +++ b/docs/user-guide/providers/github-actions/authentication.mdx @@ -0,0 +1,12 @@ +--- +title: "GitHub Actions Authentication in Prowler" +--- + +Prowler's GitHub Actions provider enables scanning of local or remote GitHub Actions workflows for security and compliance issues using [zizmor](https://github.com/woodruffw/zizmor). + +## Authentication + +- For local scans, no authentication is required. +- For remote repository scans, authentication can be provided via: + - [**GitHub Username and Personal Access Token (PAT)**](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) + - [**GitHub OAuth App Token**](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) diff --git a/docs/user-guide/providers/github-actions.md b/docs/user-guide/providers/github-actions/getting-started-github-actions.mdx similarity index 61% rename from docs/user-guide/providers/github-actions.md rename to docs/user-guide/providers/github-actions/getting-started-github-actions.mdx index 21cddced6b..82dc2040b2 100644 --- a/docs/user-guide/providers/github-actions.md +++ b/docs/user-guide/providers/github-actions/getting-started-github-actions.mdx @@ -1,12 +1,13 @@ -# GitHub Actions Security Scanning with Prowler +--- +title: "Getting Started with the GitHub Actions Provider" +--- +import { VersionBadge } from "/snippets/version-badge.mdx" -Prowler integrates with [zizmor](https://github.com/woodruffw/zizmor) to provide comprehensive security scanning for GitHub Actions workflows. This feature helps identify security vulnerabilities and misconfigurations in your CI/CD pipelines. +Prowler's GitHub Actions provider enables comprehensive security scanning for GitHub Actions workflows using [zizmor](https://github.com/woodruffw/zizmor). This provider helps identify security vulnerabilities and misconfigurations in CI/CD pipelines. ## Prerequisites -Before using the GitHub Actions provider, you need to install zizmor: - -### Install Zizmor +Before using the GitHub Actions provider, zizmor must be installed: ```bash # Using Cargo (Rust package manager) @@ -26,77 +27,84 @@ The GitHub Actions provider scans for: - **Impostor commits and confusable git references** - Spots suspicious references - **Other GitHub Actions security best practices** -## Basic Usage +## How It Works -### Scan Local Workflows +- The GitHub Actions provider scans `.github/workflows/` directories for workflow files. +- Local scans require no authentication. +- Remote repository scans support authentication via GitHub credentials. + - Check the [GitHub Actions Authentication](/user-guide/providers/github-actions/authentication) page for more details. +- Results are output in the same formats as other Prowler providers (CSV, JSON, HTML, etc.). -To scan GitHub Actions workflows in your current directory: +## Prowler CLI + + + +### Basic Usage + +#### Scan Local Workflows + +Scan GitHub Actions workflows in the current directory: ```bash prowler github_actions ``` -To scan workflows in a specific directory: +Scan workflows in a specific directory: ```bash prowler github_actions --workflow-path /path/to/repository ``` -### Scan Remote Repository +#### Scan Remote Repository -To scan a GitHub repository directly: +Scan a public GitHub repository: ```bash -# Public repository prowler github_actions --repository-url https://github.com/user/repo +``` -# Private repository with authentication +Scan a private repository with authentication: + +```bash prowler github_actions --repository-url https://github.com/user/private-repo \ --github-username YOUR_USERNAME \ --personal-access-token YOUR_TOKEN ``` -## Authentication Options +### Authentication for Private Repositories -For scanning private repositories, Prowler supports multiple authentication methods: +Authentication for private repositories can be provided using one of the following methods: -### Personal Access Token +- **Personal Access Token:** + ```bash + prowler github_actions --repository-url https://github.com/org/private-repo \ + --github-username YOUR_USERNAME \ + --personal-access-token YOUR_PAT + ``` +- **OAuth App Token:** + ```bash + prowler github_actions --repository-url https://github.com/org/private-repo \ + --oauth-app-token YOUR_OAUTH_TOKEN + ``` +- If not provided via CLI, the following environment variables will be used: + ```bash + export GITHUB_USERNAME=your-username + export GITHUB_PERSONAL_ACCESS_TOKEN=your-token + # or + export GITHUB_OAUTH_APP_TOKEN=your-oauth-token -```bash -prowler github_actions --repository-url https://github.com/org/private-repo \ - --github-username YOUR_USERNAME \ - --personal-access-token YOUR_PAT -``` + prowler github_actions --repository-url https://github.com/org/private-repo + ``` -### OAuth App Token +### Excluding Workflows -```bash -prowler github_actions --repository-url https://github.com/org/private-repo \ - --oauth-app-token YOUR_OAUTH_TOKEN -``` - -### Environment Variables - -You can also set authentication via environment variables: - -```bash -export GITHUB_USERNAME=your-username -export GITHUB_PERSONAL_ACCESS_TOKEN=your-token -# or -export GITHUB_OAUTH_APP_TOKEN=your-oauth-token - -prowler github_actions --repository-url https://github.com/org/private-repo -``` - -## Excluding Workflows - -To exclude specific workflows or patterns from scanning: +Exclude specific workflows or patterns from scanning: ```bash prowler github_actions --exclude-workflows "test-*.yml" "experimental/*" ``` -## Output Formats +### Output Formats The GitHub Actions provider supports all standard Prowler output formats: @@ -111,8 +119,6 @@ prowler github_actions --output-directory ./security-reports prowler github_actions --output-filename github-actions-security-scan ``` -## Examples - ### Complete Security Scan with Full Reporting ```bash @@ -138,7 +144,7 @@ done ## Understanding Results -The scanner will identify issues with different severity levels: +The scanner identifies issues with different severity levels: - **CRITICAL/HIGH**: Immediate security risks that should be addressed urgently - **MEDIUM**: Potential security issues that should be reviewed @@ -152,7 +158,7 @@ Each finding includes: ## Integration with CI/CD -You can integrate Prowler's GitHub Actions scanning into your CI/CD pipeline: +Integrate Prowler's GitHub Actions scanning into CI/CD pipelines: ```yaml name: Security Scan @@ -196,11 +202,11 @@ jobs: ### Zizmor Not Found -If you get an error about zizmor not being found: +If an error about zizmor not being found occurs: 1. Ensure zizmor is installed: `which zizmor` 2. Install it using: `cargo install zizmor` -3. Make sure it's in your PATH +3. Verify it is in your PATH ### Authentication Issues diff --git a/tests/providers/github_actions/test_github_actions_provider.py b/tests/providers/github_actions/test_github_actions_provider.py index b2827120c5..1449f589a5 100644 --- a/tests/providers/github_actions/test_github_actions_provider.py +++ b/tests/providers/github_actions/test_github_actions_provider.py @@ -3,29 +3,31 @@ from unittest.mock import MagicMock, patch import pytest -from prowler.providers.github_action.github_action_provider import GithubActionProvider +from prowler.providers.github_actions.github_actions_provider import ( + GithubActionsProvider, +) -class TestGithubActionProvider: - """Test cases for the GitHub Action Provider""" +class TestGithubActionsProvider: + """Test cases for the GitHub Actions Provider""" - def test_github_action_provider_init_default(self): + def test_github_actions_provider_init_default(self): """Test provider initialization with default values""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider() + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider() - assert provider.type == "github_action" + assert provider.type == "github_actions" assert provider.workflow_path == "." assert provider.repository_url is None assert provider.exclude_workflows == [] assert provider.auth_method == "No auth" assert provider.region == "global" - assert provider.audited_account == "github-actions" + assert provider.audited_account == "github_actions" - def test_github_action_provider_init_with_repository_url(self): + def test_github_actions_provider_init_with_repository_url(self): """Test provider initialization with repository URL""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider( + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider( repository_url="https://github.com/test/repo", github_username="testuser", personal_access_token="token123", @@ -36,10 +38,10 @@ class TestGithubActionProvider: assert provider.personal_access_token == "token123" assert provider.auth_method == "Personal Access Token" - def test_github_action_provider_init_with_oauth_token(self): + def test_github_actions_provider_init_with_oauth_token(self): """Test provider initialization with OAuth token""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider( + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider( repository_url="https://github.com/test/repo", oauth_app_token="oauth_token123", ) @@ -49,50 +51,65 @@ class TestGithubActionProvider: assert provider.github_username is None assert provider.personal_access_token is None - def test_process_finding(self): + def test_process_zizmor_finding(self): """Test processing a zizmor finding""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider() + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider() - # Sample zizmor finding + # Sample zizmor v1.x+ finding finding = { - "id": "template-injection", - "title": "Template Injection Vulnerability", - "level": "HIGH", - "description": "Potential code injection in workflow", - "documentation_url": "https://example.com/docs", - "location": {"line": 10, "column": 5}, - "risk": "High risk of code execution", - "remediation": "Use environment variables instead", + "ident": "template-injection", + "desc": "Template Injection Vulnerability", + "determinations": {"severity": "high", "confidence": "High"}, + "url": "https://example.com/docs", } - report = provider._process_finding(finding, ".github/workflows/test.yml") + location = { + "symbolic": { + "annotation": "High risk of code execution", + "key": { + "Local": { + "given_path": ".github/workflows/test.yml" + } + } + }, + "concrete": { + "location": { + "start_point": {"row": 10, "column": 5}, + "end_point": {"row": 10, "column": 15} + } + } + } + + report = provider._process_zizmor_finding( + finding, ".github/workflows/test.yml", location + ) assert report.resource_name == ".github/workflows/test.yml" assert report.status == "FAIL" - assert "line 10, column 5" in report.status_extended + assert "line 10" in report.resource_line_range # Check metadata assert ( - report.check_metadata.CheckID == "githubaction_template_injection" - ) # Prefixed with githubaction_ + report.check_metadata.CheckID == "githubactions_template_injection" + ) # Prefixed with githubactions_ assert report.check_metadata.Severity == "high" - assert report.check_metadata.Provider == "github_action" + assert report.check_metadata.Provider == "github_actions" def test_run_scan_no_issues(self): """Test scanning when no issues are found""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider() + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider() - # Mock subprocess to return empty findings + # Mock subprocess to return empty findings (zizmor v1.x+ format) mock_process = MagicMock() - mock_process.stdout = '{"findings": {}}' + mock_process.stdout = "[]" 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" + "prowler.providers.github_actions.github_actions_provider.alive_bar" ): reports = provider.run_scan(".", []) @@ -100,24 +117,36 @@ class TestGithubActionProvider: def test_run_scan_with_findings(self): """Test scanning with security findings""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider() + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider() - # Mock subprocess to return findings - mock_output = { - "findings": { - ".github/workflows/ci.yml": [ + # Mock subprocess to return findings (zizmor v1.x+ format) + mock_output = [ + { + "ident": "excessive-permissions", + "desc": "Workflow has write-all permissions", + "determinations": {"severity": "medium", "confidence": "High"}, + "url": "https://docs.example.com", + "locations": [ { - "id": "excessive-permissions", - "title": "Excessive Permissions", - "level": "MEDIUM", - "description": "Workflow has write-all permissions", - "documentation_url": "https://docs.example.com", - "location": {"line": 5}, + "symbolic": { + "annotation": "Excessive permissions detected", + "key": { + "Local": { + "given_path": ".github/workflows/ci.yml" + } + } + }, + "concrete": { + "location": { + "start_point": {"row": 5, "column": 1}, + "end_point": {"row": 5, "column": 20} + } + } } ] } - } + ] mock_process = MagicMock() mock_process.stdout = json.dumps(mock_output) @@ -126,7 +155,7 @@ class TestGithubActionProvider: with patch("subprocess.run", return_value=mock_process): with patch( - "prowler.providers.github_action.github_action_provider.alive_bar" + "prowler.providers.github_actions.github_actions_provider.alive_bar" ): reports = provider.run_scan(".", []) @@ -135,28 +164,28 @@ class TestGithubActionProvider: def test_run_scan_zizmor_not_found(self): """Test error handling when zizmor is not installed""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider() + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider() with patch( "subprocess.run", side_effect=FileNotFoundError("No such file or directory: 'zizmor'"), ): with patch( - "prowler.providers.github_action.github_action_provider.alive_bar" + "prowler.providers.github_actions.github_actions_provider.alive_bar" ): with pytest.raises(SystemExit): provider.run_scan(".", []) def test_clone_repository_with_pat(self): """Test cloning repository with personal access token""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider() + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider() with patch("tempfile.mkdtemp", return_value="/tmp/test"): with patch("dulwich.porcelain.clone"): with patch( - "prowler.providers.github_action.github_action_provider.alive_bar" + "prowler.providers.github_actions.github_actions_provider.alive_bar" ): temp_dir = provider._clone_repository( "https://github.com/test/repo", @@ -168,10 +197,12 @@ class TestGithubActionProvider: 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.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider(workflow_path="/path/to/workflows") - with patch("prowler.lib.utils.utils.print_boxes") as mock_print: + with patch( + "prowler.providers.github_actions.github_actions_provider.print_boxes" + ) as mock_print: provider.print_credentials() # Verify print_boxes was called with expected content @@ -181,13 +212,15 @@ class TestGithubActionProvider: def test_print_credentials_remote_scan(self): """Test printing credentials for remote repository scan""" - with patch.object(GithubActionProvider, "setup_session", return_value=None): - provider = GithubActionProvider( + with patch.object(GithubActionsProvider, "setup_session", return_value=None): + provider = GithubActionsProvider( repository_url="https://github.com/test/repo", exclude_workflows=["test*.yml"], ) - with patch("prowler.lib.utils.utils.print_boxes") as mock_print: + with patch( + "prowler.providers.github_actions.github_actions_provider.print_boxes" + ) as mock_print: provider.print_credentials() # Verify print_boxes was called with expected content