Merge ~jasonzio/cloud-init:fingerprint into cloud-init:master

Proposed by Jason Zions on 2019-02-20
Status: Merged
Approved by: Chad Smith on 2019-02-22
Approved revision: 2009ec145e03122794756406b3944665a1c643bf
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~jasonzio/cloud-init:fingerprint
Merge into: cloud-init:master
Diff against target: 516 lines (+322/-41)
7 files modified
cloudinit/sources/DataSourceAzure.py (+9/-4)
cloudinit/sources/helpers/azure.py (+78/-31)
tests/data/azure/parse_certificates_fingerprints (+4/-0)
tests/data/azure/parse_certificates_pem (+152/-0)
tests/data/azure/pubkey_extract_cert (+13/-0)
tests/data/azure/pubkey_extract_ssh_key (+1/-0)
tests/unittests/test_datasource/test_azure_helper.py (+65/-6)
Reviewer Review Type Date Requested Status
Chad Smith Approve on 2019-02-22
Server Team CI bot continuous-integration Approve on 2019-02-22
Ryan Harper 2019-02-20 Needs Fixing on 2019-02-21
Review via email: mp+363445@code.launchpad.net

Commit message

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.

Description of the change

This addresses a bug filed on 2019-01-10 titled
"additional key added to ssh authorized_keys on azure"
All I have is a fuzzy screen shot of the
marked-as-private bug so I can't read the bug number.
Steps to reproduce the problem (and thus how to test
this fix works) are given in that bug.

To post a comment you must log in.

FAILED: Continuous integration, rev:c3721d01651eed9e9be921c52f003056b34c2530
https://jenkins.ubuntu.com/server/job/cloud-init-ci/569/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/569/rebuild

review: Needs Fixing (continuous-integration)
~jasonzio/cloud-init:fingerprint updated on 2019-02-20
8170777... by "Jason Zions \(MSFT\)" <email address hidden> on 2019-02-20

Fix pycodestyle issues

FAILED: Continuous integration, rev:8170777c3a33383b692151d06a746054375523fc
https://jenkins.ubuntu.com/server/job/cloud-init-ci/570/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/570/rebuild

review: Needs Fixing (continuous-integration)
~jasonzio/cloud-init:fingerprint updated on 2019-02-20
d2cf5ab... by "Jason Zions \(MSFT\)" <email address hidden> on 2019-02-20

Redact sample Certificates data

03f8214... by "Jason Zions \(MSFT\)" <email address hidden> on 2019-02-20

Address pycodestyle again

FAILED: Continuous integration, rev:d2cf5ab9ef89dcc0029afbaeec5ee2b30527dac4
https://jenkins.ubuntu.com/server/job/cloud-init-ci/571/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    FAILED: Ubuntu LTS: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/571/rebuild

review: Needs Fixing (continuous-integration)
Jason Zions (jasonzio) wrote :

> FAILED: Continuous integration, rev:d2cf5ab9ef89dcc0029afbaeec5ee2b30527dac4
> https://jenkins.ubuntu.com/server/job/cloud-init-ci/571/
> Executed test runs:
> SUCCESS: Checkout
> SUCCESS: Unit & Style Tests
> FAILED: Ubuntu LTS: Build
>
> Click here to trigger a rebuild:
> https://jenkins.ubuntu.com/server/job/cloud-init-ci/571/rebuild

This appears to be a deficiency in the container within which the "Ubuntu LTS: Build" phase is performed; the ssh-keygen command is missing. The Azure data source in cloud-init has relied on this command for quite some time; there were just no tests that actually ran the command. This merge adds several tests that confirm the code uses that command correctly to get the desired results.

FAILED: Continuous integration, rev:03f821421e62691c0f5384893bb3cbb4e288086c
https://jenkins.ubuntu.com/server/job/cloud-init-ci/572/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    FAILED: Ubuntu LTS: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/572/rebuild

review: Needs Fixing (continuous-integration)
Ryan Harper (raharper) wrote :

I've added a few comments in-line.

review: Needs Fixing
Chad Smith (chad.smith) wrote :

Thanks for the branch Jason,
 I was starting to take a cut at that mock to try to lift some of that work off your shoulders but only got a bit through the branch. Wanted to send this your way by way of informing what we could to to avoid leaking calls onto the underlying test system.

See what you think. Thanks
https://pastebin.ubuntu.com/p/gDDVC27RyZ/

Jason Zions (jasonzio) wrote :

Thanks for the general technique you like to see; I'll add that to the mental toolkit.

I wrote raharper separately about this issue; he suggested that cloud_test was the right environment for these specific tests. My goal with these unit tests was to validate the action of taking a certificate generated by the Azure control plane and extract from it a public key and fingerprint, and then use that public key to generate a matching SSH key. That validation requires using a real cert and comparing the actual results to the expected fingerprint and ssh key. Mocking the results that come back from the openssl tools doesn't achieve the goal.

Do the tests intended for cloud_test live in a separate location from the unittests, or are they marked or flagged so as to run only in that environment?

To move forward expeditiously with this merge, though, the fastest approach is for me to either disable the tests or delete them. There's no point in mocking out the subp call; there are other tests that do that.

Jason
________________________________
From: <email address hidden> <email address hidden> on behalf of Chad Smith <email address hidden>
Sent: Thursday, February 21, 2019 10:03
To: <email address hidden>
Subject: Re: [Merge] ~jasonzio/cloud-init:fingerprint into cloud-init:master

Thanks for the branch Jason,
 I was starting to take a cut at that mock to try to lift some of that work off your shoulders but only got a bit through the branch. Wanted to send this your way by way of informing what we could to to avoid leaking calls onto the underlying test system.

