support custom admin user and user auth
In order to support cases where pools and keys are pre-created and ceph-proxy just proxies this data to client applications this change introduces support for: * having custom "admin" users which may not actually have admin privileges on the target cluster (client.admin is probably occupied by real admins in this case); * using cephx keys provided via charm config. Change-Id: I01014b6986f92bf0ad8147a08afa1d61fdd5c088 Closes-bug: #1793991
This commit is contained in:
parent
df29b5780f
commit
81383a160b
15
config.yaml
15
config.yaml
@ -59,3 +59,18 @@ options:
|
||||
.
|
||||
Valid options are "cephx" and "none". If "none" is specified, keys will
|
||||
still be created and deployed so that it can be enabled later.
|
||||
user-keys:
|
||||
type: string
|
||||
default: ""
|
||||
description: |
|
||||
A space-separated list of <username>:<cephx-base64-key> pairs used to
|
||||
lookup authentication keys for a specific user instead of trying to
|
||||
create a user and a key via ceph-mon.
|
||||
admin-user:
|
||||
type: string
|
||||
default: "client.admin"
|
||||
description: |
|
||||
A configurable admin user name. Used for scenarios where pools are
|
||||
pre-created and the user given to charm-ceph-proxy simply needs to
|
||||
check the existence of a given pool and error out if one does not
|
||||
exist. Can be used in conjunction with user-keys.
|
||||
|
@ -31,6 +31,7 @@ from charmhelpers.core.hookenv import (
|
||||
cached,
|
||||
status_set,
|
||||
WARNING,
|
||||
config,
|
||||
)
|
||||
from charmhelpers.fetch import (
|
||||
apt_cache
|
||||
@ -369,14 +370,30 @@ def get_upgrade_key():
|
||||
return get_named_key('upgrade-osd', _upgrade_caps)
|
||||
|
||||
|
||||
def _config_user_key(name):
|
||||
user_keys_list = config('user-keys')
|
||||
if user_keys_list:
|
||||
for ukpair in user_keys_list.split(' '):
|
||||
uk = ukpair.split(':')
|
||||
if len(uk) == 2:
|
||||
user_type, k = uk
|
||||
t, u = user_type.split('.')
|
||||
if u == name:
|
||||
return k
|
||||
|
||||
|
||||
def get_named_key(name, caps=None):
|
||||
config_user_key = _config_user_key(name)
|
||||
if config_user_key:
|
||||
return config_user_key
|
||||
|
||||
caps = caps or _default_caps
|
||||
cmd = [
|
||||
"sudo",
|
||||
"-u",
|
||||
ceph_user(),
|
||||
'ceph',
|
||||
'--name', 'client.admin',
|
||||
'--name', config('admin-user'),
|
||||
'--keyring',
|
||||
'/var/lib/ceph/mon/ceph-{}/keyring'.format(
|
||||
get_unit_hostname()
|
||||
|
@ -86,11 +86,16 @@ def emit_cephconf():
|
||||
render('ceph.conf', charm_ceph_conf, cephcontext, perms=0o644)
|
||||
install_alternative('ceph.conf', '/etc/ceph/ceph.conf',
|
||||
charm_ceph_conf, 100)
|
||||
keyring = 'ceph.client.admin.keyring'
|
||||
|
||||
keyring_template = 'ceph.keyring'
|
||||
keyring = 'ceph.{}.keyring'.format(config('admin-user'))
|
||||
keyring_path = '/etc/ceph/' + keyring
|
||||
ctx = {'admin_key': config('admin-key')}
|
||||
ctx = {
|
||||
'admin_key': config('admin-key'),
|
||||
'admin_user': config('admin-user'),
|
||||
}
|
||||
user = ceph.ceph_user()
|
||||
render(keyring, keyring_path, ctx, owner=user, perms=0o600)
|
||||
render(keyring_template, keyring_path, ctx, owner=user, perms=0o600)
|
||||
|
||||
keyring = 'keyring'
|
||||
keyring_path = (
|
||||
|
@ -1,3 +1,3 @@
|
||||
[client.admin]
|
||||
[{{ admin_user }}]
|
||||
key = {{admin_key}}
|
||||
|
@ -1,3 +1,3 @@
|
||||
[client.admin]
|
||||
[{{ admin_user }}]
|
||||
key = {{admin_key}}
|
||||
|
||||
|
95
unit_tests/test_ceph.py
Normal file
95
unit_tests/test_ceph.py
Normal file
@ -0,0 +1,95 @@
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
import ceph
|
||||
|
||||
|
||||
class CephTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(CephTestCase, self).setUp()
|
||||
|
||||
@staticmethod
|
||||
def populated_config_side_effect(key):
|
||||
return {
|
||||
'user-keys':
|
||||
'client.cinder-ceph:AQAij2tbMNjMOhAAqInpXQLFrltDgmYid6KXbg== '
|
||||
'client.glance:AQCnjmtbuEACMxAA7joUmgLIGI4/3LKkPzUy8g== '
|
||||
'client.gnocchi:AQDk7qJb0csAFRAAQqPU6HchVW3PT6ymgXdI/A== '
|
||||
'client.nova-compute-kvm:'
|
||||
'AQBkjmtb1hWxLxAA3UhxSblgFSCtHVoZ8W6rNQ== '
|
||||
'client.radosgw.gateway:'
|
||||
'AQBljmtb65mrHhAAGy9VRkfsatWVLb9EpoWDfw==',
|
||||
'admin-user': 'client.myadmin'
|
||||
}[key]
|
||||
|
||||
@staticmethod
|
||||
def empty_config_side_effect(key):
|
||||
return {
|
||||
'user-keys': '',
|
||||
'admin-user': 'client.myadmin'
|
||||
}[key]
|
||||
|
||||
@mock.patch('ceph.config')
|
||||
def test_config_user_key_populated(self, mock_config):
|
||||
user_name = 'glance'
|
||||
user_key = 'AQCnjmtbuEACMxAA7joUmgLIGI4/3LKkPzUy8g=='
|
||||
|
||||
mock_config.side_effect = self.populated_config_side_effect
|
||||
named_key = ceph._config_user_key(user_name)
|
||||
self.assertEqual(user_key, named_key)
|
||||
|
||||
@mock.patch('ceph.config')
|
||||
def test_config_empty_user_key(self, mock_config):
|
||||
user_name = 'cinder-ceph'
|
||||
|
||||
mock_config.side_effect = self.empty_config_side_effect
|
||||
named_key = ceph._config_user_key(user_name)
|
||||
self.assertEqual(named_key, None)
|
||||
|
||||
@mock.patch('ceph.config')
|
||||
def test_get_named_key_populated(self, mock_config):
|
||||
user_name = 'glance'
|
||||
user_key = 'AQCnjmtbuEACMxAA7joUmgLIGI4/3LKkPzUy8g=='
|
||||
|
||||
mock_config.side_effect = self.populated_config_side_effect
|
||||
named_key = ceph.get_named_key(user_name)
|
||||
|
||||
self.assertEqual(user_key, named_key)
|
||||
|
||||
@mock.patch('subprocess.check_output')
|
||||
@mock.patch('ceph.get_unit_hostname')
|
||||
@mock.patch('ceph.ceph_user')
|
||||
@mock.patch('ceph.config')
|
||||
def test_get_named_key_empty(self, mock_config, mock_ceph_user,
|
||||
mock_get_unit_hostname, mock_check_output):
|
||||
user_name = 'cinder-ceph'
|
||||
user_type = 'client'
|
||||
admin_user = 'client.myadmin'
|
||||
user_spec = '{}.{}'.format(user_type, user_name)
|
||||
expected_key = 'AQCnjmtbuEACMxAA7joUmgLIGI4/3LKkPzUy8g=='
|
||||
expected_output = ('[client.testuser]\n key = {}'
|
||||
.format(expected_key))
|
||||
caps = {
|
||||
'mon': ['allow rw'],
|
||||
'osd': ['allow rwx']
|
||||
}
|
||||
ceph_user = 'ceph'
|
||||
ceph_proxy_host = 'cephproxy'
|
||||
mock_get_unit_hostname.return_value = ceph_proxy_host
|
||||
|
||||
def check_output_side_effect(cmd):
|
||||
return {
|
||||
' '.join(['sudo', '-u', ceph_user, 'ceph', '--name',
|
||||
admin_user,
|
||||
'--keyring',
|
||||
'/var/lib/ceph/mon/ceph-{}/keyring'.format(
|
||||
ceph_proxy_host),
|
||||
'auth', 'get-or-create', user_spec, 'mon',
|
||||
'allow rw', 'osd', 'allow rwx']): expected_output
|
||||
}[' '.join(cmd)]
|
||||
mock_check_output.side_effect = check_output_side_effect
|
||||
mock_config.side_effect = self.empty_config_side_effect
|
||||
mock_ceph_user.return_value = ceph_user
|
||||
named_key = ceph.get_named_key(user_name, caps)
|
||||
self.assertEqual(named_key, expected_key)
|
@ -76,6 +76,7 @@ class TestHooks(test_utils.CharmTestCase):
|
||||
self.test_config.set('monitor-hosts', '127.0.0.1:1234')
|
||||
self.test_config.set('fsid', 'abc123')
|
||||
self.test_config.set('admin-key', 'key123')
|
||||
self.test_config.set('admin-user', 'client.myadmin')
|
||||
|
||||
def c(k):
|
||||
x = {'radosgw': ['rados:1'],
|
||||
@ -105,10 +106,15 @@ class TestHooks(test_utils.CharmTestCase):
|
||||
'/etc/ceph/ceph.conf',
|
||||
'%s/ceph.conf' % dirname,
|
||||
100)
|
||||
keyring = 'ceph.client.admin.keyring'
|
||||
context = {'admin_key': self.test_config.get('admin-key')}
|
||||
self.render.assert_any_call(keyring,
|
||||
'/etc/ceph/' + keyring,
|
||||
keyring_template = 'ceph.keyring'
|
||||
keyring_name = 'ceph.{}.keyring'.format(
|
||||
self.test_config.get('admin-user'))
|
||||
context = {
|
||||
'admin_key': self.test_config.get('admin-key'),
|
||||
'admin_user': self.test_config.get('admin-user'),
|
||||
}
|
||||
self.render.assert_any_call(keyring_template,
|
||||
'/etc/ceph/' + keyring_name,
|
||||
context, owner='ceph-user', perms=0o600)
|
||||
|
||||
mock_rgw_rel.assert_called_with(relid='rados:1', unit='rados/1')
|
||||
|
Loading…
Reference in New Issue
Block a user