diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/__init__.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured.metadata.json b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured.metadata.json new file mode 100644 index 0000000000..2d5e83b1e8 --- /dev/null +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured.metadata.json @@ -0,0 +1,36 @@ +{ + "Provider": "aws", + "CheckID": "cloudfront_distributions_multiple_origin_failover_configured", + "CheckTitle": "Check if CloudFront distributions have origin failover enabled.", + "CheckType": [ + "Software and Configuration Checks", + "Industry and Regulatory Standards", + "NIST 800-53 Controls" + ], + "ServiceName": "cloudfront", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:cloudfront:region:account-id:distribution/resource-id", + "Severity": "low", + "ResourceType": "AWSCloudFrontDistribution", + "Description": "Check if CloudFront distributions have origin failover enabled.", + "Risk": "Without origin failover, if the primary origin becomes unavailable, your CloudFront distribution may experience downtime, leading to potential service interruptions and a poor user experience.", + "RelatedUrl": "https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_OriginGroup.html", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-4", + "Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/origin-failover-enabled.html" + }, + "Recommendation": { + "Text": "Configure origin failover in your CloudFront distribution by setting up an origin group with at least two origins to enhance availability and ensure traffic is redirected if the primary origin fails.", + "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/high_availability_origin_failover.html#concept_origin_groups.creating" + } + }, + "Categories": [ + "redundancy" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured.py new file mode 100644 index 0000000000..1426a62508 --- /dev/null +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured.py @@ -0,0 +1,25 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.cloudfront.cloudfront_client import ( + cloudfront_client, +) + + +class cloudfront_distributions_multiple_origin_failover_configured(Check): + def execute(self): + findings = [] + for distribution in cloudfront_client.distributions.values(): + report = Check_Report_AWS(self.metadata()) + report.region = distribution.region + report.resource_arn = distribution.arn + report.resource_id = distribution.id + report.resource_tags = distribution.tags + report.status = "FAIL" + report.status_extended = f"CloudFront Distribution {distribution.id} does not have an origin group configured with at least 2 origins." + + if distribution.origin_failover: + report.status = "PASS" + report.status_extended = f"CloudFront Distribution {distribution.id} has an origin group with at least 2 origins configured." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_service.py b/prowler/providers/aws/services/cloudfront/cloudfront_service.py index 7bf96f7b09..d33edd4b94 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_service.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_service.py @@ -8,7 +8,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered from prowler.providers.aws.lib.service.service import AWSService -################## CloudFront class CloudFront(AWSService): def __init__(self, provider): # Call AWSService's __init__ @@ -30,6 +29,14 @@ class CloudFront(AWSService): ): distribution_id = item["Id"] distribution_arn = item["ARN"] + origin_groups = item.get("OriginGroups", {}).get( + "Items", [] + ) + origin_failover = all( + origin_group.get("Members", {}).get("Quantity", 0) >= 2 + for origin_group in origin_groups + ) + default_certificate = item["ViewerCertificate"][ "CloudFrontDefaultCertificate" ] @@ -68,8 +75,9 @@ class CloudFront(AWSService): id=distribution_id, origins=origins, region=region, - default_certificate=default_certificate, + origin_failover=origin_failover, ssl_support_method=ssl_support_method, + default_certificate=default_certificate, certificate=certificate, ) self.distributions[distribution_id] = distribution @@ -208,5 +216,6 @@ class Distribution(BaseModel): default_root_object: Optional[str] viewer_protocol_policy: Optional[str] tags: Optional[list] = [] + origin_failover: Optional[bool] ssl_support_method: Optional[SSLSupportMethod] certificate: Optional[str] diff --git a/tests/providers/aws/services/cloudfront/cloudfront_distributions_field_level_encryption_enabled/cloudfront_distributions_field_level_encryption_enabled_test.py b/tests/providers/aws/services/cloudfront/cloudfront_distributions_field_level_encryption_enabled/cloudfront_distributions_field_level_encryption_enabled_test.py index 7325dfbe23..af639bc63a 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_distributions_field_level_encryption_enabled/cloudfront_distributions_field_level_encryption_enabled_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_distributions_field_level_encryption_enabled/cloudfront_distributions_field_level_encryption_enabled_test.py @@ -45,6 +45,7 @@ class Test_cloudfront_distributions_field_level_encryption_enabled: viewer_protocol_policy=ViewerProtocolPolicy.https_only, field_level_encryption_id="AAAAAAAA", ), + origin_failover=False, ) } @@ -84,6 +85,7 @@ class Test_cloudfront_distributions_field_level_encryption_enabled: viewer_protocol_policy=ViewerProtocolPolicy.https_only, field_level_encryption_id="", ), + origin_failover=False, ) } diff --git a/tests/providers/aws/services/cloudfront/cloudfront_distributions_geo_restrictions_enabled/cloudfront_distributions_geo_restrictions_enabled_test.py b/tests/providers/aws/services/cloudfront/cloudfront_distributions_geo_restrictions_enabled/cloudfront_distributions_geo_restrictions_enabled_test.py index 381a73bbca..46396b6ef6 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_distributions_geo_restrictions_enabled/cloudfront_distributions_geo_restrictions_enabled_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_distributions_geo_restrictions_enabled/cloudfront_distributions_geo_restrictions_enabled_test.py @@ -75,6 +75,7 @@ class Test_cloudfront_distributions_geo_restrictions_enabled: region=REGION, origins=[], geo_restriction_type=GeoRestrictionType.whitelist, + origin_failover=False, ) } @@ -110,6 +111,7 @@ class Test_cloudfront_distributions_geo_restrictions_enabled: region=REGION, origins=[], geo_restriction_type=GeoRestrictionType.blacklist, + origin_failover=False, ) } diff --git a/tests/providers/aws/services/cloudfront/cloudfront_distributions_https_enabled/cloudfront_distributions_https_enabled_test.py b/tests/providers/aws/services/cloudfront/cloudfront_distributions_https_enabled/cloudfront_distributions_https_enabled_test.py index 6aa47e8b8a..44ac23ae36 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_distributions_https_enabled/cloudfront_distributions_https_enabled_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_distributions_https_enabled/cloudfront_distributions_https_enabled_test.py @@ -45,6 +45,7 @@ class Test_cloudfront_distributions_https_enabled: viewer_protocol_policy=ViewerProtocolPolicy.allow_all, field_level_encryption_id="", ), + origin_failover=False, ) } @@ -84,6 +85,7 @@ class Test_cloudfront_distributions_https_enabled: viewer_protocol_policy=ViewerProtocolPolicy.redirect_to_https, field_level_encryption_id="", ), + origin_failover=False, ) } @@ -123,6 +125,7 @@ class Test_cloudfront_distributions_https_enabled: viewer_protocol_policy=ViewerProtocolPolicy.https_only, field_level_encryption_id="", ), + origin_failover=False, ) } diff --git a/tests/providers/aws/services/cloudfront/cloudfront_distributions_logging_enabled/cloudfront_distributions_logging_enabled_test.py b/tests/providers/aws/services/cloudfront/cloudfront_distributions_logging_enabled/cloudfront_distributions_logging_enabled_test.py index 764959fa8c..41c46cb822 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_distributions_logging_enabled/cloudfront_distributions_logging_enabled_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_distributions_logging_enabled/cloudfront_distributions_logging_enabled_test.py @@ -41,6 +41,7 @@ class Test_cloudfront_distributions_logging_enabled: region=REGION, logging_enabled=True, origins=[], + origin_failover=False, ) } @@ -80,6 +81,7 @@ class Test_cloudfront_distributions_logging_enabled: field_level_encryption_id="", ), origins=[], + origin_failover=False, ) } @@ -120,6 +122,7 @@ class Test_cloudfront_distributions_logging_enabled: field_level_encryption_id="", ), origins=[], + origin_failover=False, ) } @@ -160,6 +163,7 @@ class Test_cloudfront_distributions_logging_enabled: field_level_encryption_id="", ), origins=[], + origin_failover=False, ) } diff --git a/tests/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured_test.py b/tests/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured_test.py new file mode 100644 index 0000000000..cc0ecd7bc3 --- /dev/null +++ b/tests/providers/aws/services/cloudfront/cloudfront_distributions_multiple_origin_failover_configured/cloudfront_distributions_multiple_origin_failover_configured_test.py @@ -0,0 +1,113 @@ +from unittest import mock + +from prowler.providers.aws.services.cloudfront.cloudfront_service import ( + DefaultCacheConfigBehaviour, + Distribution, + ViewerProtocolPolicy, +) +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER + +DISTRIBUTION_ID = "E27LVI50CSW06W" +DISTRIBUTION_ARN = ( + f"arn:aws:cloudfront::{AWS_ACCOUNT_NUMBER}:distribution/{DISTRIBUTION_ID}" +) +REGION = "eu-west-1" + + +class Test_cloudfront_distributions_multiple_origin_failover_configured: + def test_no_distributions(self): + cloudfront_client = mock.MagicMock + cloudfront_client.distributions = {} + with mock.patch( + "prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront", + new=cloudfront_client, + ): + # Test Check + from prowler.providers.aws.services.cloudfront.cloudfront_distributions_multiple_origin_failover_configured.cloudfront_distributions_multiple_origin_failover_configured import ( + cloudfront_distributions_multiple_origin_failover_configured, + ) + + check = cloudfront_distributions_multiple_origin_failover_configured() + result = check.execute() + + assert len(result) == 0 + + def test_no_origin_failover(self): + cloudfront_client = mock.MagicMock + cloudfront_client.distributions = { + "DISTRIBUTION_ID": Distribution( + arn=DISTRIBUTION_ARN, + id=DISTRIBUTION_ID, + region=REGION, + origins=[], + default_cache_config=DefaultCacheConfigBehaviour( + realtime_log_config_arn="", + viewer_protocol_policy=ViewerProtocolPolicy.allow_all, + field_level_encryption_id="", + ), + origin_failover=False, + ) + } + + with mock.patch( + "prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront", + new=cloudfront_client, + ): + # Test Check + from prowler.providers.aws.services.cloudfront.cloudfront_distributions_multiple_origin_failover_configured.cloudfront_distributions_multiple_origin_failover_configured import ( + cloudfront_distributions_multiple_origin_failover_configured, + ) + + check = cloudfront_distributions_multiple_origin_failover_configured() + result = check.execute() + + assert len(result) == 1 + assert result[0].region == REGION + assert result[0].resource_arn == DISTRIBUTION_ARN + assert result[0].resource_id == DISTRIBUTION_ID + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"CloudFront Distribution {DISTRIBUTION_ID} does not have an origin group configured with at least 2 origins." + ) + assert result[0].resource_tags == [] + + def test_origin_failover(self): + cloudfront_client = mock.MagicMock + cloudfront_client.distributions = { + "DISTRIBUTION_ID": Distribution( + arn=DISTRIBUTION_ARN, + id=DISTRIBUTION_ID, + region=REGION, + origins=[], + default_cache_config=DefaultCacheConfigBehaviour( + realtime_log_config_arn="", + viewer_protocol_policy=ViewerProtocolPolicy.allow_all, + field_level_encryption_id="", + ), + origin_failover=True, + ) + } + + with mock.patch( + "prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront", + new=cloudfront_client, + ): + # Test Check + from prowler.providers.aws.services.cloudfront.cloudfront_distributions_multiple_origin_failover_configured.cloudfront_distributions_multiple_origin_failover_configured import ( + cloudfront_distributions_multiple_origin_failover_configured, + ) + + check = cloudfront_distributions_multiple_origin_failover_configured() + result = check.execute() + + assert len(result) == 1 + assert result[0].region == REGION + assert result[0].resource_arn == DISTRIBUTION_ARN + assert result[0].resource_id == DISTRIBUTION_ID + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"CloudFront Distribution {DISTRIBUTION_ID} has an origin group with at least 2 origins configured." + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/cloudfront/cloudfront_distributions_using_deprecated_ssl_protocols/cloudfront_distributions_using_deprecated_ssl_protocols_test.py b/tests/providers/aws/services/cloudfront/cloudfront_distributions_using_deprecated_ssl_protocols/cloudfront_distributions_using_deprecated_ssl_protocols_test.py index b693595fbf..201a717d76 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_distributions_using_deprecated_ssl_protocols/cloudfront_distributions_using_deprecated_ssl_protocols_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_distributions_using_deprecated_ssl_protocols/cloudfront_distributions_using_deprecated_ssl_protocols_test.py @@ -46,6 +46,7 @@ class Test_cloudfront_distributions_using_deprecated_ssl_protocols: origin_ssl_protocols=["SSLv3"], ) ], + origin_failover=False, ) } @@ -90,6 +91,7 @@ class Test_cloudfront_distributions_using_deprecated_ssl_protocols: ], ) ], + origin_failover=False, ) } @@ -134,6 +136,7 @@ class Test_cloudfront_distributions_using_deprecated_ssl_protocols: ], ) ], + origin_failover=False, ) } @@ -177,6 +180,7 @@ class Test_cloudfront_distributions_using_deprecated_ssl_protocols: ], ) ], + origin_failover=False, ) } diff --git a/tests/providers/aws/services/cloudfront/cloudfront_distributions_using_waf/cloudfront_distributions_using_waf_test.py b/tests/providers/aws/services/cloudfront/cloudfront_distributions_using_waf/cloudfront_distributions_using_waf_test.py index c73fcc830a..260b342bcf 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_distributions_using_waf/cloudfront_distributions_using_waf_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_distributions_using_waf/cloudfront_distributions_using_waf_test.py @@ -38,6 +38,7 @@ class Test_cloudfront_distributions_using_waf: region=REGION, web_acl_id=wef_acl_id, origins=[], + origin_failover=False, ) } @@ -72,6 +73,7 @@ class Test_cloudfront_distributions_using_waf: id=DISTRIBUTION_ID, region=REGION, origins=[], + origin_failover=False, ) } diff --git a/tests/providers/aws/services/cloudfront/cloudfront_service_test.py b/tests/providers/aws/services/cloudfront/cloudfront_service_test.py index f670e2f188..2af8dead03 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_service_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_service_test.py @@ -30,6 +30,28 @@ def example_distribution_config(ref): }, ], }, + "OriginGroups": { + "Quantity": 1, + "Items": [ + { + "Id": "origin-group1", + "FailoverCriteria": { + "StatusCodes": {"Quantity": 1, "Items": [500]} + }, + "Members": { + "Quantity": 2, + "Items": [ + { + "OriginId": "origin1", + }, + { + "OriginId": "origin2", + }, + ], + }, + } + ], + }, "DefaultCacheBehavior": { "TargetOriginId": "origin1", "ViewerProtocolPolicy": "allow-all",