diff --git a/mkdocs.yml b/mkdocs.yml index dd04e16881..b3ee70140e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -124,7 +124,7 @@ extra: make our documentation better. analytics: provider: google - property: G-H5TFH6WJRQ + property: G-KBKV70W5Y2 social: - icon: fontawesome/brands/github link: https://github.com/prowler-cloud diff --git a/prowler/__main__.py b/prowler/__main__.py index d1aee094eb..c01f2a0a56 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -332,18 +332,21 @@ def prowler(): # Outputs # TODO: this part is needed since the checks generates a Check_Report_XXX and the output uses Finding # This will be refactored for the outputs generate directly the Finding - finding_outputs = [ - Finding.generate_output(global_provider, finding, output_options) - for finding in findings - ] + finding_outputs = [] + for finding in findings: + try: + finding_outputs.append( + Finding.generate_output(global_provider, finding, output_options) + ) + except Exception: + continue generated_outputs = {"regular": [], "compliance": []} if args.output_formats: for mode in args.output_formats: filename = ( - f"{output_options.output_directory}/" - f"{output_options.output_filename}" + f"{output_options.output_directory}/{output_options.output_filename}" ) if mode == "csv": csv_output = CSV( diff --git a/prowler/lib/outputs/finding.py b/prowler/lib/outputs/finding.py index 88db5bef38..62c5cfe47d 100644 --- a/prowler/lib/outputs/finding.py +++ b/prowler/lib/outputs/finding.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ValidationError from prowler.config.config import prowler_version from prowler.lib.check.models import Check_Report, CheckMetadata @@ -257,7 +257,13 @@ class Finding(BaseModel): ) return cls(**output_data) + except ValidationError as validation_error: + logger.error( + f"{validation_error.__class__.__name__}[{validation_error.__traceback__.tb_lineno}]: {validation_error} - {output_data}" + ) + raise validation_error except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + raise error diff --git a/prowler/lib/scan/scan.py b/prowler/lib/scan/scan.py index 8c40db55a8..b5b621f28e 100644 --- a/prowler/lib/scan/scan.py +++ b/prowler/lib/scan/scan.py @@ -296,12 +296,16 @@ class Scan: self.get_completed_checks(), ) - findings = [ - Finding.generate_output( - self._provider, finding, output_options=None - ) - for finding in check_findings - ] + findings = [] + for finding in check_findings: + try: + findings.append( + Finding.generate_output( + self._provider, finding, output_options=None + ) + ) + except Exception: + continue yield self.progress, findings # If check does not exists in the provider or is from another provider diff --git a/prowler/providers/aws/services/acm/acm_service.py b/prowler/providers/aws/services/acm/acm_service.py index ad69a32839..cb9996831c 100644 --- a/prowler/providers/aws/services/acm/acm_service.py +++ b/prowler/providers/aws/services/acm/acm_service.py @@ -57,7 +57,7 @@ class ACM(AWSService): certificate_expiration_time = 0 self.certificates[certificate["CertificateArn"]] = Certificate( arn=certificate["CertificateArn"], - name=certificate["DomainName"], + name=certificate.get("DomainName", ""), id=certificate["CertificateArn"].split("/")[-1], type=certificate["Type"], key_algorithm=certificate["KeyAlgorithm"], diff --git a/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.py b/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.py index 0734ddb062..fc6ceb9c61 100644 --- a/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.py +++ b/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.py @@ -15,7 +15,7 @@ class iam_user_with_temporary_credentials(Check): report = Check_Report_AWS( metadata=self.metadata(), - resource=iam_client.user_temporary_credentials_usage, + resource={"name": user_name, "arn": user_arn}, ) report.resource_id = user_name report.resource_arn = user_arn diff --git a/prowler/providers/aws/services/sns/sns_service.py b/prowler/providers/aws/services/sns/sns_service.py index 81474b4de6..a2f2d3be29 100644 --- a/prowler/providers/aws/services/sns/sns_service.py +++ b/prowler/providers/aws/services/sns/sns_service.py @@ -97,6 +97,7 @@ class SNS(AWSService): owner=sub["Owner"], protocol=sub["Protocol"], endpoint=sub["Endpoint"], + region=sub["SubscriptionArn"].split(":")[3], ) for sub in response["Subscriptions"] ] @@ -117,6 +118,7 @@ class Subscription(BaseModel): owner: str protocol: str endpoint: str + region: str class Topic(BaseModel): diff --git a/tests/lib/outputs/finding_test.py b/tests/lib/outputs/finding_test.py index 98af298906..6ae17416d0 100644 --- a/tests/lib/outputs/finding_test.py +++ b/tests/lib/outputs/finding_test.py @@ -1,6 +1,9 @@ from datetime import datetime from unittest.mock import MagicMock, patch +import pytest +from pydantic import ValidationError + from prowler.lib.check.models import ( CheckMetadata, Code, @@ -417,3 +420,44 @@ class TestFinding: assert metadata is not None assert isinstance(metadata, dict) self.assert_keys_lowercase(metadata) + + @patch( + "prowler.lib.outputs.finding.get_check_compliance", + new=mock_get_check_compliance, + ) + def test_generate_output_validation_error(self): + # Mock provider + provider = MagicMock() + provider.type = "aws" + provider.identity.profile = "mock_auth" + provider.identity.account = "mock_account_uid" + provider.identity.partition = "aws" + provider.organizations_metadata.account_name = "mock_account_name" + provider.organizations_metadata.account_email = "mock_account_email" + provider.organizations_metadata.organization_arn = "mock_account_org_uid" + provider.organizations_metadata.organization_id = "mock_account_org_name" + provider.organizations_metadata.account_tags = {"tag1": "value1"} + + # Mock check result + check_output = MagicMock() + check_output.resource_id = "test_resource_id" + check_output.resource_arn = "test_resource_arn" + check_output.resource_details = "test_resource_details" + check_output.resource_tags = {"tag1": "value1"} + check_output.region = "us-west-1" + check_output.partition = "aws" + check_output.status_extended = "mock_status_extended" + check_output.muted = False + check_output.check_metadata = mock_check_metadata(provider="aws") + check_output.resource = {} + + # Mock output options + output_options = MagicMock() + output_options.unix_timestamp = False + + # Bad Status Value + check_output.status = "Invalid" + + # Generate the finding + with pytest.raises(ValidationError): + Finding.generate_output(provider, check_output, output_options) diff --git a/tests/providers/aws/services/sns/sns_subscription_not_using_http_endpoints/sns_subscription_not_using_http_endpoints_test.py b/tests/providers/aws/services/sns/sns_subscription_not_using_http_endpoints/sns_subscription_not_using_http_endpoints_test.py index 456c09db8c..ac4be798e5 100644 --- a/tests/providers/aws/services/sns/sns_subscription_not_using_http_endpoints/sns_subscription_not_using_http_endpoints_test.py +++ b/tests/providers/aws/services/sns/sns_subscription_not_using_http_endpoints/sns_subscription_not_using_http_endpoints_test.py @@ -65,6 +65,7 @@ class Test_sns_subscription_not_using_http_endpoints: owner=AWS_ACCOUNT_NUMBER, protocol="https", endpoint="https://www.endpoint.com", + region=AWS_REGION_EU_WEST_1, ) ) sns_client.topics = [] @@ -100,6 +101,7 @@ class Test_sns_subscription_not_using_http_endpoints: owner=AWS_ACCOUNT_NUMBER, protocol="https", endpoint="https://www.endpoint.com", + region=AWS_REGION_EU_WEST_1, ) ) sns_client.topics = [] @@ -131,6 +133,7 @@ class Test_sns_subscription_not_using_http_endpoints: ) assert result[0].resource_id == subscription_id_1 assert result[0].resource_arn == subscription_arn_1 + assert result[0].region == AWS_REGION_EU_WEST_1 def test_subscriptions_with_http(self): sns_client = mock.MagicMock @@ -142,6 +145,7 @@ class Test_sns_subscription_not_using_http_endpoints: owner=AWS_ACCOUNT_NUMBER, protocol="http", endpoint="http://www.endpoint.com", + region=AWS_REGION_EU_WEST_1, ) ) sns_client.topics = [] @@ -173,6 +177,7 @@ class Test_sns_subscription_not_using_http_endpoints: ) assert result[0].resource_id == subscription_id_2 assert result[0].resource_arn == subscription_arn_2 + assert result[0].region == AWS_REGION_EU_WEST_1 def test_subscriptions_with_http_and_https(self): sns_client = mock.MagicMock @@ -184,6 +189,7 @@ class Test_sns_subscription_not_using_http_endpoints: owner=AWS_ACCOUNT_NUMBER, protocol="https", endpoint="https://www.endpoint.com", + region=AWS_REGION_EU_WEST_1, ) ) subscriptions.append( @@ -193,6 +199,7 @@ class Test_sns_subscription_not_using_http_endpoints: owner=AWS_ACCOUNT_NUMBER, protocol="http", endpoint="http://www.endpoint.com", + region=AWS_REGION_EU_WEST_1, ) ) sns_client.topics = [] @@ -224,6 +231,7 @@ class Test_sns_subscription_not_using_http_endpoints: ) assert result[0].resource_id == subscription_id_1 assert result[0].resource_arn == subscription_arn_1 + assert result[0].region == AWS_REGION_EU_WEST_1 assert result[1].status == "FAIL" assert ( @@ -232,3 +240,4 @@ class Test_sns_subscription_not_using_http_endpoints: ) assert result[1].resource_id == subscription_id_2 assert result[1].resource_arn == subscription_arn_2 + assert result[1].region == AWS_REGION_EU_WEST_1