From 1d01fa8a08d2d8cb69c3bf7efe2fe0e00c1e4fc8 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 16 Dec 2024 12:09:33 +0000 Subject: [PATCH 1/3] feat: added check logic and metadata --- .../__init__.py | 0 ...rces_admin_branch_protection.metadata.json | 30 ++++++++++++++ ...sitory_enforces_admin_branch_protection.py | 40 +++++++++++++++++++ .../services/repository/repository_service.py | 19 +++------ 4 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/__init__.py create mode 100644 prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.metadata.json create mode 100644 prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.py diff --git a/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/__init__.py b/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.metadata.json b/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.metadata.json new file mode 100644 index 00000000000..e99e2a4f1a7 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "github", + "CheckID": "repository_enforces_admin_branch_protection", + "CheckTitle": "Check if repository enforces admin branch protection", + "CheckType": [], + "ServiceName": "repository", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Other", + "Description": "Ensure that the repository enforces branch protection rules for administrators.", + "Risk": "Excluding administrators from branch protection rules introduces a significant risk of unauthorized or unreviewed changes being pushed to protected branches. This can lead to vulnerabilities, including the potential insertion of malicious code, especially if an administrator account is compromised.", + "RelatedUrl": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#do-not-allow-bypassing-the-above-settings", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enforce branch protection rules for administrators to ensure they adhere to the same security and quality standards as other users. This mitigates the risk of unreviewed or untrusted code being introduced, enhancing the overall integrity of the codebase.", + "Url": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.py b/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.py new file mode 100644 index 00000000000..9ef3a192f29 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.py @@ -0,0 +1,40 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_Github +from prowler.providers.github.services.repository.repository_client import ( + repository_client, +) + + +class repository_enforces_admin_branch_protection(Check): + """Check if a repository enforces administrators to be subject to the same branch protection rules as other users + + This class verifies whether each repository enforces administrators to be subject to the same branch protection rules as other users. + """ + + def execute(self) -> List[Check_Report_Github]: + """Execute the Github Repository Enforces Admin Branch Protection check + + Iterates over all repositories and checks if they enforce administrators to be subject to the same branch protection rules as other users. + + Returns: + List[Check_Report_Github]: A list of reports for each repository. + """ + findings = [] + for repo in repository_client.repositories.values(): + report = Check_Report_Github(self.metadata()) + report.resource_id = repo.id + report.resource_name = repo.name + report.status = "FAIL" + report.status_extended = f"Repository {repo.name} does not enforce administrators to be subject to the same branch protection rules as other users." + + if ( + repo.default_branch_protection + and repo.default_branch_protection.enforce_admins + ): + report.status = "PASS" + report.status_extended = f"Repository {repo.name} does enforce administrators to be subject to the same branch protection rules as other users." + + findings.append(report) + + return findings diff --git a/prowler/providers/github/services/repository/repository_service.py b/prowler/providers/github/services/repository/repository_service.py index 6226cda7694..ef76e13e7fc 100644 --- a/prowler/providers/github/services/repository/repository_service.py +++ b/prowler/providers/github/services/repository/repository_service.py @@ -44,22 +44,14 @@ def _list_repositories(self): if require_pr else 0 ) - required_linear_history = ( - protection.required_linear_history - ) - allow_force_pushes = protection.allow_force_pushes - branch_deletion = protection.allow_deletions - status_checks = ( - protection.required_status_checks.strict - ) - branch_protection = Protection( require_pull_request=require_pr, approval_count=approval_cnt, - linear_history=required_linear_history, - allow_force_push=allow_force_pushes, - allow_branch_deletion=branch_deletion, - enforce_status_checks=status_checks, + linear_history=protection.required_linear_history, + allow_force_push=protection.allow_force_pushes, + allow_branch_deletion=protection.allow_deletions, + enforce_status_checks=protection.required_status_checks.strict, + enforce_admins=protection.enforce_admins, ) except Exception as e: @@ -93,6 +85,7 @@ class Protection(BaseModel): allow_force_push: bool = True allow_branch_deletion: bool = True enforce_status_checks: bool = False + enforce_admins: bool = False class Repo(BaseModel): From 6575680f01245f62de1a7eaa3d3359bf8f4fac7b Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 16 Dec 2024 12:10:04 +0000 Subject: [PATCH 2/3] feat: added testing --- ...y_enforces_admin_branch_protection_test.py | 116 ++++++++++++++++++ .../repository/repository_service_test.py | 4 + 2 files changed, 120 insertions(+) create mode 100644 tests/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection_test.py diff --git a/tests/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection_test.py b/tests/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection_test.py new file mode 100644 index 00000000000..346f8ccd7e8 --- /dev/null +++ b/tests/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection_test.py @@ -0,0 +1,116 @@ +from unittest import mock + +from prowler.providers.github.services.repository.repository_service import ( + Protection, + Repo, +) +from tests.providers.github.github_fixtures import set_mocked_github_provider + + +class Test_repository_enforces_admin_branch_protection_test: + def test_no_repositories(self): + repository_client = mock.MagicMock + repository_client.repositories = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_enforces_admin_branch_protection.repository_enforces_admin_branch_protection.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_enforces_admin_branch_protection.repository_enforces_admin_branch_protection import ( + repository_enforces_admin_branch_protection, + ) + + check = repository_enforces_admin_branch_protection() + result = check.execute() + assert len(result) == 0 + + def test_enforce_status_checks_disabled(self): + repository_client = mock.MagicMock + repo_name = "repo1" + default_branch = "main" + repository_client.repositories = { + 1: Repo( + id=1, + name=repo_name, + full_name="account-name/repo1", + default_branch=default_branch, + private=False, + securitymd=False, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_enforces_admin_branch_protection.repository_enforces_admin_branch_protection.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_enforces_admin_branch_protection.repository_enforces_admin_branch_protection import ( + repository_enforces_admin_branch_protection, + ) + + check = repository_enforces_admin_branch_protection() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 1 + assert result[0].resource_name == "repo1" + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Repository {repo_name} does not enforce administrators to be subject to the same branch protection rules as other users." + ) + + def test_enforce_status_checks_enabled(self): + repository_client = mock.MagicMock + repo_name = "repo1" + default_branch = "main" + repository_client.repositories = { + 1: Repo( + id=1, + name=repo_name, + full_name="account-name/repo1", + private=False, + default_branch=default_branch, + default_branch_protection=Protection( + require_pull_request=True, + approval_count=2, + enforce_admins=True, + ), + securitymd=True, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_enforces_admin_branch_protection.repository_enforces_admin_branch_protection.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_enforces_admin_branch_protection.repository_enforces_admin_branch_protection import ( + repository_enforces_admin_branch_protection, + ) + + check = repository_enforces_admin_branch_protection() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 1 + assert result[0].resource_name == "repo1" + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Repository {repo_name} does enforce administrators to be subject to the same branch protection rules as other users." + ) diff --git a/tests/providers/github/services/repository/repository_service_test.py b/tests/providers/github/services/repository/repository_service_test.py index bf5c22e12e5..48b55edf480 100644 --- a/tests/providers/github/services/repository/repository_service_test.py +++ b/tests/providers/github/services/repository/repository_service_test.py @@ -23,6 +23,7 @@ def mock_list_repositories(_): allow_force_push=False, allow_branch_deletion=False, enforce_status_checks=True, + enforce_admins=True, ), securitymd=True, ), @@ -69,5 +70,8 @@ def test_list_repositories(self): assert repository_service.repositories[ 1 ].default_branch_protection.enforce_status_checks + assert repository_service.repositories[ + 1 + ].default_branch_protection.enforce_admins # Repo assert repository_service.repositories[1].securitymd From 3847b75984a08c0906eec7cc6ae0df8f16d2979b Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Thu, 16 Jan 2025 12:51:14 +0100 Subject: [PATCH 3/3] feat: add resource metadata --- .../repository_enforces_admin_branch_protection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.py b/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.py index 9ef3a192f29..ba955f09a29 100644 --- a/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.py +++ b/prowler/providers/github/services/repository/repository_enforces_admin_branch_protection/repository_enforces_admin_branch_protection.py @@ -22,9 +22,9 @@ def execute(self) -> List[Check_Report_Github]: """ findings = [] for repo in repository_client.repositories.values(): - report = Check_Report_Github(self.metadata()) - report.resource_id = repo.id - report.resource_name = repo.name + report = Check_Report_Github( + metadata=self.metadata(), resource_metadata=repo + ) report.status = "FAIL" report.status_extended = f"Repository {repo.name} does not enforce administrators to be subject to the same branch protection rules as other users."