import sys from unittest import mock import pytest from pydantic.v1 import ValidationError from prowler.lib.check.models import Check, CheckMetadata from tests.lib.check.compliance_check_test import custom_compliance_metadata mock_metadata = CheckMetadata( Provider="aws", CheckID="accessanalyzer_enabled", CheckTitle="Check 1", CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="accessanalyzer", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/accessanalyzer_enabled", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", Compliance=[], ) mock_metadata_lambda = CheckMetadata( Provider="aws", CheckID="awslambda_function_url_public", CheckTitle="Check 1", CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="awslambda", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/awslambda_function_url_public", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", Compliance=[], ) class TestCheckMetada: @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_get_bulk(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata result = CheckMetadata.get_bulk(provider="aws") # Assertions assert "accessanalyzer_enabled" in result.keys() assert result["accessanalyzer_enabled"] == mock_metadata mock_recover_checks.assert_called_once_with("aws") mock_load_metadata.assert_called_once_with( "/path/to/accessanalyzer_enabled/accessanalyzer_enabled.metadata.json" ) @mock.patch("prowler.lib.check.models.logger") @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_get_bulk_builtin_wins_on_check_id_collision( self, mock_recover_checks, mock_load_metadata, mock_logger ): """Regression guard: when an entry-point plug-in re-registers a built-in CheckID, the BUILT-IN metadata wins (first-write-wins) and the plug-in is IGNORED. The override is surfaced via a warning so the user knows their plug-in duplicate is being skipped and can rename it. Matches the precedence in `_resolve_check_module`. See PR #10700 review (HugoPBrito).""" # Built-in first, plug-in last (matches recover_checks_from_provider order) mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/builtin/accessanalyzer_enabled"), ("accessanalyzer_enabled", "/plugin/accessanalyzer_enabled"), ] builtin_metadata = mock.MagicMock(CheckID="accessanalyzer_enabled") plugin_metadata = mock.MagicMock(CheckID="accessanalyzer_enabled") mock_load_metadata.side_effect = [builtin_metadata, plugin_metadata] result = CheckMetadata.get_bulk(provider="aws") # Built-in wins (first-write-wins on CheckID), plug-in is ignored assert result["accessanalyzer_enabled"] is builtin_metadata # Override is surfaced via warning naming the plug-in metadata file mock_logger.warning.assert_called_once() warning_msg = mock_logger.warning.call_args.args[0] assert "accessanalyzer_enabled" in warning_msg assert "/plugin/accessanalyzer_enabled" in warning_msg @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list(bulk_checks_metadata=bulk_metadata) # Assertions assert result == {"accessanalyzer_enabled"} @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_get(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list(bulk_checks_metadata=bulk_metadata) # Assertions assert result == {"accessanalyzer_enabled"} @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_severity(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list(bulk_checks_metadata=bulk_metadata, severity="high") # Assertions assert result == {"accessanalyzer_enabled"} @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_severity_not_values(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list(bulk_checks_metadata=bulk_metadata, severity="low") # Assertions assert result == set() @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_category(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list( bulk_checks_metadata=bulk_metadata, category="encryption" ) # Assertions assert result == {"accessanalyzer_enabled"} @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_category_not_valid(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list( bulk_checks_metadata=bulk_metadata, category="categorytwo" ) # Assertions assert result == set() @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_service(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list( bulk_checks_metadata=bulk_metadata, service="accessanalyzer" ) # Assertions assert result == {"accessanalyzer_enabled"} @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_service_lambda(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("awslambda_function_url_public", "/path/to/awslambda_function_url_public") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata_lambda bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list( bulk_checks_metadata=bulk_metadata, service="lambda" ) # Assertions assert result == {"awslambda_function_url_public"} @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_service_awslambda(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("awslambda_function_url_public", "/path/to/awslambda_function_url_public") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata_lambda bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list( bulk_checks_metadata=bulk_metadata, service="awslambda" ) # Assertions assert result == {"awslambda_function_url_public"} @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_service_invalid(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list( bulk_checks_metadata=bulk_metadata, service="service2" ) # Assertions assert result == set() @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_by_compliance(self, mock_recover_checks, mock_load_metadata): # Mock the return value of recover_checks_from_provider mock_recover_checks.return_value = [ ("accessanalyzer_enabled", "/path/to/accessanalyzer_enabled") ] # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") bulk_compliance_frameworks = custom_compliance_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list( bulk_checks_metadata=bulk_metadata, bulk_compliance_frameworks=bulk_compliance_frameworks, compliance_framework="framework1_aws", ) # Assertions assert result == {"accessanalyzer_enabled"} @mock.patch("prowler.lib.check.models.CheckMetadata.get_bulk") def test_list_by_compliance_empty(self, mock_get_bulk): mock_get_bulk.return_value = {} bulk_compliance_frameworks = custom_compliance_metadata result = CheckMetadata.list( bulk_compliance_frameworks=bulk_compliance_frameworks, compliance_framework="framework1_azure", ) # Assertions assert result == set() @mock.patch("prowler.lib.check.models.load_check_metadata") @mock.patch("prowler.lib.check.models.recover_checks_from_provider") def test_list_only_check_metadata(self, mock_recover_checks, mock_load_metadata): # Mock the return value of load_check_metadata mock_load_metadata.return_value = mock_metadata bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list(bulk_checks_metadata=bulk_metadata) assert result == set() class TestCheckMetadataValidators: """Test class for CheckMetadata validators""" def test_valid_category_success(self): """Test valid category validation with valid categories""" valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption", "logging", "secrets"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } # Should not raise any validation error check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.Categories == ["encryption", "logging", "secrets"] def test_valid_vercel_plan_categories_success(self): """Test Vercel plan categories are accepted using hyphen-separated names.""" valid_metadata = { "Provider": "vercel", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": [ "vercel-hobby-plan", "vercel-pro-plan", "vercel-enterprise-plan", ], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.Categories == [ "vercel-hobby-plan", "vercel-pro-plan", "vercel-enterprise-plan", ] def test_valid_category_failure_non_string(self): """Test valid category validation fails with non-string category""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": [123], # Invalid: number instead of string "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "Categories must be a list of strings" in str(exc_info.value) def test_valid_category_failure_invalid_format(self): """Test valid category validation fails with invalid format""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["invalid_category!"], # Invalid: contains special character "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert ( "Categories can only contain lowercase letters, numbers, and hyphen '-'" in str(exc_info.value) ) def test_valid_category_failure_not_predefined(self): """Test valid category validation fails with non-predefined category""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["not-a-real-category"], # Invalid: not in predefined list "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "Invalid category: 'not-a-real-category'. Must be one of:" in str( exc_info.value ) def test_valid_category_all_predefined_values(self): """Test that all predefined categories are accepted""" from prowler.lib.check.models import VALID_CATEGORIES for category in VALID_CATEGORIES: valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": [category], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert category in check_metadata.Categories def test_severity_to_lower_success(self): """Test severity validation converts to lowercase""" valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "HIGH", # Uppercase - should be converted to lowercase "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.Severity == "high" def test_valid_cli_command_success(self): """Test CLI command validation with valid command""" valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "aws iam create-role --role-name test", # Valid CLI command "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } # Should not raise any validation error check_metadata = CheckMetadata(**valid_metadata) assert "aws iam create-role" in check_metadata.Remediation.Code.CLI def test_valid_cli_command_failure_url(self): """Test CLI command validation fails with URL""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "https://example.com/command", # Invalid: URL instead of command "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "CLI command cannot be an URL" in str(exc_info.value) def test_valid_resource_type_success(self): """Test resource type validation with valid resource type""" valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "AWS::IAM::Role", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.ResourceType == "AWS::IAM::Role" def test_valid_resource_type_failure_empty(self): """Test resource type validation fails with empty string""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "", # Invalid: empty string "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "ResourceType must be a non-empty string" in str(exc_info.value) def test_validate_service_name_success(self): """Test service name validation with valid service name matching CheckID""" valid_metadata = { "Provider": "azure", "CheckID": "s3_bucket_public_read", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "s3", # Matches first part of CheckID "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "AWS::S3::Bucket", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.ServiceName == "s3" def test_validate_service_name_failure_mismatch(self): """Test service name validation fails when not matching CheckID""" invalid_metadata = { "Provider": "aws", "CheckID": "s3_bucket_public_read", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "ec2", # Does not match first part of CheckID "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "AWS::S3::Bucket", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert ( "ServiceName ec2 does not belong to CheckID s3_bucket_public_read" in str(exc_info.value) ) def test_validate_service_name_failure_uppercase(self): """Test service name validation fails with uppercase""" invalid_metadata = { "Provider": "aws", "CheckID": "S3_bucket_public_read", "CheckTitle": "Test Check", "CheckType": ["TTPs/Discovery"], "ServiceName": "S3", # Invalid: uppercase "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "AWS::S3::Bucket", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "ServiceName S3 must be in lowercase" in str(exc_info.value) def test_validate_service_name_iac_provider_success(self): """Test service name validation allows any service name for IAC provider""" valid_metadata = { "Provider": "iac", "CheckID": "custom_check_id", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "CustomService", # Valid for IAC provider "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.ServiceName == "CustomService" def test_valid_check_id_success(self): """Test CheckID validation with valid check ID""" valid_metadata = { "Provider": "azure", "CheckID": "s3_bucket_public_read_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "s3", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "AWS::S3::Bucket", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.CheckID == "s3_bucket_public_read_check" def test_valid_check_id_failure_empty(self): """Test CheckID validation fails with empty string""" invalid_metadata = { "Provider": "aws", "CheckID": "", # Invalid: empty string "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "CheckID must be a non-empty string" in str(exc_info.value) def test_valid_check_id_failure_hyphen(self): """Test CheckID validation fails with hyphen""" invalid_metadata = { "Provider": "aws", "CheckID": "s3-bucket-public-read", # Invalid: contains hyphens "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "s3", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "AWS::S3::Bucket", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert ( "CheckID s3-bucket-public-read contains a hyphen, which is not allowed" in str(exc_info.value) ) def test_validate_check_title_success(self): """Test CheckTitle validation with valid title""" valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "A" * 150, # Exactly 150 characters "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert len(check_metadata.CheckTitle) == 150 def test_validate_check_title_failure_too_long(self): """Test CheckTitle validation fails when too long""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "A" * 151, # Too long: 151 characters "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "CheckTitle must not exceed 150 characters, got 151 characters" in str( exc_info.value ) def test_validate_check_title_failure_starts_with_ensure(self): """Test CheckTitle validation fails when starting with 'Ensure'""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Ensure S3 buckets have encryption enabled", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "CheckTitle must not start with 'Ensure'" in str(exc_info.value) def test_validate_related_url_must_be_empty(self): """Test RelatedUrl validation fails when not empty""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "https://example.com", # Invalid: must be empty "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "RelatedUrl must be empty" in str(exc_info.value) def test_validate_related_url_empty_is_valid(self): """Test RelatedUrl validation passes when empty""" valid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.RelatedUrl == "" def test_validate_recommendation_url_must_be_hub(self): """Test Recommendation URL validation fails when not pointing to Prowler Hub""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://docs.aws.amazon.com/some-page", # Invalid: not HUB }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "Remediation Recommendation URL must point to Prowler Hub" in str( exc_info.value ) def test_validate_recommendation_url_hub_is_valid(self): """Test Recommendation URL validation passes with Prowler Hub URL""" valid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert ( check_metadata.Remediation.Recommendation.Url == "https://hub.prowler.com/check/test_check" ) def test_validate_recommendation_url_empty_is_valid(self): """Test Recommendation URL validation passes when empty""" valid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.Remediation.Recommendation.Url == "" def test_validate_check_type_non_aws_must_be_empty(self): """Test CheckType must be empty for non-AWS providers""" invalid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": ["SomeType"], # Invalid: non-AWS must be empty "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "CheckType must be empty for non-AWS providers" in str(exc_info.value) def test_validate_check_type_success(self): """Test CheckType validation with valid check types""" valid_metadata = { "Provider": "azure", # Using non-AWS provider to avoid config validation "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.CheckType == [] def test_validate_check_type_failure_empty_string(self): """Test CheckType validation fails with empty string in list""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": ["TTPs/Discovery", ""], # Invalid: empty string in list "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "CheckType list cannot contain empty strings" in str(exc_info.value) def test_validate_description_success(self): """Test Description validation with valid description""" valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "A" * 400, # Exactly 400 characters "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert len(check_metadata.Description) == 400 def test_validate_description_failure_too_long(self): """Test Description validation fails when too long""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "A" * 401, # Too long: 401 characters "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "Description must not exceed 400 characters, got 401 characters" in str( exc_info.value ) def test_validate_risk_success(self): """Test Risk validation with valid risk""" valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "A" * 400, # Exactly 400 characters "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert len(check_metadata.Risk) == 400 def test_validate_risk_failure_too_long(self): """Test Risk validation fails when too long""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "A" * 401, # Too long: 401 characters "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "Risk must not exceed 400 characters, got 401 characters" in str( exc_info.value ) def test_validate_check_type_aws_invalid_type(self): """Test CheckType validation fails with invalid AWS CheckType""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": ["InvalidType"], # Invalid: not in AWS config "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "Invalid CheckType: 'InvalidType'" in str(exc_info.value) def test_validate_check_type_aws_valid_hierarchy_path(self): """Test CheckType validation succeeds with valid AWS CheckType hierarchy path""" valid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": ["TTPs/Initial Access"], # Valid: partial path in hierarchy "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.CheckType == ["TTPs/Initial Access"] def test_validate_check_type_non_aws_provider(self): """Test CheckType validation requires empty list for non-AWS providers""" valid_metadata = { "Provider": "azure", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [], # Non-AWS providers must have empty CheckType "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.CheckType == [] def test_validate_check_type_aws_validation_called(self): """Test that AWS CheckType validation function works for AWS provider""" valid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": ["Effects/Data Exposure"], # Valid AWS CheckType "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.CheckType == ["Effects/Data Exposure"] def test_validate_check_type_multiple_types_all_valid(self): """Test CheckType validation with multiple valid types for AWS provider""" valid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "TTPs/Discovery", "Effects/Data Exposure", ], # Multiple valid AWS types "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } check_metadata = CheckMetadata(**valid_metadata) assert check_metadata.CheckType == ["TTPs/Discovery", "Effects/Data Exposure"] def test_validate_check_type_aws_multiple_types_mixed_validity(self): """Test CheckType validation with multiple types where one is invalid for AWS""" invalid_metadata = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": ["TTPs/Discovery", "InvalidType"], # One valid, one invalid "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**invalid_metadata) assert "Invalid CheckType: 'InvalidType'" in str(exc_info.value) def test_additional_urls_valid_empty_list(self): """Test AdditionalURLs with valid empty list (default)""" metadata = CheckMetadata( Provider="aws", CheckID="test_check", CheckTitle="Test Check", CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs=[], Compliance=[], ) assert metadata.AdditionalURLs == [] def test_additional_urls_valid_with_urls(self): """Test AdditionalURLs with valid URLs""" valid_urls = [ "https://example.com/doc1", "https://example.com/doc2", "https://aws.amazon.com/docs", ] metadata = CheckMetadata( Provider="aws", CheckID="test_check", CheckTitle="Test Check", CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs=valid_urls, Compliance=[], ) assert metadata.AdditionalURLs == valid_urls def test_additional_urls_invalid_not_list(self): """Test AdditionalURLs with non-list value""" with pytest.raises(ValidationError) as exc_info: CheckMetadata( Provider="aws", CheckID="test_check", CheckTitle="Test Check", CheckType=[ "Software and Configuration Checks/AWS Security Best Practices" ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs="not_a_list", Compliance=[], ) assert "AdditionalURLs must be a list" in str(exc_info.value) def test_additional_urls_invalid_empty_items(self): """Test AdditionalURLs with empty string items""" with pytest.raises(ValidationError) as exc_info: CheckMetadata( Provider="aws", CheckID="test_check", CheckTitle="Test Check", CheckType=[ "Software and Configuration Checks/AWS Security Best Practices" ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs=["https://example.com", "", "https://example2.com"], Compliance=[], ) assert "AdditionalURLs cannot contain empty items" in str(exc_info.value) def test_additional_urls_invalid_whitespace_items(self): """Test AdditionalURLs with whitespace-only items""" with pytest.raises(ValidationError) as exc_info: CheckMetadata( Provider="aws", CheckID="test_check", CheckTitle="Test Check", CheckType=[ "Software and Configuration Checks/AWS Security Best Practices" ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs=["https://example.com", " ", "https://example2.com"], Compliance=[], ) assert "AdditionalURLs cannot contain empty items" in str(exc_info.value) def test_additional_urls_invalid_duplicates(self): """Test AdditionalURLs with duplicate items""" with pytest.raises(ValidationError) as exc_info: CheckMetadata( Provider="aws", CheckID="test_check", CheckTitle="Test Check", CheckType=[ "Software and Configuration Checks/AWS Security Best Practices" ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs=[ "https://example.com", "https://example2.com", "https://example.com", ], Compliance=[], ) assert "AdditionalURLs cannot contain duplicate items" in str(exc_info.value) def test_fields_with_explicit_empty_values(self): """Test that RelatedUrl and AdditionalURLs can be set to explicit empty values""" metadata = CheckMetadata( Provider="aws", CheckID="test_check_empty_fields", CheckTitle="Test Check with Empty Fields", CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", # Explicit empty string Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs=[], # Explicit empty list Compliance=[], ) # Assert that the fields are set to empty values assert metadata.RelatedUrl == "" assert metadata.AdditionalURLs == [] def test_fields_default_values(self): """Test that RelatedUrl and AdditionalURLs use proper defaults when not provided""" metadata = CheckMetadata( Provider="aws", CheckID="test_check_defaults", CheckTitle="Test Check with Default Fields", CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", # AdditionalURLs not provided - should default to empty list via default_factory Compliance=[], ) # Assert that the fields use their default values assert metadata.RelatedUrl == "" # Should default to empty string assert metadata.AdditionalURLs == [] # Should default to empty list def test_related_url_none_fails(self): """Test that setting RelatedUrl to None raises a ValidationError""" with pytest.raises(ValidationError) as exc_info: CheckMetadata( Provider="aws", CheckID="test_check_none_related_url", CheckTitle="Test Check with None RelatedUrl", CheckType=[ "Software and Configuration Checks/AWS Security Best Practices" ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl=None, # This should fail Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs=[], Compliance=[], ) # Should contain a validation error for RelatedUrl assert "RelatedUrl" in str(exc_info.value) def test_additional_urls_none_fails(self): """Test that setting AdditionalURLs to None raises a ValidationError""" with pytest.raises(ValidationError) as exc_info: CheckMetadata( Provider="aws", CheckID="test_check_none_additional_urls", CheckTitle="Test Check with None AdditionalURLs", CheckType=[ "Software and Configuration Checks/AWS Security Best Practices" ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs=None, # This should fail Compliance=[], ) # Should contain the validation error we set in the validator assert "AdditionalURLs must be a list" in str(exc_info.value) def test_additional_urls_invalid_type_fails(self): """Test that setting AdditionalURLs to non-list value raises a ValidationError""" with pytest.raises(ValidationError) as exc_info: CheckMetadata( Provider="aws", CheckID="test_check_invalid_additional_urls", CheckTitle="Test Check with Invalid AdditionalURLs", CheckType=[ "Software and Configuration Checks/AWS Security Best Practices" ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", Severity="high", ResourceType="resource1", Description="Description 1", Risk="risk1", RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", "NativeIaC": "native1", "Other": "other1", "Terraform": "terraform1", }, "Recommendation": { "Text": "text1", "Url": "https://hub.prowler.com/check/test_check", }, }, Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", AdditionalURLs="not_a_list", # This should fail Compliance=[], ) # Should contain the validation error we set in the validator assert "AdditionalURLs must be a list" in str(exc_info.value) class TestResourceGroupValidator: """Test class for ResourceGroup validator""" def _base_metadata(self, **overrides): """Helper to build valid metadata with overrides""" base = { "Provider": "aws", "CheckID": "test_check", "CheckTitle": "Test Check", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices" ], "ServiceName": "test", "SubServiceName": "subtest", "ResourceIdTemplate": "template", "Severity": "high", "ResourceType": "TestResource", "Description": "Test description", "Risk": "Test risk", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "test command", "NativeIaC": "test native", "Other": "test other", "Terraform": "test terraform", }, "Recommendation": { "Text": "test recommendation", "Url": "https://hub.prowler.com/check/test_check", }, }, "Categories": ["encryption"], "DependsOn": [], "RelatedTo": [], "Notes": "Test notes", } base.update(overrides) return base @pytest.mark.parametrize( "resource_group", [ "compute", "container", "serverless", "database", "storage", "network", "IAM", "messaging", "security", "monitoring", "api_gateway", "ai_ml", "governance", "collaboration", "devops", "analytics", ], ) def test_valid_resource_group(self, resource_group): """Test all valid ResourceGroup values are accepted""" metadata = CheckMetadata(**self._base_metadata(ResourceGroup=resource_group)) assert metadata.ResourceGroup == resource_group def test_resource_group_empty_string_allowed(self): """Test that empty string (default) is allowed for ResourceGroup""" metadata = CheckMetadata(**self._base_metadata(ResourceGroup="")) assert metadata.ResourceGroup == "" def test_resource_group_default_is_empty(self): """Test that ResourceGroup defaults to empty string when not provided""" metadata = CheckMetadata(**self._base_metadata()) assert metadata.ResourceGroup == "" def test_resource_group_invalid_value(self): """Test that invalid ResourceGroup value raises ValidationError""" with pytest.raises(ValidationError) as exc_info: CheckMetadata(**self._base_metadata(ResourceGroup="invalid_group")) assert "Invalid ResourceGroup: 'invalid_group'" in str(exc_info.value) def test_resource_group_case_sensitive(self): """Test that ResourceGroup validation is case-sensitive (IAM, not iam)""" with pytest.raises(ValidationError) as exc_info: CheckMetadata(**self._base_metadata(ResourceGroup="iam")) assert "Invalid ResourceGroup: 'iam'" in str(exc_info.value) def test_resource_group_typo(self): """Test that typos in ResourceGroup are rejected""" with pytest.raises(ValidationError) as exc_info: CheckMetadata(**self._base_metadata(ResourceGroup="computee")) assert "Invalid ResourceGroup: 'computee'" in str(exc_info.value) class TestCheck: @mock.patch("prowler.lib.check.models.CheckMetadata.parse_file") def test_verify_names_consistency_all_match(self, mock_parse_file): """Case where everything matches: CheckID == class_name == file_name""" mock_parse_file.return_value = mock_metadata.copy( update={ "CheckID": "accessanalyzer_enabled", "ServiceName": "accessanalyzer", } ) class accessanalyzer_enabled(Check): def execute(self): pass fake_module = mock.Mock() fake_module.__file__ = "/path/to/accessanalyzer_enabled.py" sys.modules[accessanalyzer_enabled.__module__] = fake_module accessanalyzer_enabled() @mock.patch("prowler.lib.check.models.CheckMetadata.parse_file") def test_verify_names_consistency_class_mismatch(self, mock_parse_file): """CheckID != class name, but matches file_name""" mock_parse_file.return_value = mock_metadata.copy( update={ "CheckID": "accessanalyzer_enabled", "ServiceName": "accessanalyzer", } ) class WrongClass(Check): def execute(self): pass fake_module = mock.Mock() fake_module.__file__ = "/path/to/accessanalyzer_enabled.py" sys.modules[WrongClass.__module__] = fake_module with pytest.raises(ValidationError) as excinfo: WrongClass() assert "!= class name" in str(excinfo.value) @mock.patch("prowler.lib.check.models.CheckMetadata.parse_file") def test_verify_names_consistency_file_mismatch(self, mock_parse_file): """CheckID == class name, but != file_name""" mock_parse_file.return_value = mock_metadata.copy( update={ "CheckID": "accessanalyzer_enabled", "ServiceName": "accessanalyzer", } ) class accessanalyzer_enabled(Check): def execute(self): pass fake_module = mock.Mock() fake_module.__file__ = "/path/to/OtherFile.py" sys.modules[accessanalyzer_enabled.__module__] = fake_module with pytest.raises(ValidationError) as excinfo: accessanalyzer_enabled() assert "!= file name" in str(excinfo.value) @mock.patch("prowler.lib.check.models.CheckMetadata.parse_file") def test_verify_names_consistency_both_mismatch(self, mock_parse_file): """Neither class name nor file name match the CheckID""" mock_parse_file.return_value = mock_metadata.copy( update={ "CheckID": "accessanalyzer_enabled", "ServiceName": "accessanalyzer", } ) class WrongClass(Check): def execute(self): pass fake_module = mock.Mock() fake_module.__file__ = "/path/to/OtherFile.py" sys.modules[WrongClass.__module__] = fake_module with pytest.raises(ValidationError) as excinfo: WrongClass() msg = str(excinfo.value) assert "!= class name" in msg assert "!= file name" in msg class TestExternalToolProviderValidatorBypass: """Validators skip strict rules for external tool providers (image, iac, llm).""" EXTERNAL_METADATA_BASE = { "Provider": "image", "CheckID": "CVE-2024-1234", "CheckTitle": "OpenSSL Buffer Overflow", "CheckType": ["Container Image Security"], "ServiceName": "container-image", "SubServiceName": "", "ResourceIdTemplate": "", "Severity": "high", "ResourceType": "container-image", "ResourceGroup": "container", "Description": "A buffer overflow vulnerability.", "Risk": "Remote code execution.", "RelatedUrl": "", "Remediation": { "Code": { "CLI": "", "NativeIaC": "", "Other": "", "Terraform": "", }, "Recommendation": { "Text": "Upgrade openssl", "Url": "https://avd.aquasec.com/nvd/cve-2024-1234", }, }, "Categories": ["vulnerability"], "DependsOn": [], "RelatedTo": [], "Notes": "", } def test_external_provider_allows_non_hub_recommendation_url(self): metadata = CheckMetadata(**self.EXTERNAL_METADATA_BASE) assert ( metadata.Remediation.Recommendation.Url == "https://avd.aquasec.com/nvd/cve-2024-1234" ) def test_native_provider_rejects_non_hub_recommendation_url(self): data = { **self.EXTERNAL_METADATA_BASE, "Provider": "azure", "CheckID": "test_check", "ServiceName": "test", "CheckType": [], "Remediation": { "Code": { "CLI": "", "NativeIaC": "", "Other": "", "Terraform": "", }, "Recommendation": { "Text": "Fix it", "Url": "https://avd.aquasec.com/nvd/cve-2024-1234", }, }, } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**data) assert "Prowler Hub" in str(exc_info.value) def test_external_provider_allows_long_description(self): data = {**self.EXTERNAL_METADATA_BASE, "Description": "A" * 500} metadata = CheckMetadata(**data) assert len(metadata.Description) == 500 def test_native_provider_rejects_long_description(self): data = { **self.EXTERNAL_METADATA_BASE, "Provider": "azure", "CheckID": "test_check", "ServiceName": "test", "CheckType": [], "Categories": ["encryption"], "Description": "A" * 401, "Remediation": { "Code": { "CLI": "", "NativeIaC": "", "Other": "", "Terraform": "", }, "Recommendation": { "Text": "", "Url": "", }, }, } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**data) assert "Description must not exceed 400 characters" in str(exc_info.value) def test_external_provider_allows_long_risk(self): data = {**self.EXTERNAL_METADATA_BASE, "Risk": "R" * 500} metadata = CheckMetadata(**data) assert len(metadata.Risk) == 500 def test_native_provider_rejects_long_risk(self): data = { **self.EXTERNAL_METADATA_BASE, "Provider": "azure", "CheckID": "test_check", "ServiceName": "test", "CheckType": [], "Categories": ["encryption"], "Risk": "R" * 401, "Remediation": { "Code": { "CLI": "", "NativeIaC": "", "Other": "", "Terraform": "", }, "Recommendation": { "Text": "", "Url": "", }, }, } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**data) assert "Risk must not exceed 400 characters" in str(exc_info.value) def test_external_provider_allows_long_check_title(self): data = {**self.EXTERNAL_METADATA_BASE, "CheckTitle": "T" * 200} metadata = CheckMetadata(**data) assert len(metadata.CheckTitle) == 200 def test_native_provider_rejects_long_check_title(self): data = { **self.EXTERNAL_METADATA_BASE, "Provider": "azure", "CheckID": "test_check", "ServiceName": "test", "CheckType": [], "Categories": ["encryption"], "CheckTitle": "T" * 151, "Remediation": { "Code": { "CLI": "", "NativeIaC": "", "Other": "", "Terraform": "", }, "Recommendation": { "Text": "", "Url": "", }, }, } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**data) assert "CheckTitle must not exceed 150 characters" in str(exc_info.value) def test_external_provider_allows_non_standard_category(self): data = {**self.EXTERNAL_METADATA_BASE, "Categories": ["vulnerability"]} metadata = CheckMetadata(**data) assert metadata.Categories == ["vulnerability"] def test_native_provider_rejects_non_standard_category(self): data = { **self.EXTERNAL_METADATA_BASE, "Provider": "azure", "CheckID": "test_check", "ServiceName": "test", "CheckType": [], "Categories": ["vulnerability"], "Remediation": { "Code": { "CLI": "", "NativeIaC": "", "Other": "", "Terraform": "", }, "Recommendation": { "Text": "", "Url": "", }, }, } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**data) assert "Invalid category" in str(exc_info.value) def test_external_provider_allows_ensure_prefix_in_title(self): data = { **self.EXTERNAL_METADATA_BASE, "CheckTitle": "Ensure containers run as non-root", } metadata = CheckMetadata(**data) assert metadata.CheckTitle == "Ensure containers run as non-root" def test_external_provider_allows_non_empty_related_url(self): data = { **self.EXTERNAL_METADATA_BASE, "RelatedUrl": "https://avd.aquasec.com/nvd/cve-2024-1234", } metadata = CheckMetadata(**data) assert metadata.RelatedUrl == "https://avd.aquasec.com/nvd/cve-2024-1234" def test_native_provider_rejects_non_empty_related_url(self): data = { **self.EXTERNAL_METADATA_BASE, "Provider": "azure", "CheckID": "test_check", "ServiceName": "test", "CheckType": [], "Categories": ["encryption"], "RelatedUrl": "https://example.com", "Remediation": { "Code": { "CLI": "", "NativeIaC": "", "Other": "", "Terraform": "", }, "Recommendation": { "Text": "", "Url": "", }, }, } with pytest.raises(ValidationError) as exc_info: CheckMetadata(**data) assert "RelatedUrl must be empty" in str(exc_info.value) def test_all_external_providers_bypass(self): for provider in ("image", "iac", "llm"): data = { **self.EXTERNAL_METADATA_BASE, "Provider": provider, "Description": "D" * 500, "Risk": "R" * 500, "CheckTitle": "T" * 200, "Categories": ["vulnerability"], "RelatedUrl": "https://example.com/vuln", } metadata = CheckMetadata(**data) assert metadata.Provider == provider