17dafa
From 2428320b2157a0fcc0f35bea12584286ebd02aab Mon Sep 17 00:00:00 2001
17dafa
From: Eduardo Otubo <otubo@redhat.com>
17dafa
Date: Wed, 15 May 2019 12:15:25 +0200
17dafa
Subject: [PATCH 1/5] Azure: Ensure platform random_seed is always serializable
17dafa
 as JSON.
17dafa
17dafa
RH-Author: Eduardo Otubo <otubo@redhat.com>
17dafa
Message-id: <20190515121529.11191-2-otubo@redhat.com>
17dafa
Patchwork-id: 87881
17dafa
O-Subject: [rhel-7 cloud-init PATCHv2 1/5] Azure: Ensure platform random_seed is always serializable as JSON.
17dafa
Bugzilla: 1687565
17dafa
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
17dafa
RH-Acked-by: Mohammed Gamal <mgamal@redhat.com>
17dafa
17dafa
From: "Jason Zions (MSFT)" <jasonzio@microsoft.com>
17dafa
17dafa
BZ: 1687565
17dafa
BRANCH: rhel7/master-18.5
17dafa
UPSTREAM: 0dc3a77f
17dafa
BREW: 21696239
17dafa
17dafa
commit 0dc3a77f41f4544e4cb5a41637af7693410d4cdf
17dafa
Author: Jason Zions (MSFT) <jasonzio@microsoft.com>
17dafa
Date:   Tue Mar 26 18:53:50 2019 +0000
17dafa
17dafa
    Azure: Ensure platform random_seed is always serializable as JSON.
17dafa
17dafa
    The Azure platform surfaces random bytes into /sys via Hyper-V.
17dafa
    Python 2.7 json.dump() raises an exception if asked to convert
17dafa
    a str with non-character content, and python 3.0 json.dump()
17dafa
    won't serialize a "bytes" value. As a result, c-i instance
17dafa
    data is often not written by Azure, making reboots slower (c-i
17dafa
    has to repeat work).
17dafa
17dafa
    The random data is base64-encoded and then decoded into a string
17dafa
    (str or unicode depending on the version of Python in use). The
17dafa
    base64 string has just as many bits of entropy, so we're not
17dafa
    throwing away useful "information", but we can be certain
17dafa
    json.dump() will correctly serialize the bits.
17dafa
17dafa
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
17dafa
17dafa
Conflicts:
17dafa
    tests/unittests/test_datasource/test_azure.py
17dafa
    Skipped the commit edf052c as it removes support for python-2.6
17dafa
17dafa
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
17dafa
---
17dafa
 cloudinit/sources/DataSourceAzure.py          | 24 +++++++++++++++++++-----
17dafa
 tests/data/azure/non_unicode_random_string    |  1 +
17dafa
 tests/unittests/test_datasource/test_azure.py | 24 ++++++++++++++++++++++--
17dafa
 3 files changed, 42 insertions(+), 7 deletions(-)
17dafa
 create mode 100644 tests/data/azure/non_unicode_random_string
17dafa
17dafa
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
17dafa
index 2062ca5..a768b2c 100644
17dafa
--- a/cloudinit/sources/DataSourceAzure.py
17dafa
+++ b/cloudinit/sources/DataSourceAzure.py
17dafa
@@ -54,6 +54,7 @@ REPROVISION_MARKER_FILE = "/var/lib/cloud/data/poll_imds"
17dafa
 REPORTED_READY_MARKER_FILE = "/var/lib/cloud/data/reported_ready"
17dafa
 AGENT_SEED_DIR = '/var/lib/waagent'
17dafa
 IMDS_URL = "http://169.254.169.254/metadata/"
17dafa
+PLATFORM_ENTROPY_SOURCE = "/sys/firmware/acpi/tables/OEM0"
17dafa
 
17dafa
 # List of static scripts and network config artifacts created by
17dafa
 # stock ubuntu suported images.
17dafa
@@ -195,6 +196,8 @@ if util.is_FreeBSD():
17dafa
         RESOURCE_DISK_PATH = "/dev/" + res_disk
17dafa
     else:
17dafa
         LOG.debug("resource disk is None")
17dafa
+    # TODO Find where platform entropy data is surfaced
17dafa
+    PLATFORM_ENTROPY_SOURCE = None
17dafa
 
