Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ec2 ami imdsv2 enable #2310

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelogs/fragments/add_imdsv2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
minor_changes:
- ec2_ami - adds ``imdsv2_enable`` parameter to enable IMDSv2 when creating/updating an image (https://github.com/ansible-collections/amazon.aws/pull/2310).
- ec2_ami_info - add ``imdsv2_enable`` return when ``describe_image_attributes`` is set (https://github.com/ansible-collections/amazon.aws/pull/2310).
2 changes: 1 addition & 1 deletion plugins/module_utils/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ def deregister_image(client, image_id: str) -> bool:


@EC2ImageErrorHandler.common_error_handler("modify image attribute")
@AWSRetry.jittered_backoff()
@AWSRetry.jittered_backoff(catch_extra_error_codes=["InvalidAMIID.Unavailable"])
def modify_image_attribute(client, image_id: str, **params: Dict[str, Any]) -> bool:
client.modify_image_attribute(ImageId=image_id, **params)
return True
Expand Down
48 changes: 42 additions & 6 deletions plugins/modules/ec2_ami.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@
- See the AWS documentation for more detail U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/uefi-secure-boot.html).
type: str
version_added: 5.5.0
imdsv2_enable:
description:
- Force IMDS v2 on the AMI
- See the AWS documentation for more detail
- U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-new-instances.html#configure-IMDS-new-instances-ami-configuration).
type: bool
default: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default: false
default: false
version_added: 9.0.0

version_added: 9.0.0
author:
- "Evan Duffield (@scicoin-project) <[email protected]>"
- "Constantin Bugneac (@Constantin07) <[email protected]>"
Expand Down Expand Up @@ -553,12 +561,15 @@ def get_image_by_id(connection, image_id):

result = images[0]
try:
image_attribue = describe_image_attribute(connection, attribute="launchPermission", image_id=image_id)
if image_attribue:
result["LaunchPermissions"] = image_attribue["LaunchPermissions"]
image_attribue = describe_image_attribute(connection, attribute="productCodes", image_id=image_id)
if image_attribue:
result["ProductCodes"] = image_attribue["ProductCodes"]
image_attribute = describe_image_attribute(connection, attribute="launchPermission", image_id=image_id)
if image_attribute:
result["LaunchPermissions"] = image_attribute["LaunchPermissions"]
image_attribute = describe_image_attribute(connection, attribute="productCodes", image_id=image_id)
if image_attribute:
result["ProductCodes"] = image_attribute["ProductCodes"]
image_attribute = describe_image_attribute(connection, attribute="imdsSupport", image_id=image_id)
if image_attribute:
result["ImdsSupport"] = image_attribute["ImdsSupport"]["Value"]
except AnsibleEC2Error as e:
raise Ec2AmiFailure(f"Error retrieving image attributes for image {image_id}", e)
return result
Expand Down Expand Up @@ -766,6 +777,20 @@ def set_description(connection, module, image, description):
except AnsibleEC2Error as e:
raise Ec2AmiFailure(f"Error setting description for image {image['ImageId']}", e)

@staticmethod
def set_imdsv2(connection, image, imdsv2_enable):
if not imdsv2_enable and image["ImdsSupport"] != "v2.0":
return False

if image["ImdsSupport"] == "v2.0":
return False

try:
modify_image_attribute(connection, image_id=image["imageId"], Attribute="imdsSupport", Value="v2.0")
return True
except AnsibleEC2Error as e:
raise Ec2AmiFailure(f"Error setting IMDS Support to v2 for image {image['imageId']}", e)

@classmethod
def do(cls, module, connection, image_id):
"""Entry point to update an image"""
Expand All @@ -782,6 +807,7 @@ def do(cls, module, connection, image_id):
changed |= cls.set_launch_permission(connection, image, launch_permissions, module.check_mode)
changed |= cls.set_tags(connection, module, image_id, module.params["tags"], module.params["purge_tags"])
changed |= cls.set_description(connection, module, image, module.params["description"])
changed |= cls.set_imdsv2(connection, image, module.params["imdsv2_enable"])

if changed and module.check_mode:
module.exit_json(changed=True, msg="Would have updated AMI if not in check mode.")
Expand Down Expand Up @@ -844,6 +870,13 @@ def set_launch_permissions(connection, launch_permissions, image_id):
except AnsibleEC2Error as e:
raise Ec2AmiFailure(f"Error setting launch permissions for image {image_id}", e)

@staticmethod
def set_imdsv2(connection, image_id):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: API does not support removing the attribute once set.

try:
modify_image_attribute(connection, image_id=image_id, Attribute="imdsSupport", Value="v2.0")
except AnsibleEC2Error as e:
raise Ec2AmiFailure(f"Error setting IMDS Support to v2 for image {image_id}", e)

@staticmethod
def create_or_register(create_image_parameters):
create_from_instance = "InstanceId" in create_image_parameters
Expand Down Expand Up @@ -952,6 +985,8 @@ def do(cls, module, connection, _image_id):
CreateImage.set_tags(connection, module, module.params.get("tags"), image_id)

cls.set_launch_permissions(connection, module.params.get("launch_permissions"), image_id)
if module.params.get("imdsv2_enable"):
cls.set_imdsv2(connection, image_id)

module.exit_json(
msg="AMI creation operation complete.", changed=True, **get_ami_info(get_image_by_id(connection, image_id))
Expand Down Expand Up @@ -1002,6 +1037,7 @@ def main():
tpm_support={"type": "str"},
uefi_data={"type": "str"},
virtualization_type={"default": "hvm"},
imdsv2_enable={"type": "bool", "default": False},
wait={"type": "bool", "default": False},
wait_timeout={"default": 1200, "type": "int"},
)
Expand Down
10 changes: 10 additions & 0 deletions plugins/modules/ec2_ami_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@
description: An AWS account ID with permissions to launch the AMI.
type: str
sample: [{"group": "all"}, {"user_id": "123456789012"}]
imdsv2_enabled:
description: Whether the image has IMDSv2 enabled or not.
returned: When O(describe_image_attributes=true).
type: bool
name:
description: The name of the AMI that was provided during image creation.
returned: always
Expand Down Expand Up @@ -277,6 +281,12 @@ def list_ec2_images(ec2_client, module, request_args):
ec2_client, attribute="launchPermission", image_id=image["image_id"]
).get("LaunchPermissions", [])
image["launch_permissions"] = [camel_dict_to_snake_dict(perm) for perm in launch_permissions]
imdsv2_enabled = describe_image_attribute(
ec2_client, attribute="imdsSupport", image_id=image["image_id"]
).get("ImdsSupport", {})
image["imdsv2_enabled"] = (
True if "Value" in imdsv2_enabled and imdsv2_enabled["Value"] == "v2.0" else False
)
except is_ansible_aws_error_code("AuthFailure"):
# describing launch permissions of images owned by others is not permitted, but shouldn't cause failures
pass
Expand Down
29 changes: 27 additions & 2 deletions tests/integration/targets/ec2_ami/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -708,8 +708,12 @@
tags:
Name: "{{ ec2_ami_name }}_permissions"
launch_permissions:
org_arns: ["arn:aws:organizations::123456789012:organization/o-123ab4cdef"]
org_unit_arns: ["arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5exampld"]
org_arns:
["arn:aws:organizations::123456789012:organization/o-123ab4cdef"]
org_unit_arns:
[
"arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5exampld",
]
register: permissions_update_result

