Skip to content

Commit

Permalink
ec2_metadata_facts - Handle decompression when EC2 instance user-data…
Browse files Browse the repository at this point in the history
… is compressed (ansible-collections#1575)

ec2_metadata_facts - Handle decompression when EC2 instance user-data is compressed

SUMMARY

Handle decompression when user-data is compressed. The fetch_url method from ansible.module_utils.urls does not decompress the user-data because the header is missing (The API does not set 'Content-Encoding' = 'gzip').

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ec2_metadata_facts
ADDITIONAL INFORMATION

Reviewed-by: Mike Graves <[email protected]>
Reviewed-by: Mark Chappell
Reviewed-by: Alina Buzachis
  • Loading branch information
alinabuzachis authored May 30, 2023
1 parent b7533c5 commit f1fe603
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
bugfixes:
- 'ec2_metadata_facts - Handle decompression when EC2 instance user-data is gzip compressed. The fetch_url method from ansible.module_utils.urls does not decompress the user-data unless the header explicitly contains ``Content-Encoding: gzip`` (https://github.com/ansible-collections/amazon.aws/pull/1575).'
34 changes: 34 additions & 0 deletions plugins/modules/ec2_metadata_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@
import re
import socket
import time
import zlib

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_text
Expand Down Expand Up @@ -484,11 +485,42 @@ def __init__(
self._token = None
self._prefix = "ansible_ec2_%s"

def _decode(self, data):
try:
return data.decode("utf-8")
except UnicodeDecodeError:
# Decoding as UTF-8 failed, return data without raising an error
self.module.warn("Decoding user-data as UTF-8 failed, return data as is ignoring any error")
return data.decode("utf-8", errors="ignore")

def decode_user_data(self, data):
is_compressed = False

# Check if data is compressed using zlib header
if data.startswith(b"\x78\x9c") or data.startswith(b"\x1f\x8b"):
is_compressed = True

if is_compressed:
# Data is compressed, attempt decompression and decode using UTF-8
try:
decompressed = zlib.decompress(data, zlib.MAX_WBITS | 32)
return self._decode(decompressed)
except zlib.error:
# Unable to decompress, return original data
self.module.warn(
"Unable to decompress user-data using zlib, attempt to decode original user-data as UTF-8"
)
return self._decode(data)
else:
# Data is not compressed, decode using UTF-8
return self._decode(data)

def _fetch(self, url):
encoded_url = quote(url, safe="%/:=&?~#+!$,;'@()*[]")
headers = {}
if self._token:
headers = {"X-aws-ec2-metadata-token": self._token}

response, info = fetch_url(self.module, encoded_url, headers=headers, force=True)

if info.get("status") in (401, 403):
Expand All @@ -505,6 +537,8 @@ def _fetch(self, url):
)
if response and info["status"] < 400:
data = response.read()
if "user-data" in encoded_url:
return to_text(self.decode_user_data(data))
else:
data = None
return to_text(data)
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/plugins/modules/test_ec2_metadata_facts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# This file is part of Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

import gzip
import io
import pytest
from unittest.mock import MagicMock
Expand Down Expand Up @@ -59,3 +60,41 @@ def test_fetch_recusive(m_fetch_url, ec2_instance):
]
ec2_instance.fetch("http://169.254.169.254/latest/meta-data/")
assert ec2_instance._data == {"http://169.254.169.254/latest/meta-data/whatever/my-key": "my-value"}


@patch(module_name + ".fetch_url")
def test__fetch_user_data_compressed(m_fetch_url, ec2_instance):
user_data = b"""Content-Type: multipart/mixed; boundary="MIMEBOUNDARY"
MIME-Version: 1.0
--MIMEBOUNDARY
Content-Transfer-Encoding: 7bit
Content-Type: text/cloud-config
Mime-Version: 1.0
packages: ['httpie']
--MIMEBOUNDARY--
"""

m_fetch_url.return_value = (io.BytesIO(gzip.compress(user_data)), {"status": 200})
assert ec2_instance._fetch("http://169.254.169.254/latest/user-data") == user_data.decode("utf-8")


@patch(module_name + ".fetch_url")
def test__fetch_user_data_plain(m_fetch_url, ec2_instance):
user_data = b"""Content-Type: multipart/mixed; boundary="MIMEBOUNDARY"
MIME-Version: 1.0
--MIMEBOUNDARY
Content-Transfer-Encoding: 7bit
Content-Type: text/cloud-config
Mime-Version: 1.0
packages: ['httpie']
--MIMEBOUNDARY--
"""

m_fetch_url.return_value = (io.BytesIO(user_data), {"status": 200})
assert ec2_instance._fetch("http://169.254.169.254/latest/user-data") == user_data.decode("utf-8")

0 comments on commit f1fe603

Please sign in to comment.