750655
From a584b587a61e7fb7b29f915600021ce51d0ab558 Mon Sep 17 00:00:00 2001
750655
From: Eduardo Otubo <otubo@redhat.com>
750655
Date: Wed, 6 Mar 2019 14:18:01 +0100
750655
Subject: [PATCH] azure: Filter list of ssh keys pulled from fabric
750655
750655
RH-Author: Eduardo Otubo <otubo@redhat.com>
750655
Message-id: <20190306141801.8647-1-otubo@redhat.com>
750655
Patchwork-id: 84806
750655
O-Subject: [RHEL-7.6.z cloud-init PATCH] azure: Filter list of ssh keys pulled from fabric
750655
Bugzilla: 1684038
750655
RH-Acked-by: Cathy Avery <cavery@redhat.com>
750655
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
750655
750655
From: "Jason Zions (MSFT)" <jasonzio@microsoft.com>
750655
750655
commit 34f54360fcc1e0f805002a0b639d0a84eb2cb8ee
750655
Author: Jason Zions (MSFT) <jasonzio@microsoft.com>
750655
Date:   Fri Feb 22 13:26:31 2019 +0000
750655
750655
    azure: Filter list of ssh keys pulled from fabric
750655
750655
    The Azure data source is expected to expose a list of
750655
    ssh keys for the user-to-be-provisioned in the crawled
750655
    metadata. When configured to use the __builtin__ agent
750655
    this list is built by the WALinuxAgentShim. The shim
750655
    retrieves the full set of certificates and public keys
750655
    exposed to the VM from the wireserver, extracts any
750655
    ssh keys it can, and returns that list.
750655
750655
    This fix reduces that list of ssh keys to just the
750655
    ones whose fingerprints appear in the "administrative
750655
    user" section of the ovf-env.xml file. The Azure
750655
    control plane exposes other ssh keys to the VM for
750655
    other reasons, but those should not be added to the
750655
    authorized_keys file for the provisioned user.
750655
750655
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
750655
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
750655
---
750655
 cloudinit/sources/DataSourceAzure.py               |  13 +-
750655
 cloudinit/sources/helpers/azure.py                 | 109 ++++++++++-----
750655
 tests/data/azure/parse_certificates_fingerprints   |   4 +
750655
 tests/data/azure/parse_certificates_pem            | 152 +++++++++++++++++++++
750655
 tests/data/azure/pubkey_extract_cert               |  13 ++
750655
 tests/data/azure/pubkey_extract_ssh_key            |   1 +
750655
 .../unittests/test_datasource/test_azure_helper.py |  71 +++++++++-
750655
 7 files changed, 322 insertions(+), 41 deletions(-)
750655
 create mode 100644 tests/data/azure/parse_certificates_fingerprints
750655
 create mode 100644 tests/data/azure/parse_certificates_pem
750655
 create mode 100644 tests/data/azure/pubkey_extract_cert
750655
 create mode 100644 tests/data/azure/pubkey_extract_ssh_key
750655
750655
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
750655
index 46d5744..4ebe6af 100644
750655
--- a/cloudinit/sources/DataSourceAzure.py
750655
+++ b/cloudinit/sources/DataSourceAzure.py
750655
@@ -522,9 +522,11 @@ class DataSourceAzure(sources.DataSource):
750655
         if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN:
750655
             self.bounce_network_with_azure_hostname()
750655
 
750655
+            pubkey_info = self.cfg.get('_pubkeys', None)
750655
             metadata_func = partial(get_metadata_from_fabric,
750655
                                     fallback_lease_file=self.
750655
-                                    dhclient_lease_file)
750655
+                                    dhclient_lease_file,
750655
+                                    pubkey_info=pubkey_info)
750655
         else:
750655
             metadata_func = self.get_metadata_from_agent
750655
 
750655
@@ -537,6 +539,7 @@ class DataSourceAzure(sources.DataSource):
750655
                 "Error communicating with Azure fabric; You may experience."
750655
                 "connectivity issues.", exc_info=True)
750655
             return False
750655
+
750655
         util.del_file(REPORTED_READY_MARKER_FILE)
750655
         util.del_file(REPROVISION_MARKER_FILE)
750655
         return fabric_data
750655
@@ -807,13 +810,15 @@ def find_child(node, filter_func):
750655
 def load_azure_ovf_pubkeys(sshnode):
750655
     # This parses a 'SSH' node formatted like below, and returns
750655
     # an array of dicts.
750655
-    #  [{'fp': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7',
750655
-    #    'path': 'where/to/go'}]
750655
+    #  [{'fingerprint': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7',
750655
+    #    'path': '/where/to/go'}]
750655
     #
750655
     # <SSH><PublicKeys>
