mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat(elb): add new check elb_is_in_multiple_az (#4829)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
committed by
GitHub
parent
71f50422ad
commit
bcb63d0b2d
@@ -48,6 +48,7 @@ The following list includes all the AWS checks with configurable variables that
|
||||
| `eks_control_plane_logging_all_types_enabled` | `eks_required_log_types` | List of Strings |
|
||||
| `eks_cluster_uses_a_supported_version` | `eks_cluster_oldest_version_supported` | String |
|
||||
| `elbv2_is_in_multiple_az` | `elbv2_min_azs` | Integer |
|
||||
| `elb_is_in_multiple_az` | `elb_min_azs` | Integer |
|
||||
|
||||
## Azure
|
||||
|
||||
|
||||
@@ -316,6 +316,11 @@ aws:
|
||||
|
||||
]
|
||||
|
||||
# AWS ELB Configuration
|
||||
# aws.elb_is_in_multiple_az
|
||||
# Minimum number of Availability Zones that an CLB must be in
|
||||
elb_min_azs: 2
|
||||
|
||||
# AWS ELBv2 Configuration
|
||||
# aws.elbv2_is_in_multiple_az
|
||||
# Minimum number of Availability Zones that an ELBv2 must be in
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "elb_is_in_multiple_az",
|
||||
"CheckTitle": "Ensure Classic Load Balancer is Configured Across Multiple Availability Zones",
|
||||
"CheckType": [],
|
||||
"ServiceName": "elb",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:<partition>:elasticloadbalancing:<region>:<account-id>:loadbalancer/<load-balancer-name>",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AwsElbLoadBalancer",
|
||||
"Description": "This check ensures that a Classic Load Balancer is configured to span at least the specified number of Availability Zones (AZs). The control fails if the Load Balancer does not span multiple AZs, which can lead to decreased availability and reliability in case of an AZ failure.",
|
||||
"Risk": "A Classic Load Balancer configured in a single Availability Zone risks becoming a single point of failure. If the AZ fails, the load balancer will not be able to redirect traffic to other healthy targets, leading to potential service outages.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/introduction.html#classic-load-balancer-overview",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/elb-controls.html#elb-10",
|
||||
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELB/ec2-instances-distribution-across-availability-zones.html"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Distribute your Classic Load Balancer across multiple Availability Zones to improve redundancy and fault tolerance.",
|
||||
"Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-disable-crosszone-lb.html"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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_is_in_multiple_az(Check):
|
||||
def execute(self) -> List[Check_Report_AWS]:
|
||||
findings = []
|
||||
ELB_MIN_AZS = elb_client.audit_config.get("elb_min_azs", 2)
|
||||
for loadbalancer_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 = loadbalancer_arn
|
||||
report.resource_tags = load_balancer.tags
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Classic Load Balancer {load_balancer.name} is not in at least {ELB_MIN_AZS} availability zones, it is only in {', '.join(load_balancer.availability_zones)}."
|
||||
|
||||
if len(load_balancer.availability_zones) >= ELB_MIN_AZS:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Classic Load Balancer {load_balancer.name} is in {len(load_balancer.availability_zones)} availability zones: {', '.join(load_balancer.availability_zones)}."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -45,6 +45,7 @@ class ELB(AWSService):
|
||||
region=regional_client.region,
|
||||
scheme=elb["Scheme"],
|
||||
listeners=listeners,
|
||||
availability_zones=set(elb.get("AvailabilityZones", [])),
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
@@ -99,4 +100,5 @@ class LoadBalancer(BaseModel):
|
||||
access_logs: Optional[bool]
|
||||
listeners: list[Listener]
|
||||
cross_zone_load_balancing: Optional[bool]
|
||||
availability_zones: set[str]
|
||||
tags: Optional[list] = []
|
||||
|
||||
@@ -288,6 +288,7 @@ config_aws = {
|
||||
],
|
||||
"eks_cluster_oldest_version_supported": "1.28",
|
||||
"excluded_sensitive_environment_variables": [],
|
||||
"elb_min_azs": 2,
|
||||
"elbv2_min_azs": 2,
|
||||
}
|
||||
|
||||
|
||||
@@ -315,6 +315,11 @@ aws:
|
||||
|
||||
]
|
||||
|
||||
# AWS ELB Configuration
|
||||
# aws.elb_is_in_multiple_az
|
||||
# Minimum number of Availability Zones that an CLB must be in
|
||||
elb_min_azs: 2
|
||||
|
||||
# AWS ELBv2 Configuration
|
||||
# aws.elbv2_is_in_multiple_az
|
||||
# Minimum number of Availability Zones that an ELBv2 must be in
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
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,
|
||||
AWS_REGION_EU_WEST_1_AZA,
|
||||
AWS_REGION_EU_WEST_1_AZB,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_elb_is_in_multiple_az:
|
||||
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_is_in_multiple_az.elb_is_in_multiple_az.elb_client",
|
||||
new=ELB(set_mocked_aws_provider([AWS_REGION_EU_WEST_1])),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.elb.elb_is_in_multiple_az.elb_is_in_multiple_az import (
|
||||
elb_is_in_multiple_az,
|
||||
)
|
||||
|
||||
check = elb_is_in_multiple_az()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_elbv2_in_one_avaibility_zone(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},
|
||||
],
|
||||
AvailabilityZones=[AWS_REGION_EU_WEST_1_AZA],
|
||||
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_is_in_multiple_az.elb_is_in_multiple_az.elb_client",
|
||||
new=ELB(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.elb.elb_is_in_multiple_az.elb_is_in_multiple_az import (
|
||||
elb_is_in_multiple_az,
|
||||
)
|
||||
|
||||
check = elb_is_in_multiple_az()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Classic Load Balancer my-lb is not in at least 2 availability zones, it is only in {AWS_REGION_EU_WEST_1_AZA}."
|
||||
)
|
||||
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_elbv2_in_multiple_avaibility_zones(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},
|
||||
],
|
||||
AvailabilityZones=[AWS_REGION_EU_WEST_1_AZA, AWS_REGION_EU_WEST_1_AZB],
|
||||
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_is_in_multiple_az.elb_is_in_multiple_az.elb_client",
|
||||
new=ELB(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.elb.elb_is_in_multiple_az.elb_is_in_multiple_az import (
|
||||
elb_is_in_multiple_az,
|
||||
)
|
||||
|
||||
check = elb_is_in_multiple_az()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Classic Load Balancer my-lb is in 2 availability zones: {AWS_REGION_EU_WEST_1_AZA}, {AWS_REGION_EU_WEST_1_AZB}."
|
||||
) or (
|
||||
result[0].status_extended
|
||||
== f"Classic Load Balancer my-lb is in 2 availability zones: {AWS_REGION_EU_WEST_1_AZB}, {AWS_REGION_EU_WEST_1_AZA}."
|
||||
)
|
||||
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 == []
|
||||
@@ -5,6 +5,7 @@ from prowler.providers.aws.services.elb.elb_service import ELB
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
AWS_REGION_US_EAST_1_AZA,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
@@ -51,7 +52,7 @@ class Test_ELB_Service:
|
||||
{"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080},
|
||||
{"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000},
|
||||
],
|
||||
AvailabilityZones=[f"{AWS_REGION_US_EAST_1}a"],
|
||||
AvailabilityZones=[AWS_REGION_US_EAST_1_AZA],
|
||||
Scheme="internal",
|
||||
SecurityGroups=[security_group.id],
|
||||
)["DNSName"]
|
||||
@@ -69,6 +70,8 @@ class Test_ELB_Service:
|
||||
assert elb.loadbalancers[elb_arn].listeners[0].policies == []
|
||||
assert elb.loadbalancers[elb_arn].listeners[1].protocol == "HTTP"
|
||||
assert elb.loadbalancers[elb_arn].listeners[1].policies == []
|
||||
assert len(elb.loadbalancers[elb_arn].availability_zones) == 1
|
||||
assert AWS_REGION_US_EAST_1_AZA in elb.loadbalancers[elb_arn].availability_zones
|
||||
|
||||
# Test ELB Describe Load Balancers Attributes
|
||||
@mock_aws
|
||||
|
||||
Reference in New Issue
Block a user