-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ec2): add new check launch template imdsv2 required
- Loading branch information
1 parent
50dcc48
commit 0cc913a
Showing
4 changed files
with
257 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
32 changes: 32 additions & 0 deletions
32
...ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.metadata.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": "" | ||
} |
39 changes: 39 additions & 0 deletions
39
...s/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 Codecov / codecov/patchprowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py#L25
|
||
|
||
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 Codecov / codecov/patchprowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py#L31-L32
|
||
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 |
186 changes: 186 additions & 0 deletions
186
...vices/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required_test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 == [] |