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