mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
Compare commits
14 Commits
d15dd53708
...
prwlr-7751
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd3ab3a257 | ||
|
|
73f7f1126d | ||
|
|
9240a04aca | ||
|
|
1e813dbace | ||
|
|
a0c00ab2e7 | ||
|
|
916eb0ff23 | ||
|
|
efbf641bb3 | ||
|
|
8cd83f9ea1 | ||
|
|
b1cfc2af7e | ||
|
|
e580f79523 | ||
|
|
100038e80d | ||
|
|
4866212f2d | ||
|
|
110f98e49d | ||
|
|
31d0db2d2c |
@@ -217,7 +217,8 @@ Prowler enables security scanning of your **GitHub account**, including **Reposi
|
||||
prowler github --oauth-app-token oauth_token
|
||||
|
||||
# GitHub App Credentials:
|
||||
prowler github --github-app-id app_id --github-app-key app_key
|
||||
prowler github --github-app-id app_id --github-app-key-path path/to/app_key.pem
|
||||
prowler github --github-app-id app_id --github-app-key $APP_KEY_CONTENT
|
||||
```
|
||||
|
||||
<Note>
|
||||
@@ -225,7 +226,8 @@ Prowler enables security scanning of your **GitHub account**, including **Reposi
|
||||
|
||||
1. `GITHUB_PERSONAL_ACCESS_TOKEN`
|
||||
2. `OAUTH_APP_TOKEN`
|
||||
3. `GITHUB_APP_ID` and `GITHUB_APP_KEY`
|
||||
3. `GITHUB_APP_ID` and `GITHUB_APP_KEY_PATH`
|
||||
4. `GITHUB_APP_ID` and `GITHUB_APP_KEY`
|
||||
|
||||
</Note>
|
||||
## Infrastructure as Code (IaC)
|
||||
|
||||
@@ -60,7 +60,8 @@ If no login method is explicitly provided, Prowler will automatically attempt to
|
||||
|
||||
1. `GITHUB_PERSONAL_ACCESS_TOKEN`
|
||||
2. `GITHUB_OAUTH_APP_TOKEN`
|
||||
3. `GITHUB_APP_ID` and `GITHUB_APP_KEY` (where the key is the content of the private key file)
|
||||
3. `GITHUB_APP_ID` and `GITHUB_APP_KEY_PATH` (where the key path is the path to the private key file)
|
||||
4. `GITHUB_APP_ID` and `GITHUB_APP_KEY` (where the key is the content of the private key file)
|
||||
|
||||
<Note>
|
||||
Ensure the corresponding environment variables are set up before running Prowler for automatic detection when not specifying the login method.
|
||||
@@ -88,5 +89,6 @@ prowler github --oauth-app-token oauth_token
|
||||
Use GitHub App credentials by specifying the App ID and the private key path.
|
||||
|
||||
```console
|
||||
prowler github --github-app-id app_id --github-app-key-path app_key_path
|
||||
prowler github --github-app-id app_id --github-app-key-path path/to/app_key.pem
|
||||
prowler github --github-app-id app_id --github-app-key $APP_KEY_CONTENT
|
||||
```
|
||||
|
||||
@@ -50,7 +50,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- Update oraclecloud cloudguard service metadata to new format [(#9223)](https://github.com/prowler-cloud/prowler/pull/9223)
|
||||
- Update oraclecloud blockstorage service metadata to new format [(#9222)](https://github.com/prowler-cloud/prowler/pull/9222)
|
||||
- Update oraclecloud audit service metadata to new format [(#9221)](https://github.com/prowler-cloud/prowler/pull/9221)
|
||||
|
||||
- GitHub App authentication inconsistency where `--github-app-key` and `--github-app-key-path` were incorrectly aliased to the same parameter, causing confusion between file paths and key content [(#8422)](https://github.com/prowler-cloud/prowler/pull/8422)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -242,6 +242,7 @@ class Provider(ABC):
|
||||
personal_access_token=arguments.personal_access_token,
|
||||
oauth_app_token=arguments.oauth_app_token,
|
||||
github_app_key=arguments.github_app_key,
|
||||
github_app_key_path=arguments.github_app_key_path,
|
||||
github_app_id=arguments.github_app_id,
|
||||
mutelist_path=arguments.mutelist_file,
|
||||
config_path=arguments.config_file,
|
||||
|
||||
@@ -103,6 +103,7 @@ class GithubProvider(Provider):
|
||||
personal_access_token: str = "",
|
||||
oauth_app_token: str = "",
|
||||
github_app_key: str = "",
|
||||
github_app_key_path: str = "",
|
||||
github_app_key_content: str = "",
|
||||
github_app_id: int = 0,
|
||||
# Provider configuration
|
||||
@@ -120,8 +121,9 @@ 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 (str): GitHub App key content.
|
||||
github_app_key_path (str): Path to GitHub App private key file.
|
||||
github_app_key_content (str): GitHub App private key content (legacy parameter).
|
||||
github_app_id (int): GitHub App ID.
|
||||
config_path (str): Path to the audit configuration file.
|
||||
config_content (dict): Audit configuration content.
|
||||
@@ -148,6 +150,7 @@ class GithubProvider(Provider):
|
||||
oauth_app_token,
|
||||
github_app_id,
|
||||
github_app_key,
|
||||
github_app_key_path,
|
||||
github_app_key_content,
|
||||
)
|
||||
|
||||
@@ -156,13 +159,17 @@ 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 or github_app_key_content):
|
||||
self._auth_method = "GitHub App Token"
|
||||
elif github_app_id and (
|
||||
github_app_key or github_app_key_path or github_app_key_content
|
||||
):
|
||||
self._auth_method = "GitHub App Key and ID"
|
||||
elif environ.get("GITHUB_PERSONAL_ACCESS_TOKEN", ""):
|
||||
self._auth_method = "Environment Variable for Personal Access Token"
|
||||
elif environ.get("GITHUB_OAUTH_APP_TOKEN", ""):
|
||||
self._auth_method = "Environment Variable for OAuth App Token"
|
||||
elif environ.get("GITHUB_APP_ID", "") and environ.get("GITHUB_APP_KEY", ""):
|
||||
elif environ.get("GITHUB_APP_ID", "") and (
|
||||
environ.get("GITHUB_APP_KEY", "") or environ.get("GITHUB_APP_KEY_PATH", "")
|
||||
):
|
||||
self._auth_method = "Environment Variables for GitHub App Key and ID"
|
||||
|
||||
self._identity = GithubProvider.setup_identity(self._session)
|
||||
@@ -251,6 +258,7 @@ class GithubProvider(Provider):
|
||||
oauth_app_token: str = None,
|
||||
github_app_id: int = 0,
|
||||
github_app_key: str = None,
|
||||
github_app_key_path: str = None,
|
||||
github_app_key_content: str = None,
|
||||
) -> GithubSession:
|
||||
"""
|
||||
@@ -260,8 +268,9 @@ 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 (str): GitHub App key content.
|
||||
github_app_key_path (str): Path to GitHub App private key file.
|
||||
github_app_key_content (str): GitHub App private key content (legacy parameter).
|
||||
Returns:
|
||||
GithubSession: Authenticated session token for API requests.
|
||||
"""
|
||||
@@ -278,11 +287,35 @@ 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 or github_app_key_path or github_app_key_content
|
||||
):
|
||||
app_id = github_app_id
|
||||
if github_app_key:
|
||||
with open(github_app_key, "r") as rsa_key:
|
||||
app_key = rsa_key.read()
|
||||
if github_app_key_path:
|
||||
try:
|
||||
with open(github_app_key_path, "r") as rsa_key:
|
||||
app_key = rsa_key.read()
|
||||
except OSError as e:
|
||||
if e.errno == 63:
|
||||
raise GithubEnvironmentVariableError(
|
||||
file=os.path.basename(__file__),
|
||||
message="--github-app-key-path expects a file path, not key content. Use --github-app-key for key content instead.",
|
||||
)
|
||||
else:
|
||||
raise GithubEnvironmentVariableError(
|
||||
file=os.path.basename(__file__),
|
||||
message=f"Could not read GitHub App key file '{github_app_key_path}': {e.message}",
|
||||
)
|
||||
elif github_app_key:
|
||||
if github_app_key.startswith("-----BEGIN"):
|
||||
app_key = format_rsa_key(github_app_key)
|
||||
elif os.path.isfile(github_app_key):
|
||||
raise GithubEnvironmentVariableError(
|
||||
file=os.path.basename(__file__),
|
||||
message="--github-app-key expects key content, not a file path. Use --github-app-key-path for file paths instead.",
|
||||
)
|
||||
else:
|
||||
app_key = format_rsa_key(github_app_key)
|
||||
else:
|
||||
app_key = format_rsa_key(github_app_key_content)
|
||||
|
||||
@@ -303,13 +336,24 @@ class GithubProvider(Provider):
|
||||
if not session_token:
|
||||
# APP
|
||||
logger.info(
|
||||
"Looking for GITHUB_APP_ID and GITHUB_APP_KEY environment variables as user has not provided any token...."
|
||||
"Looking for GitHub App 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", ""))
|
||||
|
||||
if app_id and app_key:
|
||||
pass
|
||||
app_key_path = environ.get("GITHUB_APP_KEY_PATH", "")
|
||||
if app_key_path:
|
||||
with open(app_key_path, "r") as rsa_key:
|
||||
app_key = rsa_key.read()
|
||||
else:
|
||||
env_key = environ.get("GITHUB_APP_KEY", "")
|
||||
if env_key:
|
||||
if env_key.startswith("-----BEGIN"):
|
||||
app_key = format_rsa_key(env_key)
|
||||
else:
|
||||
raise GithubEnvironmentVariableError(
|
||||
file=os.path.basename(__file__),
|
||||
message="GITHUB_APP_KEY must contain RSA key content (starting with -----BEGIN). Use GITHUB_APP_KEY_PATH for file paths.",
|
||||
)
|
||||
|
||||
if not session_token and not (app_id and app_key):
|
||||
raise GithubEnvironmentVariableError(
|
||||
@@ -520,6 +564,7 @@ class GithubProvider(Provider):
|
||||
personal_access_token: str = "",
|
||||
oauth_app_token: str = "",
|
||||
github_app_key: str = "",
|
||||
github_app_key_path: str = "",
|
||||
github_app_key_content: str = "",
|
||||
github_app_id: int = 0,
|
||||
raise_on_exception: bool = True,
|
||||
@@ -532,8 +577,9 @@ 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 (str): GitHub App key content.
|
||||
github_app_key_path (str): Path to GitHub App private key file.
|
||||
github_app_key_content (str): GitHub App private key content (legacy parameter).
|
||||
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.
|
||||
@@ -553,7 +599,7 @@ class GithubProvider(Provider):
|
||||
Examples:
|
||||
>>> GithubProvider.test_connection(personal_access_token="ghp_xxxxxxxxxxxxxxxx")
|
||||
Connection(is_connected=True)
|
||||
>>> GithubProvider.test_connection(github_app_id=12345, github_app_key="/path/to/key.pem")
|
||||
>>> GithubProvider.test_connection(github_app_id=12345, github_app_key_content="/path/to/key.pem")
|
||||
Connection(is_connected=True)
|
||||
>>> GithubProvider.test_connection(provider_id="my-org")
|
||||
Connection(is_connected=True)
|
||||
@@ -565,6 +611,7 @@ class GithubProvider(Provider):
|
||||
oauth_app_token=oauth_app_token,
|
||||
github_app_id=github_app_id,
|
||||
github_app_key=github_app_key,
|
||||
github_app_key_path=github_app_key_path,
|
||||
github_app_key_content=github_app_key_content,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,12 +31,18 @@ def init_parser(self):
|
||||
)
|
||||
github_auth_subparser.add_argument(
|
||||
"--github-app-key",
|
||||
"--github-app-key-path",
|
||||
nargs="?",
|
||||
help="GitHub App Key Path to log in against GitHub",
|
||||
help="GitHub App Key content (PEM format) to log in against GitHub",
|
||||
default=None,
|
||||
metavar="GITHUB_APP_KEY",
|
||||
)
|
||||
github_auth_subparser.add_argument(
|
||||
"--github-app-key-path",
|
||||
nargs="?",
|
||||
help="Path to GitHub App private key file",
|
||||
default=None,
|
||||
metavar="GITHUB_APP_KEY_PATH",
|
||||
)
|
||||
|
||||
github_scoping_subparser = github_parser.add_argument_group("Scan Scoping")
|
||||
github_scoping_subparser.add_argument(
|
||||
@@ -55,3 +61,31 @@ def init_parser(self):
|
||||
default=None,
|
||||
metavar="ORGANIZATION",
|
||||
)
|
||||
|
||||
|
||||
def validate_arguments(arguments) -> tuple[bool, str]:
|
||||
"""
|
||||
Validate GitHub provider arguments
|
||||
|
||||
Args:
|
||||
arguments: Parsed command line arguments
|
||||
|
||||
Returns:
|
||||
tuple[bool, str]: (is_valid, error_message)
|
||||
"""
|
||||
|
||||
if arguments.github_app_key and arguments.github_app_key_path:
|
||||
return (
|
||||
False,
|
||||
"Cannot specify both --github-app-key and --github-app-key-path simultaneously",
|
||||
)
|
||||
|
||||
if arguments.github_app_id and not (
|
||||
arguments.github_app_key or arguments.github_app_key_path
|
||||
):
|
||||
return (
|
||||
False,
|
||||
"GitHub App ID requires either --github-app-key or --github-app-key-path",
|
||||
)
|
||||
|
||||
return True, ""
|
||||
|
||||
@@ -39,6 +39,7 @@ class TestGitHubProvider:
|
||||
oauth_app_token = None
|
||||
github_app_id = None
|
||||
github_app_key = None
|
||||
github_app_key_path = None
|
||||
fixer_config = load_and_validate_config_file(
|
||||
"github", default_fixer_config_file_path
|
||||
)
|
||||
@@ -62,6 +63,7 @@ class TestGitHubProvider:
|
||||
oauth_app_token,
|
||||
github_app_id,
|
||||
github_app_key,
|
||||
github_app_key_path,
|
||||
)
|
||||
|
||||
assert provider._type == "github"
|
||||
@@ -80,7 +82,7 @@ class TestGitHubProvider:
|
||||
personal_access_token = None
|
||||
oauth_app_token = OAUTH_TOKEN
|
||||
github_app_id = None
|
||||
github_app_key = None
|
||||
github_app_key_path = None
|
||||
fixer_config = load_and_validate_config_file(
|
||||
"github", default_fixer_config_file_path
|
||||
)
|
||||
@@ -103,7 +105,7 @@ class TestGitHubProvider:
|
||||
personal_access_token,
|
||||
oauth_app_token,
|
||||
github_app_id,
|
||||
github_app_key,
|
||||
github_app_key_path,
|
||||
)
|
||||
|
||||
assert provider._type == "github"
|
||||
@@ -118,11 +120,13 @@ class TestGitHubProvider:
|
||||
}
|
||||
assert provider._fixer_config == fixer_config
|
||||
|
||||
def test_github_provider_App(self):
|
||||
def test_github_provider_App_with_key_path(self):
|
||||
personal_access_token = None
|
||||
oauth_app_token = None
|
||||
github_app_id = APP_ID
|
||||
github_app_key = APP_KEY
|
||||
github_app_key = None
|
||||
github_app_key_path = APP_KEY
|
||||
github_app_key_content = None
|
||||
fixer_config = load_and_validate_config_file(
|
||||
"github", default_fixer_config_file_path
|
||||
)
|
||||
@@ -144,8 +148,10 @@ class TestGitHubProvider:
|
||||
provider = GithubProvider(
|
||||
personal_access_token,
|
||||
oauth_app_token,
|
||||
github_app_id,
|
||||
github_app_key,
|
||||
github_app_key_path,
|
||||
github_app_key_content,
|
||||
github_app_id,
|
||||
)
|
||||
|
||||
assert provider._type == "github"
|
||||
@@ -158,6 +164,104 @@ class TestGitHubProvider:
|
||||
}
|
||||
assert provider._fixer_config == fixer_config
|
||||
|
||||
def test_github_provider_App_with_key_content(self):
|
||||
personal_access_token = None
|
||||
oauth_app_token = None
|
||||
github_app_id = APP_ID
|
||||
github_app_key = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
github_app_key_path = None
|
||||
github_app_key_content = None
|
||||
fixer_config = load_and_validate_config_file(
|
||||
"github", default_fixer_config_file_path
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
return_value=GithubSession(token="", id=APP_ID, key=github_app_key),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_identity",
|
||||
return_value=GithubAppIdentityInfo(
|
||||
app_id=APP_ID,
|
||||
app_name=APP_NAME,
|
||||
installations=["test-org"],
|
||||
),
|
||||
),
|
||||
):
|
||||
provider = GithubProvider(
|
||||
personal_access_token,
|
||||
oauth_app_token,
|
||||
github_app_key,
|
||||
github_app_key_path,
|
||||
github_app_key_content,
|
||||
github_app_id,
|
||||
)
|
||||
|
||||
assert provider._type == "github"
|
||||
assert provider.session == GithubSession(
|
||||
token="", id=APP_ID, key=github_app_key
|
||||
)
|
||||
assert provider.identity == GithubAppIdentityInfo(
|
||||
app_id=APP_ID, app_name=APP_NAME, installations=["test-org"]
|
||||
)
|
||||
assert provider._audit_config == {
|
||||
"inactive_not_archived_days_threshold": 180,
|
||||
}
|
||||
assert provider._fixer_config == fixer_config
|
||||
|
||||
def test_github_provider_App_with_legacy_key_content(self):
|
||||
personal_access_token = None
|
||||
oauth_app_token = None
|
||||
github_app_id = APP_ID
|
||||
github_app_key = None
|
||||
github_app_key_path = None
|
||||
github_app_key_content = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
fixer_config = load_and_validate_config_file(
|
||||
"github", default_fixer_config_file_path
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
return_value=GithubSession(
|
||||
token="", id=APP_ID, key=github_app_key_content
|
||||
),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_identity",
|
||||
return_value=GithubAppIdentityInfo(
|
||||
app_id=APP_ID,
|
||||
app_name=APP_NAME,
|
||||
installations=["test-org"],
|
||||
),
|
||||
),
|
||||
):
|
||||
provider = GithubProvider(
|
||||
personal_access_token,
|
||||
oauth_app_token,
|
||||
github_app_key,
|
||||
github_app_key_path,
|
||||
github_app_key_content,
|
||||
github_app_id,
|
||||
)
|
||||
|
||||
assert provider._type == "github"
|
||||
assert provider.session == GithubSession(
|
||||
token="", id=APP_ID, key=github_app_key_content
|
||||
)
|
||||
assert provider.identity == GithubAppIdentityInfo(
|
||||
app_id=APP_ID, app_name=APP_NAME, installations=["test-org"]
|
||||
)
|
||||
assert provider._audit_config == {
|
||||
"inactive_not_archived_days_threshold": 180,
|
||||
}
|
||||
assert provider._fixer_config == fixer_config
|
||||
|
||||
def test_test_connection_with_personal_access_token_success(self):
|
||||
"""Test successful connection with personal access token."""
|
||||
with (
|
||||
@@ -202,8 +306,8 @@ class TestGitHubProvider:
|
||||
assert connection.is_connected is True
|
||||
assert connection.error is None
|
||||
|
||||
def test_test_connection_with_github_app_success(self):
|
||||
"""Test successful connection with GitHub App credentials."""
|
||||
def test_test_connection_with_github_app_key_path_success(self):
|
||||
"""Test successful connection with GitHub App key path."""
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
@@ -217,7 +321,57 @@ class TestGitHubProvider:
|
||||
),
|
||||
):
|
||||
connection = GithubProvider.test_connection(
|
||||
github_app_id=APP_ID, github_app_key=APP_KEY
|
||||
github_app_id=APP_ID, github_app_key_path=APP_KEY
|
||||
)
|
||||
|
||||
assert isinstance(connection, Connection)
|
||||
assert connection.is_connected is True
|
||||
assert connection.error is None
|
||||
|
||||
def test_test_connection_with_github_app_key_content_success(self):
|
||||
"""Test successful connection with GitHub App key content."""
|
||||
key_content = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
return_value=GithubSession(token="", id=APP_ID, key=key_content),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_identity",
|
||||
return_value=GithubAppIdentityInfo(
|
||||
app_id=APP_ID, app_name=APP_NAME, installations=["test-org"]
|
||||
),
|
||||
),
|
||||
):
|
||||
connection = GithubProvider.test_connection(
|
||||
github_app_id=APP_ID, github_app_key=key_content
|
||||
)
|
||||
|
||||
assert isinstance(connection, Connection)
|
||||
assert connection.is_connected is True
|
||||
assert connection.error is None
|
||||
|
||||
def test_test_connection_with_github_app_legacy_key_content_success(self):
|
||||
"""Test successful connection with GitHub App legacy key content."""
|
||||
key_content = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
return_value=GithubSession(token="", id=APP_ID, key=key_content),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_identity",
|
||||
return_value=GithubAppIdentityInfo(
|
||||
app_id=APP_ID, app_name=APP_NAME, installations=["test-org"]
|
||||
),
|
||||
),
|
||||
):
|
||||
connection = GithubProvider.test_connection(
|
||||
github_app_id=APP_ID, github_app_key_content=key_content
|
||||
)
|
||||
|
||||
assert isinstance(connection, Connection)
|
||||
@@ -279,7 +433,7 @@ class TestGitHubProvider:
|
||||
):
|
||||
with pytest.raises(GithubInvalidCredentialsError):
|
||||
GithubProvider.test_connection(
|
||||
github_app_id=APP_ID, github_app_key="invalid-key"
|
||||
github_app_id=APP_ID, github_app_key_path="invalid-key"
|
||||
)
|
||||
|
||||
def test_test_connection_with_invalid_app_credentials_no_raise(self):
|
||||
@@ -298,7 +452,7 @@ class TestGitHubProvider:
|
||||
):
|
||||
connection = GithubProvider.test_connection(
|
||||
github_app_id=APP_ID,
|
||||
github_app_key="invalid-key",
|
||||
github_app_key_path="invalid-key",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
@@ -306,6 +460,104 @@ class TestGitHubProvider:
|
||||
assert connection.is_connected is False
|
||||
assert isinstance(connection.error, GithubInvalidCredentialsError)
|
||||
|
||||
def test_test_connection_github_app_key_path_with_content_raises_exception(self):
|
||||
"""Test connection when github_app_key_path receives key content instead of file path."""
|
||||
key_content = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
side_effect=GithubEnvironmentVariableError(
|
||||
file="github_provider.py",
|
||||
message="--github-app-key-path expects a file path, not key content. Use --github-app-key for key content instead.",
|
||||
),
|
||||
),
|
||||
patch("prowler.providers.github.github_provider.logger") as mock_logger,
|
||||
):
|
||||
with pytest.raises(GithubEnvironmentVariableError) as exc_info:
|
||||
GithubProvider.test_connection(
|
||||
github_app_id=APP_ID, github_app_key_path=key_content
|
||||
)
|
||||
|
||||
assert "--github-app-key-path expects a file path" in str(exc_info.value)
|
||||
mock_logger.critical.assert_called_once()
|
||||
|
||||
def test_test_connection_github_app_key_with_file_path_raises_exception(self):
|
||||
"""Test connection when github_app_key receives file path instead of key content."""
|
||||
file_path = "/path/to/key.pem"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
side_effect=GithubEnvironmentVariableError(
|
||||
file="github_provider.py",
|
||||
message="--github-app-key expects key content, not a file path. Use --github-app-key-path for file paths instead.",
|
||||
),
|
||||
),
|
||||
patch("prowler.providers.github.github_provider.logger") as mock_logger,
|
||||
):
|
||||
with pytest.raises(GithubEnvironmentVariableError) as exc_info:
|
||||
GithubProvider.test_connection(
|
||||
github_app_id=APP_ID, github_app_key=file_path
|
||||
)
|
||||
|
||||
assert "--github-app-key expects key content" in str(exc_info.value)
|
||||
mock_logger.critical.assert_called_once()
|
||||
|
||||
def test_test_connection_github_app_key_path_with_content_no_raise(self):
|
||||
"""Test connection when github_app_key_path receives key content without raising exception."""
|
||||
key_content = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
side_effect=GithubEnvironmentVariableError(
|
||||
file="github_provider.py",
|
||||
message="--github-app-key-path expects a file path, not key content. Use --github-app-key for key content instead.",
|
||||
),
|
||||
),
|
||||
patch("prowler.providers.github.github_provider.logger") as mock_logger,
|
||||
):
|
||||
connection = GithubProvider.test_connection(
|
||||
github_app_id=APP_ID,
|
||||
github_app_key_path=key_content,
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(connection, Connection)
|
||||
assert connection.is_connected is False
|
||||
assert isinstance(connection.error, GithubEnvironmentVariableError)
|
||||
assert "--github-app-key-path expects a file path" in str(connection.error)
|
||||
mock_logger.critical.assert_called_once()
|
||||
|
||||
def test_test_connection_github_app_key_with_file_path_no_raise(self):
|
||||
"""Test connection when github_app_key receives file path without raising exception."""
|
||||
file_path = "/path/to/key.pem"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_session",
|
||||
side_effect=GithubEnvironmentVariableError(
|
||||
file="github_provider.py",
|
||||
message="--github-app-key expects key content, not a file path. Use --github-app-key-path for file paths instead.",
|
||||
),
|
||||
),
|
||||
patch("prowler.providers.github.github_provider.logger") as mock_logger,
|
||||
):
|
||||
connection = GithubProvider.test_connection(
|
||||
github_app_id=APP_ID, github_app_key=file_path, raise_on_exception=False
|
||||
)
|
||||
|
||||
assert isinstance(connection, Connection)
|
||||
assert connection.is_connected is False
|
||||
assert isinstance(connection.error, GithubEnvironmentVariableError)
|
||||
assert "--github-app-key expects key content" in str(connection.error)
|
||||
mock_logger.critical.assert_called_once()
|
||||
|
||||
def test_test_connection_setup_session_error_raises_exception(self):
|
||||
"""Test connection when setup_session raises an exception."""
|
||||
with (
|
||||
@@ -538,6 +790,103 @@ class TestGitHubProvider:
|
||||
assert connection.is_connected is False
|
||||
assert isinstance(connection.error, GithubInvalidProviderIdError)
|
||||
|
||||
def test_setup_session_with_github_app_key_path_env_var(self):
|
||||
"""Test setup_session with GITHUB_APP_KEY_PATH environment variable."""
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
key_content = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".pem", delete=False) as f:
|
||||
f.write(key_content)
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"GITHUB_APP_ID": str(APP_ID), "GITHUB_APP_KEY_PATH": temp_path},
|
||||
):
|
||||
# Clear other env vars that might interfere
|
||||
for key in [
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||
"GITHUB_OAUTH_APP_TOKEN",
|
||||
"GITHUB_APP_KEY",
|
||||
]:
|
||||
if key in os.environ:
|
||||
del os.environ[key]
|
||||
|
||||
session = GithubProvider.setup_session()
|
||||
|
||||
assert session.id == str(APP_ID)
|
||||
assert session.key == key_content
|
||||
assert session.token == ""
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
|
||||
def test_setup_session_with_github_app_key_env_var_content(self):
|
||||
"""Test setup_session with GITHUB_APP_KEY environment variable containing key content."""
|
||||
import os
|
||||
|
||||
key_content = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
|
||||
with patch.dict(
|
||||
os.environ, {"GITHUB_APP_ID": str(APP_ID), "GITHUB_APP_KEY": key_content}
|
||||
):
|
||||
# Clear other env vars that might interfere
|
||||
for key in [
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||
"GITHUB_OAUTH_APP_TOKEN",
|
||||
"GITHUB_APP_KEY_PATH",
|
||||
]:
|
||||
if key in os.environ:
|
||||
del os.environ[key]
|
||||
|
||||
session = GithubProvider.setup_session()
|
||||
|
||||
assert session.id == str(APP_ID)
|
||||
assert session.key == key_content
|
||||
assert session.token == ""
|
||||
|
||||
def test_setup_session_with_github_app_key_env_var_file_path(self):
|
||||
"""Test setup_session with GITHUB_APP_KEY environment variable containing file path should raise error."""
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
key_content = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----"
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".pem", delete=False) as f:
|
||||
f.write(key_content)
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
with patch.dict(
|
||||
os.environ, {"GITHUB_APP_ID": str(APP_ID), "GITHUB_APP_KEY": temp_path}
|
||||
):
|
||||
# Clear other env vars that might interfere
|
||||
for key in [
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||
"GITHUB_OAUTH_APP_TOKEN",
|
||||
"GITHUB_APP_KEY_PATH",
|
||||
]:
|
||||
if key in os.environ:
|
||||
del os.environ[key]
|
||||
|
||||
with pytest.raises(GithubSetUpSessionError) as exc_info:
|
||||
GithubProvider.setup_session()
|
||||
|
||||
assert "GITHUB_APP_KEY must contain RSA key content" in str(
|
||||
exc_info.value
|
||||
)
|
||||
assert "Use GITHUB_APP_KEY_PATH for file paths" in str(exc_info.value)
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
|
||||
def test_validate_provider_id_with_valid_user(self):
|
||||
"""Test validate_provider_id with valid user (matches authenticated user)."""
|
||||
mock_session = GithubSession(token=PAT_TOKEN, id="", key="")
|
||||
|
||||
@@ -61,17 +61,23 @@ 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
|
||||
auth_args = [call[0][0] for call in calls]
|
||||
auth_args = []
|
||||
for call in calls:
|
||||
# Handle both single arguments and aliases
|
||||
if len(call[0]) > 1:
|
||||
auth_args.extend(call[0])
|
||||
else:
|
||||
auth_args.append(call[0][0])
|
||||
|
||||
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" in auth_args
|
||||
assert "--github-app-key-path" in auth_args
|
||||
|
||||
def test_init_parser_adds_scoping_arguments(self):
|
||||
"""Test that init_parser adds all scoping arguments"""
|
||||
@@ -303,3 +309,81 @@ class Test_GitHubArguments_Integration:
|
||||
assert args.personal_access_token == "test-token"
|
||||
assert args.repository == []
|
||||
assert args.organization == []
|
||||
|
||||
|
||||
class Test_GitHubArguments_Validation:
|
||||
def test_validate_arguments_both_key_methods_provided(self):
|
||||
"""Test validation fails when both key methods are provided"""
|
||||
from argparse import Namespace
|
||||
|
||||
args = Namespace()
|
||||
args.github_app_key = "key-content"
|
||||
args.github_app_key_path = "/path/to/key.pem"
|
||||
args.github_app_id = "12345"
|
||||
|
||||
is_valid, error_msg = arguments.validate_arguments(args)
|
||||
|
||||
assert not is_valid
|
||||
assert (
|
||||
"Cannot specify both --github-app-key and --github-app-key-path simultaneously"
|
||||
in error_msg
|
||||
)
|
||||
|
||||
def test_validate_arguments_app_id_without_key(self):
|
||||
"""Test validation fails when app ID is provided without any key"""
|
||||
from argparse import Namespace
|
||||
|
||||
args = Namespace()
|
||||
args.github_app_key = None
|
||||
args.github_app_key_path = None
|
||||
args.github_app_id = "12345"
|
||||
|
||||
is_valid, error_msg = arguments.validate_arguments(args)
|
||||
|
||||
assert not is_valid
|
||||
assert (
|
||||
"GitHub App ID requires either --github-app-key or --github-app-key-path"
|
||||
in error_msg
|
||||
)
|
||||
|
||||
def test_validate_arguments_valid_key_content(self):
|
||||
"""Test validation passes with valid key content"""
|
||||
from argparse import Namespace
|
||||
|
||||
args = Namespace()
|
||||
args.github_app_key = "-----BEGIN RSA PRIVATE KEY-----"
|
||||
args.github_app_key_path = None
|
||||
args.github_app_id = "12345"
|
||||
|
||||
is_valid, error_msg = arguments.validate_arguments(args)
|
||||
|
||||
assert is_valid
|
||||
assert error_msg == ""
|
||||
|
||||
def test_validate_arguments_valid_key_path(self):
|
||||
"""Test validation passes with valid key path"""
|
||||
from argparse import Namespace
|
||||
|
||||
args = Namespace()
|
||||
args.github_app_key = None
|
||||
args.github_app_key_path = "/path/to/key.pem"
|
||||
args.github_app_id = "12345"
|
||||
|
||||
is_valid, error_msg = arguments.validate_arguments(args)
|
||||
|
||||
assert is_valid
|
||||
assert error_msg == ""
|
||||
|
||||
def test_validate_arguments_no_app_auth(self):
|
||||
"""Test validation passes when no app authentication is used"""
|
||||
from argparse import Namespace
|
||||
|
||||
args = Namespace()
|
||||
args.github_app_key = None
|
||||
args.github_app_key_path = None
|
||||
args.github_app_id = None
|
||||
|
||||
is_valid, error_msg = arguments.validate_arguments(args)
|
||||
|
||||
assert is_valid
|
||||
assert error_msg == ""
|
||||
|
||||
Reference in New Issue
Block a user