feat(ec2): Ensure EC2 launch templates do not assign public IPs (#4852)

Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
Mario Rodriguez Lopez
2024-09-16 18:52:40 +02:00
committed by GitHub
parent 8176063fef
commit 91bf99ca45
5 changed files with 424 additions and 1 deletions
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "ec2_launch_template_no_public_ip",
"CheckTitle": "Amazon EC2 launch templates should not assign public IPs to network interfaces.",
"CheckType": [],
"ServiceName": "ec2",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "high",
"ResourceType": "AwsEc2LaunchTemplate",
"Description": "This control checks if Amazon EC2 launch templates are configured to assign public IP addresses to network interfaces upon launch. The control fails if an EC2 launch template is configured to assign a public IP address to network interfaces or if there is at least one network interface that has a public IP address.",
"Risk": "A public IP address is reachable from the internet, making associated resources potentially accessible from the internet. EC2 resources should not be publicly accessible to avoid unintended access to workloads.",
"RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/ec2-launch-template-public-ip-disabled.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-25",
"Terraform": ""
},
"Recommendation": {
"Text": "To update an EC2 launch template, see Change the default network interface settings in the Amazon EC2 Auto Scaling User Guide.",
"Url": "https://docs.aws.amazon.com/autoscaling/ec2/userguide/create-launch-template.html#change-network-interface"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,51 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
class ec2_launch_template_no_public_ip(Check):
def execute(self):
findings = []
for template in ec2_client.launch_templates:
report = Check_Report_AWS(self.metadata())
report.region = template.region
report.resource_id = template.id
report.resource_arn = template.arn
versions_with_autoassign_public_ip = []
versions_with_network_interfaces_public_ip = []
for version in template.versions:
# Check if the launch template version assigns a public IP address
if version.template_data.associate_public_ip_address:
versions_with_autoassign_public_ip.append(
str(version.version_number)
)
if version.template_data.network_interfaces:
for network_interface in version.template_data.network_interfaces:
if network_interface.public_ip_addresses:
versions_with_network_interfaces_public_ip.append(
str(version.version_number)
)
break
if (
versions_with_autoassign_public_ip
or versions_with_network_interfaces_public_ip
):
report.status = "FAIL"
if (
versions_with_autoassign_public_ip
and versions_with_network_interfaces_public_ip
):
report.status_extended = f"EC2 Launch Template {template.name} is configured to assign a public IP address to network interfaces upon launch in template versions: {', '.join(versions_with_autoassign_public_ip)} and is using a network interface with public IP addresses in template versions: {', '.join(versions_with_network_interfaces_public_ip)}."
elif versions_with_autoassign_public_ip:
report.status_extended = f"EC2 Launch Template {template.name} is configured to assign a public IP address to network interfaces upon launch in template versions: {', '.join(versions_with_autoassign_public_ip)}."
elif versions_with_network_interfaces_public_ip:
report.status_extended = f"EC2 Launch Template {template.name} is using a network interface with public IP addresses in template versions: {', '.join(versions_with_network_interfaces_public_ip)}."
else:
report.status = "PASS"
report.status_extended = f"EC2 Launch Template {template.name} is neither configured to assign a public IP address to network interfaces upon launch nor using a network interface with public IP addresses."
findings.append(report)
return findings
@@ -544,7 +544,7 @@ class EC2(AWSService):
for eni in template_version["LaunchTemplateData"].get(
"NetworkInterfaces", []
):
network_interface_id = eni.get("NetworkInterfaceId")
network_interface_id = eni.get("NetworkInterfaceId", "")
if network_interface_id in self.network_interfaces:
enis.append(self.network_interfaces[network_interface_id])
if eni.get("AssociatePublicIpAddress", False):
@@ -0,0 +1,342 @@
from base64 import b64encode
from ipaddress import IPv4Address, IPv6Address
from os import path
from pathlib import Path
from unittest import mock
import botocore
from boto3 import client
from moto import mock_aws
from prowler.config.config import encoding_format_utf_8
from prowler.providers.aws.services.ec2.ec2_service import (
LaunchTemplate,
LaunchTemplateVersion,
NetworkInterface,
TemplateData,
)
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
ACTUAL_DIRECTORY = Path(path.dirname(path.realpath(__file__)))
FIXTURES_DIR_NAME = "fixtures"
make_api_call = botocore.client.BaseClient._make_api_call
def mock_make_api_call(self, operation_name, kwarg):
if operation_name == "DescribeLaunchTemplateVersions":
return {
"LaunchTemplateVersions": [
{
"VersionNumber": 1,
"LaunchTemplateData": {
"NetworkInterfaces": [{"AssociatePublicIpAddress": False}],
},
}
]
}
return make_api_call(self, operation_name, kwarg)
class Test_ec2_launch_template_no_public_ip:
@mock_aws
def test_no_launch_templates(self):
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
ec2_client.launch_templates = []
from prowler.providers.aws.services.ec2.ec2_service import EC2
aws_provider = set_mocked_aws_provider([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_launch_template_no_public_ip.ec2_launch_template_no_public_ip.ec2_client",
new=EC2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_no_public_ip.ec2_launch_template_no_public_ip import (
ec2_launch_template_no_public_ip,
)
check = ec2_launch_template_no_public_ip()
result = check.execute()
assert len(result) == 0
@mock_aws
@mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
def test_launch_template_no_public_ip(self):
# Include launch_template to check
launch_template_name = "test-no-public-ip"
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
ec2_client.create_launch_template(
LaunchTemplateName=launch_template_name,
VersionDescription="Launch Template with no public IP",
LaunchTemplateData={
"InstanceType": "t1.micro",
},
)
launch_template_id = ec2_client.describe_launch_templates(
LaunchTemplateNames=[launch_template_name]
)["LaunchTemplates"][0]["LaunchTemplateId"]
from prowler.providers.aws.services.ec2.ec2_service import EC2
aws_provider = set_mocked_aws_provider([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_launch_template_no_public_ip.ec2_launch_template_no_public_ip.ec2_client",
new=EC2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_no_public_ip.ec2_launch_template_no_public_ip import (
ec2_launch_template_no_public_ip,
)
check = ec2_launch_template_no_public_ip()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"EC2 Launch Template {launch_template_name} is neither configured to assign a public IP address to network interfaces upon launch nor using a network interface with public IP addresses."
)
assert result[0].resource_id == launch_template_id
assert result[0].region == AWS_REGION_US_EAST_1
def test_launch_template_public_ip_auto_assign(self):
ec2_client = mock.MagicMock()
launch_template_name = "tester"
launch_template_id = "lt-1234567890"
launch_template_arn = (
f"arn:aws:ec2:us-east-1:123456789012:launch-template/{launch_template_id}"
)
launch_template_data = TemplateData(
user_data=b64encode("sinsecretos".encode(encoding_format_utf_8)).decode(
encoding_format_utf_8
),
associate_public_ip_address=True,
)
launch_template_versions = [
LaunchTemplateVersion(
version_number=2,
template_data=launch_template_data,
),
]
launch_template = LaunchTemplate(
name=launch_template_name,
id=launch_template_id,
arn=launch_template_arn,
region=AWS_REGION_US_EAST_1,
versions=launch_template_versions,
)
ec2_client.launch_templates = [launch_template]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=ec2_client,
), mock.patch(
"prowler.providers.aws.services.ec2.ec2_launch_template_no_public_ip.ec2_launch_template_no_public_ip.ec2_client",
new=ec2_client,
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_no_public_ip.ec2_launch_template_no_public_ip import (
ec2_launch_template_no_public_ip,
)
check = ec2_launch_template_no_public_ip()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"EC2 Launch Template {launch_template_name} is configured to assign a public IP address to network interfaces upon launch in template versions: 2."
)
assert result[0].resource_id == launch_template_id
assert result[0].region == AWS_REGION_US_EAST_1
def test_network_interface_with_public_ipv4_network_interface_autoassign_true_and_false(
self,
):
ec2_client = mock.MagicMock()
launch_template_name = "tester"
launch_template_id = "lt-1234567890"
launch_template_arn = (
f"arn:aws:ec2:us-east-1:123456789012:launch-template/{launch_template_id}"
)
network_interface = NetworkInterface(
id="eni-1234567890",
association={},
attachment={},
private_ip="",
public_ip_addresses=[IPv4Address("192.175.48.10")],
type="interface",
subnet_id="subnet-1234567890",
vpc_id="vpc-1234567890",
region=AWS_REGION_US_EAST_1,
)
launch_template_data = TemplateData(
user_data=b64encode("sinsecretos".encode(encoding_format_utf_8)).decode(
encoding_format_utf_8
),
associate_public_ip_address=False,
network_interfaces=[network_interface],
)
launch_template_data2 = TemplateData(
user_data=b64encode("sinsecretos".encode(encoding_format_utf_8)).decode(
encoding_format_utf_8
),
associate_public_ip_address=True,
network_interfaces=[network_interface],
)
launch_template_data3 = TemplateData(
user_data=b64encode("sinsecretos".encode(encoding_format_utf_8)).decode(
encoding_format_utf_8
),
associate_public_ip_address=False,
)
launch_template_versions = [
LaunchTemplateVersion(
version_number=3,
template_data=launch_template_data,
),
LaunchTemplateVersion(
version_number=4,
template_data=launch_template_data2,
),
LaunchTemplateVersion(
version_number=5,
template_data=launch_template_data3,
),
]
launch_template = LaunchTemplate(
name=launch_template_name,
id=launch_template_id,
arn=launch_template_arn,
region=AWS_REGION_US_EAST_1,
versions=launch_template_versions,
)
ec2_client.launch_templates = [launch_template]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=ec2_client,
), mock.patch(
"prowler.providers.aws.services.ec2.ec2_launch_template_no_public_ip.ec2_launch_template_no_public_ip.ec2_client",
new=ec2_client,
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_no_public_ip.ec2_launch_template_no_public_ip import (
ec2_launch_template_no_public_ip,
)
check = ec2_launch_template_no_public_ip()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"EC2 Launch Template {launch_template_name} is configured to assign a public IP address to network interfaces upon launch in template versions: 4 and is using a network interface with public IP addresses in template versions: 3, 4."
)
assert result[0].resource_id == launch_template_id
assert result[0].region == AWS_REGION_US_EAST_1
def test_network_interface_with_public_ipv6_network_interface_autoassign_true_and_false(
self,
):
ec2_client = mock.MagicMock()
launch_template_name = "tester"
launch_template_id = "lt-1234567890"
launch_template_arn = (
f"arn:aws:ec2:us-east-1:123456789012:launch-template/{launch_template_id}"
)
network_interface = NetworkInterface(
id="eni-1234567890",
association={},
attachment={},
private_ip="",
public_ip_addresses=[IPv6Address("::1234:5678")],
type="interface",
subnet_id="subnet-1234567890",
vpc_id="vpc-1234567890",
region=AWS_REGION_US_EAST_1,
)
launch_template_data = TemplateData(
user_data=b64encode("sinsecretos".encode(encoding_format_utf_8)).decode(
encoding_format_utf_8
),
associate_public_ip_address=True,
network_interfaces=[network_interface],
)
launch_template_data2 = TemplateData(
user_data=b64encode("sinsecretos".encode(encoding_format_utf_8)).decode(
encoding_format_utf_8
),
associate_public_ip_address=False,
network_interfaces=[network_interface],
)
launch_template_versions = [
LaunchTemplateVersion(
version_number=6,
template_data=launch_template_data,
),
LaunchTemplateVersion(
version_number=7,
template_data=launch_template_data2,
),
]
launch_template = LaunchTemplate(
name=launch_template_name,
id=launch_template_id,
arn=launch_template_arn,
region=AWS_REGION_US_EAST_1,
versions=launch_template_versions,
)
ec2_client.launch_templates = [launch_template]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=ec2_client,
), mock.patch(
"prowler.providers.aws.services.ec2.ec2_launch_template_no_public_ip.ec2_launch_template_no_public_ip.ec2_client",
new=ec2_client,
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_no_public_ip.ec2_launch_template_no_public_ip import (
ec2_launch_template_no_public_ip,
)
check = ec2_launch_template_no_public_ip()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"EC2 Launch Template {launch_template_name} is configured to assign a public IP address to network interfaces upon launch in template versions: 6 and is using a network interface with public IP addresses in template versions: 6, 7."
)
assert result[0].resource_id == launch_template_id
assert result[0].region == AWS_REGION_US_EAST_1