17dafa
 BUILTIN_DS_CONFIG = {
17dafa
     'agent_command': AGENT_START_BUILTIN,
17dafa
@@ -1100,16 +1103,27 @@ def _check_freebsd_cdrom(cdrom_dev):
17dafa
     return False
17dafa
 
17dafa
 
17dafa
-def _get_random_seed():
17dafa
+def _get_random_seed(source=PLATFORM_ENTROPY_SOURCE):
17dafa
     """Return content random seed file if available, otherwise,
17dafa
        return None."""
17dafa
     # azure / hyper-v provides random data here
17dafa
-    # TODO. find the seed on FreeBSD platform
17dafa
     # now update ds_cfg to reflect contents pass in config
17dafa
-    if util.is_FreeBSD():
17dafa
+    if source is None:
17dafa
         return None
17dafa
-    return util.load_file("/sys/firmware/acpi/tables/OEM0",
17dafa
-                          quiet=True, decode=False)
17dafa
+    seed = util.load_file(source, quiet=True, decode=False)
17dafa
+
17dafa
+    # The seed generally contains non-Unicode characters. load_file puts
17dafa
+    # them into a str (in python 2) or bytes (in python 3). In python 2,
17dafa
+    # bad octets in a str cause util.json_dumps() to throw an exception. In
17dafa
+    # python 3, bytes is a non-serializable type, and the handler load_file
17dafa
+    # uses applies b64 encoding *again* to handle it. The simplest solution
17dafa
+    # is to just b64encode the data and then decode it to a serializable
17dafa
+    # string. Same number of bits of entropy, just with 25% more zeroes.
17dafa
+    # There's no need to undo this base64-encoding when the random seed is
17dafa
+    # actually used in cc_seed_random.py.
17dafa
+    seed = base64.b64encode(seed).decode()
17dafa
+
17dafa
+    return seed
17dafa
 
17dafa
 
17dafa
 def list_possible_azure_ds_devs():
17dafa
diff --git a/tests/data/azure/non_unicode_random_string b/tests/data/azure/non_unicode_random_string
17dafa
new file mode 100644
17dafa
index 0000000..b9ecefb
17dafa
--- /dev/null
17dafa
+++ b/tests/data/azure/non_unicode_random_string
17dafa
@@ -0,0 +1 @@
17dafa
+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$
17dafa
\ No newline at end of file
17dafa
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
17dafa
index 417d86a..eacf225 100644
17dafa
--- a/tests/unittests/test_datasource/test_azure.py
17dafa
+++ b/tests/unittests/test_datasource/test_azure.py
17dafa
@@ -7,11 +7,11 @@ from cloudinit.sources import (
17dafa
     UNSET, DataSourceAzure as dsaz, InvalidMetaDataException)
17dafa
 from cloudinit.util import (b64e, decode_binary, load_file, write_file,
17dafa
                             find_freebsd_part, get_path_dev_freebsd,
17dafa
-                            MountFailedError)
17dafa
+                            MountFailedError, json_dumps, load_json)
17dafa
 from cloudinit.version import version_string as vs
17dafa
 from cloudinit.tests.helpers import (
17dafa
     HttprettyTestCase, CiTestCase, populate_dir, mock, wrap_and_call,
17dafa
-    ExitStack, PY26, SkipTest)
17dafa
+    ExitStack, PY26, SkipTest, resourceLocation)
17dafa
 
17dafa
 import crypt
17dafa
 import httpretty
17dafa
@@ -1924,4 +1924,24 @@ class TestWBIsPlatformViable(CiTestCase):
17dafa
             self.logs.getvalue())
17dafa
 
17dafa
 
17dafa
+class TestRandomSeed(CiTestCase):
17dafa
+    """Test proper handling of random_seed"""
17dafa
+
17dafa
+    def test_non_ascii_seed_is_serializable(self):
17dafa
+        """Pass if a random string from the Azure infrastructure which
17dafa
+        contains at least one non-Unicode character can be converted to/from
17dafa
+        JSON without alteration and without throwing an exception.
17dafa
+        """
17dafa
+        path = resourceLocation("azure/non_unicode_random_string")
17dafa
+        result = dsaz._get_random_seed(path)
17dafa
+
17dafa
+        obj = {'seed': result}
17dafa
+        try:
17dafa
+            serialized = json_dumps(obj)
17dafa
+            deserialized = load_json(serialized)
17dafa
+        except UnicodeDecodeError:
17dafa
+            self.fail("Non-serializable random seed returned")
17dafa
+
17dafa
+        self.assertEqual(deserialized['seed'], result)
17dafa
+
17dafa
 # vi: ts=4 expandtab
17dafa
-- 
17dafa
1.8.3.1
17dafa