Jason Zions has proposed merging ~jasonzio/cloud-init:randomSeed into cloud-init:master.
Commit message: Azure: Ensure platform random_seed is always serializable as JSON. The Azure platform surfaces random bytes into /sys via Hyper-V. Python 2.7 json.dump() raises an exception if it's asked to convert a str with non-ASCII content into JSON. As a result, c-i instance data is often not written by Azure, making reboots slower (c-i repeats work). Requested reviews: cloud-init commiters (cloud-init-dev) For more details, see: https://code.launchpad.net/~jasonzio/cloud-init/+git/cloud-init/+merge/365065 When the random string is captured, an attempt to convert it to JSON is made immediately. If that throws, the random data is base64-encoded. The encoded string has just as many bits of entropy, so we're not throwing away useful "information", but we can be certain json.dump will not throw. -- Your team cloud-init commiters is requested to review the proposed merge of ~jasonzio/cloud-init:randomSeed into cloud-init:master.
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py index eccbee5..5d39459 100644 --- a/cloudinit/sources/DataSourceAzure.py +++ b/cloudinit/sources/DataSourceAzure.py @@ -54,6 +54,7 @@ REPROVISION_MARKER_FILE = "/var/lib/cloud/data/poll_imds" REPORTED_READY_MARKER_FILE = "/var/lib/cloud/data/reported_ready" AGENT_SEED_DIR = '/var/lib/waagent' IMDS_URL = "http://169.254.169.254/metadata/" +PLATFORM_ENTROPY_SOURCE = "/sys/firmware/acpi/tables/OEM0" # List of static scripts and network config artifacts created by # stock ubuntu suported images. @@ -195,6 +196,8 @@ if util.is_FreeBSD(): RESOURCE_DISK_PATH = "/dev/" + res_disk else: LOG.debug("resource disk is None") + # TODO Find where platform entropy data is surfaced + PLATFORM_ENTROPY_SOURCE = None BUILTIN_DS_CONFIG = { 'agent_command': AGENT_START_BUILTIN, @@ -1104,12 +1107,23 @@ def _get_random_seed(): """Return content random seed file if available, otherwise, return None.""" # azure / hyper-v provides random data here - # TODO. find the seed on FreeBSD platform # now update ds_cfg to reflect contents pass in config - if util.is_FreeBSD(): + if PLATFORM_ENTROPY_SOURCE is None: return None - return util.load_file("/sys/firmware/acpi/tables/OEM0", - quiet=True, decode=False) + seed = util.load_file(PLATFORM_ENTROPY_SOURCE, quiet=True, decode=False) + + # In legacy python (2.7), if the seed contains non-Ascii characters, + # util.json_dumps() will throw. If that happens, just base64-encode it. + # Same number of bits of entropy, with 25% more zeroes. There's no need + # to undo this base64-encoding when the random seed is actually used (by + # writing it to /dev/urandom to provide more entropy to that PRNG). + test_obj = {'seed': seed} + try: + _ = util.json_dumps(test_obj) + except (TypeError, UnicodeDecodeError): + seed = base64.b64encode(seed) + + return seed def list_possible_azure_ds_devs(): diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py index 6b05b8f..e8eb872 100644 --- a/tests/unittests/test_datasource/test_azure.py +++ b/tests/unittests/test_datasource/test_azure.py @@ -7,12 +7,13 @@ from cloudinit.sources import ( UNSET, DataSourceAzure as dsaz, InvalidMetaDataException) from cloudinit.util import (b64e, decode_binary, load_file, write_file, find_freebsd_part, get_path_dev_freebsd, - MountFailedError) + MountFailedError, json_dumps, load_json) from cloudinit.version import version_string as vs from cloudinit.tests.helpers import ( HttprettyTestCase, CiTestCase, populate_dir, mock, wrap_and_call, ExitStack) +import base64 import crypt import httpretty import json @@ -1923,4 +1924,43 @@ class TestWBIsPlatformViable(CiTestCase): self.logs.getvalue()) +class TestRandomSeed(CiTestCase): + """Test proper handling of random_seed""" + + @mock.patch(MOCKPATH + 'util.load_file') + def test_ascii_seed_is_unaltered(self, m_load_file): + """Pass if a random string from the Azure infrastructure which + contains only ASCII characters is used as-is + """ + ascii_seed = "abcd12345)(*@#%^!}{" + m_load_file.return_value = ascii_seed + self.assertEqual(dsaz._get_random_seed(), ascii_seed) + + @mock.patch(MOCKPATH + 'util.load_file') + def test_non_ascii_seed_is_serializable_and_unaltered(self, m_load_file): + """Pass if a random string from the Azure infrastructure which + contains at least one non-ASCII characters is either unchanged or + decodes to the non-ASCII string, and that in either case, the seed + can be converted to/from JSON without alteration. + """ + nonascii_seed = "OEM0d\x00\x00\x00\x01\x80VRTUALMICROSFT\x02\x17\x00"\ + "\x06MSFT\x97\x00\x00\x00C\xb4{V\xf4X%\x061x\x90\x1c\xfen\x86"\ + "\xbf~\xf5\x8c\x94&\x88\xed\x84\xf9B\xbd\xd3\xf1\xdb\xee:\xd9"\ + "\x0fc\x0e\x83(\xbd\xe3'\xfc\x85,\xdf\xf4\x13\x99N\xc5\xf3Y"\ + "\x1e\xe3\x0b\xa4H\x08J\xb9\xdcdb$" + m_load_file.return_value = nonascii_seed + + result = dsaz._get_random_seed() + + obj = {'seed': result} + try: + serialized = json_dumps(obj) + deserialized = load_json(serialized) + self.assertEqual(deserialized['seed'], result) + except (TypeError, UnicodeDecodeError): + self.fail("Non-serializable random seed returned") + + if nonascii_seed != result: + self.assertEqual(base64.b64decode(result), nonascii_seed) + # vi: ts=4 expandtab
_______________________________________________ Mailing list: https://launchpad.net/~cloud-init-dev Post to : cloud-init-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~cloud-init-dev More help : https://help.launchpad.net/ListHelp