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

Proposed by Jason Zions
Status: Merged
Approved by: Chad Smith
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
Server Team CI bot continuous-integration Approve
Ryan Harper Needs Fixing
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.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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
8170777... by "Jason Zions (MSFT)" <email address hidden>

Fix pycodestyle issues

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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
d2cf5ab... by "Jason Zions (MSFT)" <email address hidden>

Redact sample Certificates data

03f8214... by "Jason Zions (MSFT)" <email address hidden>

Address pycodestyle again

Revision history for this message
Server Team CI bot (server-team-bot) 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

review: Needs Fixing (continuous-integration)
Revision history for this message
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.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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)
Revision history for this message
Ryan Harper (raharper) wrote :

I've added a few comments in-line.

review: Needs Fixing
Revision history for this message
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/

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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
8bf9c9c... by "Jason Zions (MSFT)" <email address hidden>

Disable tests that validate openssl cmds

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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
b2a0e63... by "Jason Zions (MSFT)" <email address hidden>

Extract test data into raw data files

cea6c04... by "Jason Zions (MSFT)" <email address hidden>

Don't annotate to skip tests, use raise instead

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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)
Revision history for this message
Jason Zions (jasonzio) wrote :

I think this addresses your feedback.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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
0c13e35... by "Jason Zions (MSFT)" <email address hidden>

Use proper decorator to skip tests

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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
2009ec1... by "Jason Zions (MSFT)" <email address hidden>

