Skip to content

Commit

Permalink
feat(ec2): add new check launch template imdsv2 required
Browse files Browse the repository at this point in the history
  • Loading branch information
danibarranqueroo committed Dec 10, 2024
1 parent 50dcc48 commit 0cc913a
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 0 deletions.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "ec2_launch_template_imdsv2_required",
"CheckTitle": "Amazon EC2 launch templates should have IMDSv2 enabled and required.",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices"
],
"ServiceName": "ec2",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:ec2:region:account-id:launch-template/resource-id",
"Severity": "low",
"ResourceType": "AwsEc2LaunchTemplate",
"Description": "This control checks if Amazon EC2 launch templates are configured with IMDSv2 enabled and required. The control fails if IMDSv2 is not enabled or required in the launch template versions.",
"Risk": "Without IMDSv2 required, EC2 instances may be vulnerable to metadata service attacks, allowing unauthorized access to instance metadata, potentially leading to compromise of instance credentials or other sensitive data.",
"RelatedUrl": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html",
"Remediation": {
"Code": {
"CLI": "aws ec2 modify-launch-template --launch-template-id <template-id> --version <version-number> --metadata-options HttpTokens=required",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-170",
"Terraform": ""
},
"Recommendation": {
"Text": "To ensure EC2 launch templates have IMDSv2 enabled and required, update the template to configure the Instance Metadata Service Version 2 as required.",
"Url": "https://docs.aws.amazon.com/autoscaling/ec2/userguide/create-launch-template.html#change-metadata-options"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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_imdsv2_required(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
report.resource_tags = template.tags

versions_with_imdsv2_required = []
versions_with_metadata_disabled = []

for version in template.versions:
if (
version.template_data.http_endpoint == "enabled"
and version.template_data.http_tokens == "required"
):
versions_with_imdsv2_required.append(str(version.version_number))
elif version.template_data.http_endpoint == "disabled":
versions_with_metadata_disabled.append(str(version.version_number))

Check warning on line 25 in prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py#L25

Added line #L25 was not covered by tests

if versions_with_imdsv2_required:
report.status = "PASS"
report.status_extended = f"EC2 Launch Template {template.name} has IMDSv2 required in the following versions: {', '.join(versions_with_imdsv2_required)}."
elif versions_with_metadata_disabled:
report.status = "PASS"
report.status_extended = f"EC2 Launch Template {template.name} has metadata service disabled in the following versions: {', '.join(versions_with_metadata_disabled)}."

Check warning on line 32 in prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py#L31-L32

Added lines #L31 - L32 were not covered by tests
else:
report.status = "FAIL"
report.status_extended = f"EC2 Launch Template {template.name} does not have IMDSv2 required or metadata service disabled in any of its versions."

findings.append(report)

return findings
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
from unittest import mock

import botocore
from boto3 import client
from moto import mock_aws

from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider

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": {
"MetadataOptions": {
"HttpEndpoint": "enabled",
"HttpTokens": "required",
}
},
}
]
}
return make_api_call(self, operation_name, kwarg)


def mock_make_api_call_not_required(self, operation_name, kwarg):
if operation_name == "DescribeLaunchTemplateVersions":
return {
"LaunchTemplateVersions": [
{
"VersionNumber": 1,
"LaunchTemplateData": {
"MetadataOptions": {
"HttpEndpoint": "enabled",
"HttpTokens": "optional",
}
},
}
]
}
return make_api_call(self, operation_name, kwarg)


class Test_ec2_launch_template_imdsv2_required:
@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_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client",
new=EC2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import (
ec2_launch_template_imdsv2_required,
)

check = ec2_launch_template_imdsv2_required()
result = check.execute()

assert len(result) == 0

@mock_aws
def test_launch_template_imdsv2_required(self):
with mock.patch(
"botocore.client.BaseClient._make_api_call", new=mock_make_api_call
):
launch_template_name = "test-imdsv2-required"
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
ec2_client.create_launch_template(
LaunchTemplateName=launch_template_name,
VersionDescription="Launch Template with IMDSv2 required",
LaunchTemplateData={
"InstanceType": "t1.micro",
"MetadataOptions": {
"HttpEndpoint": "enabled",
"HttpTokens": "required",
},
},
)

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_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client",
new=EC2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import (
ec2_launch_template_imdsv2_required,
)

check = ec2_launch_template_imdsv2_required()
result = check.execute()

assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"EC2 Launch Template {launch_template_name} has IMDSv2 required in the following versions: 1."
)
assert result[0].resource_id == launch_template_id
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:launch-template/{launch_template_id}"
)
assert result[0].resource_tags == []

@mock_aws
def test_launch_template_imdsv2_not_required(self):
with mock.patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_not_required,
):
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
launch_template_name = "test-imdsv2-not-required"
ec2_client.create_launch_template(
LaunchTemplateName=launch_template_name,
VersionDescription="Launch Template without IMDSv2 required",
LaunchTemplateData={
"InstanceType": "t1.micro",
"MetadataOptions": {
"HttpEndpoint": "enabled",
"HttpTokens": "optional",
},
},
)

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_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client",
new=EC2(aws_provider),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import (
ec2_launch_template_imdsv2_required,
)

check = ec2_launch_template_imdsv2_required()
result = check.execute()

assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"EC2 Launch Template {launch_template_name} does not have IMDSv2 required or metadata service disabled in any of its versions."
)
assert result[0].resource_id == launch_template_id
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:launch-template/{launch_template_id}"
)
assert result[0].resource_tags == []

0 comments on commit 0cc913a

Please sign in to comment.