diff --git a/prowler/lib/outputs/finding.py b/prowler/lib/outputs/finding.py index 70188742d8..0f8f8262eb 100644 --- a/prowler/lib/outputs/finding.py +++ b/prowler/lib/outputs/finding.py @@ -453,6 +453,9 @@ class Finding(BaseModel): f"{output_data['region']}-{output_data['resource_name']}" ) + if provider.type == "iac" and output_data.get("resource_line_range"): + output_data["uid"] += f"-{output_data['resource_line_range']}" + if not output_data["resource_uid"]: logger.error( f"Check {check_output.check_metadata.CheckID} has no resource_uid." diff --git a/prowler/lib/scan/scan.py b/prowler/lib/scan/scan.py index 0b38f16b1b..2ce7263e2b 100644 --- a/prowler/lib/scan/scan.py +++ b/prowler/lib/scan/scan.py @@ -302,7 +302,12 @@ class Scan: for report in iac_reports: # Generate unique UID for the finding - finding_uid = f"{report.check_metadata.CheckID}-{report.resource_name}-{report.resource_line_range}" + finding_uid = ( + f"prowler-iac-{report.check_metadata.CheckID}-iac-" + f"{report.region}-{report.resource_name}" + ) + if report.resource_line_range: + finding_uid += f"-{report.resource_line_range}" # Convert status string to Status enum status_enum = ( diff --git a/tests/lib/outputs/finding_test.py b/tests/lib/outputs/finding_test.py index 0da8be6a9a..beed1056f6 100644 --- a/tests/lib/outputs/finding_test.py +++ b/tests/lib/outputs/finding_test.py @@ -723,6 +723,10 @@ class TestFinding: assert finding_output.resource_name == "aws_s3_bucket.example" assert finding_output.resource_uid == "aws_s3_bucket.example" assert finding_output.region == "main" # Branch name, not line range + assert ( + finding_output.uid + == "prowler-iac-service_check_id-iac-main-aws_s3_bucket.example-1:5" + ) assert finding_output.status == Status.PASS assert finding_output.status_extended == "mock_status_extended" assert finding_output.muted is False @@ -737,6 +741,35 @@ class TestFinding: assert finding_output.metadata.SubServiceName == "" assert finding_output.metadata.ResourceIdTemplate == "" + def test_generate_output_iac_empty_line_range(self): + provider = MagicMock() + provider.type = "iac" + provider.provider_uid = None + provider.scan_repository_url = "https://github.com/user/repo" + provider.auth_method = "No auth" + + check_output = MagicMock() + check_output.file_path = "/path/to/iac/main.tf" + check_output.resource_name = "main.tf" + check_output.resource_path = "/path/to/iac/main.tf" + check_output.resource_line_range = "" + check_output.region = "main" + check_output.resource = {"resource": "main.tf", "value": {}} + check_output.resource_details = "" + check_output.status = Status.PASS + check_output.status_extended = "No issues found" + check_output.muted = False + check_output.check_metadata = mock_check_metadata(provider="iac") + check_output.compliance = {} + + output_options = MagicMock() + output_options.unix_timestamp = False + + finding_output = Finding.generate_output(provider, check_output, output_options) + + assert isinstance(finding_output, Finding) + assert finding_output.uid == "prowler-iac-service_check_id-iac-main-main.tf" + def assert_keys_lowercase(self, d): for k, v in d.items(): assert k.islower()