- name: Get ami info
Expand All @@ -727,6 +731,27 @@
- "'organizational_unit_arn' in permissions_info_result.images[0].launch_permissions[1]"
- permissions_info_result.images[0].launch_permissions[1]['organizational_unit_arn'] == 'arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5exampld'

- name: Create image with imdsv2 enabled
amazon.aws.ec2_ami:
state: present
instance_id: "{{ ec2_instance_id }}"
name: "{{ ec2_ami_name }}_imdsv2"
tags:
Name: "{{ ec2_ami_name }}_imdsv2"
imdsv2_enable: true
register: imdsv2_image

- name: Get ami info
amazon.aws.ec2_ami_info:
image_ids: "{{ imdsv2_image.image_id }}"
describe_image_attributes: true
register: imdsv2_info

- name: Assert ami IMDSV2 is set
ansible.builtin.assert:
that:
- imdsv2_info.images[0].imdsv2_enabled

# ============================================================

always:
Expand Down
48 changes: 42 additions & 6 deletions tests/integration/targets/ec2_ami_instance/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@
amazon.aws.ec2_ami:
instance_id: "{{ ec2_instance_id }}"
state: present
name: "{{ ec2_ami_name }}_ami"
name: "{{ ec2_ami_name }}_ami_check"
description: "{{ ec2_ami_description }}"
tags:
Name: "{{ ec2_ami_name }}_ami"
Name: "{{ ec2_ami_name }}_ami_check"
wait: true
root_device_name: "{{ ec2_ami_root_disk }}"
check_mode: true
Expand All @@ -110,10 +110,10 @@
amazon.aws.ec2_ami:
instance_id: "{{ ec2_instance_id }}"
state: present
name: "{{ ec2_ami_name }}_ami"
name: "{{ ec2_ami_name }}_ami_instance"
description: "{{ ec2_ami_description }}"
tags:
Name: "{{ ec2_ami_name }}_ami"
Name: "{{ ec2_ami_name }}_ami_instance"
wait: true
root_device_name: "{{ ec2_ami_root_disk }}"
register: result
Expand All @@ -127,7 +127,7 @@
that:
- result.changed
- result.image_id.startswith('ami-')
- "'Name' in result.tags and result.tags.Name == ec2_ami_name + '_ami'"
- "'Name' in result.tags and result.tags.Name == ec2_ami_name + '_ami_instance'"

