mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 04:21:52 +00:00
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:
committed by
GitHub
parent
edc78bfd6b
commit
ae794c7c32
+36
@@ -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": ""
|
||||
}
|
||||
+25
@@ -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]
|
||||
|
||||
+2
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+113
@@ -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 == []
|
||||
+4
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user