Merge "support custom admin user and user auth"

This commit is contained in:
Zuul 2018-09-26 09:55:08 +00:00 committed by Gerrit Code Review
commit 9198755128
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 Valid options are "cephx" and "none". If "none" is specified, keys will
still be created and deployed so that it can be enabled later. 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

@ -32,6 +32,7 @@ from charmhelpers.core.hookenv import (
cached, cached,
status_set, status_set,
WARNING, WARNING,
config,
) )
from charmhelpers.fetch import ( from charmhelpers.fetch import (
apt_cache apt_cache
@ -371,14 +372,30 @@ def get_upgrade_key():
return get_named_key('upgrade-osd', _upgrade_caps) 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): 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 caps = caps or _default_caps
cmd = [ cmd = [
"sudo", "sudo",
"-u", "-u",
ceph_user(), ceph_user(),
'ceph', 'ceph',
'--name', 'client.admin', '--name', config('admin-user'),
'--keyring', '--keyring',
'/var/lib/ceph/mon/ceph-{}/keyring'.format( '/var/lib/ceph/mon/ceph-{}/keyring'.format(
get_unit_hostname() get_unit_hostname()

View File

@ -86,11 +86,16 @@ def emit_cephconf():
render('ceph.conf', charm_ceph_conf, cephcontext, perms=0o644) render('ceph.conf', charm_ceph_conf, cephcontext, perms=0o644)
install_alternative('ceph.conf', '/etc/ceph/ceph.conf', install_alternative('ceph.conf', '/etc/ceph/ceph.conf',
charm_ceph_conf, 100) 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 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() 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 = 'keyring'
keyring_path = ( keyring_path = (

View File

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

View File

@ -1,3 +1,3 @@
[client.admin] [{{ admin_user }}]
key = {{admin_key}} 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('monitor-hosts', '127.0.0.1:1234')
self.test_config.set('fsid', 'abc123') self.test_config.set('fsid', 'abc123')
self.test_config.set('admin-key', 'key123') self.test_config.set('admin-key', 'key123')
self.test_config.set('admin-user', 'client.myadmin')
def c(k): def c(k):
x = {'radosgw': ['rados:1'], x = {'radosgw': ['rados:1'],
@ -105,10 +106,15 @@ class TestHooks(test_utils.CharmTestCase):
'/etc/ceph/ceph.conf', '/etc/ceph/ceph.conf',
'%s/ceph.conf' % dirname, '%s/ceph.conf' % dirname,
100) 100)
keyring = 'ceph.client.admin.keyring' keyring_template = 'ceph.keyring'
context = {'admin_key': self.test_config.get('admin-key')} keyring_name = 'ceph.{}.keyring'.format(
self.render.assert_any_call(keyring, self.test_config.get('admin-user'))
'/etc/ceph/' + keyring, 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) context, owner='ceph-user', perms=0o600)
mock_rgw_rel.assert_called_with(relid='rados:1', unit='rados/1') mock_rgw_rel.assert_called_with(relid='rados:1', unit='rados/1')