See what you think. Thanks
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fpastebin.ubuntu.com%2Fp%2FgDDVC27RyZ%2F&amp;data=02%7C01%7Cjason.zions%40microsoft.com%7C3a349ee8e485477791a708d69826de16%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636863690151634963&amp;sdata=K5GVgzxTEjhfGiSnM68Y8Sbd5CSKXQjDLt9byJvgo0Y%3D&amp;reserved=0
--
https://nam06.safelinks.protection.outlook.com/?url=https:%2F%2Fcode.launchpad.net%2F~jasonzio%2Fcloud-init%2F%2Bgit%2Fcloud-init%2F%2Bmerge%2F363445&amp;data=02%7C01%7Cjason.zions%40microsoft.com%7C3a349ee8e485477791a708d69826de16%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636863690151634963&amp;sdata=M%2FvX0Yh1NJVF4g4jbVdj%2BSQNGwOoRUqfengO4xAc4f4%3D&amp;reserved=0
You are the owner of ~jasonzio/cloud-init:fingerprint.

Chad Smith (chad.smith) wrote :

Thanks for the response Jason,

> Do the tests intended for cloud_test live in a separate location from the unittests, or are they marked or flagged so as to run only in that environment?

cloud-init/tests/cloud_tests is the location Ryan was referring to instead of something under tests/unittests. We have individual integration tests all of which are run against a given platform. Integration test documentation is at https://cloudinit.readthedocs.io/en/latest/topics/tests.html

It feels like you might be able to create an integration test that exercises some of this logic directly by adding an integration test under tests/cloud_tests/testcases/bugs/lp1811265.(yaml|py).

You'd need a yaml file which can feed #cloud-config and collect_scripts directives into an instance under test and use collect_scripts to grab any response content you expected to see when processing certs with azure_helper.OpenSSLManager. The python file is used to process the collected_script results and report failure conditions for unexpected content/results.

Additionally you could limit that integration test to only run on a specific platform by raising a SkipTest from the python test module for your specific test case.

        if self.platform != 'lxd':
            raise SkipTest(
                'Skipping lxd instance-data.json on %s' % self.platform)

> To move forward expeditiously with this merge, though, the fastest approach is for me to either disable the tests or delete them. There's no point in mocking out the subp call; there are other tests that do that.

I'd agree that integration tests probably have more value here than the mocked subp calls, but there is still value in exercising some of the interfaces called between your methods and to validate the contained logic. I'm all for simplifying or reducing what your unittests are asserting. If you are able to put an integration test together, I'd wouldn't also be less concerned your unittest coverage.

Cloud-init's unittests are run not just for continuous integration, but also during package builds in restricted environments which do not come with some runtime dependencies like openssl etc. On the other hand, our integration tests are run on "complete" systems across a variety of platforms (instead of just our build/CI environment) and exercise those external dependencies directly on a variety of platforms and distros.

Jason Zions (jasonzio) wrote :
Download full text (4.2 KiB)

I added other tests that exercise the interfaces between the methods; the tests that actually try to use openssl go beyond them. I'll disable these tests for now and, in a future merge, move them into cloud_test where they can do their thing. I have an objective for the first half of 2019 to improve the cloud-meets-distro testing of cloud-init, so it'll be easy to get the time I need to do it right.

Thanks for all the explanation. I don't suppose there's a doc anyplace that I was supposed to read that explained this stuff; although I'd be embarrassed at jumping in without doing the reading, I'd certainly go back and read it now.

On a related thought: would it be acceptable to pivot to using pyOpenSSL or even pyca/cryptography as a replacement for subp() invocation of command line tools? Not in this merge, but some time in the next few months?

Jason

-----Original Message-----
From: <email address hidden> <email address hidden> On Behalf Of Chad Smith
Sent: Thursday, February 21, 2019 1:42 PM
To: <email address hidden>
Subject: Re: [Merge] ~jasonzio/cloud-init:fingerprint into cloud-init:master

Thanks for the response Jason,

> Do the tests intended for cloud_test live in a separate location from the unittests, or are they marked or flagged so as to run only in that environment?

cloud-init/tests/cloud_tests is the location Ryan was referring to instead of something under tests/unittests. We have individual integration tests all of which are run against a given platform. Integration test documentation is at https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fcloudinit.readthedocs.io%2Fen%2Flatest%2Ftopics%2Ftests.html&amp;data=02%7C01%7Cjason.zions%40microsoft.com%7Cb9f00b35607f449fed3e08d698456ae4%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636863821245995729&amp;sdata=qLHKaOO5%2F29UD2uBYodEw7bqvlHCQ5PNU7Sh7H9W%2Bx0%3D&amp;reserved=0

It feels like you might be able to create an integration test that exercises some of this logic directly by adding an integration test under tests/cloud_tests/testcases/bugs/lp1811265.(yaml|py).

You'd need a yaml file which can feed #cloud-config and collect_scripts directives into an instance under test and use collect_scripts to grab any response content you expected to see when processing certs with azure_helper.OpenSSLManager. The python file is used to process the collected_script results and report failure conditions for unexpected content/results.

Additionally you could limit that integration test to only run on a specific platform by raising a SkipTest from the python test module for your specific test case.

        if self.platform != 'lxd':
            raise SkipTest(
                'Skipping lxd instance-data.json on %s' % self.platform)

> To move forward expeditiously with this merge, though, the fastest approach is for me to either disable the tests or delete them. There's no point in mocking out the subp call; there are other tests that do that.

I'd agree that integration tests probably have more value here than the mocked subp calls, but there is still value in exercising some of the interfaces called between your methods and to...

Read more...

~jasonzio/cloud-init:fingerprint updated on 2019-02-22
8bf9c9c... by "Jason Zions \(MSFT\)" <email address hidden> on 2019-02-22

Disable tests that validate openssl cmds

FAILED: Continuous integration, rev:8bf9c9c9bef084969826ef423eb928750ec8a6f1
https://jenkins.ubuntu.com/server/job/cloud-init-ci/582/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/582/rebuild

review: Needs Fixing (continuous-integration)
~jasonzio/cloud-init:fingerprint updated on 2019-02-22
b2a0e63... by "Jason Zions \(MSFT\)" <email address hidden> on 2019-02-22

Extract test data into raw data files