750655
-    #   <PublicKey><Fingerprint>ABC</FingerPrint><Path>/ABC</Path>
750655
+    #   <PublicKey><Fingerprint>ABC</FingerPrint><Path>/x/y/z</Path>
750655
     #   ...
750655
     # </PublicKeys></SSH>
750655
+    # Under some circumstances, there may be a <Value> element along with the
750655
+    # Fingerprint and Path. Pass those along if they appear.
750655
     results = find_child(sshnode, lambda n: n.localName == "PublicKeys")
750655
     if len(results) == 0:
750655
         return []
750655
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
750655
index 90c12df..4b4f0e0 100644
750655
--- a/cloudinit/sources/helpers/azure.py
750655
+++ b/cloudinit/sources/helpers/azure.py
750655
@@ -137,9 +137,36 @@ class OpenSSLManager(object):
750655
             self.certificate = certificate
750655
         LOG.debug('New certificate generated.')
750655
 
750655
-    def parse_certificates(self, certificates_xml):
750655
-        tag = ElementTree.fromstring(certificates_xml).find(
750655
-            './/Data')
750655
+    @staticmethod
750655
+    def _run_x509_action(action, cert):
750655
+        cmd = ['openssl', 'x509', '-noout', action]
750655
+        result, _ = util.subp(cmd, data=cert)
750655
+        return result
750655
+
750655
+    def _get_ssh_key_from_cert(self, certificate):
750655
+        pub_key = self._run_x509_action('-pubkey', certificate)
750655
+        keygen_cmd = ['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin']
750655
+        ssh_key, _ = util.subp(keygen_cmd, data=pub_key)
750655
+        return ssh_key
750655
+
750655
+    def _get_fingerprint_from_cert(self, certificate):
750655
+        """openssl x509 formats fingerprints as so:
750655
+        'SHA1 Fingerprint=07:3E:19:D1:4D:1C:79:92:24:C6:A0:FD:8D:DA:\
750655
+        B6:A8:BF:27:D4:73\n'
750655
+
750655
+        Azure control plane passes that fingerprint as so:
750655
+        '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473'
750655
+        """
750655
+        raw_fp = self._run_x509_action('-fingerprint', certificate)
750655
+        eq = raw_fp.find('=')
750655
+        octets = raw_fp[eq+1:-1].split(':')
750655
+        return ''.join(octets)
750655
+
750655
+    def _decrypt_certs_from_xml(self, certificates_xml):
750655
+        """Decrypt the certificates XML document using the our private key;
750655
+           return the list of certs and private keys contained in the doc.
750655
+        """
750655
+        tag = ElementTree.fromstring(certificates_xml).find('.//Data')
750655
         certificates_content = tag.text
750655
         lines = [
750655
             b'MIME-Version: 1.0',
750655
@@ -150,32 +177,30 @@ class OpenSSLManager(object):
750655
             certificates_content.encode('utf-8'),
750655
         ]
750655
         with cd(self.tmpdir):
750655
-            with open('Certificates.p7m', 'wb') as f:
750655
-                f.write(b'\n'.join(lines))
750655
             out, _ = util.subp(
750655
-                'openssl cms -decrypt -in Certificates.p7m -inkey'
750655
+                'openssl cms -decrypt -in /dev/stdin -inkey'
750655
                 ' {private_key} -recip {certificate} | openssl pkcs12 -nodes'
750655
                 ' -password pass:'.format(**self.certificate_names),
750655
-                shell=True)
750655
-        private_keys, certificates = [], []
750655
+                shell=True, data=b'\n'.join(lines))
750655
+        return out
750655
+
750655
+    def parse_certificates(self, certificates_xml):
750655
+        """Given the Certificates XML document, return a dictionary of
750655
+           fingerprints and associated SSH keys derived from the certs."""
750655
+        out = self._decrypt_certs_from_xml(certificates_xml)
750655
         current = []
750655
+        keys = {}
750655
         for line in out.splitlines():
750655
             current.append(line)
750655
             if re.match(r'[-]+END .*?KEY[-]+$', line):
750655
-                private_keys.append('\n'.join(current))
750655
+                # ignore private_keys
750655
                 current = []
750655
             elif re.match(r'[-]+END .*?CERTIFICATE[-]+$', line):
750655
-                certificates.append('\n'.join(current))
750655
+                certificate = '\n'.join(current)
750655
+                ssh_key = self._get_ssh_key_from_cert(certificate)
750655
+                fingerprint = self._get_fingerprint_from_cert(certificate)
750655
+                keys[fingerprint] = ssh_key
750655
                 current = []
750655
-        keys = []
750655
-        for certificate in certificates:
750655
-            with cd(self.tmpdir):
750655
-                public_key, _ = util.subp(
750655
-                    'openssl x509 -noout -pubkey |'
750655
-                    'ssh-keygen -i -m PKCS8 -f /dev/stdin',
750655
-                    data=certificate,
750655
-                    shell=True)
750655
-            keys.append(public_key)
750655
         return keys
