diff --git a/prowler/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/__init__.py b/prowler/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled.metadata.json b/prowler/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled.metadata.json new file mode 100644 index 0000000000..006a67d7ae --- /dev/null +++ b/prowler/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "elb_cross_zone_load_balancing_enabled", + "CheckTitle": "Ensure Cross-Zone Load Balancing is Enabled for Classic Load Balancers (CLBs)", + "CheckType": [], + "ServiceName": "elb", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AWSElasticLoadBalancingLoadBalancer", + "Description": "Checks whether cross-zone load balancing is enabled for Classic Load Balancers (CLBs). 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/classic/enable-disable-crosszone-lb.html", + "Remediation": { + "Code": { + "CLI": "aws elb modify-load-balancer-attributes --load-balancer-name --load-balancer-attributes \"CrossZoneLoadBalancing={Enabled=true}\"", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/elb-controls.html#elb-9", + "Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELB/elb-cross-zone-load-balancing-enabled.html" + }, + "Recommendation": { + "Text": "Enable cross-zone load balancing for Classic Load Balancers to ensure even traffic distribution and enhance fault tolerance across Availability Zones.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-disable-crosszone-lb.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled.py b/prowler/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled.py new file mode 100644 index 0000000000..f9dbbff636 --- /dev/null +++ b/prowler/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled.py @@ -0,0 +1,27 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.elb.elb_client import elb_client + + +class elb_cross_zone_load_balancing_enabled(Check): + def execute(self) -> List[Check_Report_AWS]: + findings = [] + for load_balancer_arn, load_balancer in elb_client.loadbalancers.items(): + report = Check_Report_AWS(self.metadata()) + report.region = load_balancer.region + report.resource_id = load_balancer.name + report.resource_arn = load_balancer_arn + report.resource_tags = load_balancer.tags + report.status = "FAIL" + report.status_extended = f"ELB {load_balancer.name} does not have cross-zone load balancing enabled." + + if load_balancer.cross_zone_load_balancing: + report.status = "PASS" + report.status_extended = ( + f"ELB {load_balancer.name} has cross-zone load balancing enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/elb/elb_service.py b/prowler/providers/aws/services/elb/elb_service.py index 181236cb72..b9c14a78dd 100644 --- a/prowler/providers/aws/services/elb/elb_service.py +++ b/prowler/providers/aws/services/elb/elb_service.py @@ -59,9 +59,10 @@ class ELB(AWSService): LoadBalancerName=load_balancer.name )["LoadBalancerAttributes"] - load_balancer.access_logs = attributes.get("AccessLog", {}).get( - "Enabled", False - ) + load_balancer.access_logs = attributes.get("AccessLog", {}).get("Enabled") + load_balancer.cross_zone_load_balancing = attributes.get( + "CrossZoneLoadBalancing", {} + ).get("Enabled") except Exception as error: logger.error( @@ -97,4 +98,5 @@ class LoadBalancer(BaseModel): scheme: str access_logs: Optional[bool] listeners: list[Listener] + cross_zone_load_balancing: Optional[bool] tags: Optional[list] = [] diff --git a/tests/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled_test.py b/tests/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled_test.py new file mode 100644 index 0000000000..42d68f6f35 --- /dev/null +++ b/tests/providers/aws/services/elb/elb_cross_zone_load_balancing_enabled/elb_cross_zone_load_balancing_enabled_test.py @@ -0,0 +1,183 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) + + +class Test_elb_cross_zone_load_balancing_enabled: + def test_elb_no_balancers(self): + from prowler.providers.aws.services.elb.elb_service import ELB + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_EU_WEST_1]), + ), mock.patch( + "prowler.providers.aws.services.elb.elb_ssl_listeners.elb_ssl_listeners.elb_client", + new=ELB(set_mocked_aws_provider([AWS_REGION_EU_WEST_1])), + ): + # Test Check + from prowler.providers.aws.services.elb.elb_ssl_listeners.elb_ssl_listeners import ( + elb_ssl_listeners, + ) + + check = elb_ssl_listeners() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_default_elb(self): + elb_client = client("elb", region_name=AWS_REGION_EU_WEST_1) + # Create a compliant resource + elb_client.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + Scheme="internet-facing", + ) + + from prowler.providers.aws.services.elb.elb_service import ELB + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.elb.elb_cross_zone_load_balancing_enabled.elb_cross_zone_load_balancing_enabled.elb_client", + new=ELB(aws_provider), + ): + from prowler.providers.aws.services.elb.elb_cross_zone_load_balancing_enabled.elb_cross_zone_load_balancing_enabled import ( + elb_cross_zone_load_balancing_enabled, + ) + + check = elb_cross_zone_load_balancing_enabled() + result = check.execute() + + assert len(result) == 1 + assert ( + result[0].status == "FAIL" + ) # This should be a PASS, because AWS by default enables cross-zone load balancing but moto doesn't + assert ( + result[0].status_extended + == "ELB my-lb does not have cross-zone load balancing enabled." + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_id == "my-lb" + assert ( + result[0].resource_arn + == f"arn:aws:elasticloadbalancing:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:loadbalancer/my-lb" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_elb_with_cross_zone_lb_enabled(self): + elb_client = client("elb", region_name=AWS_REGION_EU_WEST_1) + # Create a compliant resource + elb_client.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + Scheme="internet-facing", + ) + + elb_client.modify_load_balancer_attributes( + LoadBalancerName="my-lb", + LoadBalancerAttributes={ + "CrossZoneLoadBalancing": {"Enabled": True}, + }, + ) + + from prowler.providers.aws.services.elb.elb_service import ELB + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.elb.elb_cross_zone_load_balancing_enabled.elb_cross_zone_load_balancing_enabled.elb_client", + new=ELB(aws_provider), + ): + from prowler.providers.aws.services.elb.elb_cross_zone_load_balancing_enabled.elb_cross_zone_load_balancing_enabled import ( + elb_cross_zone_load_balancing_enabled, + ) + + check = elb_cross_zone_load_balancing_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "ELB my-lb has cross-zone load balancing enabled." + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_id == "my-lb" + assert ( + result[0].resource_arn + == f"arn:aws:elasticloadbalancing:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:loadbalancer/my-lb" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_elb_with_cross_zone_lb_disabled(self): + elb_client = client("elb", region_name=AWS_REGION_EU_WEST_1) + # Create a non-compliant resource + elb_client.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + Scheme="internet-facing", + ) + + elb_client.modify_load_balancer_attributes( + LoadBalancerName="my-lb", + LoadBalancerAttributes={ + "CrossZoneLoadBalancing": {"Enabled": False}, + }, + ) + + from prowler.providers.aws.services.elb.elb_service import ELB + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.elb.elb_cross_zone_load_balancing_enabled.elb_cross_zone_load_balancing_enabled.elb_client", + new=ELB(aws_provider), + ): + from prowler.providers.aws.services.elb.elb_cross_zone_load_balancing_enabled.elb_cross_zone_load_balancing_enabled import ( + elb_cross_zone_load_balancing_enabled, + ) + + check = elb_cross_zone_load_balancing_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "ELB my-lb does not have cross-zone load balancing enabled." + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_id == "my-lb" + assert ( + result[0].resource_arn + == f"arn:aws:elasticloadbalancing:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:loadbalancer/my-lb" + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/elb/elb_service_test.py b/tests/providers/aws/services/elb/elb_service_test.py index 209e63ca65..db835aec89 100644 --- a/tests/providers/aws/services/elb/elb_service_test.py +++ b/tests/providers/aws/services/elb/elb_service_test.py @@ -99,7 +99,8 @@ class Test_ELB_Service: "S3BucketName": "mb", "EmitInterval": 42, "S3BucketPrefix": "s3bf", - } + }, + "CrossZoneLoadBalancing": {"Enabled": True}, }, ) elb_arn = f"arn:aws:elasticloadbalancing:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:loadbalancer/my-lb" @@ -110,6 +111,7 @@ class Test_ELB_Service: assert elb.loadbalancers[elb_arn].region == AWS_REGION_US_EAST_1 assert elb.loadbalancers[elb_arn].scheme == "internal" assert elb.loadbalancers[elb_arn].access_logs + assert elb.loadbalancers[elb_arn].cross_zone_load_balancing # Test ELB Describe Tags @mock_aws