feat(autoscaling): add new check autoscaling_group_launch_configuration_requires_imdsv2 (#5356)

Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
Daniel Barranquero
2024-10-14 19:16:02 +02:00
committed by GitHub
parent 2c0c1f7d09
commit 808fa96407
8 changed files with 431 additions and 63 deletions
@@ -16,11 +16,14 @@ class autoscaling_find_secrets_ec2_launch_configuration(Check):
secrets_ignore_patterns = autoscaling_client.audit_config.get(
"secrets_ignore_patterns", []
)
for configuration in autoscaling_client.launch_configurations:
for (
configuration_arn,
configuration,
) in autoscaling_client.launch_configurations.items():
report = Check_Report_AWS(self.metadata())
report.region = configuration.region
report.resource_id = configuration.name
report.resource_arn = configuration.arn
report.resource_arn = configuration_arn
if configuration.user_data:
user_data = b64decode(configuration.user_data)
@@ -15,18 +15,11 @@ class autoscaling_group_launch_configuration_no_public_ip(Check):
report.resource_tags = group.tags
report.status = "PASS"
report.status_extended = f"Autoscaling group {group.name} does not have an associated launch configuration assigning a public IP address."
launch_configuration = next(
(
lc
for lc in autoscaling_client.launch_configurations
if lc.name == group.launch_configuration_name
),
None,
)
if launch_configuration and launch_configuration.public_ip:
report.status = "FAIL"
report.status_extended = f"Autoscaling group {group.name} has an associated launch configuration assigning a public IP address."
for lc in autoscaling_client.launch_configurations.values():
if lc.name == group.launch_configuration_name and lc.public_ip:
report.status = "FAIL"
report.status_extended = f"Autoscaling group {group.name} has an associated launch configuration assigning a public IP address."
findings.append(report)
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "autoscaling_group_launch_configuration_requires_imdsv2",
"CheckTitle": "Check if Auto Scaling group launch configurations require Instance Metadata Service Version 2 (IMDSv2).",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices"
],
"ServiceName": "autoscaling",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:autoscaling:region:account-id:launchConfiguration/launchConfigurationName",
"Severity": "high",
"ResourceType": "AwsAutoScalingLaunchConfiguration",
"Description": "This control checks whether IMDSv2 is enabled on all instances launched by Amazon EC2 Auto Scaling groups. The control fails if the Instance Metadata Service (IMDS) version isn't included in the launch configuration or is configured as token optional, which allows either IMDSv1 or IMDSv2.",
"Risk": "If IMDSv2 is not enforced, instances may be vulnerable to certain types of attacks that target the metadata service, potentially exposing sensitive instance information.",
"RelatedUrl": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-new-instances.html",
"Remediation": {
"Code": {
"CLI": "aws autoscaling create-launch-configuration --launch-configuration-name <new-launch-config> --metadata-options 'HttpTokens=required'",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/autoscaling-controls.html#autoscaling-3",
"Terraform": ""
},
"Recommendation": {
"Text": "Create a new launch configuration that requires IMDSv2 and update your Auto Scaling groups to use the new configuration.",
"Url": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-new-instances.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,37 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.autoscaling.autoscaling_client import (
autoscaling_client,
)
class autoscaling_group_launch_configuration_requires_imdsv2(Check):
def execute(self):
findings = []
for group in autoscaling_client.groups:
report = Check_Report_AWS(self.metadata())
report.region = group.region
report.resource_id = group.name
report.resource_arn = group.arn
report.resource_tags = group.tags
report.status = "FAIL"
report.status_extended = (
f"Autoscaling group {group.name} has IMDSv2 disabled or not required."
)
for (
launch_configuration
) in autoscaling_client.launch_configurations.values():
if launch_configuration.name == group.launch_configuration_name:
if (
launch_configuration.http_endpoint == "enabled"
and launch_configuration.http_tokens == "required"
):
report.status = "PASS"
report.status_extended = f"Autoscaling group {group.name} has IMDSv2 enabled and required."
elif launch_configuration.http_endpoint == "disabled":
report.status = "PASS"
report.status_extended = f"Autoscaling group {group.name} has metadata service disabled."
findings.append(report)
return findings
@@ -9,7 +9,7 @@ class AutoScaling(AWSService):
def __init__(self, provider):
# Call AWSService's __init__
super().__init__(__class__.__name__, provider)
self.launch_configurations = []
self.launch_configurations = {}
self.__threading_call__(self._describe_launch_configurations)
self.groups = []
self.__threading_call__(self._describe_auto_scaling_groups)
@@ -28,17 +28,23 @@ class AutoScaling(AWSService):
self.audit_resources,
)
):
self.launch_configurations.append(
LaunchConfiguration(
arn=configuration["LaunchConfigurationARN"],
name=configuration["LaunchConfigurationName"],
user_data=configuration["UserData"],
image_id=configuration["ImageId"],
region=regional_client.region,
public_ip=configuration.get(
"AssociatePublicIpAddress", False
),
)
arn = configuration["LaunchConfigurationARN"]
self.launch_configurations[arn] = LaunchConfiguration(
arn=arn,
name=configuration["LaunchConfigurationName"],
user_data=configuration["UserData"],
image_id=configuration["ImageId"],
region=regional_client.region,
http_tokens=configuration.get("MetadataOptions", {}).get(
"HttpTokens", ""
),
http_endpoint=configuration.get("MetadataOptions", {}).get(
"HttpEndpoint", ""
),
public_ip=configuration.get(
"AssociatePublicIpAddress", False
),
)
except Exception as error:
@@ -137,6 +143,8 @@ class LaunchConfiguration(BaseModel):
user_data: str
image_id: str
region: str
http_tokens: str
http_endpoint: str
public_ip: bool
@@ -0,0 +1,215 @@
from unittest import mock
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
class Test_autoscaling_group_launch_configuration_requires_imdsv2:
@mock_aws
def test_no_autoscaling(self):
autoscaling_client = client("autoscaling", region_name=AWS_REGION_US_EAST_1)
autoscaling_client.groups = []
from prowler.providers.aws.services.autoscaling.autoscaling_service import (
AutoScaling,
)
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.autoscaling.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_client",
new=AutoScaling(aws_provider),
):
# Test Check
from prowler.providers.aws.services.autoscaling.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_group_launch_configuration_requires_imdsv2 import (
autoscaling_group_launch_configuration_requires_imdsv2,
)
check = autoscaling_group_launch_configuration_requires_imdsv2()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_groups_with_imdsv2_enabled_and_required(self):
autoscaling_client = client("autoscaling", region_name=AWS_REGION_US_EAST_1)
autoscaling_client.create_launch_configuration(
LaunchConfigurationName="test",
ImageId="ami-12c6146b",
InstanceType="t1.micro",
KeyName="the_keys",
SecurityGroups=["default", "default2"],
MetadataOptions={
"HttpTokens": "required",
"HttpPutResponseHopLimit": 123,
"HttpEndpoint": "enabled",
},
)
autoscaling_group_name = "my-autoscaling-group"
autoscaling_client.create_auto_scaling_group(
AutoScalingGroupName=autoscaling_group_name,
LaunchConfigurationName="test",
MinSize=0,
MaxSize=0,
DesiredCapacity=0,
AvailabilityZones=["us-east-1a", "us-east-1b"],
)
autoscaling_group_arn = autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=[autoscaling_group_name]
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
from prowler.providers.aws.services.autoscaling.autoscaling_service import (
AutoScaling,
)
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.autoscaling.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_client",
new=AutoScaling(aws_provider),
):
# Test Check
from prowler.providers.aws.services.autoscaling.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_group_launch_configuration_requires_imdsv2 import (
autoscaling_group_launch_configuration_requires_imdsv2,
)
check = autoscaling_group_launch_configuration_requires_imdsv2()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Autoscaling group {autoscaling_group_name} has IMDSv2 enabled and required."
)
assert result[0].resource_id == autoscaling_group_name
assert result[0].resource_arn == autoscaling_group_arn
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_tags == []
@mock_aws
def test_groups_without_launch_configuration(self):
autoscaling_client = client("autoscaling", region_name=AWS_REGION_US_EAST_1)
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
ec2_client.create_launch_template(
LaunchTemplateName="test",
LaunchTemplateData={
"ImageId": "ami-12c6146b",
"InstanceType": "t1.micro",
"KeyName": "the_keys",
"SecurityGroups": ["default", "default2"],
},
)
autoscaling_group_name = "my-autoscaling-group"
autoscaling_client.create_auto_scaling_group(
AutoScalingGroupName=autoscaling_group_name,
LaunchTemplate={"LaunchTemplateName": "test", "Version": "$Latest"},
MinSize=0,
MaxSize=0,
DesiredCapacity=0,
AvailabilityZones=["us-east-1a", "us-east-1b"],
)
autoscaling_group_arn = autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=[autoscaling_group_name]
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
from prowler.providers.aws.services.autoscaling.autoscaling_service import (
AutoScaling,
)
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.autoscaling.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_client",
new=AutoScaling(aws_provider),
):
# Test Check
from prowler.providers.aws.services.autoscaling.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_group_launch_configuration_requires_imdsv2 import (
autoscaling_group_launch_configuration_requires_imdsv2,
)
check = autoscaling_group_launch_configuration_requires_imdsv2()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Autoscaling group {autoscaling_group_name} has IMDSv2 disabled or not required."
)
assert result[0].resource_id == autoscaling_group_name
assert result[0].resource_tags == []
assert result[0].resource_arn == autoscaling_group_arn
@mock_aws
def test_groups_with_imdsv2_disabled(self):
autoscaling_client = client("autoscaling", region_name=AWS_REGION_US_EAST_1)
autoscaling_client.create_launch_configuration(
LaunchConfigurationName="test",
ImageId="ami-12c6146b",
InstanceType="t1.micro",
KeyName="the_keys",
SecurityGroups=["default", "default2"],
MetadataOptions={
"HttpTokens": "required",
"HttpPutResponseHopLimit": 123,
"HttpEndpoint": "disabled",
},
)
autoscaling_group_name = "my-autoscaling-group"
autoscaling_client.create_auto_scaling_group(
AutoScalingGroupName=autoscaling_group_name,
LaunchConfigurationName="test",
MinSize=0,
MaxSize=0,
DesiredCapacity=0,
AvailabilityZones=["us-east-1a", "us-east-1b"],
)
autoscaling_group_arn = autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=[autoscaling_group_name]
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
from prowler.providers.aws.services.autoscaling.autoscaling_service import (
AutoScaling,
)
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.autoscaling.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_client",
new=AutoScaling(aws_provider),
):
# Test Check
from prowler.providers.aws.services.autoscaling.autoscaling_group_launch_configuration_requires_imdsv2.autoscaling_group_launch_configuration_requires_imdsv2 import (
autoscaling_group_launch_configuration_requires_imdsv2,
)
check = autoscaling_group_launch_configuration_requires_imdsv2()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Autoscaling group {autoscaling_group_name} has metadata service disabled."
)
assert result[0].resource_id == autoscaling_group_name
assert result[0].resource_tags == []
assert result[0].resource_arn == autoscaling_group_arn
@@ -70,22 +70,40 @@ class Test_AutoScaling_Service:
InstanceType="t1.micro",
KeyName="the_keys",
SecurityGroups=["default", "default2"],
MetadataOptions={
"HttpTokens": "required",
"HttpPutResponseHopLimit": 123,
"HttpEndpoint": "enabled",
},
)
# AutoScaling client for this test class
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
autoscaling = AutoScaling(aws_provider)
assert len(autoscaling.launch_configurations) == 2
assert autoscaling.launch_configurations[0].name == "tester1"
launch_configurations = autoscaling.launch_configurations
arn_tester1 = next(
arn
for arn, config in launch_configurations.items()
if config.name == "tester1"
)
arn_tester2 = next(
arn
for arn, config in launch_configurations.items()
if config.name == "tester2"
)
assert arn_tester1 in launch_configurations
assert (
b64decode(autoscaling.launch_configurations[0].user_data).decode(
b64decode(launch_configurations[arn_tester1].user_data).decode(
encoding_format_utf_8
)
== "DB_PASSWORD=foobar123"
)
assert autoscaling.launch_configurations[0].image_id == "ami-12c6146b"
assert autoscaling.launch_configurations[0].public_ip
assert autoscaling.launch_configurations[1].image_id == "ami-12c6146b"
assert autoscaling.launch_configurations[1].name == "tester2"
assert launch_configurations[arn_tester1].image_id == "ami-12c6146b"
assert launch_configurations[arn_tester1].public_ip
assert arn_tester2 in launch_configurations
assert launch_configurations[arn_tester2].image_id == "ami-12c6146b"
assert launch_configurations[arn_tester2].http_tokens == "required"
assert launch_configurations[arn_tester2].http_endpoint == "enabled"
# Test Describe Auto Scaling Groups
@mock_aws
@@ -168,41 +186,103 @@ class Test_AutoScaling_Service:
TargetGroupARNs=[target_group["TargetGroups"][0]["TargetGroupArn"]],
)
autoscaling_client.create_launch_configuration(
LaunchConfigurationName="test",
ImageId="ami-12c6146b",
InstanceType="t1.micro",
KeyName="the_keys",
SecurityGroups=["default", "default2"],
UserData="DB_PASSWORD=foobar123",
AssociatePublicIpAddress=True,
)
autoscaling_client.create_auto_scaling_group(
AutoScalingGroupName="my-autoscaling-group-3",
LaunchConfigurationName="test",
MinSize=0,
MaxSize=0,
DesiredCapacity=0,
AvailabilityZones=["us-east-1a", "us-east-1b"],
Tags=[],
)
# AutoScaling client for this test class
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
autoscaling = AutoScaling(aws_provider)
assert len(autoscaling.groups) == 2
assert (
autoscaling.groups[0].arn
== autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=["my-autoscaling-group"]
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
)
assert autoscaling.groups[0].name == "my-autoscaling-group"
assert autoscaling.groups[0].region == AWS_REGION_US_EAST_1
assert autoscaling.groups[0].availability_zones == ["us-east-1a", "us-east-1b"]
assert autoscaling.groups[0].tags == [
{
"Key": "tag_test",
"PropagateAtLaunch": False,
"ResourceId": "my-autoscaling-group",
"ResourceType": "auto-scaling-group",
"Value": "value_test",
}
]
assert autoscaling.groups[0].launch_template["LaunchTemplateName"] == "test"
assert (
autoscaling.groups[1].mixed_instances_policy_launch_template[
"LaunchTemplateName"
]
== "test"
)
assert autoscaling.groups[1].health_check_type == "ELB"
assert autoscaling.groups[1].load_balancers == ["my-load-balancer"]
assert autoscaling.groups[1].target_groups == [
target_group["TargetGroups"][0]["TargetGroupArn"]
]
assert autoscaling.groups[0].launch_configuration_name == "test"
assert len(autoscaling.groups) == 3
for group in autoscaling.groups:
if group.name == "my-autoscaling-group":
assert (
group.arn
== autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=["my-autoscaling-group"]
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
)
assert group.name == "my-autoscaling-group"
assert group.region == AWS_REGION_US_EAST_1
assert group.availability_zones == ["us-east-1a", "us-east-1b"]
assert group.tags == [
{
"Key": "tag_test",
"PropagateAtLaunch": False,
"ResourceId": "my-autoscaling-group",
"ResourceType": "auto-scaling-group",
"Value": "value_test",
}
]
assert group.launch_template["LaunchTemplateName"] == "test"
assert not group.mixed_instances_policy_launch_template
assert group.health_check_type == "EC2"
assert not group.load_balancers
assert not group.target_groups
assert not group.launch_configuration_name
elif group.name == "my-autoscaling-group-2":
assert (
group.arn
== autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=["my-autoscaling-group-2"]
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
)
assert group.name == "my-autoscaling-group-2"
assert group.region == AWS_REGION_US_EAST_1
assert group.availability_zones == ["us-east-1a", "us-east-1b"]
assert group.tags == [
{
"Key": "tag_test",
"PropagateAtLaunch": False,
"ResourceId": "my-autoscaling-group-2",
"ResourceType": "auto-scaling-group",
"Value": "value_test",
}
]
assert not group.launch_template
assert (
group.mixed_instances_policy_launch_template["LaunchTemplateName"]
== "test"
)
assert group.health_check_type == "ELB"
assert group.load_balancers == ["my-load-balancer"]
assert group.target_groups == [
target_group["TargetGroups"][0]["TargetGroupArn"]
]
assert not group.launch_configuration_name
elif group.name == "my-autoscaling-group-3":
assert (
group.arn
== autoscaling_client.describe_auto_scaling_groups(
AutoScalingGroupNames=["my-autoscaling-group-3"]
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
)
assert group.name == "my-autoscaling-group-3"
assert group.region == AWS_REGION_US_EAST_1
assert group.availability_zones == ["us-east-1a", "us-east-1b"]
assert not group.tags
assert not group.launch_template
assert not group.mixed_instances_policy_launch_template
assert group.health_check_type == "EC2"
assert not group.load_balancers
assert not group.target_groups
assert group.launch_configuration_name == "test"
# Test Application AutoScaling Describe Scalable Targets
@mock_aws