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:
Dmitrii Shcherbakov 2018-09-20 04:31:15 +03:00
parent df29b5780f
commit 81383a160b
7 changed files with 148 additions and 10 deletions

View File

@ -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.

View File

@ -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()

View File

@ -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 = (

View File

@ -1,3 +1,3 @@
[client.admin]
[{{ admin_user }}]
key = {{admin_key}}

View File

@ -1,3 +1,3 @@
[client.admin]
[{{ admin_user }}]
key = {{admin_key}}

95
unit_tests/test_ceph.py Normal file
View 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)

View File

@ -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')