cea6c04... by "Jason Zions \(MSFT\)" <email address hidden> on 2019-02-22

Don't annotate to skip tests, use raise instead

FAILED: Continuous integration, rev:b2a0e6312decc113d621fbc60430f9ee9f135357
https://jenkins.ubuntu.com/server/job/cloud-init-ci/583/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/583/rebuild

review: Needs Fixing (continuous-integration)
Jason Zions (jasonzio) wrote :

I think this addresses your feedback.

FAILED: Continuous integration, rev:cea6c04fcd9f53cfe0d4f5937f69c442e5607825
https://jenkins.ubuntu.com/server/job/cloud-init-ci/584/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/584/rebuild

review: Needs Fixing (continuous-integration)
~jasonzio/cloud-init:fingerprint updated on 2019-02-22
0c13e35... by "Jason Zions \(MSFT\)" <email address hidden> on 2019-02-22

Use proper decorator to skip tests

PASSED: Continuous integration, rev:0c13e353ff1631f9465732535ef64754cffee03c
https://jenkins.ubuntu.com/server/job/cloud-init-ci/585/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/585/rebuild

review: Approve (continuous-integration)
~jasonzio/cloud-init:fingerprint updated on 2019-02-22
2009ec1... by "Jason Zions \(MSFT\)" <email address hidden> on 2019-02-22

Make get_metadata_from_fabric mock return dict with array

PASSED: Continuous integration, rev:2009ec145e03122794756406b3944665a1c643bf
https://jenkins.ubuntu.com/server/job/cloud-init-ci/586/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/586/rebuild

review: Approve (continuous-integration)
Chad Smith (chad.smith) wrote :

Thanks so much for this rework Jason. From the looks of things it looks like you are properly filtering keys for the user. Let's look more at the integration tests in the future.

I ran a couple of upgrade tests on azure to validate instances that have been deployed without regression or adding unexpected keys.

review: Approve
Jason Zions (jasonzio) wrote :

Greatly appreciate the additional testing in Azure. We beat it up pretty thoroughly before I submitted, but every extra set of eyes is a big plus.

I'll definitely work with you on integration testing in the coming weeks.
________________________________
From: <email address hidden> <email address hidden> on behalf of Chad Smith <email address hidden>
Sent: Friday, February 22, 2019 02:01
To: <email address hidden>
Subject: Re: [Merge] ~jasonzio/cloud-init:fingerprint into cloud-init:master

Review: Approve

Thanks so much for this rework Jason. From the looks of things it looks like you are properly filtering keys for the user. Let's look more at the integration tests in the future.

