← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~jasonzio/cloud-init:randomSeed into cloud-init:master

 

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

Follow ups