Skip to content

Commit

Permalink
feat(elbv2): add new check elbv2_is_in_multiple_az (#4800)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergio Garcia <[email protected]>
  • Loading branch information
puchy22 and sergargar authored Aug 22, 2024
1 parent df40525 commit 0b23824
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/tutorials/configuration_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ The following list includes all the AWS checks with configurable variables that
| `ec2_securitygroup_allow_ingress_from_internet_to_any_port` | `ec2_allowed_instance_owners` | List of Strings |
| `acm_certificates_expiration_check` | `days_to_expire_threshold` | Integer |
| `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 |
| `eks_cluster_uses_a_supported_version` | `eks_cluster_oldest_version_supported` | String |
| `elbv2_is_in_multiple_az` | `elbv2_min_azs` | Integer |

## Azure

Expand Down
5 changes: 5 additions & 0 deletions prowler/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@ aws:

]

# AWS ELBv2 Configuration
# aws.elbv2_is_in_multiple_az
# Minimum number of Availability Zones that an ELBv2 must be in
elbv2_min_azs: 2

# Azure Configuration
azure:
# Azure Network Configuration
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "elbv2_is_in_multiple_az",
"CheckTitle": "Elastic Load Balancer V2 (ELBv2) is Configured Across Multiple Availability Zones (AZs)",
"CheckType": [],
"ServiceName": "elbv2",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Ensure whether Elastic Load Balancer V2 (Application, Network, or Gateway Load Balancer) is configured to operate across multiple Availability Zones (AZs). Ensuring that your load balancer is spread across at least two AZs helps maintain high availability and fault tolerance in case of an AZ failure.",
"Risk": "If an ELBv2 is not configured across multiple AZs, there is a risk that an Availability Zone failure could lead to downtime for your application. This could result in a single point of failure, impacting the availability and reliability of your services.",
"RelatedUrl": "https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html#availability-zones",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/elb-controls.html#elb-13",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELBv2/enable-multi-az.html"
},
"Recommendation": {
"Text": "It is recommended to configure your ELBv2 to operate across at least two Availability Zones to enhance fault tolerance and availability.",
"Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-subnets.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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_is_in_multiple_az(Check):
def execute(self) -> List[Check_Report_AWS]:
findings = []
elbv2_min_azs = elbv2_client.audit_config.get("elbv2_min_azs", 2)
for load_balancer_arn, load_balancer in elbv2_client.loadbalancersv2.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"ELBv2 {load_balancer.name} is not in at least {elbv2_min_azs} AZs. Is only in {', '.join(load_balancer.availability_zones.keys())}."

if len(load_balancer.availability_zones) >= elbv2_min_azs:
report.status = "PASS"
report.status_extended = f"ELBv2 {load_balancer.name} is at least in {elbv2_min_azs} AZs: {', '.join(load_balancer.availability_zones.keys())}."

findings.append(report)

return findings
6 changes: 6 additions & 0 deletions prowler/providers/aws/services/elbv2/elbv2_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def _describe_load_balancers(self, regional_client):
type=elbv2["Type"],
dns=elbv2.get("DNSName", None),
scheme=elbv2.get("Scheme", None),
availability_zones={
az["ZoneName"]: az["SubnetId"]
for az in elbv2.get("AvailabilityZones", [])
},
)
except Exception as error:
logger.error(
Expand Down Expand Up @@ -200,4 +204,6 @@ class LoadBalancerv2(BaseModel):
drop_invalid_header_fields: Optional[str]
listeners: Dict[str, Listenerv2] = {}
scheme: Optional[str]
# Key: ZoneName, Value: SubnetId
availability_zones: Dict[str, str] = {}
tags: Optional[list] = []
1 change: 1 addition & 0 deletions tests/config/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ def mock_prowler_get_latest_release(_, **kwargs):
],
"eks_cluster_oldest_version_supported": "1.28",
"excluded_sensitive_environment_variables": [],
"elbv2_min_azs": 2,
}

config_azure = {
Expand Down
5 changes: 5 additions & 0 deletions tests/config/fixtures/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@ aws:

]

# AWS ELBv2 Configuration
# aws.elbv2_is_in_multiple_az
# Minimum number of Availability Zones that an ELBv2 must be in
elbv2_min_azs: 2

# Azure Configuration
azure:
# Azure Network Configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
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,
set_mocked_aws_provider,
)


class Test_elbv2_is_in_multiple_az:
@mock_aws
def test_no_elbs(self):
from prowler.providers.aws.services.elbv2.elbv2_service import ELBv2

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.elbv2.elbv2_is_in_multiple_az.elbv2_is_in_multiple_az.elbv2_client",
new=ELBv2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.elbv2.elbv2_is_in_multiple_az.elbv2_is_in_multiple_az import (
elbv2_is_in_multiple_az,
)

check = elbv2_is_in_multiple_az()
result = check.execute()

assert len(result) == 0

@mock_aws
def test_elbv2_in_one_avaibility_zone(self):
# Create VPC, Subnets and Security Group
elbv2_client = 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="10.0.0.0/16")

subnet1 = ec2.create_subnet(
AvailabilityZone=AWS_REGION_EU_WEST_1_AZA,
CidrBlock="10.0.1.0/24",
VpcId=vpc.id,
)

lb_arn = elbv2_client.create_load_balancer(
Name="test_elbv2",
Subnets=[subnet1.id],
SecurityGroups=[security_group.id],
)["LoadBalancers"][0]["LoadBalancerArn"]

from prowler.providers.aws.services.elbv2.elbv2_service import ELBv2

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.elbv2.elbv2_is_in_multiple_az.elbv2_is_in_multiple_az.elbv2_client",
new=ELBv2(aws_provider),
):
from prowler.providers.aws.services.elbv2.elbv2_is_in_multiple_az.elbv2_is_in_multiple_az import (
elbv2_is_in_multiple_az,
)

check = elbv2_is_in_multiple_az()
result = check.execute()

assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"ELBv2 test_elbv2 is not in at least 2 AZs. Is only in {AWS_REGION_EU_WEST_1_AZA}."
)
assert result[0].region == AWS_REGION_EU_WEST_1
assert result[0].resource_id == "test_elbv2"
assert result[0].resource_arn == lb_arn
assert result[0].resource_tags == []

@mock_aws
def test_elbv2_in_two_avaibility_zones(self):
# Create VPC, Subnets and Security Group
elbv2_client = 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="10.0.0.0/16")

subnet1 = ec2.create_subnet(
AvailabilityZone=AWS_REGION_EU_WEST_1_AZA,
CidrBlock="10.0.1.0/24",
VpcId=vpc.id,
)

subnet2 = ec2.create_subnet(
AvailabilityZone=AWS_REGION_EU_WEST_1_AZB,
CidrBlock="10.0.2.0/24",
VpcId=vpc.id,
)

lb_arn = elbv2_client.create_load_balancer(
Name="test_elbv2",
Subnets=[subnet1.id, subnet2.id],
SecurityGroups=[security_group.id],
)["LoadBalancers"][0]["LoadBalancerArn"]

from prowler.providers.aws.services.elbv2.elbv2_service import ELBv2

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.elbv2.elbv2_is_in_multiple_az.elbv2_is_in_multiple_az.elbv2_client",
new=ELBv2(aws_provider),
):
from prowler.providers.aws.services.elbv2.elbv2_is_in_multiple_az.elbv2_is_in_multiple_az import (
elbv2_is_in_multiple_az,
)

check = elbv2_is_in_multiple_az()
result = check.execute()

assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"ELBv2 test_elbv2 is at least in 2 AZs: {AWS_REGION_EU_WEST_1_AZA}, {AWS_REGION_EU_WEST_1_AZB}."
)
assert result[0].region == AWS_REGION_EU_WEST_1
assert result[0].resource_id == "test_elbv2"
assert result[0].resource_arn == lb_arn
assert result[0].resource_tags == []
13 changes: 13 additions & 0 deletions tests/providers/aws/services/elbv2/elbv2_service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ def test_describe_load_balancers(self):
elbv2.loadbalancersv2[lb["LoadBalancerArn"]].dns
== "my-lb-1.eu-west-1.elb.amazonaws.com"
)
assert len(elbv2.loadbalancersv2[lb["LoadBalancerArn"]].availability_zones) == 2
assert (
elbv2.loadbalancersv2[lb["LoadBalancerArn"]].availability_zones[
AWS_REGION_EU_WEST_1_AZA
]
== subnet1.id
)
assert (
elbv2.loadbalancersv2[lb["LoadBalancerArn"]].availability_zones[
AWS_REGION_EU_WEST_1_AZB
]
== subnet2.id
)

# Test ELBv2 Describe Listeners
@mock_aws
Expand Down

0 comments on commit 0b23824

Please sign in to comment.