diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index a78c191711..253939864a 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -238,6 +238,7 @@ class Provider(ABC): provider_class( personal_access_token=arguments.personal_access_token, oauth_app_token=arguments.oauth_app_token, + github_app_key_path=arguments.github_app_key_path, github_app_key=arguments.github_app_key, github_app_id=arguments.github_app_id, mutelist_path=arguments.mutelist_file, diff --git a/prowler/providers/github/github_provider.py b/prowler/providers/github/github_provider.py index d338933f20..6e5bf9bd52 100644 --- a/prowler/providers/github/github_provider.py +++ b/prowler/providers/github/github_provider.py @@ -102,8 +102,8 @@ class GithubProvider(Provider): # Authentication credentials personal_access_token: str = "", oauth_app_token: str = "", + github_app_key_path: str = "", github_app_key: str = "", - github_app_key_content: str = "", github_app_id: int = 0, # Provider configuration config_path: str = None, @@ -120,8 +120,8 @@ class GithubProvider(Provider): Args: personal_access_token (str): GitHub personal access token. oauth_app_token (str): GitHub OAuth App token. - github_app_key (str): GitHub App key. - github_app_key_content (str): GitHub App key content. + github_app_key_path (str): Path to GitHub App private key file. + github_app_key (str): GitHub App private key content. github_app_id (int): GitHub App ID. config_path (str): Path to the audit configuration file. config_content (dict): Audit configuration content. @@ -141,8 +141,8 @@ class GithubProvider(Provider): personal_access_token, oauth_app_token, github_app_id, + github_app_key_path, github_app_key, - github_app_key_content, ) # Set the authentication method @@ -150,7 +150,7 @@ class GithubProvider(Provider): self._auth_method = "Personal Access Token" elif oauth_app_token: self._auth_method = "OAuth App Token" - elif github_app_id and github_app_key: + elif github_app_id and (github_app_key_path or github_app_key): self._auth_method = "GitHub App Token" elif environ.get("GITHUB_PERSONAL_ACCESS_TOKEN", ""): self._auth_method = "Environment Variable for Personal Access Token" @@ -244,8 +244,8 @@ class GithubProvider(Provider): personal_access_token: str = None, oauth_app_token: str = None, github_app_id: int = 0, + github_app_key_path: str = None, github_app_key: str = None, - github_app_key_content: str = None, ) -> GithubSession: """ Returns the GitHub headers responsible authenticating API calls. @@ -254,8 +254,8 @@ class GithubProvider(Provider): personal_access_token (str): GitHub personal access token. oauth_app_token (str): GitHub OAuth App token. github_app_id (int): GitHub App ID. - github_app_key (str): GitHub App key. - github_app_key_content (str): GitHub App key content. + github_app_key_path (str): Path to GitHub App private key file. + github_app_key (str): GitHub App private key content. Returns: GithubSession: Authenticated session token for API requests. """ @@ -272,13 +272,16 @@ class GithubProvider(Provider): elif oauth_app_token: session_token = oauth_app_token - elif github_app_id and (github_app_key or github_app_key_content): + elif github_app_id and (github_app_key_path or github_app_key): app_id = github_app_id - if github_app_key: - with open(github_app_key, "r") as rsa_key: + if github_app_key_path: + with open(github_app_key_path, "r") as rsa_key: app_key = rsa_key.read() + elif github_app_key: + app_key = format_rsa_key(github_app_key) else: - app_key = format_rsa_key(github_app_key_content) + # This shouldn't happen due to the condition above, but handle gracefully + app_key = "" else: # PAT @@ -300,10 +303,23 @@ class GithubProvider(Provider): "Looking for GITHUB_APP_ID and GITHUB_APP_KEY environment variables as user has not provided any token...." ) app_id = environ.get("GITHUB_APP_ID", "") - app_key = format_rsa_key(environ.get("GITHUB_APP_KEY", "")) + env_key = environ.get("GITHUB_APP_KEY", "") - if app_id and app_key: - pass + if app_id and env_key: + # Smart detection: check if env_key is file path or key content + if env_key.startswith("-----BEGIN"): + # It's key content + app_key = format_rsa_key(env_key) + elif os.path.isfile(env_key): + # It's a file path + with open(env_key, "r") as rsa_key: + app_key = rsa_key.read() + else: + # Invalid format + raise GithubEnvironmentVariableError( + file=os.path.basename(__file__), + message="GITHUB_APP_KEY must contain either RSA key content (starting with -----BEGIN) or a valid file path.", + ) if not session_token and not (app_id and app_key): raise GithubEnvironmentVariableError( @@ -484,8 +500,8 @@ class GithubProvider(Provider): def test_connection( personal_access_token: str = "", oauth_app_token: str = "", + github_app_key_path: str = "", github_app_key: str = "", - github_app_key_content: str = "", github_app_id: int = 0, raise_on_exception: bool = True, provider_id: str = None, @@ -497,8 +513,8 @@ class GithubProvider(Provider): Args: personal_access_token (str): GitHub personal access token. oauth_app_token (str): GitHub OAuth App token. - github_app_key (str): GitHub App key. - github_app_key_content (str): GitHub App key content. + github_app_key_path (str): Path to GitHub App private key file. + github_app_key (str): GitHub App private key content. github_app_id (int): GitHub App ID. raise_on_exception (bool): Flag indicating whether to raise an exception if the connection fails. provider_id (str): The provider ID, in this case it's the GitHub organization/username. @@ -529,8 +545,8 @@ class GithubProvider(Provider): personal_access_token=personal_access_token, oauth_app_token=oauth_app_token, github_app_id=github_app_id, + github_app_key_path=github_app_key_path, github_app_key=github_app_key, - github_app_key_content=github_app_key_content, ) # Set up the identity to test the connection diff --git a/prowler/providers/github/lib/arguments/arguments.py b/prowler/providers/github/lib/arguments/arguments.py index 57aed863ab..129ce9102f 100644 --- a/prowler/providers/github/lib/arguments/arguments.py +++ b/prowler/providers/github/lib/arguments/arguments.py @@ -30,12 +30,18 @@ def init_parser(self): metavar="GITHUB_APP_ID", ) github_auth_subparser.add_argument( - "--github-app-key", "--github-app-key-path", nargs="?", - help="GitHub App Key Path to log in against GitHub", + help="Path to GitHub App private key file", default=None, - metavar="GITHUB_APP_KEY", + metavar="GITHUB_APP_KEY_PATH", + ) + github_auth_subparser.add_argument( + "--github-app-key", + nargs="?", + help="GitHub App private key content", + default=None, + metavar="GITHUB_APP_KEY_CONTENT", ) github_scoping_subparser = github_parser.add_argument_group("Scan Scoping") diff --git a/tests/providers/github/lib/arguments/github_arguments_test.py b/tests/providers/github/lib/arguments/github_arguments_test.py index fd2f813b6e..2c4e985ac4 100644 --- a/tests/providers/github/lib/arguments/github_arguments_test.py +++ b/tests/providers/github/lib/arguments/github_arguments_test.py @@ -61,7 +61,7 @@ class Test_GitHubArguments: arguments.init_parser(mock_github_args) # Verify authentication arguments were added - assert self.mock_auth_group.add_argument.call_count == 4 + assert self.mock_auth_group.add_argument.call_count == 5 # Check that all authentication arguments are present calls = self.mock_auth_group.add_argument.call_args_list @@ -70,8 +70,8 @@ class Test_GitHubArguments: assert "--personal-access-token" in auth_args assert "--oauth-app-token" in auth_args assert "--github-app-id" in auth_args - # Check for either form of the github app key argument - assert any("--github-app-key" in arg for arg in auth_args) + assert "--github-app-key-path" in auth_args + assert "--github-app-key" in auth_args def test_init_parser_adds_scoping_arguments(self): """Test that init_parser adds all scoping arguments"""