- name: get related snapshot info and ensure the tags have been propagated
amazon.aws.ec2_snapshot_info:
Expand All @@ -139,7 +139,7 @@
ansible.builtin.assert:
that:
- "'tags' in snapshot_result.snapshots[0]"
- "'Name' in snapshot_result.snapshots[0].tags and snapshot_result.snapshots[0].tags.Name == ec2_ami_name + '_ami'"
- "'Name' in snapshot_result.snapshots[0].tags and snapshot_result.snapshots[0].tags.Name == ec2_ami_name + '_ami_instance'"

# ============================================================

Expand Down Expand Up @@ -347,6 +347,34 @@
- result.failed
- "result.msg == 'state is absent but all of the following are missing: image_id'"

# ==============================================================

- name: Create image with imdsv2 enabled
amazon.aws.ec2_ami:
state: present
instance_id: "{{ ec2_instance_id }}"
name: "{{ ec2_ami_name }}_imdsv2"
imdsv2_enable: true
wait: true
tags:
Name: "{{ ec2_ami_name }}_imdsv2"
register: imdsv2_image

- name: Get ami info
amazon.aws.ec2_ami_info:
image_ids: "{{ imdsv2_image.image_id }}"
describe_image_attributes: true
register: imdsv2_info

- name: Assert ami IMDSV2 is set
ansible.builtin.assert:
that:
- imdsv2_info.images[0].imdsv2_enabled

- name: set image id fact for deletion later
ansible.builtin.set_fact:
ec2_ami_image_id_imdsv2: "{{ imdsv2_image.image_id }}"

always:
# ============================================================

Expand Down Expand Up @@ -379,6 +407,14 @@
wait: true
ignore_errors: true

- name: delete imdsv2 ami
amazon.aws.ec2_ami:
state: absent
image_id: "{{ ec2_ami_image_id_imdsv2 }}"
name: "{{ ec2_ami_name }}_imdsv2"
wait: true
ignore_errors: true

- name: delete ami
amazon.aws.ec2_ami:
state: absent
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/plugins/modules/test_ec2_ami.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def test_get_image_by_id_found(m_describe_images, m_describe_image_attribute):
image = ec2_ami.get_image_by_id(connection, "ami-0c7a795306730b288")
assert image["ImageId"] == "ami-0c7a795306730b288"
assert m_describe_images.call_count == 1
assert m_describe_image_attribute.call_count == 2
assert m_describe_image_attribute.call_count == 3
m_describe_images.assert_has_calls(
[
call(
Expand Down
8 changes: 7 additions & 1 deletion tests/unit/plugins/modules/test_ec2_ami_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ def test_list_ec2_images(m_get_images, m_describe_image_attribute):
m_describe_image_attribute.return_value = {
"ImageId": "ami-1234567890",
"LaunchPermissions": [{"UserId": "1234567890"}, {"UserId": "0987654321"}],
"ImdsSupport": {"Value": "v2.0"},
}

images = m_get_images.return_value
Expand All @@ -160,15 +161,20 @@ def test_list_ec2_images(m_get_images, m_describe_image_attribute):
assert m_get_images.call_count == 1
m_get_images.assert_called_with(ec2_client, request_args)

assert m_describe_image_attribute.call_count == 2
assert m_describe_image_attribute.call_count == 4
m_describe_image_attribute.assert_has_calls(
[call(ec2_client, attribute="launchPermission", image_id=images[0]["image_id"])],
[call(ec2_client, attribute="launchPermission", image_id=images[1]["image_id"])],
)
m_describe_image_attribute.assert_has_calls(
[call(ec2_client, attribute="imdsSupport", image_id=images[0]["image_id"])],
[call(ec2_client, attribute="imdsSupport", image_id=images[1]["image_id"])],
)

assert len(list_ec2_images_result) == 2
assert list_ec2_images_result[0]["image_id"] == "ami-1234567890"
assert list_ec2_images_result[1]["image_id"] == "ami-1523498760"
assert list_ec2_images_result[0]["imdsv2_enabled"]


@patch(module_name + ".AnsibleAWSModule")
Expand Down
Loading