feat(cloudfront): Ensure Cloudfront distributions have origin failover configured (#4868)

Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
Hugo Pereira Brito
2024-09-18 19:26:35 +02:00
committed by GitHub
parent edc78bfd6b
commit ae794c7c32
12 changed files with 224 additions and 2 deletions
@@ -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": ""
}
@@ -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
@@ -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]
@@ -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,
)
}
@@ -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,
)
}
@@ -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,
)
}
@@ -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,
)
}
@@ -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 == []
@@ -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,
)
}
@@ -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,
)
}
@@ -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",