feat(ec2): Ensure not default Network Access Control Lists are used (#4917)

Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
Mario Rodriguez Lopez
2024-09-11 15:55:18 +02:00
committed by GitHub
parent 482aee0d9d
commit 2e4f8cbfc7
9 changed files with 210 additions and 18 deletions
@@ -8,7 +8,7 @@ class ec2_networkacl_allow_ingress_any_port(Check):
findings = []
tcp_protocol = "-1"
check_port = 0
for network_acl in ec2_client.network_acls:
for arn, network_acl in ec2_client.network_acls.items():
if (
ec2_client.provider.scan_unused_services
or network_acl.region in ec2_client.regions_with_sgs
@@ -18,7 +18,7 @@ class ec2_networkacl_allow_ingress_any_port(Check):
report = Check_Report_AWS(self.metadata())
report.resource_id = network_acl.id
report.region = network_acl.region
report.resource_arn = network_acl.arn
report.resource_arn = arn
report.resource_tags = network_acl.tags
report.status = "FAIL"
report.status_extended = f"Network ACL {network_acl.name if network_acl.name else network_acl.id} has every port open to the Internet."
@@ -8,7 +8,7 @@ class ec2_networkacl_allow_ingress_tcp_port_22(Check):
findings = []
tcp_protocol = "6"
check_port = 22
for network_acl in ec2_client.network_acls:
for arn, network_acl in ec2_client.network_acls.items():
if (
ec2_client.provider.scan_unused_services
or network_acl.region in ec2_client.regions_with_sgs
@@ -18,7 +18,7 @@ class ec2_networkacl_allow_ingress_tcp_port_22(Check):
report = Check_Report_AWS(self.metadata())
report.resource_id = network_acl.id
report.region = network_acl.region
report.resource_arn = network_acl.arn
report.resource_arn = arn
report.resource_tags = network_acl.tags
report.status = "FAIL"
report.status_extended = f"Network ACL {network_acl.name if network_acl.name else network_acl.id} has SSH port 22 open to the Internet."
@@ -8,7 +8,7 @@ class ec2_networkacl_allow_ingress_tcp_port_3389(Check):
findings = []
tcp_protocol = "6"
check_port = 3389
for network_acl in ec2_client.network_acls:
for arn, network_acl in ec2_client.network_acls.items():
if (
ec2_client.provider.scan_unused_services
or network_acl.region in ec2_client.regions_with_sgs
@@ -18,7 +18,7 @@ class ec2_networkacl_allow_ingress_tcp_port_3389(Check):
report = Check_Report_AWS(self.metadata())
report.resource_id = network_acl.id
report.region = network_acl.region
report.resource_arn = network_acl.arn
report.resource_arn = arn
report.resource_tags = network_acl.tags
report.status = "FAIL"
report.status_extended = f"Network ACL {network_acl.name if network_acl.name else network_acl.id} has Microsoft RDP port 3389 open to the Internet."
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "ec2_networkacl_unused",
"CheckTitle": "Unused Network Access Control Lists should be removed.",
"CheckType": [],
"ServiceName": "ec2",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "low",
"ResourceType": "AwsEc2NetworkAcl",
"Description": "Ensure that there are no unused network access control lists (network ACLs) in your virtual private cloud (VPC). The control fails if the network ACL isn't associated with a subnet. The control doesn't generate findings for an unused default network ACL.",
"Risk": "Unused network ACLs may represent a potential security risk if left in place without purpose, as they could be mistakenly associated with subnets later.",
"RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/vpc-network-acl-unused-check.html",
"Remediation": {
"Code": {
"CLI": "aws ec2 delete-network-acl --network-acl-id <nacl_id>",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-16",
"Terraform": ""
},
"Recommendation": {
"Text": "For instructions on deleting an unused network ACL, see Deleting a network ACL in the Amazon VPC User Guide.",
"Url": "https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html#vpc-network-acl-delete"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Infrastructure Security"
}
@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
class ec2_networkacl_unused(Check):
def execute(self):
findings = []
for arn, network_acl in ec2_client.network_acls.items():
if not network_acl.default:
report = Check_Report_AWS(self.metadata())
report.resource_id = network_acl.id
report.region = network_acl.region
report.resource_arn = arn
report.resource_tags = network_acl.tags
if not network_acl.in_use:
report.status = "FAIL"
report.status_extended = f"Network ACL {network_acl.name if network_acl.name else network_acl.id} is not associated with any subnet and is not the default network ACL."
else:
report.status = "PASS"
report.status_extended = f"Network ACL {network_acl.name if network_acl.name else network_acl.id} is associated with a subnet."
findings.append(report)
return findings
@@ -22,7 +22,7 @@ class EC2(AWSService):
self.security_groups = {}
self.regions_with_sgs = []
self.__threading_call__(self._describe_security_groups)
self.network_acls = []
self.network_acls = {}
self.__threading_call__(self._describe_network_acls)
self.snapshots = []
self.volumes_with_snapshots = {}
@@ -157,15 +157,20 @@ class EC2(AWSService):
for tag in nacl.get("Tags", []):
if tag["Key"] == "Name":
nacl_name = tag["Value"]
self.network_acls.append(
NetworkACL(
id=nacl["NetworkAclId"],
arn=arn,
name=nacl_name,
region=regional_client.region,
entries=nacl["Entries"],
tags=nacl.get("Tags"),
)
in_use = False
for subnet in nacl["Associations"]:
if subnet["SubnetId"]:
in_use = True
break
self.network_acls[arn] = NetworkACL(
id=nacl["NetworkAclId"],
arn=arn,
name=nacl_name,
region=regional_client.region,
entries=nacl["Entries"],
tags=nacl.get("Tags"),
in_use=in_use,
default=nacl["IsDefault"],
)
except Exception as error:
logger.error(
@@ -684,6 +689,8 @@ class NetworkACL(BaseModel):
name: str
region: str
entries: list[dict]
default: bool
in_use: bool
tags: Optional[list] = []
@@ -0,0 +1,126 @@
from unittest import mock
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_REGION_EU_WEST_1,
AWS_REGION_US_EAST_1,
set_mocked_aws_provider,
)
class Test_ec2_networkacl_unused:
@mock_aws
def test_ec2_default_nacls(self):
from prowler.providers.aws.services.ec2.ec2_service import EC2
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.ec2.ec2_networkacl_unused.ec2_networkacl_unused.ec2_client",
new=EC2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_networkacl_unused.ec2_networkacl_unused import (
ec2_networkacl_unused,
)
check = ec2_networkacl_unused()
result = check.execute()
# One default nacl per region
assert len(result) == 0
@mock_aws
def test_ec2_unused_non_default_nacl(self):
# Create EC2 Mocked Resources
ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1)
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
nacl_id = ec2_client.create_network_acl(VpcId=vpc_id)["NetworkAcl"][
"NetworkAclId"
]
arn = f"arn:aws:ec2:eu-west-1:123456789012:network-acl/{nacl_id}"
from prowler.providers.aws.services.ec2.ec2_service import EC2
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.ec2.ec2_networkacl_unused.ec2_networkacl_unused.ec2_client",
new=EC2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_networkacl_unused.ec2_networkacl_unused import (
ec2_networkacl_unused,
)
check = ec2_networkacl_unused()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].region == "eu-west-1"
assert result[0].resource_id == nacl_id
assert result[0].resource_arn == arn
assert (
result[0].status_extended
== f"Network ACL {nacl_id} is not associated with any subnet and is not the default network ACL."
)
@mock_aws
def test_ec2_used_non_default_nacl(self):
# Create EC2 Mocked Resources
ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1)
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
nacl_id = ec2_client.create_network_acl(VpcId=vpc_id)["NetworkAcl"][
"NetworkAclId"
]
arn = f"arn:aws:ec2:eu-west-1:123456789012:network-acl/{nacl_id}"
default_nacl_association_id = ec2_client.describe_network_acls()["NetworkAcls"][
0
]["Associations"][0]["NetworkAclAssociationId"]
# Replace the default association with the new Network ACL
ec2_client.replace_network_acl_association(
AssociationId=default_nacl_association_id, NetworkAclId=nacl_id
)
from prowler.providers.aws.services.ec2.ec2_service import EC2
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.ec2.ec2_networkacl_unused.ec2_networkacl_unused.ec2_client",
new=EC2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_networkacl_unused.ec2_networkacl_unused import (
ec2_networkacl_unused,
)
check = ec2_networkacl_unused()
result = check.execute()
assert len(result) == 1
# Search changed sg
assert result[0].status == "PASS"
assert result[0].region == "eu-west-1"
assert result[0].resource_id == nacl_id
assert result[0].resource_arn == arn
assert (
result[0].status_extended
== f"Network ACL {nacl_id} is associated with a subnet."
)
@@ -240,14 +240,16 @@ class Test_EC2_Service:
ec2 = EC2(aws_provider)
assert nacl_id in str(ec2.network_acls)
for acl in ec2.network_acls:
for arn, acl in ec2.network_acls.items():
if acl.id == nacl_id:
assert re.match(r"acl-[0-9a-z]{8}", acl.id)
assert (
acl.arn
arn
== f"arn:{aws_provider.identity.partition}:ec2:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:network-acl/{acl.id}"
)
assert acl.entries == []
assert not acl.in_use
assert not acl.default
assert acl.tags == [
{"Key": "test", "Value": "test"},
]