750655
 
750655
 
750655
@@ -205,7 +230,6 @@ class WALinuxAgentShim(object):
750655
         self.dhcpoptions = dhcp_options
750655
         self._endpoint = None
750655
         self.openssl_manager = None
750655
-        self.values = {}
750655
         self.lease_file = fallback_lease_file
750655
 
750655
     def clean_up(self):
750655
@@ -327,8 +351,9 @@ class WALinuxAgentShim(object):
750655
         LOG.debug('Azure endpoint found at %s', endpoint_ip_address)
750655
         return endpoint_ip_address
750655
 
750655
-    def register_with_azure_and_fetch_data(self):
750655
-        self.openssl_manager = OpenSSLManager()
750655
+    def register_with_azure_and_fetch_data(self, pubkey_info=None):
750655
+        if self.openssl_manager is None:
750655
+            self.openssl_manager = OpenSSLManager()
750655
         http_client = AzureEndpointHttpClient(self.openssl_manager.certificate)
750655
         LOG.info('Registering with Azure...')
750655
         attempts = 0
750655
@@ -346,16 +371,37 @@ class WALinuxAgentShim(object):
750655
             attempts += 1
750655
         LOG.debug('Successfully fetched GoalState XML.')
750655
         goal_state = GoalState(response.contents, http_client)
750655
-        public_keys = []
750655
-        if goal_state.certificates_xml is not None:
750655
+        ssh_keys = []
750655
+        if goal_state.certificates_xml is not None and pubkey_info is not None:
750655
             LOG.debug('Certificate XML found; parsing out public keys.')
750655
-            public_keys = self.openssl_manager.parse_certificates(
750655
+            keys_by_fingerprint = self.openssl_manager.parse_certificates(
750655
                 goal_state.certificates_xml)
750655
-        data = {
750655
-            'public-keys': public_keys,
750655
-        }
750655
+            ssh_keys = self._filter_pubkeys(keys_by_fingerprint, pubkey_info)
750655
         self._report_ready(goal_state, http_client)
750655
-        return data
750655
+        return {'public-keys': ssh_keys}
750655
+
750655
+    def _filter_pubkeys(self, keys_by_fingerprint, pubkey_info):
750655
+        """cloud-init expects a straightforward array of keys to be dropped
750655
+           into the user's authorized_keys file. Azure control plane exposes
750655
+           multiple public keys to the VM via wireserver. Select just the
750655
+           user's key(s) and return them, ignoring any other certs.
750655
+        """
750655
+        keys = []
750655
+        for pubkey in pubkey_info:
750655
+            if 'value' in pubkey and pubkey['value']:
750655
+                keys.append(pubkey['value'])
750655
+            elif 'fingerprint' in pubkey and pubkey['fingerprint']:
750655
+                fingerprint = pubkey['fingerprint']
750655
+                if fingerprint in keys_by_fingerprint:
750655
+                    keys.append(keys_by_fingerprint[fingerprint])
750655
+                else:
750655
+                    LOG.warning("ovf-env.xml specified PublicKey fingerprint "
750655
+                                "%s not found in goalstate XML", fingerprint)
750655
+            else:
750655
+                LOG.warning("ovf-env.xml specified PublicKey with neither "
750655
+                            "value nor fingerprint: %s", pubkey)
750655
+
750655
+        return keys
750655
 
750655
     def _report_ready(self, goal_state, http_client):
750655
         LOG.debug('Reporting ready to Azure fabric.')
750655
@@ -372,11 +418,12 @@ class WALinuxAgentShim(object):
750655
         LOG.info('Reported ready to Azure fabric.')
750655
 
750655
 
750655
-def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None):
750655
+def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None,
750655
+                             pubkey_info=None):
750655
     shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file,
750655
                             dhcp_options=dhcp_opts)
750655
     try:
750655
-        return shim.register_with_azure_and_fetch_data()
750655
+        return shim.register_with_azure_and_fetch_data(pubkey_info=pubkey_info)
750655
     finally:
750655
         shim.clean_up()
750655
 
