Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(repository): add new check repository_enforces_admin_branch_protection #6205

Open
wants to merge 5 commits into
base: PRWLR-5532-ensure-all-checks-have-passed-before-merging-new-code
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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": ""
}
Original file line number Diff line number Diff line change
@@ -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(
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."

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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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."
)
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
Expand Down Expand Up @@ -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
Loading