From d4e5b7c7c7c741ac4aba9f831305b7804e7635f6 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 19 Nov 2024 17:14:34 +0100 Subject: [PATCH] feat: add new check to service --- prowler/lib/check/models.py | 2 +- prowler/lib/outputs/finding.py | 8 ++++ prowler/lib/outputs/html/html.py | 45 +++++++++++++++++++ prowler/providers/github/github_provider.py | 17 +++++-- .../providers/github/lib/mutelist/mutelist.py | 4 +- .../providers/github/lib/service/service.py | 2 - .../services/repository/repository_client.py | 2 +- ...y_public_has_securitymd_file.metadata.json | 32 +++++++++++++ .../repository_public_has_securitymd_file.py | 42 +++++++++++++++++ .../services/repository/repository_service.py | 10 +++-- 10 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 prowler/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file.metadata.json create mode 100644 prowler/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file.py diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index 71c6e35487..8b4acf5733 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -505,7 +505,7 @@ def load_check_metadata(metadata_file: str) -> CheckMetadata: @dataclass -class Check_Report_GitHub(Check_Report): +class Check_Report_Github(Check_Report): """Contains the GitHub Check's finding information.""" account_name: str diff --git a/prowler/lib/outputs/finding.py b/prowler/lib/outputs/finding.py index 9867111765..423460c16f 100644 --- a/prowler/lib/outputs/finding.py +++ b/prowler/lib/outputs/finding.py @@ -232,6 +232,14 @@ class Finding(BaseModel): ) output_data["region"] = f"namespace: {check_output.namespace}" + elif provider.type == "github": + output_data["auth_method"] = provider.auth_method + output_data["resource_name"] = check_output.resource_name + output_data["resource_uid"] = check_output.resource_id + output_data["account_name"] = provider.identity.account_name + output_data["account_uid"] = provider.identity.account_id + output_data["region"] = "global" + # check_output Unique ID # TODO: move this to a function # TODO: in Azure, GCP and K8s there are fidings without resource_name diff --git a/prowler/lib/outputs/html/html.py b/prowler/lib/outputs/html/html.py index 5d5c471593..5263f13f1f 100644 --- a/prowler/lib/outputs/html/html.py +++ b/prowler/lib/outputs/html/html.py @@ -538,6 +538,51 @@ class HTML(Output): ) return "" + @staticmethod + def get_github_assessment_summary(provider: Provider) -> str: + """ + get_github_assessment_summary gets the HTML assessment summary for the provider + + Args: + provider (Provider): the provider object + + Returns: + str: the HTML assessment summary + """ + try: + return f""" +
+
+
+ GitHub Assessment Summary +
+
    +
  • + GitHub account: {provider.identity.account_name} +
  • +
+
+
+
+
+
+ GitHub Credentials +
+
    +
  • + GitHub authentication method: {provider.auth_method} +
  • +