Make get_metadata_from_fabric mock return dict with array

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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)
Revision history for this message
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
Revision history for this message
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
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index a4f998b..eccbee5 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -627,9 +627,11 @@ class DataSourceAzure(sources.DataSource):
627 if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN:627 if self.ds_cfg['agent_command'] == AGENT_START_BUILTIN:
628 self.bounce_network_with_azure_hostname()628 self.bounce_network_with_azure_hostname()
629629
630 pubkey_info = self.cfg.get('_pubkeys', None)
630 metadata_func = partial(get_metadata_from_fabric,631 metadata_func = partial(get_metadata_from_fabric,
631 fallback_lease_file=self.632 fallback_lease_file=self.
632 dhclient_lease_file)633 dhclient_lease_file,
634 pubkey_info=pubkey_info)
633 else:635 else:
634 metadata_func = self.get_metadata_from_agent636 metadata_func = self.get_metadata_from_agent
635637
@@ -642,6 +644,7 @@ class DataSourceAzure(sources.DataSource):
642 "Error communicating with Azure fabric; You may experience."644 "Error communicating with Azure fabric; You may experience."
643 "connectivity issues.", exc_info=True)645 "connectivity issues.", exc_info=True)
644 return False646 return False
647
645 util.del_file(REPORTED_READY_MARKER_FILE)648 util.del_file(REPORTED_READY_MARKER_FILE)
646 util.del_file(REPROVISION_MARKER_FILE)649 util.del_file(REPROVISION_MARKER_FILE)
647 return fabric_data650 return fabric_data
@@ -909,13 +912,15 @@ def find_child(node, filter_func):
909def load_azure_ovf_pubkeys(sshnode):912def load_azure_ovf_pubkeys(sshnode):
910 # This parses a 'SSH' node formatted like below, and returns913 # This parses a 'SSH' node formatted like below, and returns
911 # an array of dicts.914 # an array of dicts.
912 # [{'fp': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7',915 # [{'fingerprint': '6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7',
913 # 'path': 'where/to/go'}]916 # 'path': '/where/to/go'}]
914 #917 #
915 # <SSH><PublicKeys>918 # <SSH><PublicKeys>
916 # <PublicKey><Fingerprint>ABC</FingerPrint><Path>/ABC</Path>919 # <PublicKey><Fingerprint>ABC</FingerPrint><Path>/x/y/z</Path>
917 # ...920 # ...
918 # </PublicKeys></SSH>921 # </PublicKeys></SSH>
922 # Under some circumstances, there may be a <Value> element along with the
923 # Fingerprint and Path. Pass those along if they appear.
919 results = find_child(sshnode, lambda n: n.localName == "PublicKeys")924 results = find_child(sshnode, lambda n: n.localName == "PublicKeys")
920 if len(results) == 0:925 if len(results) == 0:
921 return []926 return []
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
index e5696b1..2829dd2 100644
--- a/cloudinit/sources/helpers/azure.py
+++ b/cloudinit/sources/helpers/azure.py
@@ -138,9 +138,36 @@ class OpenSSLManager(object):
138 self.certificate = certificate138 self.certificate = certificate
139 LOG.debug('New certificate generated.')139 LOG.debug('New certificate generated.')
140140
141 def parse_certificates(self, certificates_xml):141 @staticmethod
142 tag = ElementTree.fromstring(certificates_xml).find(142 def _run_x509_action(action, cert):
143 './/Data')143 cmd = ['openssl', 'x509', '-noout', action]
144 result, _ = util.subp(cmd, data=cert)
145 return result
146
147 def _get_ssh_key_from_cert(self, certificate):
148 pub_key = self._run_x509_action('-pubkey', certificate)
149 keygen_cmd = ['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin']
150 ssh_key, _ = util.subp(keygen_cmd, data=pub_key)
151 return ssh_key
152
153 def _get_fingerprint_from_cert(self, certificate):
154 """openssl x509 formats fingerprints as so:
155 'SHA1 Fingerprint=07:3E:19:D1:4D:1C:79:92:24:C6:A0:FD:8D:DA:\
156 B6:A8:BF:27:D4:73\n'
157
158 Azure control plane passes that fingerprint as so:
159 '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473'
160 """
161 raw_fp = self._run_x509_action('-fingerprint', certificate)
162 eq = raw_fp.find('=')
163 octets = raw_fp[eq+1:-1].split(':')
164 return ''.join(octets)
165
166 def _decrypt_certs_from_xml(self, certificates_xml):
167 """Decrypt the certificates XML document using the our private key;
168 return the list of certs and private keys contained in the doc.
169 """
170 tag = ElementTree.fromstring(certificates_xml).find('.//Data')
144 certificates_content = tag.text171 certificates_content = tag.text
145 lines = [172 lines = [
146 b'MIME-Version: 1.0',173 b'MIME-Version: 1.0',
@@ -151,32 +178,30 @@ class OpenSSLManager(object):
151 certificates_content.encode('utf-8'),178 certificates_content.encode('utf-8'),
152 ]179 ]
153 with cd(self.tmpdir):180 with cd(self.tmpdir):
154 with open('Certificates.p7m', 'wb') as f:
155 f.write(b'\n'.join(lines))
156 out, _ = util.subp(181 out, _ = util.subp(
157 'openssl cms -decrypt -in Certificates.p7m -inkey'182 'openssl cms -decrypt -in /dev/stdin -inkey'
158 ' {private_key} -recip {certificate} | openssl pkcs12 -nodes'183 ' {private_key} -recip {certificate} | openssl pkcs12 -nodes'
159 ' -password pass:'.format(**self.certificate_names),184 ' -password pass:'.format(**self.certificate_names),
160 shell=True)185 shell=True, data=b'\n'.join(lines))
161 private_keys, certificates = [], []186 return out
187
188 def parse_certificates(self, certificates_xml):
189 """Given the Certificates XML document, return a dictionary of
190 fingerprints and associated SSH keys derived from the certs."""
191 out = self._decrypt_certs_from_xml(certificates_xml)
162 current = []192 current = []
193 keys = {}
163 for line in out.splitlines():194 for line in out.splitlines():
164 current.append(line)195 current.append(line)
165 if re.match(r'[-]+END .*?KEY[-]+$', line):196 if re.match(r'[-]+END .*?KEY[-]+$', line):
166 private_keys.append('\n'.join(current))197 # ignore private_keys
167 current = []198 current = []
168 elif re.match(r'[-]+END .*?CERTIFICATE[-]+$', line):199 elif re.match(r'[-]+END .*?CERTIFICATE[-]+$', line):
169 certificates.append('\n'.join(current))200 certificate = '\n'.join(current)
201 ssh_key = self._get_ssh_key_from_cert(certificate)
202 fingerprint = self._get_fingerprint_from_cert(certificate)
203 keys[fingerprint] = ssh_key
170 current = []204 current = []
171 keys = []
172 for certificate in certificates:
173 with cd(self.tmpdir):
174 public_key, _ = util.subp(
175 'openssl x509 -noout -pubkey |'
176 'ssh-keygen -i -m PKCS8 -f /dev/stdin',
177 data=certificate,
178 shell=True)
179 keys.append(public_key)
180 return keys205 return keys
181206
182207
@@ -206,7 +231,6 @@ class WALinuxAgentShim(object):
206 self.dhcpoptions = dhcp_options231 self.dhcpoptions = dhcp_options
207 self._endpoint = None232 self._endpoint = None
208 self.openssl_manager = None233 self.openssl_manager = None
209 self.values = {}
210 self.lease_file = fallback_lease_file234 self.lease_file = fallback_lease_file
211235
212 def clean_up(self):236 def clean_up(self):
@@ -328,8 +352,9 @@ class WALinuxAgentShim(object):
328 LOG.debug('Azure endpoint found at %s', endpoint_ip_address)352 LOG.debug('Azure endpoint found at %s', endpoint_ip_address)
329 return endpoint_ip_address353 return endpoint_ip_address
330354
331 def register_with_azure_and_fetch_data(self):355 def register_with_azure_and_fetch_data(self, pubkey_info=None):
332 self.openssl_manager = OpenSSLManager()356 if self.openssl_manager is None:
357 self.openssl_manager = OpenSSLManager()
333 http_client = AzureEndpointHttpClient(self.openssl_manager.certificate)358 http_client = AzureEndpointHttpClient(self.openssl_manager.certificate)
334 LOG.info('Registering with Azure...')359 LOG.info('Registering with Azure...')
335 attempts = 0360 attempts = 0
@@ -347,16 +372,37 @@ class WALinuxAgentShim(object):
347 attempts += 1372 attempts += 1
348 LOG.debug('Successfully fetched GoalState XML.')373 LOG.debug('Successfully fetched GoalState XML.')
349 goal_state = GoalState(response.contents, http_client)374 goal_state = GoalState(response.contents, http_client)
350 public_keys = []375 ssh_keys = []
351 if goal_state.certificates_xml is not None:376 if goal_state.certificates_xml is not None and pubkey_info is not None:
352 LOG.debug('Certificate XML found; parsing out public keys.')377 LOG.debug('Certificate XML found; parsing out public keys.')
353 public_keys = self.openssl_manager.parse_certificates(378 keys_by_fingerprint = self.openssl_manager.parse_certificates(
354 goal_state.certificates_xml)379 goal_state.certificates_xml)
355 data = {380 ssh_keys = self._filter_pubkeys(keys_by_fingerprint, pubkey_info)
356 'public-keys': public_keys,
357 }
358 self._report_ready(goal_state, http_client)381 self._report_ready(goal_state, http_client)
359 return data382 return {'public-keys': ssh_keys}
383
384 def _filter_pubkeys(self, keys_by_fingerprint, pubkey_info):
385 """cloud-init expects a straightforward array of keys to be dropped
386 into the user's authorized_keys file. Azure control plane exposes
387 multiple public keys to the VM via wireserver. Select just the
388 user's key(s) and return them, ignoring any other certs.
389 """
390 keys = []
391 for pubkey in pubkey_info:
392 if 'value' in pubkey and pubkey['value']:
393 keys.append(pubkey['value'])
394 elif 'fingerprint' in pubkey and pubkey['fingerprint']:
395 fingerprint = pubkey['fingerprint']
396 if fingerprint in keys_by_fingerprint:
397 keys.append(keys_by_fingerprint[fingerprint])
398 else:
399 LOG.warning("ovf-env.xml specified PublicKey fingerprint "
400 "%s not found in goalstate XML", fingerprint)
401 else:
402 LOG.warning("ovf-env.xml specified PublicKey with neither "
403 "value nor fingerprint: %s", pubkey)
404
405 return keys
360406
361 def _report_ready(self, goal_state, http_client):407 def _report_ready(self, goal_state, http_client):
362 LOG.debug('Reporting ready to Azure fabric.')408 LOG.debug('Reporting ready to Azure fabric.')
@@ -373,11 +419,12 @@ class WALinuxAgentShim(object):
373 LOG.info('Reported ready to Azure fabric.')419 LOG.info('Reported ready to Azure fabric.')
374420
375421
376def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None):422def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None,
423 pubkey_info=None):
377 shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file,424 shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file,
378 dhcp_options=dhcp_opts)425 dhcp_options=dhcp_opts)
379 try:426 try:
380 return shim.register_with_azure_and_fetch_data()427 return shim.register_with_azure_and_fetch_data(pubkey_info=pubkey_info)
381 finally:428 finally:
382 shim.clean_up()429 shim.clean_up()
383430
diff --git a/tests/data/azure/parse_certificates_fingerprints b/tests/data/azure/parse_certificates_fingerprints
384new file mode 100644431new file mode 100644
index 0000000..f7293c5
--- /dev/null
+++ b/tests/data/azure/parse_certificates_fingerprints
@@ -0,0 +1,4 @@
1ECEDEB3B8488D31AF3BC4CCED493F64B7D27D7B1
2073E19D14D1C799224C6A0FD8DDAB6A8BF27D473
34C16E7FAD6297D74A9B25EB8F0A12808CEBE293E
4929130695289B450FE45DCD5F6EF0CDE69865867
diff --git a/tests/data/azure/parse_certificates_pem b/tests/data/azure/parse_certificates_pem
0new file mode 1006445new file mode 100644
index 0000000..3521ea3
--- /dev/null
+++ b/tests/data/azure/parse_certificates_pem
@@ -0,0 +1,152 @@
1Bag Attributes
2 localKeyID: 01 00 00 00
3 Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
4Key Attributes
5 X509v3 Key Usage: 10
6-----BEGIN PRIVATE KEY-----
7MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDlEe5fUqwdrQTP
8W2oVlGK2f31q/8ULT8KmOTyUvL0RPdJQ69vvHOc5Q2CKg2eviHC2LWhF8WmpnZj6
961RL0GeFGizwvU8Moebw5p3oqdcgoGpHVtxf+mr4QcWF58/Fwez0dA4hcsimVNBz
10eNpBBUIKNBMTBG+4d6hcQBUAGKUdGRcCGEyTqXLU0MgHjxC9JgVqWJl+X2LcAGj5
117J+tGYGTLzKJmeCeGVNN5ZtJ0T85MYHCKQk1/FElK+Kq5akovXffQHjlnCPcx0NJ
1247NBjlPaFp2gjnAChn79bT4iCjOFZ9avWpqRpeU517UCnY7djOr3fuod/MSQyh3L
13Wuem1tWBAgMBAAECggEBAM4ZXQRs6Kjmo95BHGiAEnSqrlgX+dycjcBq3QPh8KZT
14nifqnf48XhnackENy7tWIjr3DctoUq4mOp8AHt77ijhqfaa4XSg7fwKeK9NLBGC5
15lAXNtAey0o2894/sKrd+LMkgphoYIUnuI4LRaGV56potkj/ZDP/GwTcG/R4SDnTn
16C1Nb05PNTAPQtPZrgPo7TdM6gGsTnFbVrYHQLyg2Sq/osHfF15YohB01esRLCAwb
17EF8JkRC4hWIZoV7BsyQ39232zAJQGGla7+wKFs3kObwh3VnFkQpT94KZnNiZuEfG
18x5pW4Pn3gXgNsftscXsaNe/M9mYZqo//Qw7NvUIvAvECgYEA9AVveyK0HOA06fhh
19+3hUWdvw7Pbrl+e06jO9+bT1RjQMbHKyI60DZyVGuAySN86iChJRoJr5c6xj+iXU
20cR6BVJDjGH5t1tyiK2aYf6hEpK9/j8Z54UiVQ486zPP0PGfT2TO4lBLK+8AUmoaH
21gk21ul8QeVCeCJa/o+xEoRFvzcUCgYEA8FCbbvInrUtNY+9eKaUYoNodsgBVjm5X
22I0YPUL9D4d+1nvupHSV2NVmQl0w1RaJwrNTafrl5LkqjhQbmuWNta6QgfZzSA3LB
23lWXo1Mm0azKdcD3qMGbvn0Q3zU+yGNEgmB/Yju3/NtgYRG6tc+FCWRbPbiCnZWT8
24v3C2Y0XggI0CgYEA2/jCZBgGkTkzue5kNVJlh5OS/aog+pCvL6hxCtarfBuTT3ed
25Sje+p46cz3DVpmUpATc+Si8py7KNdYQAm/BJ2be6X+woi9Xcgo87zWgcaPCjZzId
260I2jsIE/Gl6XvpRCDrxnGWRPgt3GNP4szbPLrDPiH9oie8+Y9eYYf7G+PZkCgYEA
27nRSzZOPYV4f/QDF4pVQLMykfe/iH9B/fyWjEHg3He19VQmRReIHCMMEoqBziPXAe
28onpHj8oAkeer1wpZyhhZr6CKtFDLXgGm09bXSC/IRMHC81klORovyzU2HHfZfCtG
29WOmIDnU2+0xpIGIP8sztJ3qnf97MTJSkOSadsWo9gwkCgYEAh5AQmJQmck88Dff2
30qIfJIX8d+BDw47BFJ89OmMFjGV8TNB+JO+AV4Vkodg4hxKpLqTFZTTUFgoYfy5u1
311/BhAjpmCDCrzubCFhx+8VEoM2+2+MmnuQoMAm9+/mD/IidwRaARgXgvEmp7sfdt
32RyWd+p2lYvFkC/jORQtDMY4uW1o=
33-----END PRIVATE KEY-----
34Bag Attributes
35 localKeyID: 02 00 00 00
36 Microsoft CSP Name: Microsoft Strong Cryptographic Provider
37Key Attributes
38 X509v3 Key Usage: 10
39-----BEGIN PRIVATE KEY-----
40MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlQhPrZwVQYFV4
41FBc0H1iTXYaznMpwZvEITKtXWACzTdguUderEVOkXW3HTi5HvC2rMayt0nqo3zcd
42x1eGiqdjpZQ/wMrkz9wNEM/nNMsXntEwxk0jCVNKB/jz6vf+BOtrSI01SritAGZW
43dpKoTUyztT8C2mA3X6D8g3m4Dd07ltnzxaDqAQIU5jBHh3f/Q14tlPNZWUIiqVTC
44gDxgAe7MDmfs9h3CInTBX1XM5J4UsLTL23/padgeSvP5YF5qr1+0c7Tdftxr2lwA
45N3rLkisf5EiLAToVyJJlgP/exo2I8DaIKe7DZzD3Y1CrurOpkcMKYu5kM1Htlbua
46tDkAa2oDAgMBAAECggEAOvdueS9DyiMlCKAeQb1IQosdQOh0l0ma+FgEABC2CWhd
470LgjQTBRM6cGO+urcq7/jhdWQ1UuUG4tVn71z7itCi/F/Enhxc2C22d2GhFVpWsn
48giSXJYpZ/mIjkdVfWNo6FRuRmmHwMys1p0qTOS+8qUJWhSzW75csqJZGgeUrAI61
49LBV5F0SGR7dR2xZfy7PeDs9xpD0QivDt5DpsZWPaPvw4QlhdLgw6/YU1h9vtm6ci
50xLjnPRLZ7JMpcQHO8dUDl6FiEI7yQ11BDm253VQAVMddYRPQABn7SpEF8kD/aZVh
512Clvz61Rz80SKjPUthMPLWMCRp7zB0xDMzt3/1i+tQKBgQD6Ar1/oD3eFnRnpi4u
52n/hdHJtMuXWNfUA4dspNjP6WGOid9sgIeUUdif1XyVJ+afITzvgpWc7nUWIqG2bQ
53WxJ/4q2rjUdvjNXTy1voVungR2jD5WLQ9DKeaTR0yCliWlx4JgdPG7qGI5MMwsr+
54R/PUoUUhGeEX+o/sCSieO3iUrQKBgQDqwBEMvIdhAv/CK2sG3fsKYX8rFT55ZNX3
55Tix9DbUGY3wQColNuI8U1nDlxE9U6VOfT9RPqKelBLCgbzB23kdEJnjSlnqlTxrx
56E+Hkndyf2ckdJAR3XNxoQ6SRLJNBsgoBj/z5tlfZE9/Jc+uh0mYy3e6g6XCVPBcz
57MgoIc+ofbwKBgQCGQhZ1hR30N+bHCozeaPW9OvGDIE0qcEqeh9xYDRFilXnF6pK9
58SjJ9jG7KR8jPLiHb1VebDSl5O1EV/6UU2vNyTc6pw7LLCryBgkGW4aWy1WZDXNnW
59EG1meGS9GghvUss5kmJ2bxOZmV0Mi0brisQ8OWagQf+JGvtS7BAt+Q3l+QKBgAb9
608YQPmXiqPjPqVyW9Ntz4SnFeEJ5NApJ7IZgX8GxgSjGwHqbR+HEGchZl4ncE/Bii
61qBA3Vcb0fM5KgYcI19aPzsl28fA6ivLjRLcqfIfGVNcpW3iyq13vpdctHLW4N9QU
62FdTaOYOds+ysJziKq8CYG6NvUIshXw+HTgUybqbBAoGBAIIOqcmmtgOClAwipA17
63dAHsI9Sjk+J0+d4JU6o+5TsmhUfUKIjXf5+xqJkJcQZMEe5GhxcCuYkgFicvh4Hz
64kv2H/EU35LcJTqC6KTKZOWIbGcn1cqsvwm3GQJffYDiO8fRZSwCaif2J3F2lfH4Y
65R/fA67HXFSTT+OncdRpY1NOn
66-----END PRIVATE KEY-----
67Bag Attributes: <Empty Attributes>
68subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US
69issuer=/CN=Root Agency
70-----BEGIN CERTIFICATE-----
71MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
72IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
73BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
74cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
75BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIlPjJXzrRih4C
76k/XsoI01oqo7IUxH3dA2F7vHGXQoIpKCp8Qe6Z6cFfdD8Uj+s+B1BX6hngwzIwjN
77jE/23X3SALVzJVWzX4Y/IEjbgsuao6sOyNyB18wIU9YzZkVGj68fmMlUw3LnhPbe
78eWkufZaJCaLyhQOwlRMbOcn48D6Ys8fccOyXNzpq3rH1OzeQpxS2M8zaJYP4/VZ/
79sf6KRpI7bP+QwyFvNKfhcaO9/gj4kMo9lVGjvDU20FW6g8UVNJCV9N4GO6mOcyqo
80OhuhVfjCNGgW7N1qi0TIVn0/MQM4l4dcT2R7Z/bV9fhMJLjGsy5A4TLAdRrhKUHT
81bzi9HyDvAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
82-----END CERTIFICATE-----
83Bag Attributes
84 localKeyID: 01 00 00 00
85subject=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com
86issuer=/C=US/ST=WASHINGTON/L=Seattle/O=Microsoft/OU=Azure/CN=AnhVo/emailAddress=redacted@microsoft.com
87-----BEGIN CERTIFICATE-----
88MIID7TCCAtWgAwIBAgIJALQS3yMg3R41MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD
89VQQGEwJVUzETMBEGA1UECAwKV0FTSElOR1RPTjEQMA4GA1UEBwwHU2VhdHRsZTES
90MBAGA1UECgwJTWljcm9zb2Z0MQ4wDAYDVQQLDAVBenVyZTEOMAwGA1UEAwwFQW5o
91Vm8xIjAgBgkqhkiG9w0BCQEWE2FuaHZvQG1pY3Jvc29mdC5jb20wHhcNMTkwMjE0
92MjMxMjQwWhcNMjExMTEwMjMxMjQwWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgM
93CldBU0hJTkdUT04xEDAOBgNVBAcMB1NlYXR0bGUxEjAQBgNVBAoMCU1pY3Jvc29m
94dDEOMAwGA1UECwwFQXp1cmUxDjAMBgNVBAMMBUFuaFZvMSIwIAYJKoZIhvcNAQkB
95FhNhbmh2b0BtaWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
96CgKCAQEA5RHuX1KsHa0Ez1tqFZRitn99av/FC0/Cpjk8lLy9ET3SUOvb7xznOUNg
97ioNnr4hwti1oRfFpqZ2Y+utUS9BnhRos8L1PDKHm8Oad6KnXIKBqR1bcX/pq+EHF
98hefPxcHs9HQOIXLIplTQc3jaQQVCCjQTEwRvuHeoXEAVABilHRkXAhhMk6ly1NDI
99B48QvSYFaliZfl9i3ABo+eyfrRmBky8yiZngnhlTTeWbSdE/OTGBwikJNfxRJSvi
100quWpKL1330B45Zwj3MdDSeOzQY5T2hadoI5wAoZ+/W0+IgozhWfWr1qakaXlOde1
101Ap2O3Yzq937qHfzEkMody1rnptbVgQIDAQABo1AwTjAdBgNVHQ4EFgQUPvdgLiv3
102pAk4r0QTPZU3PFOZJvgwHwYDVR0jBBgwFoAUPvdgLiv3pAk4r0QTPZU3PFOZJvgw
103DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVUHZT+h9+uCPLTEl5IDg
104kqd9WpzXA7PJd/V+7DeDDTkEd06FIKTWZLfxLVVDjQJnQqubQb//e0zGu1qKbXnX
105R7xqWabGU4eyPeUFWddmt1OHhxKLU3HbJNJJdL6XKiQtpGGUQt/mqNQ/DEr6hhNF
106im5I79iA8H/dXA2gyZrj5Rxea4mtsaYO0mfp1NrFtJpAh2Djy4B1lBXBIv4DWG9e
107mMEwzcLCOZj2cOMA6+mdLMUjYCvIRtnn5MKUHyZX5EmX79wsqMTvVpddlVLB9Kgz
108Qnvft9+SBWh9+F3ip7BsL6Q4Q9v8eHRbnP0ya7ddlgh64uwf9VOfZZdKCnwqudJP
1093g==
110-----END CERTIFICATE-----
111Bag Attributes
112 localKeyID: 02 00 00 00
113subject=/CN=/subscriptions/redacted/resourcegroups/redacted/providers/Microsoft.Compute/virtualMachines/redacted
114issuer=/CN=Microsoft.ManagedIdentity
115-----BEGIN CERTIFICATE-----
116MIIDnTCCAoWgAwIBAgIUB2lauSRccvFkoJybUfIwOUqBN7MwDQYJKoZIhvcNAQEL
117BQAwJDEiMCAGA1UEAxMZTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eTAeFw0xOTAy
118MTUxOTA5MDBaFw0xOTA4MTQxOTA5MDBaMIGUMYGRMIGOBgNVBAMTgYYvc3Vic2Ny
119aXB0aW9ucy8yN2I3NTBjZC1lZDQzLTQyZmQtOTA0NC04ZDc1ZTEyNGFlNTUvcmVz
120b3VyY2Vncm91cHMvYW5oZXh0cmFzc2gvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21w
121dXRlL3ZpcnR1YWxNYWNoaW5lcy9hbmh0ZXN0Y2VydDCCASIwDQYJKoZIhvcNAQEB
122BQADggEPADCCAQoCggEBAOVCE+tnBVBgVXgUFzQfWJNdhrOcynBm8QhMq1dYALNN
1232C5R16sRU6RdbcdOLke8LasxrK3SeqjfNx3HV4aKp2OllD/AyuTP3A0Qz+c0yxee
1240TDGTSMJU0oH+PPq9/4E62tIjTVKuK0AZlZ2kqhNTLO1PwLaYDdfoPyDebgN3TuW
1252fPFoOoBAhTmMEeHd/9DXi2U81lZQiKpVMKAPGAB7swOZ+z2HcIidMFfVczknhSw
126tMvbf+lp2B5K8/lgXmqvX7RztN1+3GvaXAA3esuSKx/kSIsBOhXIkmWA/97GjYjw
127Nogp7sNnMPdjUKu6s6mRwwpi7mQzUe2Vu5q0OQBragMCAwEAAaNWMFQwDgYDVR0P
128AQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYD
129VR0jBBgwFoAUOJvzEsriQWdJBndPrK+Me1bCPjYwDQYJKoZIhvcNAQELBQADggEB
130AFGP/g8o7Hv/to11M0UqfzJuW/AyH9RZtSRcNQFLZUndwweQ6fap8lFsA4REUdqe
1317Quqp5JNNY1XzKLWXMPoheIDH1A8FFXdsAroArzlNs9tO3TlIHE8A7HxEVZEmR4b
1327ZiixmkQPS2RkjEoV/GM6fheBrzuFn7X5kVZyE6cC5sfcebn8xhk3ZcXI0VmpdT0
133jFBsf5IvFCIXXLLhJI4KXc8VMoKFU1jT9na/jyaoGmfwovKj4ib8s2aiXGAp7Y38
134UCmY+bJapWom6Piy5Jzi/p/kzMVdJcSa+GqpuFxBoQYEVs2XYVl7cGu/wPM+NToC
135pkSoWwF1QAnHn0eokR9E1rU=
136-----END CERTIFICATE-----
137Bag Attributes: <Empty Attributes>
138subject=/CN=CRP/OU=AzureRT/O=Microsoft Corporation/L=Redmond/ST=WA/C=US
139issuer=/CN=Root Agency
140-----BEGIN CERTIFICATE-----
141MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
142IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
143BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
144cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
145BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb
146Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi
147nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW
148vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+
149lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y
150WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7
151t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
152-----END CERTIFICATE-----
diff --git a/tests/data/azure/pubkey_extract_cert b/tests/data/azure/pubkey_extract_cert
0new file mode 100644153new file mode 100644
index 0000000..ce9b852
--- /dev/null
+++ b/tests/data/azure/pubkey_extract_cert
@@ -0,0 +1,13 @@
1-----BEGIN CERTIFICATE-----
2MIIB+TCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtSb290
3IEFnZW5jeTAeFw0xOTAyMTUxOTA0MDRaFw0yOTAyMTUxOTE0MDRaMGwxDDAKBgNV
4BAMMA0NSUDEQMA4GA1UECwwHQXp1cmVSVDEeMBwGA1UECgwVTWljcm9zb2Z0IENv
5cnBvcmF0aW9uMRAwDgYDVQQHDAdSZWRtb25kMQswCQYDVQQIDAJXQTELMAkGA1UE
6BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHU9IDclbKVYVb
7Yuv0+zViX+wTwlKspslmy/uf3hkWLh7pyzyrq70S7qtSW2EGixUPxZS/R8pOLHoi
8nlKF9ILgj0gVTCJsSwnWpXRg3rhZwIVoYMHN50BHS1SqVD0lsWNMXmo76LoJcjmW
9vwIznvj5C/gnhU+K7+c3m7AlCyU2wjwpBAEYj7PQs6l/wTqpEiaqC5NytNBd7qp+
10lYYysVrpa1PFL0Nj4MMZARIfjkiJtL9qDhy9YZeJRQ6q/Fhz0kjvkZnfxixfKF4y
11WzOfhBrAtpF6oOnuYKk3hxjh9KjTTX4/U8zdLojalX09iyHyEjwJKGlGEpzh1aY7
12t5btUyvpAgMBAAEwDQYJKoZIhvcNAQEFBQADAQA=
13-----END CERTIFICATE-----
diff --git a/tests/data/azure/pubkey_extract_ssh_key b/tests/data/azure/pubkey_extract_ssh_key
0new file mode 10064414new file mode 100644
index 0000000..54d749e
--- /dev/null
+++ b/tests/data/azure/pubkey_extract_ssh_key
@@ -0,0 +1 @@
1ssh-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 26b2b93..0255616 100644
--- a/tests/unittests/test_datasource/test_azure_helper.py
+++ b/tests/unittests/test_datasource/test_azure_helper.py
@@ -1,11 +1,13 @@
1# This file is part of cloud-init. See LICENSE file for license information.1# This file is part of cloud-init. See LICENSE file for license information.
22
3import os3import os
4import unittest2
4from textwrap import dedent5from textwrap import dedent
56
6from cloudinit.sources.helpers import azure as azure_helper7from cloudinit.sources.helpers import azure as azure_helper
7from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir8from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir
89
10from cloudinit.util import load_file
9from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim11from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim
1012
11GOAL_STATE_TEMPLATE = """\13GOAL_STATE_TEMPLATE = """\
@@ -289,6 +291,50 @@ class TestOpenSSLManager(CiTestCase):
289 self.assertEqual([mock.call(manager.tmpdir)], del_dir.call_args_list)291 self.assertEqual([mock.call(manager.tmpdir)], del_dir.call_args_list)
290292
291293
294class TestOpenSSLManagerActions(CiTestCase):
295
296 def setUp(self):
297 super(TestOpenSSLManagerActions, self).setUp()
298
299 self.allowed_subp = True
300
301 def _data_file(self, name):
302 path = 'tests/data/azure'
303 return os.path.join(path, name)
304
305 @unittest2.skip("todo move to cloud_test")
306 def test_pubkey_extract(self):
307 cert = load_file(self._data_file('pubkey_extract_cert'))
308 good_key = load_file(self._data_file('pubkey_extract_ssh_key'))
309 sslmgr = azure_helper.OpenSSLManager()
310 key = sslmgr._get_ssh_key_from_cert(cert)
311 self.assertEqual(good_key, key)
312
313 good_fingerprint = '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473'
314 fingerprint = sslmgr._get_fingerprint_from_cert(cert)
315 self.assertEqual(good_fingerprint, fingerprint)
316
317 @unittest2.skip("todo move to cloud_test")
318 @mock.patch.object(azure_helper.OpenSSLManager, '_decrypt_certs_from_xml')
319 def test_parse_certificates(self, mock_decrypt_certs):
320 """Azure control plane puts private keys as well as certificates
321 into the Certificates XML object. Make sure only the public keys
322 from certs are extracted and that fingerprints are converted to
323 the form specified in the ovf-env.xml file.
324 """
325 cert_contents = load_file(self._data_file('parse_certificates_pem'))
326 fingerprints = load_file(self._data_file(
327 'parse_certificates_fingerprints')
328 ).splitlines()
329 mock_decrypt_certs.return_value = cert_contents
330 sslmgr = azure_helper.OpenSSLManager()
331 keys_by_fp = sslmgr.parse_certificates('')
332 for fp in keys_by_fp.keys():
333 self.assertIn(fp, fingerprints)
334 for fp in fingerprints:
335 self.assertIn(fp, keys_by_fp)
336
337
292class TestWALinuxAgentShim(CiTestCase):338class TestWALinuxAgentShim(CiTestCase):
293339
294 def setUp(self):340 def setUp(self):
@@ -329,18 +375,31 @@ class TestWALinuxAgentShim(CiTestCase):
329375
330 def test_certificates_used_to_determine_public_keys(self):376 def test_certificates_used_to_determine_public_keys(self):
331 shim = wa_shim()377 shim = wa_shim()
332 data = shim.register_with_azure_and_fetch_data()378 """if register_with_azure_and_fetch_data() isn't passed some info about
379 the user's public keys, there's no point in even trying to parse
380 the certificates
381 """
382 mypk = [{'fingerprint': 'fp1', 'path': 'path1'},
383 {'fingerprint': 'fp3', 'path': 'path3', 'value': ''}]
384 certs = {'fp1': 'expected-key',
385 'fp2': 'should-not-be-found',
386 'fp3': 'expected-no-value-key',
387 }
388 sslmgr = self.OpenSSLManager.return_value
389 sslmgr.parse_certificates.return_value = certs
390 data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk)
333 self.assertEqual(391 self.assertEqual(
334 [mock.call(self.GoalState.return_value.certificates_xml)],392 [mock.call(self.GoalState.return_value.certificates_xml)],
335 self.OpenSSLManager.return_value.parse_certificates.call_args_list)393 sslmgr.parse_certificates.call_args_list)
336 self.assertEqual(394 self.assertIn('expected-key', data['public-keys'])
337 self.OpenSSLManager.return_value.parse_certificates.return_value,395 self.assertIn('expected-no-value-key', data['public-keys'])
338 data['public-keys'])396 self.assertNotIn('should-not-be-found', data['public-keys'])
339397
340 def test_absent_certificates_produces_empty_public_keys(self):398 def test_absent_certificates_produces_empty_public_keys(self):
399 mypk = [{'fingerprint': 'fp1', 'path': 'path1'}]
341 self.GoalState.return_value.certificates_xml = None400 self.GoalState.return_value.certificates_xml = None
342 shim = wa_shim()401 shim = wa_shim()
343 data = shim.register_with_azure_and_fetch_data()402 data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk)
344 self.assertEqual([], data['public-keys'])403 self.assertEqual([], data['public-keys'])
345404
346 def test_correct_url_used_for_report_ready(self):405 def test_correct_url_used_for_report_ready(self):

Subscribers

People subscribed via source and target branches