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