750655
diff --git a/tests/data/azure/parse_certificates_fingerprints b/tests/data/azure/parse_certificates_fingerprints
750655
new file mode 100644
750655
index 0000000..f7293c5
750655
--- /dev/null
750655
+++ b/tests/data/azure/parse_certificates_fingerprints
750655
@@ -0,0 +1,4 @@
750655
+ECEDEB3B8488D31AF3BC4CCED493F64B7D27D7B1
750655
+073E19D14D1C799224C6A0FD8DDAB6A8BF27D473
750655
+4C16E7FAD6297D74A9B25EB8F0A12808CEBE293E
750655
+929130695289B450FE45DCD5F6EF0CDE69865867
750655
diff --git a/tests/data/azure/parse_certificates_pem b/tests/data/azure/parse_certificates_pem
750655
new file mode 100644
750655
index 0000000..3521ea3
750655
--- /dev/null
750655
+++ b/tests/data/azure/parse_certificates_pem
750655
@@ -0,0 +1,152 @@
750655
+Bag Attributes
750655
+    localKeyID: 01 00 00 00
750655
+    Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
750655
+Key Attributes
750655
+    X509v3 Key Usage: 10
750655
+-----BEGIN PRIVATE KEY-----
750655
+MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDlEe5fUqwdrQTP
750655
+W2oVlGK2f31q/8ULT8KmOTyUvL0RPdJQ69vvHOc5Q2CKg2eviHC2LWhF8WmpnZj6
750655
+61RL0GeFGizwvU8Moebw5p3oqdcgoGpHVtxf+mr4QcWF58/Fwez0dA4hcsimVNBz
750655
+eNpBBUIKNBMTBG+4d6hcQBUAGKUdGRcCGEyTqXLU0MgHjxC9JgVqWJl+X2LcAGj5
750655
+7J+tGYGTLzKJmeCeGVNN5ZtJ0T85MYHCKQk1/FElK+Kq5akovXffQHjlnCPcx0NJ
750655
+47NBjlPaFp2gjnAChn79bT4iCjOFZ9avWpqRpeU517UCnY7djOr3fuod/MSQyh3L
750655
+Wuem1tWBAgMBAAECggEBAM4ZXQRs6Kjmo95BHGiAEnSqrlgX+dycjcBq3QPh8KZT
750655
+nifqnf48XhnackENy7tWIjr3DctoUq4mOp8AHt77ijhqfaa4XSg7fwKeK9NLBGC5
750655
+lAXNtAey0o2894/sKrd+LMkgphoYIUnuI4LRaGV56potkj/ZDP/GwTcG/R4SDnTn
750655
+C1Nb05PNTAPQtPZrgPo7TdM6gGsTnFbVrYHQLyg2Sq/osHfF15YohB01esRLCAwb
750655
+EF8JkRC4hWIZoV7BsyQ39232zAJQGGla7+wKFs3kObwh3VnFkQpT94KZnNiZuEfG
750655
+x5pW4Pn3gXgNsftscXsaNe/M9mYZqo//Qw7NvUIvAvECgYEA9AVveyK0HOA06fhh
750655
++3hUWdvw7Pbrl+e06jO9+bT1RjQMbHKyI60DZyVGuAySN86iChJRoJr5c6xj+iXU
750655
+cR6BVJDjGH5t1tyiK2aYf6hEpK9/j8Z54UiVQ486zPP0PGfT2TO4lBLK+8AUmoaH
750655
+gk21ul8QeVCeCJa/o+xEoRFvzcUCgYEA8FCbbvInrUtNY+9eKaUYoNodsgBVjm5X
750655
+I0YPUL9D4d+1nvupHSV2NVmQl0w1RaJwrNTafrl5LkqjhQbmuWNta6QgfZzSA3LB
750655
+lWXo1Mm0azKdcD3qMGbvn0Q3zU+yGNEgmB/Yju3/NtgYRG6tc+FCWRbPbiCnZWT8
750655
+v3C2Y0XggI0CgYEA2/jCZBgGkTkzue5kNVJlh5OS/aog+pCvL6hxCtarfBuTT3ed
750655
+Sje+p46cz3DVpmUpATc+Si8py7KNdYQAm/BJ2be6X+woi9Xcgo87zWgcaPCjZzId
750655
+0I2jsIE/Gl6XvpRCDrxnGWRPgt3GNP4szbPLrDPiH9oie8+Y9eYYf7G+PZkCgYEA
750655
+nRSzZOPYV4f/QDF4pVQLMykfe/iH9B/fyWjEHg3He19VQmRReIHCMMEoqBziPXAe
750655
+onpHj8oAkeer1wpZyhhZr6CKtFDLXgGm09bXSC/IRMHC81klORovyzU2HHfZfCtG
750655
+WOmIDnU2+0xpIGIP8sztJ3qnf97MTJSkOSadsWo9gwkCgYEAh5AQmJQmck88Dff2
750655
+qIfJIX8d+BDw47BFJ89OmMFjGV8TNB+JO+AV4Vkodg4hxKpLqTFZTTUFgoYfy5u1
750655
+1/BhAjpmCDCrzubCFhx+8VEoM2+2+MmnuQoMAm9+/mD/IidwRaARgXgvEmp7sfdt
750655
+RyWd+p2lYvFkC/jORQtDMY4uW1o=
750655
+-----END PRIVATE KEY-----
750655
+Bag Attributes
750655
+    localKeyID: 02 00 00 00
750655
+    Microsoft CSP Name: Microsoft Strong Cryptographic Provider
750655
+Key Attributes
750655
+    X509v3 Key Usage: 10
750655
+-----BEGIN PRIVATE KEY-----
750655
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlQhPrZwVQYFV4
750655
+FBc0H1iTXYaznMpwZvEITKtXWACzTdguUderEVOkXW3HTi5HvC2rMayt0nqo3zcd
750655
+x1eGiqdjpZQ/wMrkz9wNEM/nNMsXntEwxk0jCVNKB/jz6vf+BOtrSI01SritAGZW
750655
+dpKoTUyztT8C2mA3X6D8g3m4Dd07ltnzxaDqAQIU5jBHh3f/Q14tlPNZWUIiqVTC
750655
+gDxgAe7MDmfs9h3CInTBX1XM5J4UsLTL23/padgeSvP5YF5qr1+0c7Tdftxr2lwA
750655
+N3rLkisf5EiLAToVyJJlgP/exo2I8DaIKe7DZzD3Y1CrurOpkcMKYu5kM1Htlbua
750655
+tDkAa2oDAgMBAAECggEAOvdueS9DyiMlCKAeQb1IQosdQOh0l0ma+FgEABC2CWhd
750655
+0LgjQTBRM6cGO+urcq7/jhdWQ1UuUG4tVn71z7itCi/F/Enhxc2C22d2GhFVpWsn
750655
+giSXJYpZ/mIjkdVfWNo6FRuRmmHwMys1p0qTOS+8qUJWhSzW75csqJZGgeUrAI61
750655
+LBV5F0SGR7dR2xZfy7PeDs9xpD0QivDt5DpsZWPaPvw4QlhdLgw6/YU1h9vtm6ci
750655
+xLjnPRLZ7JMpcQHO8dUDl6FiEI7yQ11BDm253VQAVMddYRPQABn7SpEF8kD/aZVh
750655
+2Clvz61Rz80SKjPUthMPLWMCRp7zB0xDMzt3/1i+tQKBgQD6Ar1/oD3eFnRnpi4u
750655
+n/hdHJtMuXWNfUA4dspNjP6WGOid9sgIeUUdif1XyVJ+afITzvgpWc7nUWIqG2bQ
750655
+WxJ/4q2rjUdvjNXTy1voVungR2jD5WLQ9DKeaTR0yCliWlx4JgdPG7qGI5MMwsr+
750655
+R/PUoUUhGeEX+o/sCSieO3iUrQKBgQDqwBEMvIdhAv/CK2sG3fsKYX8rFT55ZNX3
750655
+Tix9DbUGY3wQColNuI8U1nDlxE9U6VOfT9RPqKelBLCgbzB23kdEJnjSlnqlTxrx
750655
+E+Hkndyf2ckdJAR3XNxoQ6SRLJNBsgoBj/z5tlfZE9/Jc+uh0mYy3e6g6XCVPBcz
750655
+MgoIc+ofbwKBgQCGQhZ1hR30N+bHCozeaPW9OvGDIE0qcEqeh9xYDRFilXnF6pK9
750655
+SjJ9jG7KR8jPLiHb1VebDSl5O1EV/6UU2vNyTc6pw7LLCryBgkGW4aWy1WZDXNnW
750655
+EG1meGS9GghvUss5kmJ2bxOZmV0Mi0brisQ8OWagQf+JGvtS7BAt+Q3l+QKBgAb9
750655
+8YQPmXiqPjPqVyW9Ntz4SnFeEJ5NApJ7IZgX8GxgSjGwHqbR+HEGchZl4ncE/Bii
750655
+qBA3Vcb0fM5KgYcI19aPzsl28fA6ivLjRLcqfIfGVNcpW3iyq13vpdctHLW4N9QU
750655
+FdTaOYOds+ysJziKq8CYG6NvUIshXw+HTgUybqbBAoGBAIIOqcmmtgOClAwipA17
750655
+dAHsI9Sjk+J0+d4JU6o+5TsmhUfUKIjXf5+xqJkJcQZMEe5GhxcCuYkgFicvh4Hz
750655
+kv2H/EU35LcJTqC6KTKZOWIbGcn1cqsvwm3GQJffYDiO8fRZSwCaif2J3F2lfH4Y
750655
+R/fA67HXFSTT+OncdRpY1NOn
750655
+-----END PRIVATE KEY-----
750655
+Bag Attributes: <Empty Attributes>
750655
+subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US
750655
+issuer=/CN=Root Agency
750655
+-----BEGIN CERTIFICATE-----
750655
+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
750655
+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
750655
+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
750655
+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
750655
+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIlPjJXzrRih4C
750655
+k/XsoI01oqo7IUxH3dA2F7vHGXQoIpKCp8Qe6Z6cFfdD8Uj+s+B1BX6hngwzIwjN
750655
+jE/23X3SALVzJVWzX4Y/IEjbgsuao6sOyNyB18wIU9YzZkVGj68fmMlUw3LnhPbe
750655
+eWkufZaJCaLyhQOwlRMbOcn48D6Ys8fccOyXNzpq3rH1OzeQpxS2M8zaJYP4/VZ/
750655
+sf6KRpI7bP+QwyFvNKfhcaO9/gj4kMo9lVGjvDU20FW6g8UVNJCV9N4GO6mOcyqo
750655
+OhuhVfjCNGgW7N1qi0TIVn0/MQM4l4dcT2R7Z/bV9fhMJLjGsy5A4TLAdRrhKUHT
750655
+bzi9HyDvAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
750655
+-----END CERTIFICATE-----
750655
+Bag Attributes
750655
+    localKeyID: 01 00 00 00
750655
+subject=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com
750655
+issuer=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com
750655
+-----BEGIN CERTIFICATE-----
750655
+MIID7TCCAtWgAwIBAgIJALQS3yMg3R41MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD
750655
+VQQGEwJVUzETMBEGA1UECAwKV0FTSElOR1RPTjEQMA4GA1UEBwwHU2VhdHRsZTES
750655
+MBAGA1UECgwJTWljcm9zb2Z0MQ4wDAYDVQQLDAVBenVyZTEOMAwGA1UEAwwFQW5o
750655
+Vm8xIjAgBgkqhkiG9w0BCQEWE2FuaHZvQG1pY3Jvc29mdC5jb20wHhcNMTkwMjE0
750655
+MjMxMjQwWhcNMjExMTEwMjMxMjQwWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgM
750655
+CldBU0hJTkdUT04xEDAOBgNVBAcMB1NlYXR0bGUxEjAQBgNVBAoMCU1pY3Jvc29m
750655
+dDEOMAwGA1UECwwFQXp1cmUxDjAMBgNVBAMMBUFuaFZvMSIwIAYJKoZIhvcNAQkB
750655
+FhNhbmh2b0BtaWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
750655
+CgKCAQEA5RHuX1KsHa0Ez1tqFZRitn99av/FC0/Cpjk8lLy9ET3SUOvb7xznOUNg
750655
+ioNnr4hwti1oRfFpqZ2Y+utUS9BnhRos8L1PDKHm8Oad6KnXIKBqR1bcX/pq+EHF
750655
+hefPxcHs9HQOIXLIplTQc3jaQQVCCjQTEwRvuHeoXEAVABilHRkXAhhMk6ly1NDI
750655
+B48QvSYFaliZfl9i3ABo+eyfrRmBky8yiZngnhlTTeWbSdE/OTGBwikJNfxRJSvi
750655
+quWpKL1330B45Zwj3MdDSeOzQY5T2hadoI5wAoZ+/W0+IgozhWfWr1qakaXlOde1
750655
+Ap2O3Yzq937qHfzEkMody1rnptbVgQIDAQABo1AwTjAdBgNVHQ4EFgQUPvdgLiv3
750655
+pAk4r0QTPZU3PFOZJvgwHwYDVR0jBBgwFoAUPvdgLiv3pAk4r0QTPZU3PFOZJvgw
750655
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVUHZT+h9+uCPLTEl5IDg
750655
+kqd9WpzXA7PJd/V+7DeDDTkEd06FIKTWZLfxLVVDjQJnQqubQb//e0zGu1qKbXnX
750655
+R7xqWabGU4eyPeUFWddmt1OHhxKLU3HbJNJJdL6XKiQtpGGUQt/mqNQ/DEr6hhNF
750655
+im5I79iA8H/dXA2gyZrj5Rxea4mtsaYO0mfp1NrFtJpAh2Djy4B1lBXBIv4DWG9e
750655
+mMEwzcLCOZj2cOMA6+mdLMUjYCvIRtnn5MKUHyZX5EmX79wsqMTvVpddlVLB9Kgz
750655
+Qnvft9+SBWh9+F3ip7BsL6Q4Q9v8eHRbnP0ya7ddlgh64uwf9VOfZZdKCnwqudJP
750655
+3g==
750655
+-----END CERTIFICATE-----
750655
+Bag Attributes
750655
+    localKeyID: 02 00 00 00
750655
+subject=/CN=/subscriptions/redacted/resourcegroups/redacted/providers/Microsoft.Compute/virtualMachines/redacted
750655
+issuer=/CN=Microsoft.ManagedIdentity
750655
+-----BEGIN CERTIFICATE-----
750655
+MIIDnTCCAoWgAwIBAgIUB2lauSRccvFkoJybUfIwOUqBN7MwDQYJKoZIhvcNAQEL
750655
+BQAwJDEiMCAGA1UEAxMZTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eTAeFw0xOTAy
750655
+MTUxOTA5MDBaFw0xOTA4MTQxOTA5MDBaMIGUMYGRMIGOBgNVBAMTgYYvc3Vic2Ny
750655
+aXB0aW9ucy8yN2I3NTBjZC1lZDQzLTQyZmQtOTA0NC04ZDc1ZTEyNGFlNTUvcmVz
750655
+b3VyY2Vncm91cHMvYW5oZXh0cmFzc2gvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21w
750655
+dXRlL3ZpcnR1YWxNYWNoaW5lcy9hbmh0ZXN0Y2VydDCCASIwDQYJKoZIhvcNAQEB
750655
+BQADggEPADCCAQoCggEBAOVCE+tnBVBgVXgUFzQfWJNdhrOcynBm8QhMq1dYALNN
750655
+2C5R16sRU6RdbcdOLke8LasxrK3SeqjfNx3HV4aKp2OllD/AyuTP3A0Qz+c0yxee
750655
+0TDGTSMJU0oH+PPq9/4E62tIjTVKuK0AZlZ2kqhNTLO1PwLaYDdfoPyDebgN3TuW
750655
+2fPFoOoBAhTmMEeHd/9DXi2U81lZQiKpVMKAPGAB7swOZ+z2HcIidMFfVczknhSw
750655
+tMvbf+lp2B5K8/lgXmqvX7RztN1+3GvaXAA3esuSKx/kSIsBOhXIkmWA/97GjYjw
750655
+Nogp7sNnMPdjUKu6s6mRwwpi7mQzUe2Vu5q0OQBragMCAwEAAaNWMFQwDgYDVR0P
750655
+AQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYD
750655
+VR0jBBgwFoAUOJvzEsriQWdJBndPrK+Me1bCPjYwDQYJKoZIhvcNAQELBQADggEB
750655
+AFGP/g8o7Hv/to11M0UqfzJuW/AyH9RZtSRcNQFLZUndwweQ6fap8lFsA4REUdqe
750655
+7Quqp5JNNY1XzKLWXMPoheIDH1A8FFXdsAroArzlNs9tO3TlIHE8A7HxEVZEmR4b
750655
+7ZiixmkQPS2RkjEoV/GM6fheBrzuFn7X5kVZyE6cC5sfcebn8xhk3ZcXI0VmpdT0
750655
+jFBsf5IvFCIXXLLhJI4KXc8VMoKFU1jT9na/jyaoGmfwovKj4ib8s2aiXGAp7Y38
750655
+UCmY+bJapWom6Piy5Jzi/p/kzMVdJcSa+GqpuFxBoQYEVs2XYVl7cGu/wPM+NToC
750655
+pkSoWwF1QAnHn0eokR9E1rU=
750655
+-----END CERTIFICATE-----
750655
+Bag Attributes: <Empty Attributes>
750655
+subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US
750655
+issuer=/CN=Root Agency
750655
+-----BEGIN CERTIFICATE-----
750655
+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
750655
+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
750655
+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
750655
+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
750655
+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb
750655
+Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi
750655
+nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW
750655
+vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+
750655
+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y
750655
+WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7
750655
+t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
750655
+-----END CERTIFICATE-----
750655
diff --git a/tests/data/azure/pubkey_extract_cert b/tests/data/azure/pubkey_extract_cert
750655
new file mode 100644
750655
index 0000000..ce9b852
750655
--- /dev/null
750655
+++ b/tests/data/azure/pubkey_extract_cert
750655
@@ -0,0 +1,13 @@
750655
+-----BEGIN CERTIFICATE-----
750655
+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
750655
+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
750655
+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
750655
+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
750655
+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb
750655
+Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi
750655
+nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW
750655
+vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+
750655
+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y
750655
+WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7
750655
+t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
750655
+-----END CERTIFICATE-----
750655
diff --git a/tests/data/azure/pubkey_extract_ssh_key b/tests/data/azure/pubkey_extract_ssh_key
750655
new file mode 100644
750655
index 0000000..54d749e
750655
--- /dev/null
750655
+++ b/tests/data/azure/pubkey_extract_ssh_key
750655
@@ -0,0 +1 @@
750655
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHU9IDclbKVYVbYuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoinlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmWvwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4yWzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7t5btUyvp
750655
diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
750655
index b42b073..5eb26eb 100644
750655
--- a/tests/unittests/test_datasource/test_azure_helper.py
750655
+++ b/tests/unittests/test_datasource/test_azure_helper.py
750655
@@ -1,11 +1,13 @@
750655
 # This file is part of cloud-init. See LICENSE file for license information.
