[caribou,r=james-page,t=james-page] SSH key exchange in large environments

In large OpenStack deployments, the length of the base64 encoded SSH keys and authorized hosts file exceeds the maximum command line parameter length.

Pass keys/lines individually to work around this issue.
This commit is contained in:
james.page@ubuntu.com 2014-06-23 13:14:21 +01:00
commit e7018eea6d
3 changed files with 89 additions and 35 deletions

View File

@ -25,7 +25,7 @@ options:
rabbit-vhost:
default: openstack
type: string
decsription: Rabbitmq vhost
description: Rabbitmq vhost
use-syslog:
type: boolean
default: False

View File

@ -337,26 +337,46 @@ def import_authorized_keys(user='root', prefix=None):
"""Import SSH authorized_keys + known_hosts from a cloud-compute relation
and store in user's $HOME/.ssh.
"""
known_hosts = []
authorized_keys = []
if prefix:
hosts = relation_get('{}_known_hosts'.format(prefix))
auth_keys = relation_get('{}_authorized_keys'.format(prefix))
known_hosts_index = relation_get(
'{}_known_hosts_max_index'.format(prefix))
if known_hosts_index:
for index in range(0, int(known_hosts_index)):
known_hosts.append(relation_get(
'{}_known_hosts_{}'.format(prefix, index)))
authorized_keys_index = relation_get(
'{}_authorized_keys_max_index'.format(prefix))
if authorized_keys_index:
for index in range(0, int(authorized_keys_index)):
authorized_keys.append(relation_get(
'{}_authorized_keys_{}'.format(prefix, index)))
else:
# XXX: Should this be managed via templates + contexts?
hosts = relation_get('known_hosts')
auth_keys = relation_get('authorized_keys')
known_hosts_index = relation_get('known_hosts_max_index')
if known_hosts_index:
for index in range(0, int(known_hosts_index)):
known_hosts.append(relation_get(
'known_hosts_{}'.format(index)))
authorized_keys_index = relation_get('authorized_keys_max_index')
if authorized_keys_index:
for index in range(0, int(authorized_keys_index)):
authorized_keys.append(relation_get(
'authorized_keys_{}'.format(index)))
# XXX: Need to fix charm-helpers to return None for empty settings,
# in all cases.
if not hosts or not auth_keys:
# XXX: Should partial return of known_hosts or authorized_keys
# be allowed ?
if not len(known_hosts) or not len(authorized_keys):
return
dest = os.path.join(pwd.getpwnam(user).pw_dir, '.ssh')
log('Saving new known_hosts and authorized_keys file to: %s.' % dest)
with open(os.path.join(dest, 'authorized_keys'), 'wb') as _keys:
_keys.write(b64decode(auth_keys))
with open(os.path.join(dest, 'known_hosts'), 'wb') as _hosts:
_hosts.write(b64decode(hosts))
for index in range(0, int(known_hosts_index)):
_hosts.write('{}\n'.format(known_hosts[index]))
with open(os.path.join(dest, 'authorized_keys'), 'wb') as _keys:
for index in range(0, int(authorized_keys_index)):
_keys.write('{}\n'.format(authorized_keys[index]))
def do_openstack_upgrade():

View File

@ -185,55 +185,89 @@ class NovaComputeUtilsTests(CharmTestCase):
def _test_import_authorized_keys_base(self, getpwnam, prefix=None):
getpwnam.return_value = self.fake_user('foo')
self.relation_get.side_effect = [
'Zm9vX2tleQo=', # relation_get('known_hosts')
'Zm9vX2hvc3QK', # relation_get('authorized_keys')
3, # relation_get('known_hosts_max_index')
'k_h_0', # relation_get_('known_hosts_0')
'k_h_1', # relation_get_('known_hosts_1')
'k_h_2', # relation_get_('known_hosts_2')
3, # relation_get('authorized_keys_max_index')
'auth_0', # relation_get('authorized_keys_0')
'auth_1', # relation_get('authorized_keys_1')
'auth_2', # relation_get('authorized_keys_2')
]
ex_open = [
call('/home/foo/.ssh/authorized_keys', 'wb'),
call('/home/foo/.ssh/known_hosts', 'wb')
call('/home/foo/.ssh/known_hosts', 'wb'),
call('/home/foo/.ssh/authorized_keys', 'wb')
]
ex_write = [
call('foo_host\n'),
call('foo_key\n'),
call('k_h_0\n'),
call('k_h_1\n'),
call('k_h_2\n'),
call('auth_0\n'),
call('auth_1\n'),
call('auth_2\n')
]
with patch_open() as (_open, _file):
utils.import_authorized_keys(user='foo')
self.assertEquals(ex_open, _open.call_args_list)
self.assertEquals(ex_write, _file.write.call_args_list)
self.relation_get.assert_has_called([
call('known_hosts').
call('authorized_keys')
])
expected_relations = [
call('known_hosts_max_index'),
call('known_hosts_0'),
call('known_hosts_1'),
call('known_hosts_2'),
call('authorized_keys_max_index'),
call('authorized_keys_0'),
call('authorized_keys_1'),
call('authorized_keys_2')
]
self.assertEquals(sorted(self.relation_get.call_args_list),
sorted(expected_relations))
@patch('pwd.getpwnam')
def test_import_authorized_keys_prefix(self, getpwnam):
getpwnam.return_value = self.fake_user('foo')
self.relation_get.side_effect = [
'Zm9vX2tleQo=', # relation_get('known_hosts')
'Zm9vX2hvc3QK', # relation_get('authorized_keys')
3, # relation_get('bar_known_hosts_max_index')
'k_h_0', # relation_get_('bar_known_hosts_0')
'k_h_1', # relation_get_('bar_known_hosts_1')
'k_h_2', # relation_get_('bar_known_hosts_2')
3, # relation_get('bar_authorized_keys_max_index')
'auth_0', # relation_get('bar_authorized_keys_0')
'auth_1', # relation_get('bar_authorized_keys_1')
'auth_2', # relation_get('bar_authorized_keys_2')
]
ex_open = [
call('/home/foo/.ssh/authorized_keys', 'wb'),
call('/home/foo/.ssh/known_hosts', 'wb')
call('/home/foo/.ssh/known_hosts', 'wb'),
call('/home/foo/.ssh/authorized_keys', 'wb')
]
ex_write = [
call('foo_host\n'),
call('foo_key\n'),
call('k_h_0\n'),
call('k_h_1\n'),
call('k_h_2\n'),
call('auth_0\n'),
call('auth_1\n'),
call('auth_2\n')
]
with patch_open() as (_open, _file):
utils.import_authorized_keys(user='foo', prefix='bar')
self.assertEquals(ex_open, _open.call_args_list)
self.assertEquals(ex_write, _file.write.call_args_list)
self.relation_get.assert_has_called([
call('bar_known_hosts').
call('bar_authorized_keys')
])
expected_relations = [
call('bar_known_hosts_max_index'),
call('bar_known_hosts_0'),
call('bar_known_hosts_1'),
call('bar_known_hosts_2'),
call('bar_authorized_keys_max_index'),
call('bar_authorized_keys_0'),
call('bar_authorized_keys_1'),
call('bar_authorized_keys_2')
]
self.assertEquals(sorted(self.relation_get.call_args_list),
sorted(expected_relations))
@patch('subprocess.check_call')
def test_import_keystone_cert_missing_data(self, check_call):