I ran a couple of upgrade tests on azure to validate instances that have been deployed without regression or adding unexpected keys.
--
https://nam06.safelinks.protection.outlook.com/?url=https:%2F%2Fcode.launchpad.net%2F~jasonzio%2Fcloud-init%2F%2Bgit%2Fcloud-init%2F%2Bmerge%2F363445&amp;data=02%7C01%7Cjason.zions%40microsoft.com%7C4e832ffc6f6346dd0a0c08d698acbddd%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636864265029655888&amp;sdata=YePRgi4unfv9WBX5f6JiFwjDwk3fYKLA3SXEaYtlFC8%3D&amp;reserved=0
You are the owner of ~jasonzio/cloud-init:fingerprint.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
2index a4f998b..eccbee5 100644
3--- a/cloudinit/sources/DataSourceAzure.py
4+++ b/cloudinit/sources/DataSourceAzure.py
5@@ -627,9 +627,11 @@ class DataSourceAzure(sources.DataSource):
6 if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN:
7 self.bounce_network_with_azure_hostname()
8
9+ pubkey_info = self.cfg.get('_pubkeys', None)
10 metadata_func = partial(get_metadata_from_fabric,
11 fallback_lease_file=self.
12- dhclient_lease_file)
13+ dhclient_lease_file,
14+ pubkey_info=pubkey_info)
15 else:
16 metadata_func = self.get_metadata_from_agent
17
18@@ -642,6 +644,7 @@ class DataSourceAzure(sources.DataSource):
19 "Error communicating with Azure fabric; You may experience."
20 "connectivity issues.", exc_info=True)
21 return False
22+
23 util.del_file(REPORTED_READY_MARKER_FILE)
24 util.del_file(REPROVISION_MARKER_FILE)
25 return fabric_data
26@@ -909,13 +912,15 @@ def find_child(node, filter_func):
27 def load_azure_ovf_pubkeys(sshnode):
28 # This parses a 'SSH' node formatted like below, and returns
29 # an array of dicts.
30- # [{'fp': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7',
31- # 'path': 'where/to/go'}]
32+ # [{'fingerprint': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7',
33+ # 'path': '/where/to/go'}]
34 #
35 # <SSH><PublicKeys>
36- # <PublicKey><Fingerprint>ABC</FingerPrint><Path>/ABC</Path>
37+ # <PublicKey><Fingerprint>ABC</FingerPrint><Path>/x/y/z</Path>
38 # ...
39 # </PublicKeys></SSH>
40+ # Under some circumstances, there may be a <Value> element along with the
41+ # Fingerprint and Path. Pass those along if they appear.
42 results = find_child(sshnode, lambda n: n.localName == "PublicKeys")
43 if len(results) == 0:
44 return []
45diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
46index e5696b1..2829dd2 100644
47--- a/cloudinit/sources/helpers/azure.py
48+++ b/cloudinit/sources/helpers/azure.py
49@@ -138,9 +138,36 @@ class OpenSSLManager(object):
50 self.certificate = certificate
51 LOG.debug('New certificate generated.')
52
53- def parse_certificates(self, certificates_xml):
54- tag = ElementTree.fromstring(certificates_xml).find(
55- './/Data')
56+ @staticmethod
57+ def _run_x509_action(action, cert):
58+ cmd = ['openssl', 'x509', '-noout', action]
59+ result, _ = util.subp(cmd, data=cert)
60+ return result
61+
62+ def _get_ssh_key_from_cert(self, certificate):
63+ pub_key = self._run_x509_action('-pubkey', certificate)
64+ keygen_cmd = ['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin']
65+ ssh_key, _ = util.subp(keygen_cmd, data=pub_key)
66+ return ssh_key
67+
68+ def _get_fingerprint_from_cert(self, certificate):
69+ """openssl x509 formats fingerprints as so:
70+ 'SHA1 Fingerprint=07:3E:19:D1:4D:1C:79:92:24:C6:A0:FD:8D:DA:\
71+ B6:A8:BF:27:D4:73\n'
72+
73+ Azure control plane passes that fingerprint as so:
74+ '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473'
75+ """
76+ raw_fp = self._run_x509_action('-fingerprint', certificate)
77+ eq = raw_fp.find('=')
78+ octets = raw_fp[eq+1:-1].split(':')
79+ return ''.join(octets)
80+
81+ def _decrypt_certs_from_xml(self, certificates_xml):
82+ """Decrypt the certificates XML document using the our private key;
83+ return the list of certs and private keys contained in the doc.
84+ """
85+ tag = ElementTree.fromstring(certificates_xml).find('.//Data')
86 certificates_content = tag.text
87 lines = [
88 b'MIME-Version: 1.0',
89@@ -151,32 +178,30 @@ class OpenSSLManager(object):
90 certificates_content.encode('utf-8'),
91 ]
92 with cd(self.tmpdir):
93- with open('Certificates.p7m', 'wb') as f:
94- f.write(b'\n'.join(lines))
95 out, _ = util.subp(
96- 'openssl cms -decrypt -in Certificates.p7m -inkey'
97+ 'openssl cms -decrypt -in /dev/stdin -inkey'
98 ' {private_key} -recip {certificate} | openssl pkcs12 -nodes'
99 ' -password pass:'.format(**self.certificate_names),
100- shell=True)
101- private_keys, certificates = [], []
102+ shell=True, data=b'\n'.join(lines))
103+ return out
104+
105+ def parse_certificates(self, certificates_xml):
106+ """Given the Certificates XML document, return a dictionary of
107+ fingerprints and associated SSH keys derived from the certs."""
108+ out = self._decrypt_certs_from_xml(certificates_xml)
109 current = []
110+ keys = {}
111 for line in out.splitlines():
112 current.append(line)
113 if re.match(r'[-]+END .*?KEY[-]+$', line):
114- private_keys.append('\n'.join(current))
115+ # ignore private_keys
116 current = []
117 elif re.match(r'[-]+END .*?CERTIFICATE[-]+$', line):
118- certificates.append('\n'.join(current))
119+ certificate = '\n'.join(current)
120+ ssh_key = self._get_ssh_key_from_cert(certificate)
121+ fingerprint = self._get_fingerprint_from_cert(certificate)
122+ keys[fingerprint] = ssh_key
123 current = []
124- keys = []
125- for certificate in certificates:
126- with cd(self.tmpdir):
127- public_key, _ = util.subp(
128- 'openssl x509 -noout -pubkey |'
129- 'ssh-keygen -i -m PKCS8 -f /dev/stdin',
130- data=certificate,
131- shell=True)
132- keys.append(public_key)
133 return keys
134
135
136@@ -206,7 +231,6 @@ class WALinuxAgentShim(object):
137 self.dhcpoptions = dhcp_options
138 self._endpoint = None
139 self.openssl_manager = None
140- self.values = {}
141 self.lease_file = fallback_lease_file
142
143 def clean_up(self):
144@@ -328,8 +352,9 @@ class WALinuxAgentShim(object):
145 LOG.debug('Azure endpoint found at %s', endpoint_ip_address)
146 return endpoint_ip_address
147
148- def register_with_azure_and_fetch_data(self):
149- self.openssl_manager = OpenSSLManager()
150+ def register_with_azure_and_fetch_data(self, pubkey_info=None):
151+ if self.openssl_manager is None:
152+ self.openssl_manager = OpenSSLManager()
153 http_client = AzureEndpointHttpClient(self.openssl_manager.certificate)
154 LOG.info('Registering with Azure...')
155 attempts = 0
156@@ -347,16 +372,37 @@ class WALinuxAgentShim(object):
157 attempts += 1
158 LOG.debug('Successfully fetched GoalState XML.')
159 goal_state = GoalState(response.contents, http_client)
160- public_keys = []
161- if goal_state.certificates_xml is not None:
162+ ssh_keys = []
163+ if goal_state.certificates_xml is not None and pubkey_info is not None:
164 LOG.debug('Certificate XML found; parsing out public keys.')
165- public_keys = self.openssl_manager.parse_certificates(
166+ keys_by_fingerprint = self.openssl_manager.parse_certificates(
167 goal_state.certificates_xml)
168- data = {
169- 'public-keys': public_keys,
170- }
171+ ssh_keys = self._filter_pubkeys(keys_by_fingerprint, pubkey_info)
172 self._report_ready(goal_state, http_client)
173- return data
174+ return {'public-keys': ssh_keys}
175+
176+ def _filter_pubkeys(self, keys_by_fingerprint, pubkey_info):
177+ """cloud-init expects a straightforward array of keys to be dropped
178+ into the user's authorized_keys file. Azure control plane exposes
179+ multiple public keys to the VM via wireserver. Select just the
180+ user's key(s) and return them, ignoring any other certs.
181+ """
182+ keys = []
183+ for pubkey in pubkey_info:
184+ if 'value' in pubkey and pubkey['value']:
185+ keys.append(pubkey['value'])
186+ elif 'fingerprint' in pubkey and pubkey['fingerprint']:
187+ fingerprint = pubkey['fingerprint']
188+ if fingerprint in keys_by_fingerprint:
189+ keys.append(keys_by_fingerprint[fingerprint])
190+ else:
191+ LOG.warning("ovf-env.xml specified PublicKey fingerprint "
192+ "%s not found in goalstate XML", fingerprint)
193+ else:
194+ LOG.warning("ovf-env.xml specified PublicKey with neither "
195+ "value nor fingerprint: %s", pubkey)
196+
197+ return keys
198
199 def _report_ready(self, goal_state, http_client):
200 LOG.debug('Reporting ready to Azure fabric.')
201@@ -373,11 +419,12 @@ class WALinuxAgentShim(object):
202 LOG.info('Reported ready to Azure fabric.')
203
204
205-def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None):
206+def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None,
207+ pubkey_info=None):
208 shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file,
209 dhcp_options=dhcp_opts)
210 try:
211- return shim.register_with_azure_and_fetch_data()
212+ return shim.register_with_azure_and_fetch_data(pubkey_info=pubkey_info)
213 finally:
214 shim.clean_up()
215
216diff --git a/tests/data/azure/parse_certificates_fingerprints b/tests/data/azure/parse_certificates_fingerprints
217new file mode 100644
218index 0000000..f7293c5
219--- /dev/null
220+++ b/tests/data/azure/parse_certificates_fingerprints
221@@ -0,0 +1,4 @@
222+ECEDEB3B8488D31AF3BC4CCED493F64B7D27D7B1
223+073E19D14D1C799224C6A0FD8DDAB6A8BF27D473
224+4C16E7FAD6297D74A9B25EB8F0A12808CEBE293E
225+929130695289B450FE45DCD5F6EF0CDE69865867
226diff --git a/tests/data/azure/parse_certificates_pem b/tests/data/azure/parse_certificates_pem
227new file mode 100644
228index 0000000..3521ea3
229--- /dev/null
230+++ b/tests/data/azure/parse_certificates_pem
231@@ -0,0 +1,152 @@
232+Bag Attributes
233+ localKeyID: 01 00 00 00
234+ Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
235+Key Attributes
236+ X509v3 Key Usage: 10
237+-----BEGIN PRIVATE KEY-----
238+MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDlEe5fUqwdrQTP
239+W2oVlGK2f31q/8ULT8KmOTyUvL0RPdJQ69vvHOc5Q2CKg2eviHC2LWhF8WmpnZj6
240+61RL0GeFGizwvU8Moebw5p3oqdcgoGpHVtxf+mr4QcWF58/Fwez0dA4hcsimVNBz
241+eNpBBUIKNBMTBG+4d6hcQBUAGKUdGRcCGEyTqXLU0MgHjxC9JgVqWJl+X2LcAGj5
242+7J+tGYGTLzKJmeCeGVNN5ZtJ0T85MYHCKQk1/FElK+Kq5akovXffQHjlnCPcx0NJ
243+47NBjlPaFp2gjnAChn79bT4iCjOFZ9avWpqRpeU517UCnY7djOr3fuod/MSQyh3L
244+Wuem1tWBAgMBAAECggEBAM4ZXQRs6Kjmo95BHGiAEnSqrlgX+dycjcBq3QPh8KZT
245+nifqnf48XhnackENy7tWIjr3DctoUq4mOp8AHt77ijhqfaa4XSg7fwKeK9NLBGC5
246+lAXNtAey0o2894/sKrd+LMkgphoYIUnuI4LRaGV56potkj/ZDP/GwTcG/R4SDnTn
247+C1Nb05PNTAPQtPZrgPo7TdM6gGsTnFbVrYHQLyg2Sq/osHfF15YohB01esRLCAwb
248+EF8JkRC4hWIZoV7BsyQ39232zAJQGGla7+wKFs3kObwh3VnFkQpT94KZnNiZuEfG
249+x5pW4Pn3gXgNsftscXsaNe/M9mYZqo//Qw7NvUIvAvECgYEA9AVveyK0HOA06fhh
250++3hUWdvw7Pbrl+e06jO9+bT1RjQMbHKyI60DZyVGuAySN86iChJRoJr5c6xj+iXU
251+cR6BVJDjGH5t1tyiK2aYf6hEpK9/j8Z54UiVQ486zPP0PGfT2TO4lBLK+8AUmoaH
252+gk21ul8QeVCeCJa/o+xEoRFvzcUCgYEA8FCbbvInrUtNY+9eKaUYoNodsgBVjm5X
253+I0YPUL9D4d+1nvupHSV2NVmQl0w1RaJwrNTafrl5LkqjhQbmuWNta6QgfZzSA3LB
254+lWXo1Mm0azKdcD3qMGbvn0Q3zU+yGNEgmB/Yju3/NtgYRG6tc+FCWRbPbiCnZWT8
255+v3C2Y0XggI0CgYEA2/jCZBgGkTkzue5kNVJlh5OS/aog+pCvL6hxCtarfBuTT3ed
256+Sje+p46cz3DVpmUpATc+Si8py7KNdYQAm/BJ2be6X+woi9Xcgo87zWgcaPCjZzId
257+0I2jsIE/Gl6XvpRCDrxnGWRPgt3GNP4szbPLrDPiH9oie8+Y9eYYf7G+PZkCgYEA
258+nRSzZOPYV4f/QDF4pVQLMykfe/iH9B/fyWjEHg3He19VQmRReIHCMMEoqBziPXAe
259+onpHj8oAkeer1wpZyhhZr6CKtFDLXgGm09bXSC/IRMHC81klORovyzU2HHfZfCtG
260+WOmIDnU2+0xpIGIP8sztJ3qnf97MTJSkOSadsWo9gwkCgYEAh5AQmJQmck88Dff2
261+qIfJIX8d+BDw47BFJ89OmMFjGV8TNB+JO+AV4Vkodg4hxKpLqTFZTTUFgoYfy5u1
262+1/BhAjpmCDCrzubCFhx+8VEoM2+2+MmnuQoMAm9+/mD/IidwRaARgXgvEmp7sfdt
263+RyWd+p2lYvFkC/jORQtDMY4uW1o=
264+-----END PRIVATE KEY-----
265+Bag Attributes
266+ localKeyID: 02 00 00 00
267+ Microsoft CSP Name: Microsoft Strong Cryptographic Provider
268+Key Attributes
269+ X509v3 Key Usage: 10
270+-----BEGIN PRIVATE KEY-----
271+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlQhPrZwVQYFV4
272+FBc0H1iTXYaznMpwZvEITKtXWACzTdguUderEVOkXW3HTi5HvC2rMayt0nqo3zcd
273+x1eGiqdjpZQ/wMrkz9wNEM/nNMsXntEwxk0jCVNKB/jz6vf+BOtrSI01SritAGZW
274+dpKoTUyztT8C2mA3X6D8g3m4Dd07ltnzxaDqAQIU5jBHh3f/Q14tlPNZWUIiqVTC
275+gDxgAe7MDmfs9h3CInTBX1XM5J4UsLTL23/padgeSvP5YF5qr1+0c7Tdftxr2lwA
276+N3rLkisf5EiLAToVyJJlgP/exo2I8DaIKe7DZzD3Y1CrurOpkcMKYu5kM1Htlbua
277+tDkAa2oDAgMBAAECggEAOvdueS9DyiMlCKAeQb1IQosdQOh0l0ma+FgEABC2CWhd
278+0LgjQTBRM6cGO+urcq7/jhdWQ1UuUG4tVn71z7itCi/F/Enhxc2C22d2GhFVpWsn
279+giSXJYpZ/mIjkdVfWNo6FRuRmmHwMys1p0qTOS+8qUJWhSzW75csqJZGgeUrAI61
280+LBV5F0SGR7dR2xZfy7PeDs9xpD0QivDt5DpsZWPaPvw4QlhdLgw6/YU1h9vtm6ci
281+xLjnPRLZ7JMpcQHO8dUDl6FiEI7yQ11BDm253VQAVMddYRPQABn7SpEF8kD/aZVh
282+2Clvz61Rz80SKjPUthMPLWMCRp7zB0xDMzt3/1i+tQKBgQD6Ar1/oD3eFnRnpi4u
283+n/hdHJtMuXWNfUA4dspNjP6WGOid9sgIeUUdif1XyVJ+afITzvgpWc7nUWIqG2bQ
284+WxJ/4q2rjUdvjNXTy1voVungR2jD5WLQ9DKeaTR0yCliWlx4JgdPG7qGI5MMwsr+
285+R/PUoUUhGeEX+o/sCSieO3iUrQKBgQDqwBEMvIdhAv/CK2sG3fsKYX8rFT55ZNX3
286+Tix9DbUGY3wQColNuI8U1nDlxE9U6VOfT9RPqKelBLCgbzB23kdEJnjSlnqlTxrx
287+E+Hkndyf2ckdJAR3XNxoQ6SRLJNBsgoBj/z5tlfZE9/Jc+uh0mYy3e6g6XCVPBcz
288+MgoIc+ofbwKBgQCGQhZ1hR30N+bHCozeaPW9OvGDIE0qcEqeh9xYDRFilXnF6pK9
289+SjJ9jG7KR8jPLiHb1VebDSl5O1EV/6UU2vNyTc6pw7LLCryBgkGW4aWy1WZDXNnW
290+EG1meGS9GghvUss5kmJ2bxOZmV0Mi0brisQ8OWagQf+JGvtS7BAt+Q3l+QKBgAb9
291+8YQPmXiqPjPqVyW9Ntz4SnFeEJ5NApJ7IZgX8GxgSjGwHqbR+HEGchZl4ncE/Bii
292+qBA3Vcb0fM5KgYcI19aPzsl28fA6ivLjRLcqfIfGVNcpW3iyq13vpdctHLW4N9QU
293+FdTaOYOds+ysJziKq8CYG6NvUIshXw+HTgUybqbBAoGBAIIOqcmmtgOClAwipA17
294+dAHsI9Sjk+J0+d4JU6o+5TsmhUfUKIjXf5+xqJkJcQZMEe5GhxcCuYkgFicvh4Hz
295+kv2H/EU35LcJTqC6KTKZOWIbGcn1cqsvwm3GQJffYDiO8fRZSwCaif2J3F2lfH4Y
296+R/fA67HXFSTT+OncdRpY1NOn
297+-----END PRIVATE KEY-----
298+Bag Attributes: <Empty Attributes>
299+subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US
300+issuer=/CN=Root Agency
301+-----BEGIN CERTIFICATE-----
302+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
303+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
304+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
305+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
306+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIlPjJXzrRih4C
307+k/XsoI01oqo7IUxH3dA2F7vHGXQoIpKCp8Qe6Z6cFfdD8Uj+s+B1BX6hngwzIwjN
308+jE/23X3SALVzJVWzX4Y/IEjbgsuao6sOyNyB18wIU9YzZkVGj68fmMlUw3LnhPbe
309+eWkufZaJCaLyhQOwlRMbOcn48D6Ys8fccOyXNzpq3rH1OzeQpxS2M8zaJYP4/VZ/
310+sf6KRpI7bP+QwyFvNKfhcaO9/gj4kMo9lVGjvDU20FW6g8UVNJCV9N4GO6mOcyqo
311+OhuhVfjCNGgW7N1qi0TIVn0/MQM4l4dcT2R7Z/bV9fhMJLjGsy5A4TLAdRrhKUHT
312+bzi9HyDvAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
313+-----END CERTIFICATE-----
314+Bag Attributes
315+ localKeyID: 01 00 00 00
316+subject=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com
317+issuer=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com
318+-----BEGIN CERTIFICATE-----
319+MIID7TCCAtWgAwIBAgIJALQS3yMg3R41MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD
320+VQQGEwJVUzETMBEGA1UECAwKV0FTSElOR1RPTjEQMA4GA1UEBwwHU2VhdHRsZTES
321+MBAGA1UECgwJTWljcm9zb2Z0MQ4wDAYDVQQLDAVBenVyZTEOMAwGA1UEAwwFQW5o
322+Vm8xIjAgBgkqhkiG9w0BCQEWE2FuaHZvQG1pY3Jvc29mdC5jb20wHhcNMTkwMjE0
323+MjMxMjQwWhcNMjExMTEwMjMxMjQwWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgM
324+CldBU0hJTkdUT04xEDAOBgNVBAcMB1NlYXR0bGUxEjAQBgNVBAoMCU1pY3Jvc29m
325+dDEOMAwGA1UECwwFQXp1cmUxDjAMBgNVBAMMBUFuaFZvMSIwIAYJKoZIhvcNAQkB
326+FhNhbmh2b0BtaWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
327+CgKCAQEA5RHuX1KsHa0Ez1tqFZRitn99av/FC0/Cpjk8lLy9ET3SUOvb7xznOUNg
328+ioNnr4hwti1oRfFpqZ2Y+utUS9BnhRos8L1PDKHm8Oad6KnXIKBqR1bcX/pq+EHF
329+hefPxcHs9HQOIXLIplTQc3jaQQVCCjQTEwRvuHeoXEAVABilHRkXAhhMk6ly1NDI
330+B48QvSYFaliZfl9i3ABo+eyfrRmBky8yiZngnhlTTeWbSdE/OTGBwikJNfxRJSvi
331+quWpKL1330B45Zwj3MdDSeOzQY5T2hadoI5wAoZ+/W0+IgozhWfWr1qakaXlOde1
332+Ap2O3Yzq937qHfzEkMody1rnptbVgQIDAQABo1AwTjAdBgNVHQ4EFgQUPvdgLiv3
333+pAk4r0QTPZU3PFOZJvgwHwYDVR0jBBgwFoAUPvdgLiv3pAk4r0QTPZU3PFOZJvgw
334+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVUHZT+h9+uCPLTEl5IDg
335+kqd9WpzXA7PJd/V+7DeDDTkEd06FIKTWZLfxLVVDjQJnQqubQb//e0zGu1qKbXnX
336+R7xqWabGU4eyPeUFWddmt1OHhxKLU3HbJNJJdL6XKiQtpGGUQt/mqNQ/DEr6hhNF
337+im5I79iA8H/dXA2gyZrj5Rxea4mtsaYO0mfp1NrFtJpAh2Djy4B1lBXBIv4DWG9e
338+mMEwzcLCOZj2cOMA6+mdLMUjYCvIRtnn5MKUHyZX5EmX79wsqMTvVpddlVLB9Kgz
339+Qnvft9+SBWh9+F3ip7BsL6Q4Q9v8eHRbnP0ya7ddlgh64uwf9VOfZZdKCnwqudJP
340+3g==
341+-----END CERTIFICATE-----
342+Bag Attributes
343+ localKeyID: 02 00 00 00
344+subject=/CN=/subscriptions/redacted/resourcegroups/redacted/providers/Microsoft.Compute/virtualMachines/redacted
345+issuer=/CN=Microsoft.ManagedIdentity
346+-----BEGIN CERTIFICATE-----
347+MIIDnTCCAoWgAwIBAgIUB2lauSRccvFkoJybUfIwOUqBN7MwDQYJKoZIhvcNAQEL
348+BQAwJDEiMCAGA1UEAxMZTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eTAeFw0xOTAy
349+MTUxOTA5MDBaFw0xOTA4MTQxOTA5MDBaMIGUMYGRMIGOBgNVBAMTgYYvc3Vic2Ny
350+aXB0aW9ucy8yN2I3NTBjZC1lZDQzLTQyZmQtOTA0NC04ZDc1ZTEyNGFlNTUvcmVz
351+b3VyY2Vncm91cHMvYW5oZXh0cmFzc2gvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21w
352+dXRlL3ZpcnR1YWxNYWNoaW5lcy9hbmh0ZXN0Y2VydDCCASIwDQYJKoZIhvcNAQEB
353+BQADggEPADCCAQoCggEBAOVCE+tnBVBgVXgUFzQfWJNdhrOcynBm8QhMq1dYALNN
354+2C5R16sRU6RdbcdOLke8LasxrK3SeqjfNx3HV4aKp2OllD/AyuTP3A0Qz+c0yxee
355+0TDGTSMJU0oH+PPq9/4E62tIjTVKuK0AZlZ2kqhNTLO1PwLaYDdfoPyDebgN3TuW
356+2fPFoOoBAhTmMEeHd/9DXi2U81lZQiKpVMKAPGAB7swOZ+z2HcIidMFfVczknhSw
357+tMvbf+lp2B5K8/lgXmqvX7RztN1+3GvaXAA3esuSKx/kSIsBOhXIkmWA/97GjYjw
358+Nogp7sNnMPdjUKu6s6mRwwpi7mQzUe2Vu5q0OQBragMCAwEAAaNWMFQwDgYDVR0P
359+AQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYD
360+VR0jBBgwFoAUOJvzEsriQWdJBndPrK+Me1bCPjYwDQYJKoZIhvcNAQELBQADggEB
361+AFGP/g8o7Hv/to11M0UqfzJuW/AyH9RZtSRcNQFLZUndwweQ6fap8lFsA4REUdqe
362+7Quqp5JNNY1XzKLWXMPoheIDH1A8FFXdsAroArzlNs9tO3TlIHE8A7HxEVZEmR4b
363+7ZiixmkQPS2RkjEoV/GM6fheBrzuFn7X5kVZyE6cC5sfcebn8xhk3ZcXI0VmpdT0
364+jFBsf5IvFCIXXLLhJI4KXc8VMoKFU1jT9na/jyaoGmfwovKj4ib8s2aiXGAp7Y38
365+UCmY+bJapWom6Piy5Jzi/p/kzMVdJcSa+GqpuFxBoQYEVs2XYVl7cGu/wPM+NToC
366+pkSoWwF1QAnHn0eokR9E1rU=
367+-----END CERTIFICATE-----
368+Bag Attributes: <Empty Attributes>
369+subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US
370+issuer=/CN=Root Agency
371+-----BEGIN CERTIFICATE-----
372+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
373+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
374+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
375+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
376+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb
377+Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi
378+nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW
379+vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+
380+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y
381+WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7
382+t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
383+-----END CERTIFICATE-----
384diff --git a/tests/data/azure/pubkey_extract_cert b/tests/data/azure/pubkey_extract_cert
385new file mode 100644
386index 0000000..ce9b852
387--- /dev/null
388+++ b/tests/data/azure/pubkey_extract_cert
389@@ -0,0 +1,13 @@
390+-----BEGIN CERTIFICATE-----
391+MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
392+IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
393+BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
394+cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
395+BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb
396+Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi
397+nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW
398+vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+
399+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y
400+WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7
401+t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
402+-----END CERTIFICATE-----
403diff --git a/tests/data/azure/pubkey_extract_ssh_key b/tests/data/azure/pubkey_extract_ssh_key
404new file mode 100644
405index 0000000..54d749e
406--- /dev/null
407+++ b/tests/data/azure/pubkey_extract_ssh_key
408@@ -0,0 +1 @@
409+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHU9IDclbKVYVbYuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoinlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmWvwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4yWzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7t5btUyvp
410diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
411index 26b2b93..0255616 100644
412--- a/tests/unittests/test_datasource/test_azure_helper.py
413+++ b/tests/unittests/test_datasource/test_azure_helper.py
414@@ -1,11 +1,13 @@
415 # This file is part of cloud-init. See LICENSE file for license information.
416
417 import os
418+import unittest2
419 from textwrap import dedent
420
421 from cloudinit.sources.helpers import azure as azure_helper
422 from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir
423
424+from cloudinit.util import load_file
425 from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim
426
427 GOAL_STATE_TEMPLATE = """\
428@@ -289,6 +291,50 @@ class TestOpenSSLManager(CiTestCase):
429 self.assertEqual([mock.call(manager.tmpdir)], del_dir.call_args_list)
430
431
432+class TestOpenSSLManagerActions(CiTestCase):
433+
434+ def setUp(self):
435+ super(TestOpenSSLManagerActions, self).setUp()
436+
437+ self.allowed_subp = True
438+
439+ def _data_file(self, name):
440+ path = 'tests/data/azure'
441+ return os.path.join(path, name)
442+
443+ @unittest2.skip("todo move to cloud_test")
444+ def test_pubkey_extract(self):
445+ cert = load_file(self._data_file('pubkey_extract_cert'))
446+ good_key = load_file(self._data_file('pubkey_extract_ssh_key'))
447+ sslmgr = azure_helper.OpenSSLManager()
448+ key = sslmgr._get_ssh_key_from_cert(cert)
449+ self.assertEqual(good_key, key)
450+
451+ good_fingerprint = '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473'
452+ fingerprint = sslmgr._get_fingerprint_from_cert(cert)
453+ self.assertEqual(good_fingerprint, fingerprint)
454+
455+ @unittest2.skip("todo move to cloud_test")
456+ @mock.patch.object(azure_helper.OpenSSLManager, '_decrypt_certs_from_xml')
457+ def test_parse_certificates(self, mock_decrypt_certs):
458+ """Azure control plane puts private keys as well as certificates
459+ into the Certificates XML object. Make sure only the public keys
460+ from certs are extracted and that fingerprints are converted to
461+ the form specified in the ovf-env.xml file.
462+ """
463+ cert_contents = load_file(self._data_file('parse_certificates_pem'))
464+ fingerprints = load_file(self._data_file(
465+ 'parse_certificates_fingerprints')
466+ ).splitlines()
467+ mock_decrypt_certs.return_value = cert_contents
468+ sslmgr = azure_helper.OpenSSLManager()
469+ keys_by_fp = sslmgr.parse_certificates('')
470+ for fp in keys_by_fp.keys():
471+ self.assertIn(fp, fingerprints)
472+ for fp in fingerprints:
473+ self.assertIn(fp, keys_by_fp)
474+
475+
476 class TestWALinuxAgentShim(CiTestCase):
477
478 def setUp(self):
479@@ -329,18 +375,31 @@ class TestWALinuxAgentShim(CiTestCase):
480
481 def test_certificates_used_to_determine_public_keys(self):
482 shim = wa_shim()
483- data = shim.register_with_azure_and_fetch_data()
484+ """if register_with_azure_and_fetch_data() isn't passed some info about
485+ the user's public keys, there's no point in even trying to parse
486+ the certificates
487+ """
488+ mypk = [{'fingerprint': 'fp1', 'path': 'path1'},
489+ {'fingerprint': 'fp3', 'path': 'path3', 'value': ''}]
490+ certs = {'fp1': 'expected-key',
491+ 'fp2': 'should-not-be-found',
492+ 'fp3': 'expected-no-value-key',
493+ }
494+ sslmgr = self.OpenSSLManager.return_value
495+ sslmgr.parse_certificates.return_value = certs
496+ data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk)
497 self.assertEqual(
498 [mock.call(self.GoalState.return_value.certificates_xml)],
499- self.OpenSSLManager.return_value.parse_certificates.call_args_list)
500- self.assertEqual(
501- self.OpenSSLManager.return_value.parse_certificates.return_value,
502- data['public-keys'])
503+ sslmgr.parse_certificates.call_args_list)
504+ self.assertIn('expected-key', data['public-keys'])
505+ self.assertIn('expected-no-value-key', data['public-keys'])
506+ self.assertNotIn('should-not-be-found', data['public-keys'])
507
508 def test_absent_certificates_produces_empty_public_keys(self):
509+ mypk = [{'fingerprint': 'fp1', 'path': 'path1'}]
510 self.GoalState.return_value.certificates_xml = None
511 shim = wa_shim()
512- data = shim.register_with_azure_and_fetch_data()
513+ data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk)
514 self.assertEqual([], data['public-keys'])
515
516 def test_correct_url_used_for_report_ready(self):

Subscribers

People subscribed via source and target branches