mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
feat(github): add User Email and APP name/installations information (#8501)
Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
committed by
GitHub
parent
55099abc86
commit
89e657561c
@@ -9,6 +9,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `vm_sufficient_daily_backup_retention_period` check for Azure provider [(#8200)](https://github.com/prowler-cloud/prowler/pull/8200)
|
||||
- `vm_jit_access_enabled` check for Azure provider [(#8202)](https://github.com/prowler-cloud/prowler/pull/8202)
|
||||
- Bedrock AgentCore privilege escalation combination for AWS provider [(#8526)](https://github.com/prowler-cloud/prowler/pull/8526)
|
||||
- Add User Email and APP name/installations information in GitHub provider [(#8501)](https://github.com/prowler-cloud/prowler/pull/8501)
|
||||
- Remove standalone iam:PassRole from privesc detection and add missing patterns [(#8530)](https://github.com/prowler-cloud/prowler/pull/8530)
|
||||
- `eks_cluster_deletion_protection_enabled` check for AWS provider [(#8536)](https://github.com/prowler-cloud/prowler/pull/8536)
|
||||
- ECS privilege escalation patterns (StartTask and RunTask) for AWS provider [(#8541)](https://github.com/prowler-cloud/prowler/pull/8541)
|
||||
|
||||
@@ -19,6 +19,7 @@ from prowler.lib.outputs.compliance.compliance import get_check_compliance
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
from prowler.lib.utils.utils import dict_to_lowercase, get_nested_attribute
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.github.models import GithubAppIdentityInfo, GithubIdentityInfo
|
||||
|
||||
|
||||
class Finding(BaseModel):
|
||||
@@ -250,15 +251,16 @@ class Finding(BaseModel):
|
||||
output_data["resource_name"] = check_output.resource_name
|
||||
output_data["resource_uid"] = check_output.resource_id
|
||||
|
||||
if hasattr(provider.identity, "account_name"):
|
||||
if isinstance(provider.identity, GithubIdentityInfo):
|
||||
# GithubIdentityInfo (Personal Access Token, OAuth)
|
||||
output_data["account_name"] = provider.identity.account_name
|
||||
output_data["account_uid"] = provider.identity.account_id
|
||||
elif hasattr(provider.identity, "app_id"):
|
||||
output_data["account_email"] = provider.identity.account_email
|
||||
elif isinstance(provider.identity, GithubAppIdentityInfo):
|
||||
# GithubAppIdentityInfo (GitHub App)
|
||||
# TODO: Get Github App name
|
||||
output_data["account_name"] = f"app-{provider.identity.app_id}"
|
||||
output_data["account_name"] = provider.identity.app_name
|
||||
output_data["account_uid"] = provider.identity.app_id
|
||||
output_data["installations"] = provider.identity.installations
|
||||
|
||||
output_data["region"] = check_output.owner
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class HTML(Output):
|
||||
<td>{finding_status}</td>
|
||||
<td>{finding.metadata.Severity.value}</td>
|
||||
<td>{finding.metadata.ServiceName}</td>
|
||||
<td>{":".join([finding.resource_metadata['file_path'], "-".join(map(str, finding.resource_metadata['file_line_range']))]) if finding.metadata.Provider == "iac" else finding.region.lower()}</td>
|
||||
<td>{":".join([finding.resource_metadata["file_path"], "-".join(map(str, finding.resource_metadata["file_line_range"]))]) if finding.metadata.Provider == "iac" else finding.region.lower()}</td>
|
||||
<td>{finding.metadata.CheckID.replace("_", "<wbr />_")}</td>
|
||||
<td>{finding.metadata.CheckTitle}</td>
|
||||
<td>{finding.resource_uid.replace("<", "<").replace(">", ">").replace("_", "<wbr />_")}</td>
|
||||
@@ -558,10 +558,65 @@ class HTML(Output):
|
||||
try:
|
||||
if hasattr(provider.identity, "account_name"):
|
||||
# GithubIdentityInfo (Personal Access Token, OAuth)
|
||||
account_display = provider.identity.account_name
|
||||
account_info_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub account:</b> {provider.identity.account_name}
|
||||
</li>
|
||||
"""
|
||||
# Add email if available
|
||||
if (
|
||||
hasattr(provider.identity, "account_email")
|
||||
and provider.identity.account_email
|
||||
):
|
||||
account_info_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub account email:</b> {provider.identity.account_email}
|
||||
</li>"""
|
||||
elif hasattr(provider.identity, "app_id"):
|
||||
# GithubAppIdentityInfo (GitHub App)
|
||||
account_display = f"app-{provider.identity.app_id}"
|
||||
# Assessment items: App Name and Installations
|
||||
account_info_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub App Name:</b> {provider.identity.app_name}
|
||||
</li>"""
|
||||
# Add installations if available
|
||||
if (
|
||||
hasattr(provider.identity, "installations")
|
||||
and provider.identity.installations
|
||||
):
|
||||
installations_display = ", ".join(provider.identity.installations)
|
||||
account_info_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Installations:</b> {installations_display}
|
||||
</li>"""
|
||||
else:
|
||||
account_info_items += """
|
||||
<li class="list-group-item">
|
||||
<b>Installations:</b> No installations found
|
||||
</li>"""
|
||||
|
||||
# Credentials items: Authentication method and App ID
|
||||
credentials_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> {provider.auth_method}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>GitHub App ID:</b> {provider.identity.app_id}
|
||||
</li>"""
|
||||
else:
|
||||
# Fallback for other identity types
|
||||
account_info_items = ""
|
||||
credentials_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> {provider.auth_method}
|
||||
</li>"""
|
||||
|
||||
# For PAT/OAuth, use default credentials structure
|
||||
if hasattr(provider.identity, "account_name"):
|
||||
credentials_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> {provider.auth_method}
|
||||
</li>"""
|
||||
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
@@ -569,11 +624,8 @@ class HTML(Output):
|
||||
<div class="card-header">
|
||||
GitHub Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GitHub account:</b> {account_display}
|
||||
</li>
|
||||
<ul class="list-group list-group-flush">
|
||||
{account_info_items}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -582,11 +634,8 @@ class HTML(Output):
|
||||
<div class="card-header">
|
||||
GitHub Credentials
|
||||
</div>
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> {provider.auth_method}
|
||||
</li>
|
||||
<ul class="list-group list-group-flush">
|
||||
{credentials_items}
|
||||
</ul>
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
@@ -350,10 +350,12 @@ class GithubProvider(Provider):
|
||||
auth = Auth.Token(session.token)
|
||||
g = Github(auth=auth, retry=retry_config)
|
||||
try:
|
||||
user = g.get_user()
|
||||
identity = GithubIdentityInfo(
|
||||
account_id=g.get_user().id,
|
||||
account_name=g.get_user().login,
|
||||
account_url=g.get_user().url,
|
||||
account_id=user.id,
|
||||
account_name=user.login,
|
||||
account_url=user.url,
|
||||
account_email=user.get_emails()[0].email,
|
||||
)
|
||||
return identity
|
||||
|
||||
@@ -371,8 +373,10 @@ class GithubProvider(Provider):
|
||||
installation.raw_data.get("account", {}).get("login")
|
||||
)
|
||||
try:
|
||||
app = gi.get_app()
|
||||
identity = GithubAppIdentityInfo(
|
||||
app_id=gi.get_app().id,
|
||||
app_id=app.id,
|
||||
app_name=app.name,
|
||||
installations=installations,
|
||||
)
|
||||
return identity
|
||||
@@ -401,11 +405,13 @@ class GithubProvider(Provider):
|
||||
report_lines = [
|
||||
f"GitHub Account: {Fore.YELLOW}{self.identity.account_name}{Style.RESET_ALL}",
|
||||
f"GitHub Account ID: {Fore.YELLOW}{self.identity.account_id}{Style.RESET_ALL}",
|
||||
f"GitHub Account Email: {Fore.YELLOW}{self.identity.account_email}{Style.RESET_ALL}",
|
||||
f"Authentication Method: {Fore.YELLOW}{self.auth_method}{Style.RESET_ALL}",
|
||||
]
|
||||
elif isinstance(self.identity, GithubAppIdentityInfo):
|
||||
report_lines = [
|
||||
f"GitHub App ID: {Fore.YELLOW}{self.identity.app_id}{Style.RESET_ALL}",
|
||||
f"GitHub App Name: {Fore.YELLOW}{self.identity.app_name}{Style.RESET_ALL}",
|
||||
f"Authentication Method: {Fore.YELLOW}{self.auth_method}{Style.RESET_ALL}",
|
||||
]
|
||||
report_title = (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.v1 import BaseModel
|
||||
|
||||
from prowler.config.config import output_file_timestamp
|
||||
@@ -14,10 +16,12 @@ class GithubIdentityInfo(BaseModel):
|
||||
account_id: str
|
||||
account_name: str
|
||||
account_url: str
|
||||
account_email: Optional[str] = None
|
||||
|
||||
|
||||
class GithubAppIdentityInfo(BaseModel):
|
||||
app_id: str
|
||||
app_name: str
|
||||
installations: list[str]
|
||||
|
||||
|
||||
|
||||
@@ -586,7 +586,7 @@ class TestFinding:
|
||||
provider.type = "github"
|
||||
# GitHub App identity only has app_id, not account_name/account_id
|
||||
provider.identity = GithubAppIdentityInfo(
|
||||
app_id=APP_ID, installations=["test-org"]
|
||||
app_id=APP_ID, app_name="test-app", installations=["test-org"]
|
||||
)
|
||||
provider.auth_method = "GitHub App Token"
|
||||
|
||||
@@ -634,8 +634,8 @@ class TestFinding:
|
||||
|
||||
# Assert account information for GitHub App - this is the core of the bug fix
|
||||
# Before the fix, this would fail because GithubAppIdentityInfo doesn't have account_name
|
||||
# After the fix, it should use app_id with "app-" prefix
|
||||
assert finding_output.account_name == f"app-{APP_ID}"
|
||||
# After the fix, it should use app_name
|
||||
assert finding_output.account_name == "test-app"
|
||||
assert finding_output.account_uid == APP_ID
|
||||
assert finding_output.account_email is None
|
||||
assert finding_output.account_organization_uid is None
|
||||
|
||||
@@ -4,6 +4,7 @@ from io import StringIO
|
||||
from mock import patch
|
||||
|
||||
from prowler.config.config import prowler_version, timestamp
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.outputs.html.html import HTML
|
||||
from prowler.providers.github.models import GithubAppIdentityInfo
|
||||
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
|
||||
@@ -231,11 +232,12 @@ github_personal_access_token_html_assessment_summary = """
|
||||
<div class="card-header">
|
||||
GitHub Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<ul class="list-group list-group-flush">
|
||||
|
||||
<li class="list-group-item">
|
||||
<b>GitHub account:</b> account-name
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,8 +246,8 @@ github_personal_access_token_html_assessment_summary = """
|
||||
<div class="card-header">
|
||||
GitHub Credentials
|
||||
</div>
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<ul class="list-group list-group-flush">
|
||||
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> Personal Access Token
|
||||
</li>
|
||||
@@ -259,10 +261,12 @@ github_app_html_assessment_summary = """
|
||||
<div class="card-header">
|
||||
GitHub Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GitHub account:</b> app-app-id
|
||||
<b>GitHub App Name:</b> test-app
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Installations:</b> test-org
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -272,11 +276,13 @@ github_app_html_assessment_summary = """
|
||||
<div class="card-header">
|
||||
GitHub Credentials
|
||||
</div>
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> GitHub App Token
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>GitHub App ID:</b> app-id
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>"""
|
||||
@@ -664,7 +670,12 @@ class TestHTML:
|
||||
|
||||
summary = output.get_assessment_summary(provider)
|
||||
|
||||
assert summary == github_personal_access_token_html_assessment_summary
|
||||
# Check for expected content in the summary
|
||||
assert "GitHub Assessment Summary" in summary
|
||||
assert "GitHub Credentials" in summary
|
||||
assert "<b>GitHub account:</b> account-name" in summary
|
||||
assert "<b>GitHub authentication method:</b> Personal Access Token" in summary
|
||||
# Note: account_email is None in the default fixture, so it shouldn't appear
|
||||
|
||||
def test_github_app_get_assessment_summary(self):
|
||||
"""Test GitHub HTML assessment summary generation with GitHub App authentication."""
|
||||
@@ -673,9 +684,18 @@ class TestHTML:
|
||||
|
||||
provider = set_mocked_github_provider(
|
||||
auth_method="GitHub App Token",
|
||||
identity=GithubAppIdentityInfo(app_id=APP_ID, installations=["test-org"]),
|
||||
identity=GithubAppIdentityInfo(
|
||||
app_id=APP_ID, app_name="test-app", installations=["test-org"]
|
||||
),
|
||||
)
|
||||
|
||||
summary = output.get_assessment_summary(provider)
|
||||
logger.error(summary)
|
||||
|
||||
assert summary == github_app_html_assessment_summary
|
||||
# Check for expected content in the summary
|
||||
assert "GitHub Assessment Summary" in summary
|
||||
assert "GitHub Credentials" in summary
|
||||
assert "<b>GitHub App Name:</b> test-app" in summary
|
||||
assert "<b>Installations:</b> test-org" in summary
|
||||
assert "<b>GitHub authentication method:</b> GitHub App Token" in summary
|
||||
assert f"<b>GitHub App ID:</b> {APP_ID}" in summary
|
||||
|
||||
@@ -12,6 +12,7 @@ ACCOUNT_URL = "/user"
|
||||
PAT_TOKEN = "github-token"
|
||||
OAUTH_TOKEN = "oauth-token"
|
||||
APP_ID = "app-id"
|
||||
APP_NAME = "app-name"
|
||||
APP_KEY = "app-key"
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ from tests.providers.github.github_fixtures import (
|
||||
ACCOUNT_URL,
|
||||
APP_ID,
|
||||
APP_KEY,
|
||||
APP_NAME,
|
||||
OAUTH_TOKEN,
|
||||
PAT_TOKEN,
|
||||
)
|
||||
@@ -135,6 +136,7 @@ class TestGitHubProvider:
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_identity",
|
||||
return_value=GithubAppIdentityInfo(
|
||||
app_id=APP_ID,
|
||||
app_name=APP_NAME,
|
||||
installations=["test-org"],
|
||||
),
|
||||
),
|
||||
@@ -149,7 +151,7 @@ class TestGitHubProvider:
|
||||
assert provider._type == "github"
|
||||
assert provider.session == GithubSession(token="", id=APP_ID, key=APP_KEY)
|
||||
assert provider.identity == GithubAppIdentityInfo(
|
||||
app_id=APP_ID, installations=["test-org"]
|
||||
app_id=APP_ID, app_name=APP_NAME, installations=["test-org"]
|
||||
)
|
||||
assert provider._audit_config == {
|
||||
"inactive_not_archived_days_threshold": 180,
|
||||
@@ -210,7 +212,7 @@ class TestGitHubProvider:
|
||||
patch(
|
||||
"prowler.providers.github.github_provider.GithubProvider.setup_identity",
|
||||
return_value=GithubAppIdentityInfo(
|
||||
app_id=APP_ID, installations=["test-org"]
|
||||
app_id=APP_ID, app_name=APP_NAME, installations=["test-org"]
|
||||
),
|
||||
),
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user