fix(github): handle GithubAppIdentityInfo in output generation (#8423)

Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
This commit is contained in:
Andoni Alonso
2025-08-06 02:55:44 -07:00
committed by GitHub
parent 5f53a9ec6f
commit fc69e195e4
5 changed files with 243 additions and 3 deletions
+1
View File
@@ -26,6 +26,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Use the correct @staticmethod decorator for `set_identity` and `set_session_config` methods in AwsProvider [(#8056)](https://github.com/prowler-cloud/prowler/pull/8056)
- Use the correct default value for `role_session_name` and `session_duration` in AwsSetUpSession [(#8056)](https://github.com/prowler-cloud/prowler/pull/8056)
- Use the correct default value for `role_session_name` and `session_duration` in S3 [(#8417)](https://github.com/prowler-cloud/prowler/pull/8417)
- GitHub App authentication fails to generate output files and HTML header sections [(#8423)](https://github.com/prowler-cloud/prowler/pull/8423)
---
+11 -2
View File
@@ -249,8 +249,17 @@ class Finding(BaseModel):
output_data["auth_method"] = provider.auth_method
output_data["resource_name"] = check_output.resource_name
output_data["resource_uid"] = check_output.resource_id
output_data["account_name"] = provider.identity.account_name
output_data["account_uid"] = provider.identity.account_id
if hasattr(provider.identity, "account_name"):
# 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"):
# GithubAppIdentityInfo (GitHub App)
# TODO: Get Github App name
output_data["account_name"] = f"app-{provider.identity.app_id}"
output_data["account_uid"] = provider.identity.app_id
output_data["region"] = check_output.owner
elif provider.type == "m365":
+8 -1
View File
@@ -556,6 +556,13 @@ class HTML(Output):
str: the HTML assessment summary
"""
try:
if hasattr(provider.identity, "account_name"):
# GithubIdentityInfo (Personal Access Token, OAuth)
account_display = provider.identity.account_name
elif hasattr(provider.identity, "app_id"):
# GithubAppIdentityInfo (GitHub App)
account_display = f"app-{provider.identity.app_id}"
return f"""
<div class="col-md-2">
<div class="card">
@@ -565,7 +572,7 @@ class HTML(Output):
<ul class="list-group
list-group-flush">
<li class="list-group-item">
<b>GitHub account:</b> {provider.identity.account_name}
<b>GitHub account:</b> {account_display}
</li>
</ul>
</div>
+141
View File
@@ -14,7 +14,14 @@ from prowler.lib.check.models import (
)
from prowler.lib.outputs.common import Status
from prowler.lib.outputs.finding import Finding
from prowler.providers.github.models import GithubAppIdentityInfo
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
from tests.providers.github.github_fixtures import (
ACCOUNT_ID,
ACCOUNT_NAME,
ACCOUNT_URL,
APP_ID,
)
def mock_check_metadata(provider):
@@ -506,6 +513,140 @@ class TestFinding:
assert finding_output.metadata.Notes == "mock_notes"
assert finding_output.metadata.Compliance == []
def test_generate_output_github_personal_access_token(self):
"""Test GitHub output generation with Personal Access Token authentication."""
# Mock provider using Personal Access Token
provider = MagicMock()
provider.type = "github"
# Use the actual GithubIdentityInfo for Personal Access Token
from prowler.providers.github.models import GithubIdentityInfo
provider.identity = GithubIdentityInfo(
account_name=ACCOUNT_NAME, account_id=ACCOUNT_ID, account_url=ACCOUNT_URL
)
provider.auth_method = "Personal Access Token"
# Mock check result
check_output = MagicMock()
check_output.resource_id = "test_repository"
check_output.resource_name = "test_repository"
check_output.resource_details = "GitHub repository test_repository"
check_output.resource_tags = {"topic": "security"}
check_output.owner = "test-owner" # GitHub uses owner for region
check_output.status = Status.PASS
check_output.status_extended = "Repository has security features enabled"
check_output.muted = False
check_output.check_metadata = mock_check_metadata(provider="github")
check_output.resource = {"url": "https://github.com/owner/test_repository"}
check_output.compliance = {
"CIS-2.0": ["1.12"],
"ENS-RD2022": ["op.acc.2.gcp.rbak.1"],
}
# Mock output options
output_options = MagicMock()
output_options.unix_timestamp = False
# Generate the finding
finding_output = Finding.generate_output(provider, check_output, output_options)
# Assert basic finding properties
assert isinstance(finding_output, Finding)
assert finding_output.provider == "github"
assert finding_output.auth_method == "Personal Access Token"
assert finding_output.resource_name == "test_repository"
assert finding_output.resource_uid == "test_repository"
assert finding_output.region == "test-owner"
assert finding_output.status == Status.PASS
assert (
finding_output.status_extended == "Repository has security features enabled"
)
assert finding_output.muted is False
assert finding_output.resource_tags == {"topic": "security"}
# Assert account information for Personal Access Token
assert finding_output.account_name == ACCOUNT_NAME
assert finding_output.account_uid == ACCOUNT_ID
assert finding_output.account_email is None
assert finding_output.account_organization_uid is None
assert finding_output.account_organization_name is None
assert finding_output.account_tags == {}
# Metadata checks
assert finding_output.metadata.Provider == "github"
assert finding_output.metadata.CheckID == "service_check_id"
assert finding_output.metadata.ServiceName == "service"
assert finding_output.metadata.Severity == Severity.high
assert finding_output.metadata.ResourceType == "mock_resource_type"
def test_generate_output_github_app_authentication(self):
"""Test GitHub output generation with GitHub App authentication."""
# Mock provider using GitHub App authentication - this is the key test case for the bug fix
provider = MagicMock()
provider.type = "github"
# GitHub App identity only has app_id, not account_name/account_id
provider.identity = GithubAppIdentityInfo(app_id=APP_ID)
provider.auth_method = "GitHub App Token"
# Mock check result
check_output = MagicMock()
check_output.resource_id = "test_repository"
check_output.resource_name = "test_repository"
check_output.resource_details = "GitHub repository test_repository"
check_output.resource_tags = {"language": "python"}
check_output.owner = "test-owner" # GitHub provider uses owner for region
check_output.status = Status.FAIL
check_output.status_extended = (
"Repository lacks required security configuration"
)
check_output.muted = False
check_output.check_metadata = mock_check_metadata(provider="github")
check_output.resource = {"url": "https://github.com/org/test_repository"}
check_output.compliance = {
"CIS-2.0": ["1.12"],
"MITRE-ATTACK": ["T1098"],
}
# Mock output options
output_options = MagicMock()
output_options.unix_timestamp = True
# Generate the finding - this was failing before the fix
finding_output = Finding.generate_output(provider, check_output, output_options)
# Assert basic finding properties
assert isinstance(finding_output, Finding)
assert finding_output.provider == "github"
assert finding_output.auth_method == "GitHub App Token"
assert finding_output.resource_name == "test_repository"
assert finding_output.resource_uid == "test_repository"
assert finding_output.region == "test-owner"
assert finding_output.status == Status.FAIL
assert (
finding_output.status_extended
== "Repository lacks required security configuration"
)
assert finding_output.muted is False
assert finding_output.resource_tags == {"language": "python"}
assert isinstance(finding_output.timestamp, int)
# 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}"
assert finding_output.account_uid == APP_ID
assert finding_output.account_email is None
assert finding_output.account_organization_uid is None
assert finding_output.account_organization_name is None
assert finding_output.account_tags == {}
# Metadata checks
assert finding_output.metadata.Provider == "github"
assert finding_output.metadata.CheckID == "service_check_id"
assert finding_output.metadata.ServiceName == "service"
assert finding_output.metadata.Severity == Severity.high
assert finding_output.metadata.ResourceType == "mock_resource_type"
def test_generate_output_iac_remote(self):
# Mock provider
provider = MagicMock()
+82
View File
@@ -5,10 +5,12 @@ from mock import patch
from prowler.config.config import prowler_version, timestamp
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
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider
from tests.providers.azure.azure_fixtures import set_mocked_azure_provider
from tests.providers.gcp.gcp_fixtures import GCP_PROJECT_ID, set_mocked_gcp_provider
from tests.providers.github.github_fixtures import APP_ID, set_mocked_github_provider
from tests.providers.kubernetes.kubernetes_fixtures import (
set_mocked_kubernetes_provider,
)
@@ -223,6 +225,62 @@ kubernetes_html_assessment_summary = """
</div>
</div>"""
github_personal_access_token_html_assessment_summary = """
<div class="col-md-2">
<div class="card">
<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-name
</li>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
GitHub Credentials
</div>
<ul class="list-group
list-group-flush">
<li class="list-group-item">
<b>GitHub authentication method:</b> Personal Access Token
</li>
</ul>
</div>
</div>"""
github_app_html_assessment_summary = """
<div class="col-md-2">
<div class="card">
<div class="card-header">
GitHub Assessment Summary
</div>
<ul class="list-group
list-group-flush">
<li class="list-group-item">
<b>GitHub account:</b> app-app-id
</li>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
GitHub Credentials
</div>
<ul class="list-group
list-group-flush">
<li class="list-group-item">
<b>GitHub authentication method:</b> GitHub App Token
</li>
</ul>
</div>
</div>"""
m365_html_assessment_summary = """
<div class="col-md-2">
<div class="card">
@@ -597,3 +655,27 @@ class TestHTML:
expected_summary = m365_html_assessment_summary
assert summary == expected_summary
def test_github_personal_access_token_get_assessment_summary(self):
"""Test GitHub HTML assessment summary generation with Personal Access Token authentication."""
findings = [generate_finding_output()]
output = HTML(findings)
provider = set_mocked_github_provider(auth_method="Personal Access Token")
summary = output.get_assessment_summary(provider)
assert summary == github_personal_access_token_html_assessment_summary
def test_github_app_get_assessment_summary(self):
"""Test GitHub HTML assessment summary generation with GitHub App authentication."""
findings = [generate_finding_output()]
output = HTML(findings)
provider = set_mocked_github_provider(
auth_method="GitHub App Token",
identity=GithubAppIdentityInfo(app_id=APP_ID),
)
summary = output.get_assessment_summary(provider)
assert summary == github_app_html_assessment_summary