From 6f961139a1ab0f23676cae349784ce4782bd7dc1 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 26 Nov 2024 14:10:02 +0100 Subject: [PATCH] feat: complete github app login --- prowler/lib/outputs/summary_table.py | 9 +++- .../providers/github/exceptions/exceptions.py | 2 +- prowler/providers/github/github_provider.py | 37 ++++++++------- .../providers/github/lib/service/service.py | 20 ++++---- prowler/providers/github/models.py | 27 +++++++---- .../services/repository/repository_service.py | 47 ++++++++++--------- 6 files changed, 81 insertions(+), 61 deletions(-) diff --git a/prowler/lib/outputs/summary_table.py b/prowler/lib/outputs/summary_table.py index 60048617c3..1e81e9a209 100644 --- a/prowler/lib/outputs/summary_table.py +++ b/prowler/lib/outputs/summary_table.py @@ -11,6 +11,7 @@ from prowler.config.config import ( orange_color, ) from prowler.lib.logger import logger +from prowler.providers.github.models import GithubAppIdentityInfo, GithubIdentityInfo def display_summary_table( @@ -41,8 +42,12 @@ def display_summary_table( entity_type = "Context" audited_entities = provider.identity.context elif provider.type == "github": - entity_type = "User Name" - audited_entities = provider.identity.account_name + if isinstance(provider.identity, GithubIdentityInfo): + entity_type = "User Name" + audited_entities = provider.identity.account_name + elif isinstance(provider.identity, GithubAppIdentityInfo): + entity_type = "App ID" + audited_entities = provider.identity.app_id # Check if there are findings and that they are not all MANUAL if findings and not all(finding.status == "MANUAL" for finding in findings): diff --git a/prowler/providers/github/exceptions/exceptions.py b/prowler/providers/github/exceptions/exceptions.py index 5037fa2bf0..f7058d96ea 100644 --- a/prowler/providers/github/exceptions/exceptions.py +++ b/prowler/providers/github/exceptions/exceptions.py @@ -27,7 +27,7 @@ class GithubBaseException(ProwlerException): "remediation": "Check credentials and ensure they are properly set up for Github and the identity provider.", }, (5005, "GithubInvalidCredentialsError"): { - "message": "Github invalid user or password for basic login", + "message": "Github invalid App Key or App ID for GitHub APP login", "remediation": "Check user and password and ensure they are properly set up as in your Github account.", }, } diff --git a/prowler/providers/github/github_provider.py b/prowler/providers/github/github_provider.py index 3e6abefdb1..aeaaf2ff25 100644 --- a/prowler/providers/github/github_provider.py +++ b/prowler/providers/github/github_provider.py @@ -1,7 +1,7 @@ import os from os import getenv -from github import Auth, Github, GithubIntegration +from github import Auth, Github from prowler.config.config import ( default_config_file_path, @@ -20,7 +20,11 @@ from prowler.providers.github.exceptions.exceptions import ( GithubSetUpSessionError, ) from prowler.providers.github.lib.mutelist.mutelist import GithubMutelist -from prowler.providers.github.models import GithubIdentityInfo, GithubSession +from prowler.providers.github.models import ( + GithubAppIdentityInfo, + GithubIdentityInfo, + GithubSession, +) class GithubProvider(Provider): @@ -216,6 +220,7 @@ class GithubProvider(Provider): file=os.path.basename(__file__), message="Missing Github environment variable GITHUB_OAUTH_APP_TOKEN required to authenticate.", ) + session_token = getenv("GITHUB_OAUTH_APP_TOKEN") self._auth_method = "Enviroment Variable for OAuth App Token" elif github_app_key and github_app_id: @@ -224,16 +229,17 @@ class GithubProvider(Provider): self._auth_method = "GitHub App Token" elif github_app: - if not getenv("github_app_key"): + if not getenv("GITHUB_APP_KEY") or not getenv("GITHUB_APP_ID"): logger.critical( - "GitHub provider: Missing enviroment variable github_app_key needed to authenticate against GitHub." + "GitHub provider: Missing enviroment variables GITHUB_APP_KEY or GITHUB_APP_ID needed to authenticate against GitHub." ) raise GithubEnvironmentVariableError( file=os.path.basename(__file__), - message="Missing Github environment variable github_app_key required to authenticate.", + message="Missing enviroment variables GITHUB_APP_KEY or GITHUB_APP_ID required to authenticate.", ) - session_token = getenv("github_app_key") - self._auth_method = "Enviroment Variable for GitHub App Token" + app_key = getenv("GITHUB_APP_KEY") + app_id = getenv("GITHUB_APP_ID") + self._auth_method = "Enviroment Variables for GitHub App Key and ID" else: logger.critical( @@ -266,7 +272,7 @@ class GithubProvider(Provider): oauth_app_token, github_app_key, github_app_id: int = 0, - ) -> GithubIdentityInfo: + ) -> GithubIdentityInfo | GithubAppIdentityInfo: """ Returns the GitHub identity information @@ -276,7 +282,7 @@ class GithubProvider(Provider): oauth_app (bool): Flag indicating whether to use OAuth App as authentication method. Returns: - GithubIdentityInfo: An instance of GithubIdentityInfo containing the identity information. + GithubIdentityInfo | GithubAppIdentityInfo: An instance of GithubIdentityInfo or GithubAppIdentityInfo containing the identity information. """ credentials = self.session @@ -296,8 +302,8 @@ class GithubProvider(Provider): try: identity = GithubIdentityInfo( - account_name=g.get_user().login, account_id=g.get_user().id, + account_name=g.get_user().login, account_url=g.get_user().url, ) return identity @@ -308,16 +314,11 @@ class GithubProvider(Provider): original_exception=error, ) - elif github_app_key and github_app_id: - auth = Auth.AppAuth(credentials.key, credentials.id) - gi = GithubIntegration(auth=auth) + elif (github_app_key and github_app_id) or github_app: + auth = Auth.AppAuth(credentials.id, credentials.key) try: - identity = GithubIdentityInfo( - account_name=gi.get_user().login, - account_id=gi.get_user().id, - account_url=gi.get_user().url, - ) + identity = GithubAppIdentityInfo(app_id=auth.app_id) return identity except Exception as error: diff --git a/prowler/providers/github/lib/service/service.py b/prowler/providers/github/lib/service/service.py index 65d0f6e26f..b9e4bf2974 100644 --- a/prowler/providers/github/lib/service/service.py +++ b/prowler/providers/github/lib/service/service.py @@ -10,28 +10,32 @@ class GithubService: service: str, provider: GithubProvider, ): - self.client = self.__set_client__( + self.clients = self.__set_clients__( provider.session, ) self.audit_config = provider.audit_config self.fixer_config = provider.fixer_config - def __set_client__(self, session): + def __set_clients__(self, session): + clients = [] try: if session.token: auth = Auth.Token(session.token) - client = Github(auth=auth) + clients = [Github(auth=auth)] elif session.key and session.id: - auth = Auth.GithubApp( - session.github_app_id, - session.github_app_key, + auth = Auth.AppAuth( + session.id, + session.key, ) - client = GithubIntegration(auth=auth) + gi = GithubIntegration(auth=auth) + + for installation in gi.get_installations(): + clients.append(installation.get_github_for_installation()) except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - return client + return clients diff --git a/prowler/providers/github/models.py b/prowler/providers/github/models.py index fa7e4ae3bc..a4fb2fdc36 100644 --- a/prowler/providers/github/models.py +++ b/prowler/providers/github/models.py @@ -4,18 +4,22 @@ from prowler.config.config import output_file_timestamp from prowler.providers.common.models import ProviderOutputOptions -class GithubIdentityInfo(BaseModel): - account_name: str - account_id: str - account_url: str - - class GithubSession(BaseModel): token: str key: str id: str +class GithubIdentityInfo(BaseModel): + account_id: str + account_name: str + account_url: str + + +class GithubAppIdentityInfo(BaseModel): + app_id: str + + class GithubOutputOptions(ProviderOutputOptions): def __init__(self, arguments, bulk_checks_metadata, identity): # First call ProviderOutputOptions init @@ -26,8 +30,13 @@ class GithubOutputOptions(ProviderOutputOptions): not hasattr(arguments, "output_filename") or arguments.output_filename is None ): - self.output_filename = ( - f"prowler-output-{identity.account_name}-{output_file_timestamp}" - ) + if isinstance(identity, GithubIdentityInfo): + self.output_filename = ( + f"prowler-output-{identity.account_name}-{output_file_timestamp}" + ) + elif isinstance(identity, GithubAppIdentityInfo): + self.output_filename = ( + f"prowler-output-{identity.app_id}-{output_file_timestamp}" + ) else: self.output_filename = arguments.output_filename diff --git a/prowler/providers/github/services/repository/repository_service.py b/prowler/providers/github/services/repository/repository_service.py index ba1341e31f..0d342ce766 100644 --- a/prowler/providers/github/services/repository/repository_service.py +++ b/prowler/providers/github/services/repository/repository_service.py @@ -15,29 +15,30 @@ class Repository(GithubService): logger.info("Repository - Listing Repositories...") repos = {} try: - for repo in self.client.get_user().get_repos(): - try: - securitymd_exists = repo.get_contents("SECURITY.md") is not None - """ - securitymd_exists = False - contents = repo.get_contents("") - while contents: - file_content = contents.pop(0) - if file_content.type == "dir": - contents.extend(repo.get_contents(file_content.path)) - elif file_content.path.endswith("SECURITY.md"): - securitymd_exists = True - break - """ - except Exception: - securitymd_exists = False - repos[repo.id] = Repo( - id=repo.id, - name=repo.name, - full_name=repo.full_name, - private=repo.private, - securitymd=securitymd_exists, - ) + for client in self.clients: + for repo in client.get_user().get_repos(): + try: + securitymd_exists = repo.get_contents("SECURITY.md") is not None + """ + securitymd_exists = False + contents = repo.get_contents("") + while contents: + file_content = contents.pop(0) + if file_content.type == "dir": + contents.extend(repo.get_contents(file_content.path)) + elif file_content.path.endswith("SECURITY.md"): + securitymd_exists = True + break + """ + except Exception: + securitymd_exists = False + repos[repo.id] = Repo( + id=repo.id, + name=repo.name, + full_name=repo.full_name, + private=repo.private, + securitymd=securitymd_exists, + ) except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"