diff --git a/contrib/k8s/helm/prowler-api/values.yaml b/contrib/k8s/helm/prowler-api/values.yaml index db861f7b88..32149aeb7f 100644 --- a/contrib/k8s/helm/prowler-api/values.yaml +++ b/contrib/k8s/helm/prowler-api/values.yaml @@ -439,6 +439,9 @@ mainConfig: elbv2_min_azs: 2 # AWS Post-Quantum TLS Configuration + # aws.cloudfront_distributions_pqc_tls_enabled + cloudfront_pqc_min_protocol_versions: + - "TLSv1.3_2025" # aws.apigateway_domain_name_pqc_tls_enabled apigateway_pqc_tls_allowed_policies: - "SecurityPolicy_TLS13_1_2_FIPS_PFS_PQ_2025_09" diff --git a/docs/user-guide/cli/tutorials/configuration_file.mdx b/docs/user-guide/cli/tutorials/configuration_file.mdx index 58f30f413d..9f9f38a0dc 100644 --- a/docs/user-guide/cli/tutorials/configuration_file.mdx +++ b/docs/user-guide/cli/tutorials/configuration_file.mdx @@ -55,6 +55,7 @@ The following list includes all the AWS checks with configurable variables that | `elasticache_redis_cluster_backup_enabled` | `minimum_snapshot_retention_period` | Integer | | `elb_is_in_multiple_az` | `elb_min_azs` | Integer | | `elbv2_is_in_multiple_az` | `elbv2_min_azs` | Integer | +| `cloudfront_distributions_pqc_tls_enabled` | `cloudfront_pqc_min_protocol_versions` | List of Strings | | `apigateway_domain_name_pqc_tls_enabled` | `apigateway_pqc_tls_allowed_policies` | List of Strings | | `guardduty_is_enabled` | `mute_non_default_regions` | Boolean | | `iam_user_access_not_stale_to_sagemaker` | `max_unused_sagemaker_access_days` | Integer | diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index e4bf63c602..731f774d1a 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -44,6 +44,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - DORA (Digital Operational Resilience Act, Regulation (EU) 2022/2554) compliance coverage for the GCP provider, mapping existing GCP checks across the five DORA pillars [(#11642)](https://github.com/prowler-cloud/prowler/pull/11642) - DORA (Digital Operational Resilience Act, Regulation (EU) 2022/2554) compliance coverage for the Cloudflare provider, mapping existing Cloudflare edge/network checks across the applicable DORA pillars [(#11645)](https://github.com/prowler-cloud/prowler/pull/11645) - DORA (Digital Operational Resilience Act, Regulation (EU) 2022/2554) compliance coverage for the AlibabaCloud provider, mapping existing AlibabaCloud checks across the applicable DORA pillars [(#11646)](https://github.com/prowler-cloud/prowler/pull/11646) +- `cloudfront_distributions_pqc_tls_enabled` check for AWS provider to verify CloudFront distributions enforce a post-quantum TLS 1.3 security policy [(#11317)](https://github.com/prowler-cloud/prowler/pull/11317) - `apigateway_domain_name_pqc_tls_enabled` check for AWS provider to verify API Gateway custom domain names use a post-quantum TLS security policy [(#11316)](https://github.com/prowler-cloud/prowler/pull/11316) - `transfer_server_pqc_ssh_kex_enabled` check for AWS provider to verify Transfer Family servers use a post-quantum hybrid SSH key exchange security policy [(#11315)](https://github.com/prowler-cloud/prowler/pull/11315) diff --git a/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json b/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json index 8002f5d61c..4c4eaee252 100644 --- a/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json +++ b/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json @@ -1151,6 +1151,7 @@ "elb_insecure_ssl_ciphers", "elb_ssl_listeners", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elbv2_ssl_listeners", diff --git a/prowler/compliance/aws/ccc_aws.json b/prowler/compliance/aws/ccc_aws.json index 7fec0c7d71..003e9e17a0 100644 --- a/prowler/compliance/aws/ccc_aws.json +++ b/prowler/compliance/aws/ccc_aws.json @@ -49,6 +49,7 @@ "elb_insecure_ssl_ciphers", "elb_ssl_listeners", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elbv2_ssl_listeners", diff --git a/prowler/compliance/aws/ens_rd2022_aws.json b/prowler/compliance/aws/ens_rd2022_aws.json index 7b0658c040..8fcd3263e9 100644 --- a/prowler/compliance/aws/ens_rd2022_aws.json +++ b/prowler/compliance/aws/ens_rd2022_aws.json @@ -2367,6 +2367,7 @@ ], "Checks": [ "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled" ] @@ -2392,6 +2393,7 @@ ], "Checks": [ "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled" ] diff --git a/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json b/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json index 11ce0fc381..11f66ada6b 100644 --- a/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json +++ b/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json @@ -1145,6 +1145,7 @@ "Checks": [ "apigateway_restapi_client_certificate_enabled", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elb_ssl_listeners", @@ -1166,6 +1167,7 @@ "Checks": [ "apigateway_restapi_client_certificate_enabled", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elb_ssl_listeners", diff --git a/prowler/compliance/aws/ffiec_aws.json b/prowler/compliance/aws/ffiec_aws.json index 5a7fa14bc3..53f7275a85 100644 --- a/prowler/compliance/aws/ffiec_aws.json +++ b/prowler/compliance/aws/ffiec_aws.json @@ -487,6 +487,7 @@ "Checks": [ "apigateway_restapi_client_certificate_enabled", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elb_ssl_listeners", diff --git a/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json b/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json index 022a778bad..bcf8f19c3b 100644 --- a/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json +++ b/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json @@ -266,6 +266,7 @@ "ec2_ebs_default_encryption", "efs_encryption_at_rest_enabled", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elb_ssl_listeners", diff --git a/prowler/compliance/aws/iso27001_2013_aws.json b/prowler/compliance/aws/iso27001_2013_aws.json index 791aea8993..190693d121 100644 --- a/prowler/compliance/aws/iso27001_2013_aws.json +++ b/prowler/compliance/aws/iso27001_2013_aws.json @@ -36,6 +36,7 @@ "Checks": [ "elb_insecure_ssl_ciphers", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled" ] diff --git a/prowler/compliance/aws/kisa_isms_p_2023_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_aws.json index 734a92e8d3..c24b5a23e5 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_aws.json @@ -2040,6 +2040,7 @@ "elb_ssl_listeners", "elb_ssl_listeners_use_acm_certificate", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elbv2_nlb_tls_termination_enabled", @@ -3092,6 +3093,7 @@ "elb_ssl_listeners_use_acm_certificate", "elbv2_desync_mitigation_mode", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elbv2_internet_facing", diff --git a/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json index 05ef1c0082..50e2a149a5 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json @@ -2042,6 +2042,7 @@ "elb_ssl_listeners", "elb_ssl_listeners_use_acm_certificate", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elbv2_nlb_tls_termination_enabled", @@ -3095,6 +3096,7 @@ "elb_ssl_listeners_use_acm_certificate", "elbv2_desync_mitigation_mode", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elbv2_internet_facing", diff --git a/prowler/compliance/aws/nist_800_171_revision_2_aws.json b/prowler/compliance/aws/nist_800_171_revision_2_aws.json index d5a55398ce..e5a456bbb3 100644 --- a/prowler/compliance/aws/nist_800_171_revision_2_aws.json +++ b/prowler/compliance/aws/nist_800_171_revision_2_aws.json @@ -653,6 +653,7 @@ "apigateway_restapi_client_certificate_enabled", "ec2_ebs_volume_encryption", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "opensearch_service_domains_node_to_node_encryption_enabled", diff --git a/prowler/compliance/aws/nist_800_53_revision_5_aws.json b/prowler/compliance/aws/nist_800_53_revision_5_aws.json index a1cf795225..c9eb755e49 100644 --- a/prowler/compliance/aws/nist_800_53_revision_5_aws.json +++ b/prowler/compliance/aws/nist_800_53_revision_5_aws.json @@ -5262,6 +5262,7 @@ "Checks": [ "apigateway_restapi_client_certificate_enabled", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elb_ssl_listeners", @@ -5551,6 +5552,7 @@ ], "Checks": [ "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elb_ssl_listeners" diff --git a/prowler/compliance/aws/rbi_cyber_security_framework_aws.json b/prowler/compliance/aws/rbi_cyber_security_framework_aws.json index 46d2411902..f4e8d1d70e 100644 --- a/prowler/compliance/aws/rbi_cyber_security_framework_aws.json +++ b/prowler/compliance/aws/rbi_cyber_security_framework_aws.json @@ -40,6 +40,7 @@ "ec2_instance_public_ip", "efs_encryption_at_rest_enabled", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "elb_ssl_listeners", diff --git a/prowler/compliance/aws/secnumcloud_3.2_aws.json b/prowler/compliance/aws/secnumcloud_3.2_aws.json index c49e7c201a..d8736c2d6b 100644 --- a/prowler/compliance/aws/secnumcloud_3.2_aws.json +++ b/prowler/compliance/aws/secnumcloud_3.2_aws.json @@ -474,6 +474,7 @@ "elbv2_ssl_listeners", "elb_insecure_ssl_ciphers", "elbv2_insecure_ssl_ciphers", + "cloudfront_distributions_pqc_tls_enabled", "apigateway_domain_name_pqc_tls_enabled", "transfer_server_pqc_ssh_kex_enabled", "redshift_cluster_in_transit_encryption_enabled", diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml index 004fa1207a..0e063de264 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -381,6 +381,10 @@ aws: elbv2_min_azs: 2 # AWS Post-Quantum TLS Configuration + # aws.cloudfront_distributions_pqc_tls_enabled + # Allowed CloudFront MinimumProtocolVersion values that enable post-quantum hybrid key exchange + cloudfront_pqc_min_protocol_versions: + - "TLSv1.3_2025" # aws.apigateway_domain_name_pqc_tls_enabled # Allowed post-quantum TLS security policies for API Gateway custom domain names apigateway_pqc_tls_allowed_policies: diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/__init__.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled.metadata.json b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled.metadata.json new file mode 100644 index 0000000000..465f4335c2 --- /dev/null +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled.metadata.json @@ -0,0 +1,43 @@ +{ + "Provider": "aws", + "CheckID": "cloudfront_distributions_pqc_tls_enabled", + "CheckTitle": "CloudFront distributions enforce a post-quantum TLS 1.3 security policy", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "cloudfront", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "low", + "ResourceType": "AwsCloudFrontDistribution", + "ResourceGroup": "network", + "Description": "**CloudFront distributions** are assessed for use of a **TLS 1.3-only security policy** (`TLSv1.3_2025`). CloudFront's quantum-safe key exchanges (`X25519MLKEM768`, `SecP256r1MLKEM768`) only work with TLS 1.3. Distributions that allow TLS 1.2 (or older) fallback to classical key exchanges and are exposed to `harvest-now, decrypt-later` attacks.", + "Risk": "Without a TLS 1.3-only policy, viewer traffic captured today can be decrypted once a **cryptographically relevant quantum computer** is available. Distributions using the default CloudFront certificate (`*.cloudfront.net`) cannot enable a post-quantum policy because they are pinned to the legacy `TLSv1` policy.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html", + "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesGeneral.html#DownloadDistValues-security-policy", + "https://aws.amazon.com/security/post-quantum-cryptography/", + "https://csrc.nist.gov/projects/post-quantum-cryptography" + ], + "Remediation": { + "Code": { + "CLI": "aws cloudfront update-distribution --id --distribution-config '{...\"ViewerCertificate\":{\"MinimumProtocolVersion\":\"TLSv1.3_2025\",...}}'", + "NativeIaC": "```yaml\nResources:\n :\n Type: AWS::CloudFront::Distribution\n Properties:\n DistributionConfig:\n ViewerCertificate:\n AcmCertificateArn: \n SslSupportMethod: sni-only\n MinimumProtocolVersion: TLSv1.3_2025 # FIX: enforces TLS 1.3 + post-quantum KEX\n```", + "Other": "1. In the AWS Console, go to CloudFront > Distributions\n2. Select the distribution and open the General tab\n3. Choose Edit on Settings\n4. Set Custom SSL certificate (do not use the default *.cloudfront.net certificate)\n5. Set Security policy to TLSv1.3_2025\n6. Save changes", + "Terraform": "```hcl\nresource \"aws_cloudfront_distribution\" \"\" {\n # ...\n viewer_certificate {\n acm_certificate_arn = \"\"\n ssl_support_method = \"sni-only\"\n minimum_protocol_version = \"TLSv1.3_2025\" # FIX: enforces TLS 1.3 + post-quantum KEX\n }\n}\n```" + }, + "Recommendation": { + "Text": "Use a **custom SSL certificate** with **SNI** support and set `MinimumProtocolVersion` to `TLSv1.3_2025` so CloudFront refuses TLS 1.2 handshakes and uses the hybrid ML-KEM key exchange. Distributions still using the default CloudFront certificate must be migrated to a custom certificate to enable post-quantum TLS.", + "Url": "https://hub.prowler.com/check/cloudfront_distributions_pqc_tls_enabled" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [ + "cloudfront_distributions_using_deprecated_ssl_protocols" + ], + "Notes": "" +} diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled.py new file mode 100644 index 0000000000..6c472d43c7 --- /dev/null +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled.py @@ -0,0 +1,56 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.cloudfront.cloudfront_client import ( + cloudfront_client, +) + +PQC_CLOUDFRONT_POLICIES_DEFAULT = [ + "TLSv1.3_2025", +] + + +class cloudfront_distributions_pqc_tls_enabled(Check): + """Verify that every CloudFront distribution enforces TLS 1.3 with post-quantum key exchange. + + Quantum-safe key exchanges (``X25519MLKEM768``, ``SecP256r1MLKEM768``) are + only available on TLS 1.3 viewer connections. A distribution PASSES when + its ``MinimumProtocolVersion`` belongs to the configured allowlist of + TLS 1.3-only policies. Distributions that rely on the default CloudFront + certificate are pinned to the legacy ``TLSv1`` policy and therefore FAIL. + """ + + def execute(self) -> list[Check_Report_AWS]: + """Execute the CloudFront post-quantum TLS policy check. + + Returns: + A list of reports containing each CloudFront distribution's + post-quantum TLS compliance status. + """ + findings = [] + pqc_policies = cloudfront_client.audit_config.get( + "cloudfront_pqc_min_protocol_versions", PQC_CLOUDFRONT_POLICIES_DEFAULT + ) + for distribution in cloudfront_client.distributions.values(): + report = Check_Report_AWS(metadata=self.metadata(), resource=distribution) + policy = distribution.minimum_protocol_version or "" + if distribution.default_certificate: + report.status = "FAIL" + report.status_extended = ( + f"CloudFront Distribution {distribution.id} uses the default " + "CloudFront certificate, which pins the security policy to " + "TLSv1 and cannot enable post-quantum TLS." + ) + elif distribution.minimum_protocol_version in pqc_policies: + report.status = "PASS" + report.status_extended = ( + f"CloudFront Distribution {distribution.id} uses post-quantum " + f"TLS policy {policy}." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"CloudFront Distribution {distribution.id} uses TLS policy " + f"{policy}, which is not in the post-quantum allowlist." + ) + 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 0826c9adfd..660b483c4c 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_service.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_service.py @@ -48,6 +48,9 @@ class CloudFront(AWSService): "SSLSupportMethod", "static-ip" ) ) + minimum_protocol_version = item["ViewerCertificate"].get( + "MinimumProtocolVersion", "" + ) origins = [] for origin in item.get("Origins", {}).get("Items", []): origins.append( @@ -79,6 +82,7 @@ class CloudFront(AWSService): ssl_support_method=ssl_support_method, default_certificate=default_certificate, certificate=certificate, + minimum_protocol_version=minimum_protocol_version, ) self.distributions[distribution_id] = distribution @@ -268,3 +272,4 @@ class Distribution(BaseModel): origin_failover: Optional[bool] = None ssl_support_method: Optional[SSLSupportMethod] = None certificate: Optional[str] = None + minimum_protocol_version: str = "" diff --git a/tests/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled_test.py b/tests/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled_test.py new file mode 100644 index 0000000000..5170d5538d --- /dev/null +++ b/tests/providers/aws/services/cloudfront/cloudfront_distributions_pqc_tls_enabled/cloudfront_distributions_pqc_tls_enabled_test.py @@ -0,0 +1,158 @@ +import sys +from unittest import mock + +from prowler.providers.aws.services.cloudfront.cloudfront_service import ( + Distribution, + Origin, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +DISTRIBUTION_ID = "E27LVI50CSW06W" +DISTRIBUTION_ARN = ( + f"arn:aws:cloudfront::{AWS_ACCOUNT_NUMBER}:distribution/{DISTRIBUTION_ID}" +) +REGION = "us-east-1" +CHECK_MODULE = "prowler.providers.aws.services.cloudfront.cloudfront_distributions_pqc_tls_enabled.cloudfront_distributions_pqc_tls_enabled" +CLIENT_MODULE = "prowler.providers.aws.services.cloudfront.cloudfront_client" + + +def _clear_cloudfront_modules(): + sys.modules.pop(CHECK_MODULE, None) + sys.modules.pop(CLIENT_MODULE, None) + + +def _build_distribution( + *, + minimum_protocol_version: str, + default_certificate: bool = False, +): + return Distribution( + arn=DISTRIBUTION_ARN, + id=DISTRIBUTION_ID, + region=REGION, + origins=[ + Origin( + id="o1", + domain_name="origin.example.com", + origin_protocol_policy="https-only", + origin_ssl_protocols=["TLSv1.2"], + ) + ], + origin_failover=False, + minimum_protocol_version=minimum_protocol_version, + default_certificate=default_certificate, + ) + + +def _build_client(distributions: dict, audit_config: dict | None = None): + cloudfront_client = mock.MagicMock() + cloudfront_client.distributions = distributions + cloudfront_client.audit_config = audit_config or {} + return cloudfront_client + + +def _execute_check(cloudfront_client): + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + _clear_cloudfront_modules() + + try: + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.cloudfront.cloudfront_service.CloudFront", + return_value=cloudfront_client, + ), + ): + from prowler.providers.aws.services.cloudfront.cloudfront_distributions_pqc_tls_enabled.cloudfront_distributions_pqc_tls_enabled import ( + cloudfront_distributions_pqc_tls_enabled, + ) + + check = cloudfront_distributions_pqc_tls_enabled() + return check.execute() + finally: + _clear_cloudfront_modules() + + +class Test_cloudfront_distributions_pqc_tls_enabled: + def test_no_distributions(self): + cloudfront_client = _build_client({}) + + result = _execute_check(cloudfront_client) + + assert len(result) == 0 + + def test_pq_policy_tls13_2025(self): + cloudfront_client = _build_client( + { + DISTRIBUTION_ID: _build_distribution( + minimum_protocol_version="TLSv1.3_2025" + ) + } + ) + + result = _execute_check(cloudfront_client) + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "TLSv1.3_2025" in result[0].status_extended + assert result[0].resource_id == DISTRIBUTION_ID + assert result[0].resource_arn == DISTRIBUTION_ARN + + def test_classical_tls12_2021(self): + cloudfront_client = _build_client( + { + DISTRIBUTION_ID: _build_distribution( + minimum_protocol_version="TLSv1.2_2021" + ) + } + ) + + result = _execute_check(cloudfront_client) + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "TLSv1.2_2021" in result[0].status_extended + assert "not in the post-quantum allowlist" in result[0].status_extended + + def test_default_cloudfront_certificate(self): + cloudfront_client = _build_client( + { + DISTRIBUTION_ID: _build_distribution( + minimum_protocol_version="TLSv1", + default_certificate=True, + ) + } + ) + + result = _execute_check(cloudfront_client) + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "default CloudFront certificate" in result[0].status_extended + + def test_configurable_allowlist(self): + cloudfront_client = _build_client( + { + DISTRIBUTION_ID: _build_distribution( + minimum_protocol_version="TLSv1.2_2021" + ) + }, + audit_config={ + "cloudfront_pqc_min_protocol_versions": [ + "TLSv1.3_2025", + "TLSv1.2_2021", + ] + }, + ) + + result = _execute_check(cloudfront_client) + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/aws/services/cloudfront/cloudfront_service_test.py b/tests/providers/aws/services/cloudfront/cloudfront_service_test.py index 4c0a9b349e..5dd6f712a7 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_service_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_service_test.py @@ -64,6 +64,7 @@ def example_distribution_config(ref): "ViewerCertificate": { "SSLSupportMethod": "static-ip", "Certificate": "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012", + "MinimumProtocolVersion": "TLSv1.3_2025", }, "Comment": "an optional comment that's not actually optional", "Enabled": False, @@ -234,6 +235,7 @@ class Test_CloudFront_Service: ] SSL_SUPPORT_METHOD = SSLSupportMethod.sni_only CERTIFICATE = "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012" + MINIMUM_PROTOCOL_VERSION = "TLSv1.3_2025" cloudfront = mock.MagicMock cloudfront.distributions = { @@ -249,6 +251,7 @@ class Test_CloudFront_Service: tags=TAGS, ssl_support_method=SSL_SUPPORT_METHOD, certificate=CERTIFICATE, + minimum_protocol_version=MINIMUM_PROTOCOL_VERSION, ) } @@ -288,6 +291,10 @@ class Test_CloudFront_Service: == DEFAULT_CACHE_CONFIG.field_level_encryption_id ) assert cloudfront.distributions[DISTRIBUTION_ID].tags == TAGS + assert ( + cloudfront.distributions[DISTRIBUTION_ID].minimum_protocol_version + == MINIMUM_PROTOCOL_VERSION + ) def test_get_log_delivery_sources_with_active_delivery(self): from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER