From 7d10727c3455ebf157f87608ce4c1baaa1cb7ae5 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Wed, 11 Dec 2024 14:22:17 +0100 Subject: [PATCH] feat: added check logic and metadata --- ...code_changes_multi_approval_requirement.py | 2 +- .../__init__.py | 0 ...es_default_branch_protection.metadata.json | 30 ++++++ ...tory_enforces_default_branch_protection.py | 37 ++++++++ .../services/repository/repository_service.py | 92 +++++++++++-------- 5 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 prowler/providers/github/services/repository/repository_enforces_default_branch_protection/__init__.py create mode 100644 prowler/providers/github/services/repository/repository_enforces_default_branch_protection/repository_enforces_default_branch_protection.metadata.json create mode 100644 prowler/providers/github/services/repository/repository_enforces_default_branch_protection/repository_enforces_default_branch_protection.py diff --git a/prowler/providers/github/services/repository/repository_code_changes_multi_approval_requirement/repository_code_changes_multi_approval_requirement.py b/prowler/providers/github/services/repository/repository_code_changes_multi_approval_requirement/repository_code_changes_multi_approval_requirement.py index 1c76dae69c..9ef2b02ee6 100644 --- a/prowler/providers/github/services/repository/repository_code_changes_multi_approval_requirement/repository_code_changes_multi_approval_requirement.py +++ b/prowler/providers/github/services/repository/repository_code_changes_multi_approval_requirement/repository_code_changes_multi_approval_requirement.py @@ -28,7 +28,7 @@ class repository_code_changes_multi_approval_requirement(Check): report.status = "FAIL" report.status_extended = f"Repository {repo.name} does not enforce at least 2 approvals for code changes." - if repo.approval_count >= 2: + if repo.default_branch_protection.approval_count >= 2: report.status = "PASS" report.status_extended = f"Repository {repo.name} does enforce at least 2 approvals for code changes." diff --git a/prowler/providers/github/services/repository/repository_enforces_default_branch_protection/__init__.py b/prowler/providers/github/services/repository/repository_enforces_default_branch_protection/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/github/services/repository/repository_enforces_default_branch_protection/repository_enforces_default_branch_protection.metadata.json b/prowler/providers/github/services/repository/repository_enforces_default_branch_protection/repository_enforces_default_branch_protection.metadata.json new file mode 100644 index 0000000000..2ac4f37f3b --- /dev/null +++ b/prowler/providers/github/services/repository/repository_enforces_default_branch_protection/repository_enforces_default_branch_protection.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "github", + "CheckID": "repository_enforces_default_branch_protection", + "CheckTitle": "Check if branch protection is enforced on the default branch ", + "CheckType": [], + "ServiceName": "repository", + "SubServiceName": "", + "ResourceIdTemplate": "github:user-id:repository/repository-name", + "Severity": "critical", + "ResourceType": "Other", + "Description": "Ensure branch protection is enforced on the default branch", + "Risk": "The absence of branch protection on the default branch increases the risk of unauthorized, unreviewed, or untested changes being merged. This can compromise the stability, security, and reliability of the codebase, which is especially critical for production deployments.", + "RelatedUrl": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Apply branch protection rules to the default branch to ensure it is safeguarded against unauthorized or improper modifications. This helps maintain code quality, enforces proper review and testing procedures, and reduces the risk of accidental or malicious changes.", + "Url": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule#creating-a-branch-protection-rule" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/github/services/repository/repository_enforces_default_branch_protection/repository_enforces_default_branch_protection.py b/prowler/providers/github/services/repository/repository_enforces_default_branch_protection/repository_enforces_default_branch_protection.py new file mode 100644 index 0000000000..6d6ae71c83 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_enforces_default_branch_protection/repository_enforces_default_branch_protection.py @@ -0,0 +1,37 @@ +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_default_branch_protection(Check): + """Check if a repository enforces default branch protection + + This class verifies whether each repository enforces default branch protection. + """ + + def execute(self) -> List[Check_Report_Github]: + """Execute the Github Repository Enforces Default Branch Protection check + + Iterates over all repositories and checks if they enforce default branch protection. + + 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 branch protection on default branch ({repo.default_branch})." + + if repo.default_branch_protection: + report.status = "PASS" + report.status_extended = f"Repository {repo.name} does enforce branch protection on default branch ({repo.default_branch})." + + 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 96ccbbe7ed..8bc1d75257 100644 --- a/prowler/providers/github/services/repository/repository_service.py +++ b/prowler/providers/github/services/repository/repository_service.py @@ -17,45 +17,51 @@ class Repository(GithubService): try: for client in self.clients: for repo in client.get_user().get_repos(): - default_branch = repo.default_branch - securitymd_exists = False - try: - securitymd_exists = repo.get_contents("SECURITY.md") is not None - except Exception as e: - logger.warning( - f"Could not find SECURITY.md for repo {repo.name}: {e}" - ) + if not repo.private: # Only for testing purposes + default_branch = repo.default_branch + securitymd_exists = False + try: + securitymd_exists = ( + repo.get_contents("SECURITY.md") is not None + ) + except Exception as e: + logger.warning( + f"Could not find SECURITY.md for repo {repo.name}: {e}" + ) - require_pr = False - approval_cnt = 0 - try: - branch = repo.get_branch(default_branch) - if branch.protected: - protection = branch.get_protection() - if protection: - require_pr = ( - protection.required_pull_request_reviews is not None - ) - approval_cnt = ( - protection.required_pull_request_reviews.required_approving_review_count - if require_pr - else 0 - ) - except Exception as e: - logger.warning( - f"Could not get branch protection for repo {repo.name}: {e}" - ) + branch_protection = None + try: + branch = repo.get_branch(default_branch) + if branch.protected: + protection = branch.get_protection() + if protection: + require_pr = ( + protection.required_pull_request_reviews + is not None + ) + approval_cnt = ( + protection.required_pull_request_reviews.required_approving_review_count + if require_pr + else 0 + ) + branch_protection = Protection( + require_pull_request=require_pr, + approval_count=approval_cnt, + ) + except Exception as e: + logger.warning( + f"Could not get branch protection for repo {repo.name}: {e}" + ) - repos[repo.id] = Repo( - id=repo.id, - name=repo.name, - full_name=repo.full_name, - default_branch=repo.default_branch, - private=repo.private, - securitymd=securitymd_exists, - require_pull_request=require_pr, - approval_count=approval_cnt, - ) + repos[repo.id] = Repo( + id=repo.id, + name=repo.name, + full_name=repo.full_name, + default_branch=repo.default_branch, + private=repo.private, + securitymd=securitymd_exists, + default_branch_protection=branch_protection, + ) except Exception as error: logger.error( @@ -64,14 +70,20 @@ class Repository(GithubService): return repos +class Protection(BaseModel): + """Model for Github Branch Protection""" + + require_pull_request: Optional[bool] = False + approval_count: Optional[int] = 0 + + class Repo(BaseModel): """Model for Github Repository""" id: int name: str full_name: str - default_branch: str private: bool + default_branch: str + default_branch_protection: Optional[Protection] securitymd: Optional[bool] = False - require_pull_request: Optional[bool] = False - approval_count: Optional[int] = 0