+
+
""" + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}" + ) + return "" + @staticmethod def get_assessment_summary(provider: Provider) -> str: """ diff --git a/prowler/providers/github/github_provider.py b/prowler/providers/github/github_provider.py index 3b2b0ad143..953d78f43c 100644 --- a/prowler/providers/github/github_provider.py +++ b/prowler/providers/github/github_provider.py @@ -17,6 +17,7 @@ from prowler.providers.github.models import GithubIdentityInfo, GithubSession class GithubProvider(Provider): _type: str = "github" + _auth_method: str _session: GithubSession _identity: GithubIdentityInfo _audit_config: dict @@ -81,15 +82,20 @@ class GithubProvider(Provider): Provider.set_global_provider(self) @property - def identity(self): - """Returns the identity information for the GitHub provider.""" - return self._identity + def auth_method(self): + """Returns the authentication method for the GitHub provider.""" + return self._auth_method @property def session(self): """Returns the session object for the GitHub provider.""" return self._session + @property + def identity(self): + """Returns the identity information for the GitHub provider.""" + return self._identity + @property def type(self): """Returns the type of the GitHub provider.""" @@ -130,10 +136,13 @@ class GithubProvider(Provider): if personal_access_token: session_token = getenv("GITHUB_PERSONAL_ACCESS_TOKEN") + self._auth_method = "personal_access_token" elif github_app: session_token = getenv("GITHUB_APP_TOKEN") + self._auth_method = "github_app" elif oauth_app: session_token = getenv("GITHUB_OAUTH_TOKEN") + self._auth_method = "oauth_app" else: raise ValueError( "A GitHub API token of some kind is required to initialize GitHub provider." @@ -175,4 +184,4 @@ class GithubProvider(Provider): return identity def print_credentials(self): - print("Using GitHub PAT") + print(f"You are using a {self.auth_method} as authentication method.") diff --git a/prowler/providers/github/lib/mutelist/mutelist.py b/prowler/providers/github/lib/mutelist/mutelist.py index 31ec3b1b8d..ba1ed7a56e 100644 --- a/prowler/providers/github/lib/mutelist/mutelist.py +++ b/prowler/providers/github/lib/mutelist/mutelist.py @@ -1,4 +1,4 @@ -from prowler.lib.check.models import Check_Report_GitHub +from prowler.lib.check.models import Check_Report_Github from prowler.lib.mutelist.mutelist import Mutelist from prowler.lib.outputs.utils import unroll_dict, unroll_tags @@ -6,7 +6,7 @@ from prowler.lib.outputs.utils import unroll_dict, unroll_tags class GitHubMutelist(Mutelist): def is_finding_muted( self, - finding: Check_Report_GitHub, + finding: Check_Report_Github, ) -> bool: return self.is_muted( finding.account_name, diff --git a/prowler/providers/github/lib/service/service.py b/prowler/providers/github/lib/service/service.py index e113e5a480..43ae18a1d4 100644 --- a/prowler/providers/github/lib/service/service.py +++ b/prowler/providers/github/lib/service/service.py @@ -11,9 +11,7 @@ class GithubService: provider: GithubProvider, ): self.client = self.__set_client__( - provider.identity, provider.session, - service, ) self.audit_config = provider.audit_config diff --git a/prowler/providers/github/services/repository/repository_client.py b/prowler/providers/github/services/repository/repository_client.py index 232f8ddf81..3ea47cb952 100644 --- a/prowler/providers/github/services/repository/repository_client.py +++ b/prowler/providers/github/services/repository/repository_client.py @@ -1,4 +1,4 @@ from prowler.providers.common.provider import Provider -from prowler.providers.github.services.repository.repository_client import Repository +from prowler.providers.github.services.repository.repository_service import Repository repository_client = Repository(Provider.get_global_provider()) diff --git a/prowler/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file.metadata.json b/prowler/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file.metadata.json new file mode 100644 index 0000000000..490c417dc4 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "github", + "CheckID": "repository_public_has_securitymd_file", + "CheckTitle": "Check if public repositories have a SECURITY.md file", + "CheckType": [ + "CIS GitHub Benchmark v1.0.0" + ], + "ServiceName": "repository", + "SubServiceName": "", + "ResourceIdTemplate": "github:user-id:repository/repository-name", + "Severity": "low", + "ResourceType": "Other", + "Description": "Ensure that public repositories have a SECURITY.md file", + "Risk": "Not having a SECURITY.md file in a public repository may lead to security vulnerabilities being overlooked by users and contributors.", + "RelatedUrl": "https://gist.github.com/bayudwiyansatria/3aa6a865aad8d45d69cdb80090bea7c7", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Add a SECURITY.md file to the root of the repository. The file should contain information on how to report a security vulnerability, the security policy of the repository, and any other relevant information.", + "Url": "https://github.blog/changelog/2019-05-23-security-policy/" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file.py b/prowler/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file.py new file mode 100644 index 0000000000..bb55aa72b1 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_public_has_securitymd_file/repository_public_has_securitymd_file.py @@ -0,0 +1,42 @@ +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_public_has_securitymd_file(Check): + """Check if a public repository has a SECURITY.md file + + This class verifies wheter each public repository has a SECURITY.md file. + """ + + def execute(self) -> List[Check_Report_Github]: + """Execute the Github Repository Public Has Securitymd File check + + Iterates over all public repositories and checks if they have a SECURITY.md file. + + 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 = "PASS" + report.status_extended = ( + f"Repository {repo.name} does have a SECURITY.md file." + ) + + if not repo.private: + if not repo.securitymd: + report.status = "FAIL" + report.status_extended = ( + f"Repository {repo.name} does not have a SECURITY.md file." + ) + + 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 8292702ab4..6566058c70 100644 --- a/prowler/providers/github/services/repository/repository_service.py +++ b/prowler/providers/github/services/repository/repository_service.py @@ -10,19 +10,22 @@ class Repository(GithubService): def __init__(self, provider): super().__init__(__class__.__name__, provider) self.repositories = {} - self.__threading_call__(self._list_repositories) + self._list_repositories() def _list_repositories(self): logger.info("Repository - Listing Repositories...") try: for repo in self.client.get_user().get_repos(): + try: + securitymd_exists = repo.get_contents("SECURITY.md") is not None + except Exception: + securitymd_exists = False self.repositories[repo.id] = Repo( id=repo.id, name=repo.name, full_name=repo.full_name, private=repo.private, - description=repo.description, - securitymd=repo.get_contents("SECURITY.md") is not None, + securitymd=securitymd_exists, ) except Exception as error: logger.error( @@ -40,5 +43,4 @@ class Repo(BaseModel): name: str full_name: str private: bool - description: Optional[str] securitymd: Optional[bool] = False