750655
 
750655
 import os
750655
+import unittest2
750655
 from textwrap import dedent
750655
 
750655
 from cloudinit.sources.helpers import azure as azure_helper
750655
 from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir
750655
 
750655
+from cloudinit.util import load_file
750655
 from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim
750655
 
750655
 GOAL_STATE_TEMPLATE = """\
750655
@@ -287,6 +289,50 @@ class TestOpenSSLManager(CiTestCase):
750655
         self.assertEqual([mock.call(manager.tmpdir)], del_dir.call_args_list)
750655
 
750655
 
750655
+class TestOpenSSLManagerActions(CiTestCase):
750655
+
750655
+    def setUp(self):
750655
+        super(TestOpenSSLManagerActions, self).setUp()
750655
+
750655
+        self.allowed_subp = True
750655
+
750655
+    def _data_file(self, name):
750655
+        path = 'tests/data/azure'
750655
+        return os.path.join(path, name)
750655
+
750655
+    @unittest2.skip("todo move to cloud_test")
750655
+    def test_pubkey_extract(self):
750655
+        cert = load_file(self._data_file('pubkey_extract_cert'))
750655
+        good_key = load_file(self._data_file('pubkey_extract_ssh_key'))
750655
+        sslmgr = azure_helper.OpenSSLManager()
750655
+        key = sslmgr._get_ssh_key_from_cert(cert)
750655
+        self.assertEqual(good_key, key)
750655
+
750655
+        good_fingerprint = '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473'
750655
+        fingerprint = sslmgr._get_fingerprint_from_cert(cert)
750655
+        self.assertEqual(good_fingerprint, fingerprint)
750655
+
750655
+    @unittest2.skip("todo move to cloud_test")
750655
+    @mock.patch.object(azure_helper.OpenSSLManager, '_decrypt_certs_from_xml')
750655
+    def test_parse_certificates(self, mock_decrypt_certs):
750655
+        """Azure control plane puts private keys as well as certificates
750655
+           into the Certificates XML object. Make sure only the public keys
750655
+           from certs are extracted and that fingerprints are converted to
750655
+           the form specified in the ovf-env.xml file.
750655
+        """
750655
+        cert_contents = load_file(self._data_file('parse_certificates_pem'))
750655
+        fingerprints = load_file(self._data_file(
750655
+            'parse_certificates_fingerprints')
750655
+        ).splitlines()
750655
+        mock_decrypt_certs.return_value = cert_contents
750655
+        sslmgr = azure_helper.OpenSSLManager()
750655
+        keys_by_fp = sslmgr.parse_certificates('')
750655
+        for fp in keys_by_fp.keys():
750655
+            self.assertIn(fp, fingerprints)
750655
+        for fp in fingerprints:
750655
+            self.assertIn(fp, keys_by_fp)
750655
+
750655
+
750655
 class TestWALinuxAgentShim(CiTestCase):
750655
 
750655
     def setUp(self):
750655
@@ -327,18 +373,31 @@ class TestWALinuxAgentShim(CiTestCase):
750655
 
750655
     def test_certificates_used_to_determine_public_keys(self):
750655
         shim = wa_shim()
750655
-        data = shim.register_with_azure_and_fetch_data()
750655
+        """if register_with_azure_and_fetch_data() isn't passed some info about
750655
+           the user's public keys, there's no point in even trying to parse
750655
+           the certificates
750655
+        """
750655
+        mypk = [{'fingerprint': 'fp1', 'path': 'path1'},
750655
+                {'fingerprint': 'fp3', 'path': 'path3', 'value': ''}]
750655
+        certs = {'fp1': 'expected-key',
750655
+                 'fp2': 'should-not-be-found',
750655
+                 'fp3': 'expected-no-value-key',
750655
+                 }
750655
+        sslmgr = self.OpenSSLManager.return_value
750655
+        sslmgr.parse_certificates.return_value = certs
750655
+        data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk)
750655
         self.assertEqual(
750655
             [mock.call(self.GoalState.return_value.certificates_xml)],
750655
-            self.OpenSSLManager.return_value.parse_certificates.call_args_list)
750655
-        self.assertEqual(
750655
-            self.OpenSSLManager.return_value.parse_certificates.return_value,
750655
-            data['public-keys'])
750655
+            sslmgr.parse_certificates.call_args_list)
750655
+        self.assertIn('expected-key', data['public-keys'])
750655
+        self.assertIn('expected-no-value-key', data['public-keys'])
750655
+        self.assertNotIn('should-not-be-found', data['public-keys'])
750655
 
750655
     def test_absent_certificates_produces_empty_public_keys(self):
750655
+        mypk = [{'fingerprint': 'fp1', 'path': 'path1'}]
750655
         self.GoalState.return_value.certificates_xml = None
750655
         shim = wa_shim()
750655
-        data = shim.register_with_azure_and_fetch_data()
750655
+        data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk)
750655
         self.assertEqual([], data['public-keys'])
750655
 
750655
     def test_correct_url_used_for_report_ready(self):
750655
-- 
750655
1.8.3.1
750655