From 9c2cb5efa85260dc559789470a0b8cca3ec9a4e4 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Tue, 3 Mar 2026 09:18:00 +0000 Subject: [PATCH] fix(elbv2): Handle post-quantum (PQ) TLS policies (#10219) --- .github/workflows/sdk-tests.yml | 2 +- prowler/CHANGELOG.md | 2 +- .../elbv2_insecure_ssl_ciphers.py | 13 +++ .../elbv2_insecure_ssl_ciphers_test.py | 104 ++++++++++++++++++ 4 files changed, 119 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk-tests.yml b/.github/workflows/sdk-tests.yml index 48da5cc97f..7c5f5c7851 100644 --- a/.github/workflows/sdk-tests.yml +++ b/.github/workflows/sdk-tests.yml @@ -122,7 +122,7 @@ jobs: "wafv2": ["cognito", "elbv2"], } - changed_raw = """${STEPS_CHANGED_AWS_OUTPUTS_ALL_CHANGED_FILES}""" + changed_raw = os.environ.get("STEPS_CHANGED_AWS_OUTPUTS_ALL_CHANGED_FILES", "") # all_changed_files is space-separated, not newline-separated # Strip leading "./" if present for consistent path handling changed_files = [Path(f.lstrip("./")) for f in changed_raw.split() if f] diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index c34d797011..451313e595 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -72,7 +72,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Handle serialization errors in OCSF output for non-serializable resource metadata [(#10129)](https://github.com/prowler-cloud/prowler/pull/10129) - Respect `AWS_ENDPOINT_URL` environment variable for STS session creation [(#10228)](https://github.com/prowler-cloud/prowler/pull/10228) - Help text and typos in CLI flags [(#10040)](https://github.com/prowler-cloud/prowler/pull/10040) - +- `elbv2_insecure_ssl_ciphers` false positive on AWS post-quantum (PQ) TLS policies like `ELBSecurityPolicy-TLS13-1-2-PQ-2025-09` [(#10219)](https://github.com/prowler-cloud/prowler/pull/10219) ### 🔐 Security diff --git a/prowler/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.py b/prowler/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.py index ba43d463e3..974ca5d992 100644 --- a/prowler/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.py +++ b/prowler/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.py @@ -16,6 +16,19 @@ class elbv2_insecure_ssl_ciphers(Check): "ELBSecurityPolicy-TLS13-1-2-Res-2021-06", "ELBSecurityPolicy-TLS13-1-2-Ext1-2021-06", "ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06", + # AWS post-quantum (PQ) TLS policies (TLS 1.2+ minimum) + "ELBSecurityPolicy-TLS13-1-2-Ext1-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Ext2-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Res-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-3-PQ-2025-09", + # AWS FIPS post-quantum (PQ) TLS policies (TLS 1.2+ minimum) + "ELBSecurityPolicy-TLS13-1-2-Ext0-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Ext1-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Ext2-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Res-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-3-FIPS-PQ-2025-09", ] for lb in elbv2_client.loadbalancersv2.values(): report = Check_Report_AWS(metadata=self.metadata(), resource=lb) diff --git a/tests/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers_test.py b/tests/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers_test.py index fe96f2ea17..5a0fd88c28 100644 --- a/tests/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers_test.py +++ b/tests/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers_test.py @@ -1,5 +1,6 @@ from unittest import mock +import pytest from boto3 import client, resource from moto import mock_aws @@ -216,3 +217,106 @@ class Test_elbv2_insecure_ssl_ciphers: ) assert result[0].resource_id == "my-lb" assert result[0].resource_arn == lb["LoadBalancerArn"] + + @pytest.mark.parametrize( + "ssl_policy", + [ + "ELBSecurityPolicy-TLS13-1-3-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Res-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Ext1-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Ext2-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-3-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Res-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Ext0-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Ext1-FIPS-PQ-2025-09", + "ELBSecurityPolicy-TLS13-1-2-Ext2-FIPS-PQ-2025-09", + ], + ) + @mock_aws + def test_elbv2_listener_with_pq_tls_policy(self, ssl_policy): + conn = client("elbv2", region_name=AWS_REGION_EU_WEST_1) + ec2 = resource("ec2", region_name=AWS_REGION_EU_WEST_1) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, + CidrBlock="172.28.7.192/26", + AvailabilityZone=AWS_REGION_EU_WEST_1_AZA, + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, + CidrBlock="172.28.7.0/26", + AvailabilityZone=AWS_REGION_EU_WEST_1_AZB, + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + response = conn.create_target_group( + Name="a-target", + Protocol="HTTP", + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol="HTTP", + HealthCheckPort="8080", + HealthCheckPath="/", + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=3, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={"HttpCode": "200"}, + ) + target_group = response.get("TargetGroups")[0] + target_group_arn = target_group["TargetGroupArn"] + conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTPS", + Port=443, + SslPolicy=ssl_policy, + DefaultActions=[{"Type": "forward", "TargetGroupArn": target_group_arn}], + ) + + from prowler.providers.aws.services.elbv2.elbv2_service import ELBv2 + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ), + ), + mock.patch( + "prowler.providers.aws.services.elbv2.elbv2_insecure_ssl_ciphers.elbv2_insecure_ssl_ciphers.elbv2_client", + new=ELBv2( + set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1], + create_default_organization=False, + ) + ), + ), + ): + from prowler.providers.aws.services.elbv2.elbv2_insecure_ssl_ciphers.elbv2_insecure_ssl_ciphers import ( + elbv2_insecure_ssl_ciphers, + ) + + check = elbv2_insecure_ssl_ciphers() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "ELBv2 my-lb does not have insecure SSL protocols or ciphers." + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"]