diff --git a/prowler/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/__init__.py b/prowler/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled.metadata.json b/prowler/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled.metadata.json new file mode 100644 index 0000000000..9fe1ffff0d --- /dev/null +++ b/prowler/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_cross_zone_load_balancing_enabled", + "CheckTitle": "Ensure Cross-Zone Load Balancing is enabled for Network (NLBs) and Gateway (GWLB) Load Balancers", + "CheckType": [], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElbv2LoadBalancer", + "Description": "Checks whether cross-zone load balancing is enabled for Network Load Balancers (NLBs) and Gateway Load Balancers (GWLB). Cross-zone load balancing ensures even distribution of traffic across all registered targets in all Availability Zones, improving fault tolerance and load distribution.", + "Risk": "If cross-zone load balancing is not enabled, traffic may not be evenly distributed across Availability Zones, leading to over-utilization of resources in certain zones and potential application performance degradation or outages.", + "RelatedUrl": "https://docs.aws.amazon.com/elasticloadbalancing/latest/network/network-load-balancers.html#cross-zone-load-balancing", + "Remediation": { + "Code": { + "CLI": "aws elbv2 modify-load-balancer-attributes --load-balancer-name --attributes Key=load_balancing.cross_zone.enabled,Value=true", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELBv2/enable-cross-zone-load-balancing.html#", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable cross-zone load balancing for Network and Gateway Load Balancers to ensure even traffic distribution and enhance fault tolerance across Availability Zones.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/network/network-load-balancers.html#cross-zone-load-balancing" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled.py b/prowler/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled.py new file mode 100644 index 0000000000..f7340f5797 --- /dev/null +++ b/prowler/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled.py @@ -0,0 +1,29 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_cross_zone_load_balancing_enabled(Check): + def execute(self) -> List[Check_Report_AWS]: + findings = [] + for lb_arn, lb in elbv2_client.loadbalancersv2.items(): + if lb.type != "application": + report = Check_Report_AWS(self.metadata()) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb_arn + report.resource_tags = lb.tags + report.status = "FAIL" + report.status_extended = ( + f"ELBv2 {lb.name} does not have cross-zone load balancing enabled." + ) + if lb.cross_zone_load_balancing == "true": + report.status = "PASS" + report.status_extended = ( + f"ELBv2 {lb.name} has cross-zone load balancing enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/elbv2/elbv2_service.py b/prowler/providers/aws/services/elbv2/elbv2_service.py index 4a1293e395..3955dfcb8f 100644 --- a/prowler/providers/aws/services/elbv2/elbv2_service.py +++ b/prowler/providers/aws/services/elbv2/elbv2_service.py @@ -103,6 +103,8 @@ class ELBv2(AWSService): )["Attributes"]: if attribute["Key"] == "routing.http.desync_mitigation_mode": load_balancer[1].desync_mitigation_mode = attribute["Value"] + if attribute["Key"] == "load_balancing.cross_zone.enabled": + load_balancer[1].cross_zone_load_balancing = attribute["Value"] if attribute["Key"] == "deletion_protection.enabled": load_balancer[1].deletion_protection = attribute["Value"] if attribute["Key"] == "access_logs.s3.enabled": @@ -203,6 +205,7 @@ class LoadBalancerv2(BaseModel): deletion_protection: Optional[str] dns: Optional[str] drop_invalid_header_fields: Optional[str] + cross_zone_load_balancing: Optional[str] listeners: Dict[str, Listenerv2] = {} scheme: Optional[str] security_groups: list[str] = [] diff --git a/tests/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled_test.py b/tests/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled_test.py new file mode 100644 index 0000000000..2c372e81f4 --- /dev/null +++ b/tests/providers/aws/services/elbv2/elbv2_cross_zone_load_balancing_enabled/elbv2_cross_zone_load_balancing_enabled_test.py @@ -0,0 +1,224 @@ +from unittest import mock + +from boto3 import client, resource +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_REGION_EU_WEST_1, + AWS_REGION_EU_WEST_1_AZA, + AWS_REGION_EU_WEST_1_AZB, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_elbv2_cross_zone_load_balancing_enabled: + @mock_aws + def test_elb_no_balancers(self): + 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_cross_zone_load_balancing_enabled.elbv2_cross_zone_load_balancing_enabled.elbv2_client", + new=ELBv2( + set_mocked_aws_provider([AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]) + ), + ): + # Test Check + from prowler.providers.aws.services.elbv2.elbv2_cross_zone_load_balancing_enabled.elbv2_cross_zone_load_balancing_enabled import ( + elbv2_cross_zone_load_balancing_enabled, + ) + + check = elbv2_cross_zone_load_balancing_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_elbv2_alb(self): + 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] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + {"Key": "load_balancing.cross_zone.enabled", "Value": "false"}, + ], + ) + + 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_cross_zone_load_balancing_enabled.elbv2_cross_zone_load_balancing_enabled.elbv2_client", + new=ELBv2( + set_mocked_aws_provider([AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]) + ), + ): + from prowler.providers.aws.services.elbv2.elbv2_cross_zone_load_balancing_enabled.elbv2_cross_zone_load_balancing_enabled import ( + elbv2_cross_zone_load_balancing_enabled, + ) + + check = elbv2_cross_zone_load_balancing_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_elbv2_without_cross_zone_load_balancing_enabled(self): + 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="network", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + {"Key": "load_balancing.cross_zone.enabled", "Value": "false"}, + ], + ) + + 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_cross_zone_load_balancing_enabled.elbv2_cross_zone_load_balancing_enabled.elbv2_client", + new=ELBv2( + set_mocked_aws_provider([AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]) + ), + ): + from prowler.providers.aws.services.elbv2.elbv2_cross_zone_load_balancing_enabled.elbv2_cross_zone_load_balancing_enabled import ( + elbv2_cross_zone_load_balancing_enabled, + ) + + check = elbv2_cross_zone_load_balancing_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "ELBv2 my-lb does not have cross-zone load balancing enabled." + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_aws + def test_elbv2_with_cross_zone_load_balancing_enabled(self): + 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="network", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + {"Key": "load_balancing.cross_zone.enabled", "Value": "true"}, + ], + ) + + 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_cross_zone_load_balancing_enabled.elbv2_cross_zone_load_balancing_enabled.elbv2_client", + new=ELBv2( + set_mocked_aws_provider([AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]) + ), + ): + from prowler.providers.aws.services.elbv2.elbv2_cross_zone_load_balancing_enabled.elbv2_cross_zone_load_balancing_enabled import ( + elbv2_cross_zone_load_balancing_enabled, + ) + + check = elbv2_cross_zone_load_balancing_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "ELBv2 my-lb has cross-zone load balancing enabled." + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/tests/providers/aws/services/elbv2/elbv2_service_test.py b/tests/providers/aws/services/elbv2/elbv2_service_test.py index 5cebd06734..d9e4c729cb 100644 --- a/tests/providers/aws/services/elbv2/elbv2_service_test.py +++ b/tests/providers/aws/services/elbv2/elbv2_service_test.py @@ -210,6 +210,7 @@ class Test_ELBv2_Service: Attributes=[ {"Key": "routing.http.desync_mitigation_mode", "Value": "defensive"}, {"Key": "access_logs.s3.enabled", "Value": "true"}, + {"Key": "load_balancing.cross_zone.enabled", "Value": "true"}, {"Key": "deletion_protection.enabled", "Value": "true"}, { "Key": "routing.http.drop_invalid_header_fields.enabled", @@ -231,6 +232,10 @@ class Test_ELBv2_Service: assert ( elbv2.loadbalancersv2[lb["LoadBalancerArn"]].deletion_protection == "true" ) + assert ( + elbv2.loadbalancersv2[lb["LoadBalancerArn"]].cross_zone_load_balancing + == "true" + ) assert ( elbv2.loadbalancersv2[lb["LoadBalancerArn"]].drop_invalid_header_fields == "false"