mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
fix(github): handle GithubAppIdentityInfo in output generation (#8423)
Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user