mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
feat(repository): add new check repository_default_branch_requires_signed_commits (#7777)
Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
This commit is contained in:
1 parent
7ebc5d3c31
commit
99164ce93e
6 files changed
+186
No files matched your search
@@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- Add a level for Prowler ThreatScore in the accordion in Dashboard. [(#7739)](https://github.com/prowler-cloud/prowler/pull/7739)
|
||||
- Add CIS 4.0 compliance framework for GCP. [(7785)](https://github.com/prowler-cloud/prowler/pull/7785)
|
||||
- Add `repository_has_codeowners_file` check for GitHub provider. [(#7752)](https://github.com/prowler-cloud/prowler/pull/7752)
|
||||
- Add `repository_default_branch_requires_signed_commits` check for GitHub provider. [(#7777)](https://github.com/prowler-cloud/prowler/pull/7777)
|
||||
- Add `repository_dependency_scanning_enabled` check for GitHub provider. [(#7771)](https://github.com/prowler-cloud/prowler/pull/7771)
|
||||
- Add `repository_secret_scanning_enabled` check for GitHub provider. [(#7759)](https://github.com/prowler-cloud/prowler/pull/7759)
|
||||
- Add `repository_default_branch_requires_codeowners_review` check for GitHub provider. [(#7753)](https://github.com/prowler-cloud/prowler/pull/7753)
|
||||
|
||||
Whitespace-only changes.
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "github",
|
||||
"CheckID": "repository_default_branch_requires_signed_commits",
|
||||
"CheckTitle": "Check if repository requires signed commits",
|
||||
"CheckType": [],
|
||||
"ServiceName": "repository",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "github:user-id:repository/repository-name",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "GitHubRepository",
|
||||
"Description": "Ensure that every commit in a pull request is signed and verified before merging to the default branch.",
|
||||
"Risk": "If repositories do not require signed commits, there is no way to verify the authenticity and integrity of code changes. This could allow malicious actors to impersonate legitimate contributors and introduce unauthorized or harmful changes to the codebase.",
|
||||
"RelatedUrl": "https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable the 'Require signed commits' option in branch protection rules to ensure that all commits are cryptographically signed and verified before they can be merged.",
|
||||
"Url": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-signed-commits"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+38
@@ -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_requires_signed_commits(Check):
|
||||
"""Check if a repository requires signed commits
|
||||
|
||||
This class verifies whether each repository requires signed commits for the default branch.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportGithub]:
|
||||
"""Execute the Github Repository Requires Signed Commits check
|
||||
|
||||
Iterates over all repositories and checks if they require signed commits.
|
||||
|
||||
Returns:
|
||||
List[CheckReportGithub]: A list of reports for each repository
|
||||
"""
|
||||
findings = []
|
||||
for repo in repository_client.repositories.values():
|
||||
if repo.require_signed_commits 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 require signed commits on default branch ({repo.default_branch})."
|
||||
|
||||
if repo.require_signed_commits:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Repository {repo.name} does require signed commits on default branch ({repo.default_branch})."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -61,6 +61,7 @@ class Repository(GithubService):
|
||||
allow_force_pushes = True
|
||||
branch_deletion = True
|
||||
require_code_owner_reviews = False
|
||||
require_signed_commits = False
|
||||
status_checks = False
|
||||
enforce_admins = False
|
||||
conversation_resolution = False
|
||||
@@ -95,6 +96,9 @@ class Repository(GithubService):
|
||||
if require_pr
|
||||
else False
|
||||
)
|
||||
require_signed_commits = (
|
||||
branch.get_required_signatures()
|
||||
)
|
||||
except Exception as error:
|
||||
# If the branch is not found, it is not protected
|
||||
if "404" in str(error):
|
||||
@@ -110,6 +114,7 @@ class Repository(GithubService):
|
||||
allow_force_pushes = None
|
||||
branch_deletion = None
|
||||
require_code_owner_reviews = None
|
||||
require_signed_commits = None
|
||||
status_checks = None
|
||||
enforce_admins = None
|
||||
conversation_resolution = None
|
||||
@@ -171,6 +176,7 @@ class Repository(GithubService):
|
||||
codeowners_exists=codeowners_exists,
|
||||
require_code_owner_reviews=require_code_owner_reviews,
|
||||
secret_scanning_enabled=secret_scanning_enabled,
|
||||
require_signed_commits=require_signed_commits,
|
||||
dependabot_alerts_enabled=dependabot_alerts_enabled,
|
||||
delete_branch_on_merge=delete_branch_on_merge,
|
||||
)
|
||||
@@ -202,6 +208,7 @@ class Repo(BaseModel):
|
||||
codeowners_exists: Optional[bool]
|
||||
require_code_owner_reviews: Optional[bool]
|
||||
secret_scanning_enabled: Optional[bool]
|
||||
require_signed_commits: Optional[bool]
|
||||
dependabot_alerts_enabled: Optional[bool]
|
||||
delete_branch_on_merge: Optional[bool]
|
||||
conversation_resolution: Optional[bool]
|
||||
+110
@@ -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_requires_signed_commits:
|
||||
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_requires_signed_commits.repository_default_branch_requires_signed_commits.repository_client",
|
||||
new=repository_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.repository.repository_default_branch_requires_signed_commits.repository_default_branch_requires_signed_commits import (
|
||||
repository_default_branch_requires_signed_commits,
|
||||
)
|
||||
|
||||
check = repository_default_branch_requires_signed_commits()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_signed_commits_not_required(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,
|
||||
require_signed_commits=False,
|
||||
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_requires_signed_commits.repository_default_branch_requires_signed_commits.repository_client",
|
||||
new=repository_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.repository.repository_default_branch_requires_signed_commits.repository_default_branch_requires_signed_commits import (
|
||||
repository_default_branch_requires_signed_commits,
|
||||
)
|
||||
|
||||
check = repository_default_branch_requires_signed_commits()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].resource_id == 1
|
||||
assert result[0].resource_name == repo_name
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Repository {repo_name} does not require signed commits on default branch ({default_branch})."
|
||||
)
|
||||
|
||||
def test_signed_commits_required(self):
|
||||
repository_client = mock.MagicMock
|
||||
repo_name = "repo2"
|
||||
default_branch = "main"
|
||||
repository_client.repositories = {
|
||||
2: Repo(
|
||||
id=2,
|
||||
name=repo_name,
|
||||
full_name="account-name/repo2",
|
||||
private=False,
|
||||
default_branch=default_branch,
|
||||
require_signed_commits=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_requires_signed_commits.repository_default_branch_requires_signed_commits.repository_client",
|
||||
new=repository_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.repository.repository_default_branch_requires_signed_commits.repository_default_branch_requires_signed_commits import (
|
||||
repository_default_branch_requires_signed_commits,
|
||||
)
|
||||
|
||||
check = repository_default_branch_requires_signed_commits()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].resource_id == 2
|
||||
assert result[0].resource_name == repo_name
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Repository {repo_name} does require signed commits on default branch ({default_branch})."
|
||||
)
|
||||
Reference in new issue
Block a user