Added metadata service encryption key retrieval

The encryption public key, if existent, will be used to encrypt the
user password to be sent to the metadata service.
By default, in case of OpenStack, the first public key set by the user
will be used to encrypt the user password.

This special method is needed by metadata services implementations
which will not rely on the same behaviour, as the the encryption key
can be set at another metadata endpoint.

Partially implements: blueprint packet-metadata-suport

Co-Authored-By: Paula Madalina Crismaru <pcrismaru@cloudbasesolutions.com>

Change-Id: I8bfde766d74dcd09e659d09a33a12f6f50b36ed2
This commit is contained in:
Adrian Vladu 2017-06-20 13:54:00 +03:00
parent 526d939240
commit 8dd0d9dbc3
4 changed files with 26 additions and 30 deletions

View File

@ -110,6 +110,19 @@ class BaseMetadataService(object):
"""Get a list of space-stripped strings as public keys.""" """Get a list of space-stripped strings as public keys."""
pass pass
def get_user_pwd_encryption_key(self):
"""Get the user password encryption public key as a string.
The encryption public key, if existent, will be used to encrypt the
user password to be sent to the metadata service.
By default, the first public key set by the user
will be used to encrypt the user password.
"""
public_keys = self.get_public_keys()
if public_keys:
return list(public_keys)[0]
def get_network_details(self): def get_network_details(self):
"""Return a list of `NetworkDetails` objects. """Return a list of `NetworkDetails` objects.

View File

@ -36,11 +36,6 @@ class SetUserPasswordPlugin(base.BasePlugin):
enc_password = rsa.public_encrypt(password.encode()) enc_password = rsa.public_encrypt(password.encode())
return base64.b64encode(enc_password) return base64.b64encode(enc_password)
def _get_ssh_public_key(self, service):
public_keys = service.get_public_keys()
if public_keys:
return list(public_keys)[0]
def _get_password(self, service, shared_data): def _get_password(self, service, shared_data):
injected = False injected = False
if CONF.inject_user_password: if CONF.inject_user_password:
@ -63,10 +58,10 @@ class SetUserPasswordPlugin(base.BasePlugin):
'and it cannot be updated in the instance metadata') 'and it cannot be updated in the instance metadata')
return True return True
else: else:
ssh_pub_key = self._get_ssh_public_key(service) user_pwd_encryption_key = service.get_user_pwd_encryption_key()
if ssh_pub_key: if user_pwd_encryption_key:
enc_password_b64 = self._encrypt_password(ssh_pub_key, enc_password_b64 = self._encrypt_password(
password) user_pwd_encryption_key, password)
return service.post_password(enc_password_b64) return service.post_password(enc_password_b64)
else: else:
LOG.info('No SSH public key available for password encryption') LOG.info('No SSH public key available for password encryption')

View File

@ -54,6 +54,13 @@ class TestBase(unittest.TestCase):
def test_is_password_changed(self): def test_is_password_changed(self):
self.assertFalse(self._service.is_password_changed()) self.assertFalse(self._service.is_password_changed())
@mock.patch('cloudbaseinit.metadata.services.base.'
'BaseMetadataService.get_public_keys')
def test_get_user_pwd_encryption_key(self, mock_get_public_keys):
mock_get_public_keys.return_value = ['fake', 'keys']
result = self._service.get_user_pwd_encryption_key()
self.assertEqual(result, mock_get_public_keys.return_value[0])
class TestBaseHTTPMetadataService(unittest.TestCase): class TestBaseHTTPMetadataService(unittest.TestCase):

View File

@ -57,22 +57,6 @@ class SetUserPasswordPluginTests(unittest.TestCase):
mock_b64encode.assert_called_with('public encrypted') mock_b64encode.assert_called_with('public encrypted')
self.assertEqual('encrypted password', response) self.assertEqual('encrypted password', response)
def _test_get_ssh_public_key(self, data_exists):
mock_service = mock.MagicMock()
public_keys = self.fake_data['public_keys']
mock_service.get_public_keys.return_value = public_keys.values()
response = self._setpassword_plugin._get_ssh_public_key(mock_service)
mock_service.get_public_keys.assert_called_with()
self.assertEqual(list(public_keys.values())[0], response)
def test_get_ssh_plublic_key(self):
self._test_get_ssh_public_key(data_exists=True)
def test_get_ssh_plublic_key_no_pub_keys(self):
self._test_get_ssh_public_key(data_exists=False)
def _test_get_password(self, inject_password): def _test_get_password(self, inject_password):
shared_data = {} shared_data = {}
expected_password = 'Passw0rd' expected_password = 'Passw0rd'
@ -112,18 +96,16 @@ class SetUserPasswordPluginTests(unittest.TestCase):
def test_get_password_inject_false(self): def test_get_password_inject_false(self):
self._test_get_password(inject_password=False) self._test_get_password(inject_password=False)
@mock.patch('cloudbaseinit.plugins.common.setuserpassword.'
'SetUserPasswordPlugin._get_ssh_public_key')
@mock.patch('cloudbaseinit.plugins.common.setuserpassword.' @mock.patch('cloudbaseinit.plugins.common.setuserpassword.'
'SetUserPasswordPlugin._encrypt_password') 'SetUserPasswordPlugin._encrypt_password')
def _test_set_metadata_password(self, mock_encrypt_password, def _test_set_metadata_password(self, mock_encrypt_password,
mock_get_key, ssh_pub_key): ssh_pub_key):
fake_passw0rd = 'fake Passw0rd' fake_passw0rd = 'fake Passw0rd'
mock_service = mock.MagicMock() mock_service = mock.MagicMock()
mock_get_key.return_value = ssh_pub_key
mock_encrypt_password.return_value = 'encrypted password' mock_encrypt_password.return_value = 'encrypted password'
mock_service.post_password.return_value = 'value' mock_service.post_password.return_value = 'value'
mock_service.can_post_password = True mock_service.can_post_password = True
mock_service.get_user_pwd_encryption_key.return_value = ssh_pub_key
mock_service.is_password_set = False mock_service.is_password_set = False
with testutils.LogSnatcher('cloudbaseinit.plugins.common.' with testutils.LogSnatcher('cloudbaseinit.plugins.common.'
'setuserpassword') as snatcher: 'setuserpassword') as snatcher:
@ -137,7 +119,6 @@ class SetUserPasswordPluginTests(unittest.TestCase):
] ]
self.assertTrue(response) self.assertTrue(response)
else: else:
mock_get_key.assert_called_once_with(mock_service)
mock_encrypt_password.assert_called_once_with(ssh_pub_key, mock_encrypt_password.assert_called_once_with(ssh_pub_key,
fake_passw0rd) fake_passw0rd)
mock_service.post_password.assert_called_with( mock_service.post_password.assert_called_with(