feat(repository): add new check repository_default_branch_protection_applies_to_admins (#6205)

Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
Hugo Pereira Brito
2025-05-19 10:47:03 +02:00
committed by César Arroba
co-authored by MrCloudSec
parent 6cb1acd93d
commit 98da709202
7 changed files with 186 additions and 0 deletions
+1
View File
@@ -15,6 +15,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Add `repository_default_branch_disallows_force_push` check for GitHub provider. [(#6197)](https://github.com/prowler-cloud/prowler/pull/6197)
- Add `repository_default_branch_deletion_disabled` check for GitHub provider. [(#6200)](https://github.com/prowler-cloud/prowler/pull/6200)
- Add `repository_default_branch_status_checks_required` check for GitHub provider. [(#6204)](https://github.com/prowler-cloud/prowler/pull/6204)
- Add `repository_default_branch_protection_applies_to_admins` check for GitHub provider. [(#6205)](https://github.com/prowler-cloud/prowler/pull/6205)
- Add `organization_members_mfa_required` check for GitHub provider. [(#6304)](https://github.com/prowler-cloud/prowler/pull/6304)
- Add GitHub provider documentation and CIS v1.0.0 compliance. [(#6116)](https://github.com/prowler-cloud/prowler/pull/6116)
@@ -0,0 +1,30 @@
{
"Provider": "github",
"CheckID": "repository_default_branch_protection_applies_to_admins",
"CheckTitle": "Check if repository enforces admin branch protection",
"CheckType": [],
"ServiceName": "repository",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "GithubRepository",
"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": ""
}
@@ -0,0 +1,38 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGithub
from prowler.providers.github.services.repository.repository_client import (
repository_client,
)
class repository_default_branch_protection_applies_to_admins(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[CheckReportGithub]:
"""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[CheckReportGithub]: A list of reports for each repository.
"""
findings = []
for repo in repository_client.repositories.values():
if repo.enforce_admins is not None:
report = CheckReportGithub(
metadata=self.metadata(), resource=repo, repository=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.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
@@ -37,6 +37,7 @@ class Repository(GithubService):
allow_force_pushes = True
branch_deletion = True
status_checks = False
enforce_admins = False
try:
branch = repo.get_branch(default_branch)
if branch.protected:
@@ -58,6 +59,7 @@ class Repository(GithubService):
status_checks = (
protection.required_status_checks is not None
)
enforce_admins = protection.enforce_admins
branch_protection = True
except Exception as error:
# If the branch is not found, it is not protected
@@ -74,6 +76,7 @@ class Repository(GithubService):
allow_force_pushes = None
branch_deletion = None
status_checks = None
enforce_admins = None
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
@@ -91,6 +94,7 @@ class Repository(GithubService):
allow_force_pushes=allow_force_pushes,
default_branch_deletion=branch_deletion,
status_checks=status_checks,
enforce_admins=enforce_admins,
default_branch_protection=branch_protection,
)
@@ -116,4 +120,5 @@ class Repo(BaseModel):
allow_force_pushes: Optional[bool]
default_branch_deletion: Optional[bool]
status_checks: Optional[bool]
enforce_admins: Optional[bool]
approval_count: Optional[int]
@@ -0,0 +1,110 @@
from unittest import mock
from prowler.providers.github.services.repository.repository_service import Repo
from tests.providers.github.github_fixtures import set_mocked_github_provider
class Test_repository_default_branch_protection_applies_to_admins_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_default_branch_protection_applies_to_admins.repository_default_branch_protection_applies_to_admins.repository_client",
new=repository_client,
),
):
from prowler.providers.github.services.repository.repository_default_branch_protection_applies_to_admins.repository_default_branch_protection_applies_to_admins import (
repository_default_branch_protection_applies_to_admins,
)
check = repository_default_branch_protection_applies_to_admins()
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,
enforce_admins=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_default_branch_protection_applies_to_admins.repository_default_branch_protection_applies_to_admins.repository_client",
new=repository_client,
),
):
from prowler.providers.github.services.repository.repository_default_branch_protection_applies_to_admins.repository_default_branch_protection_applies_to_admins import (
repository_default_branch_protection_applies_to_admins,
)
check = repository_default_branch_protection_applies_to_admins()
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,
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_default_branch_protection_applies_to_admins.repository_default_branch_protection_applies_to_admins.repository_client",
new=repository_client,
),
):
from prowler.providers.github.services.repository.repository_default_branch_protection_applies_to_admins.repository_default_branch_protection_applies_to_admins import (
repository_default_branch_protection_applies_to_admins,
)
check = repository_default_branch_protection_applies_to_admins()
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."
)
@@ -23,6 +23,7 @@ def mock_list_repositories(_):
default_branch_deletion=True,
status_checks=True,
approval_count=2,
enforce_admins=True,
),
}
@@ -53,4 +54,5 @@ class Test_Repository_Service:
assert repository_service.repositories[1].allow_force_pushes
assert repository_service.repositories[1].default_branch_deletion
assert repository_service.repositories[1].status_checks
assert repository_service.repositories[1].enforce_admins
assert repository_service.repositories[1].approval_count == 2