feat(cloudfront): Add new check cloudfront_distributions_default_root_object (#4938)

Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
Hugo Pereira Brito
2024-09-12 16:58:24 +02:00
committed by GitHub
parent 8ace8c01cf
commit edbe463d73
5 changed files with 173 additions and 0 deletions
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "cloudfront_distributions_default_root_object",
"CheckTitle": "Check if CloudFront distributions have a default root object.",
"CheckType": [],
"ServiceName": "cloudfront",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudfront:region:account-id:distribution/resource-id",
"Severity": "high",
"ResourceType": "AWSCloudFrontDistribution",
"Description": "Check if CloudFront distributions have a default root object.",
"Risk": "Without a default root object, requests to the root URL may result in an error or expose unintended content, leading to potential security risks and a poor user experience.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html#DefaultRootObjectHow",
"Remediation": {
"Code": {
"CLI": "aws cloudfront update-distribution --id <distribution-id> --default-root-object <new-root-object>",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-1",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/cloudfront-default-object.html"
},
"Recommendation": {
"Text": "Configure a default root object for your CloudFront distribution to ensure that a specific file (such as index.html) is returned when users access the root URL. This improves user experience and ensures that sensitive content isn't accidentally exposed.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html#DefaultRootObjectHowToDefine"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,26 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
cloudfront_client,
)
class cloudfront_distributions_default_root_object(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
if distribution.default_root_object:
report.status = "PASS"
report.status_extended = f"CloudFront Distribution {distribution.id} does have a default root object ({distribution.default_root_object}) configured."
else:
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} does not have a default root object configured."
findings.append(report)
return findings
@@ -88,6 +88,9 @@ class CloudFront(AWSService):
distributions[distribution_id].web_acl_id = distribution_config[
"DistributionConfig"
]["WebACLId"]
distributions[distribution_id].default_root_object = (
distribution_config["DistributionConfig"].get("DefaultRootObject")
)
# Default Cache Config
default_cache_config = DefaultCacheConfigBehaviour(
@@ -181,6 +184,7 @@ class Distribution(BaseModel):
geo_restriction_type: Optional[GeoRestrictionType]
origins: list[Origin]
web_acl_id: str = ""
default_root_object: Optional[str]
tags: Optional[list] = []
ssl_support_method: Optional[SSLSupportMethod]
certificate: Optional[str]
@@ -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_default_root_object:
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_default_root_object.cloudfront_distributions_default_root_object import (
cloudfront_distributions_default_root_object,
)
check = cloudfront_distributions_default_root_object()
result = check.execute()
assert len(result) == 0
def test_distribution_no_root_object(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="",
),
)
}
with mock.patch(
"prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront",
new=cloudfront_client,
):
# Test Check
from prowler.providers.aws.services.cloudfront.cloudfront_distributions_default_root_object.cloudfront_distributions_default_root_object import (
cloudfront_distributions_default_root_object,
)
check = cloudfront_distributions_default_root_object()
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 a default root object configured."
)
assert result[0].resource_tags == []
def test_distribution_root_object(self):
cloudfront_client = mock.MagicMock
dro = "index.html"
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="",
),
default_root_object=dro,
)
}
with mock.patch(
"prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront",
new=cloudfront_client,
):
# Test Check
from prowler.providers.aws.services.cloudfront.cloudfront_distributions_default_root_object.cloudfront_distributions_default_root_object import (
cloudfront_distributions_default_root_object,
)
check = cloudfront_distributions_default_root_object()
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} does have a default root object ({dro}) configured."
)
assert result[0].resource_tags == []