mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 21:11:53 +00:00
feat(cloudfront): Ensure distributions use SNI to serve HTTPS requests (#4888)
Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
committed by
GitHub
parent
c0c59968bf
commit
8f37252676
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "cloudfront_distributions_https_sni_enabled",
|
||||
"CheckTitle": "Check if CloudFront distributions are using SNI to serve HTTPS requests.",
|
||||
"CheckType": [
|
||||
"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 are using SNI to serve HTTPS requests.",
|
||||
"Risk": "If SNI is not used, CloudFront will allocate a dedicated IP address for each SSL certificate, leading to higher costs and inefficient IP address utilization. This could also complicate scaling and managing multiple distributions, especially if your domain requires multiple SSL certificates.",
|
||||
"RelatedUrl": "https://www.cloudflare.com/es-es/learning/ssl/what-is-sni/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-https-dedicated-ip-or-sni.html#cnames-https-sni",
|
||||
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/cloudfront-sni.html"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that your CloudFront distributions are configured to use Server Name Indication (SNI) when serving HTTPS requests with custom SSL/TLS certificates. This is the recommended approach for reducing costs and optimizing IP address usage.",
|
||||
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-https-dedicated-ip-or-sni.html#cnames-https-sni"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"encryption"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
|
||||
cloudfront_client,
|
||||
)
|
||||
from prowler.providers.aws.services.cloudfront.cloudfront_service import (
|
||||
SSLSupportMethod,
|
||||
)
|
||||
|
||||
|
||||
class cloudfront_distributions_https_sni_enabled(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for distribution in cloudfront_client.distributions.values():
|
||||
if distribution.certificate:
|
||||
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.ssl_support_method == SSLSupportMethod.sni_only:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"CloudFront Distribution {distribution.id} is serving HTTPS requests using SNI."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"CloudFront Distribution {distribution.id} is not serving HTTPS requests using SNI."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -30,6 +30,14 @@ class CloudFront(AWSService):
|
||||
):
|
||||
distribution_id = item["Id"]
|
||||
distribution_arn = item["ARN"]
|
||||
certificate = item["ViewerCertificate"].get(
|
||||
"Certificate", ""
|
||||
)
|
||||
ssl_support_method = SSLSupportMethod(
|
||||
item["ViewerCertificate"].get(
|
||||
"SSLSupportMethod", "static-ip"
|
||||
)
|
||||
)
|
||||
origins = []
|
||||
for origin in item.get("Origins", {}).get("Items", []):
|
||||
origins.append(
|
||||
@@ -51,6 +59,8 @@ class CloudFront(AWSService):
|
||||
id=distribution_id,
|
||||
origins=origins,
|
||||
region=region,
|
||||
ssl_support_method=ssl_support_method,
|
||||
certificate=certificate,
|
||||
)
|
||||
self.distributions[distribution_id] = distribution
|
||||
|
||||
@@ -139,6 +149,14 @@ class GeoRestrictionType(Enum):
|
||||
whitelist = "whitelist"
|
||||
|
||||
|
||||
class SSLSupportMethod(Enum):
|
||||
"""Method types that viewer want to accept HTTPS requests from"""
|
||||
|
||||
static_ip = "static-ip"
|
||||
sni_only = "sni-only"
|
||||
vip = "vip"
|
||||
|
||||
|
||||
class DefaultCacheConfigBehaviour(BaseModel):
|
||||
realtime_log_config_arn: Optional[str]
|
||||
viewer_protocol_policy: ViewerProtocolPolicy
|
||||
@@ -164,3 +182,5 @@ class Distribution(BaseModel):
|
||||
origins: list[Origin]
|
||||
web_acl_id: str = ""
|
||||
tags: Optional[list] = []
|
||||
ssl_support_method: Optional[SSLSupportMethod]
|
||||
certificate: Optional[str]
|
||||
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.aws.services.cloudfront.cloudfront_service import (
|
||||
Distribution,
|
||||
SSLSupportMethod,
|
||||
)
|
||||
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 = "us-east-1"
|
||||
|
||||
|
||||
class Test_cloudfront_distributions_https_sni_enabled:
|
||||
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_https_sni_enabled.cloudfront_distributions_https_sni_enabled import (
|
||||
cloudfront_distributions_https_sni_enabled,
|
||||
)
|
||||
|
||||
check = cloudfront_distributions_https_sni_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
def test_distribution_no_certificate(self):
|
||||
cloudfront_client = mock.MagicMock
|
||||
cloudfront_client.distributions = {
|
||||
"DISTRIBUTION_ID": Distribution(
|
||||
arn=DISTRIBUTION_ARN,
|
||||
id=DISTRIBUTION_ID,
|
||||
region=REGION,
|
||||
logging_enabled=True,
|
||||
origins=[],
|
||||
)
|
||||
}
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront",
|
||||
new=cloudfront_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudfront.cloudfront_distributions_https_sni_enabled.cloudfront_distributions_https_sni_enabled import (
|
||||
cloudfront_distributions_https_sni_enabled,
|
||||
)
|
||||
|
||||
check = cloudfront_distributions_https_sni_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
def test_distribution_certificate_not_set_up(self):
|
||||
cloudfront_client = mock.MagicMock
|
||||
cloudfront_client.distributions = {
|
||||
"DISTRIBUTION_ID": Distribution(
|
||||
arn=DISTRIBUTION_ARN,
|
||||
id=DISTRIBUTION_ID,
|
||||
region=REGION,
|
||||
logging_enabled=True,
|
||||
origins=[],
|
||||
ssl_support_method=SSLSupportMethod.static_ip,
|
||||
certificate="arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012",
|
||||
)
|
||||
}
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront",
|
||||
new=cloudfront_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudfront.cloudfront_distributions_https_sni_enabled.cloudfront_distributions_https_sni_enabled import (
|
||||
cloudfront_distributions_https_sni_enabled,
|
||||
)
|
||||
|
||||
check = cloudfront_distributions_https_sni_enabled()
|
||||
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} is not serving HTTPS requests using SNI."
|
||||
)
|
||||
|
||||
def test_distribution_valid_configuration(self):
|
||||
cloudfront_client = mock.MagicMock
|
||||
cloudfront_client.distributions = {
|
||||
"DISTRIBUTION_ID": Distribution(
|
||||
arn=DISTRIBUTION_ARN,
|
||||
id=DISTRIBUTION_ID,
|
||||
region=REGION,
|
||||
logging_enabled=True,
|
||||
origins=[],
|
||||
ssl_support_method=SSLSupportMethod.sni_only,
|
||||
certificate="arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012",
|
||||
)
|
||||
}
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront",
|
||||
new=cloudfront_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudfront.cloudfront_distributions_https_sni_enabled.cloudfront_distributions_https_sni_enabled import (
|
||||
cloudfront_distributions_https_sni_enabled,
|
||||
)
|
||||
|
||||
check = cloudfront_distributions_https_sni_enabled()
|
||||
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} is serving HTTPS requests using SNI."
|
||||
)
|
||||
@@ -1,12 +1,16 @@
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import botocore
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
from prowler.providers.aws.services.cloudfront.cloudfront_service import (
|
||||
CloudFront,
|
||||
DefaultCacheConfigBehaviour,
|
||||
Distribution,
|
||||
GeoRestrictionType,
|
||||
Origin,
|
||||
SSLSupportMethod,
|
||||
ViewerProtocolPolicy,
|
||||
)
|
||||
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
|
||||
@@ -35,6 +39,10 @@ def example_distribution_config(ref):
|
||||
"Cookies": {"Forward": "none"},
|
||||
},
|
||||
},
|
||||
"ViewerCertificate": {
|
||||
"SSLSupportMethod": "static-ip",
|
||||
"Certificate": "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012",
|
||||
},
|
||||
"Comment": "an optional comment that's not actually optional",
|
||||
"Enabled": False,
|
||||
}
|
||||
@@ -171,63 +179,86 @@ class Test_CloudFront_Service:
|
||||
|
||||
assert len(cloudfront.distributions) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_list_distributionscomplete(self):
|
||||
cloudfront_client = client("cloudfront")
|
||||
config = example_distribution_config("ref")
|
||||
response = cloudfront_client.create_distribution(DistributionConfig=config)
|
||||
cloudfront_distribution_id = response["Distribution"]["Id"]
|
||||
cloudfront_distribution_arn = response["Distribution"]["ARN"]
|
||||
cloudfront = CloudFront(set_mocked_aws_provider())
|
||||
def test__list_distributions__complete(self):
|
||||
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 = "us-east-1"
|
||||
LOGGING_ENABLED = True
|
||||
ORIGINS = [
|
||||
Origin(
|
||||
id="origin1",
|
||||
domain_name="asdf.s3.us-east-1.amazonaws.com",
|
||||
origin_protocol_policy="",
|
||||
origin_ssl_protocols=[],
|
||||
),
|
||||
]
|
||||
DEFAULT_CACHE_CONFIG = DefaultCacheConfigBehaviour(
|
||||
realtime_log_config_arn="test-log-arn",
|
||||
viewer_protocol_policy=ViewerProtocolPolicy.https_only,
|
||||
field_level_encryption_id="enabled",
|
||||
)
|
||||
GEO_RESTRICTION_TYPE = GeoRestrictionType.blacklist
|
||||
WEB_ACL_ID = "test-web-acl"
|
||||
TAGS = [
|
||||
{"Key": "test", "Value": "test"},
|
||||
]
|
||||
SSL_SUPPORT_METHOD = SSLSupportMethod.sni_only
|
||||
CERTIFICATE = "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
|
||||
|
||||
cloudfront = mock.MagicMock
|
||||
cloudfront.distributions = {
|
||||
DISTRIBUTION_ID: Distribution(
|
||||
arn=DISTRIBUTION_ARN,
|
||||
id=DISTRIBUTION_ID,
|
||||
region=REGION,
|
||||
logging_enabled=LOGGING_ENABLED,
|
||||
origins=ORIGINS,
|
||||
default_cache_config=DEFAULT_CACHE_CONFIG,
|
||||
geo_restriction_type=GEO_RESTRICTION_TYPE,
|
||||
web_acl_id=WEB_ACL_ID,
|
||||
tags=TAGS,
|
||||
ssl_support_method=SSL_SUPPORT_METHOD,
|
||||
certificate=CERTIFICATE,
|
||||
)
|
||||
}
|
||||
|
||||
assert len(cloudfront.distributions) == 1
|
||||
assert cloudfront.distributions[DISTRIBUTION_ID].arn == DISTRIBUTION_ARN
|
||||
assert cloudfront.distributions[DISTRIBUTION_ID].id == DISTRIBUTION_ID
|
||||
assert cloudfront.distributions[DISTRIBUTION_ID].region == AWS_REGION_US_EAST_1
|
||||
assert (
|
||||
cloudfront.distributions[cloudfront_distribution_id].arn
|
||||
== cloudfront_distribution_arn
|
||||
cloudfront.distributions[DISTRIBUTION_ID].logging_enabled is LOGGING_ENABLED
|
||||
)
|
||||
assert (
|
||||
cloudfront.distributions[cloudfront_distribution_id].id
|
||||
== cloudfront_distribution_id
|
||||
)
|
||||
assert (
|
||||
cloudfront.distributions[cloudfront_distribution_id].region
|
||||
== AWS_REGION_US_EAST_1
|
||||
)
|
||||
assert (
|
||||
cloudfront.distributions[cloudfront_distribution_id].logging_enabled is True
|
||||
)
|
||||
for origin in cloudfront.distributions[cloudfront_distribution_id].origins:
|
||||
for origin in cloudfront.distributions[DISTRIBUTION_ID].origins:
|
||||
assert origin.id == "origin1"
|
||||
assert origin.domain_name == "asdf.s3.us-east-1.amazonaws.com"
|
||||
assert origin.origin_protocol_policy == ""
|
||||
assert origin.origin_ssl_protocols == []
|
||||
assert (
|
||||
cloudfront.distributions[cloudfront_distribution_id].geo_restriction_type
|
||||
== GeoRestrictionType.blacklist
|
||||
)
|
||||
assert (
|
||||
cloudfront.distributions[cloudfront_distribution_id].web_acl_id
|
||||
== "test-web-acl"
|
||||
cloudfront.distributions[DISTRIBUTION_ID].geo_restriction_type
|
||||
== GEO_RESTRICTION_TYPE
|
||||
)
|
||||
assert cloudfront.distributions[DISTRIBUTION_ID].web_acl_id == "test-web-acl"
|
||||
assert (
|
||||
cloudfront.distributions[
|
||||
cloudfront_distribution_id
|
||||
DISTRIBUTION_ID
|
||||
].default_cache_config.realtime_log_config_arn
|
||||
== "test-log-arn"
|
||||
== DEFAULT_CACHE_CONFIG.realtime_log_config_arn
|
||||
)
|
||||
assert (
|
||||
cloudfront.distributions[
|
||||
cloudfront_distribution_id
|
||||
DISTRIBUTION_ID
|
||||
].default_cache_config.viewer_protocol_policy
|
||||
== ViewerProtocolPolicy.https_only
|
||||
== DEFAULT_CACHE_CONFIG.viewer_protocol_policy
|
||||
)
|
||||
assert (
|
||||
cloudfront.distributions[
|
||||
cloudfront_distribution_id
|
||||
DISTRIBUTION_ID
|
||||
].default_cache_config.field_level_encryption_id
|
||||
== "enabled"
|
||||
== DEFAULT_CACHE_CONFIG.field_level_encryption_id
|
||||
)
|
||||
|
||||
assert cloudfront.distributions[cloudfront_distribution_id].tags == [
|
||||
{"Key": "test", "Value": "test"},
|
||||
]
|
||||
assert cloudfront.distributions[DISTRIBUTION_ID].tags == TAGS
|
||||
|
||||
Reference in New Issue
Block a user