feat(github): add User Email and APP name/installations information (#8501)

Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
Hugo Pereira Brito
2025-08-20 12:26:38 +02:00
committed by GitHub
parent 55099abc86
commit 89e657561c
9 changed files with 123 additions and 38 deletions
+1
View File
@@ -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)
+6 -4
View File
@@ -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
+62 -13
View File
@@ -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("<", "&lt;").replace(">", "&gt;").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>"""
+10 -4
View File
@@ -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 = (
+4
View File
@@ -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]
+3 -3
View File
@@ -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
+32 -12
View File
@@ -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"]
),
),
):