This patch does not change the options in config file yet to showcase backward compatibility with old config options. Change-Id: I1da93b59b2f4813c42008277bd6479dc6673e7f1changes/10/286510/12
parent
a12d1af680
commit
35f332539d
@ -0,0 +1,129 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
|
||||
from keystoneauth1 import exceptions
|
||||
from keystoneauth1 import loading
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from six.moves.urllib import parse # for legacy options loading only
|
||||
|
||||
from ironic_inspector.common.i18n import _LW
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def register_auth_opts(group):
|
||||
loading.register_session_conf_options(CONF, group)
|
||||
loading.register_auth_conf_options(CONF, group)
|
||||
CONF.set_default('auth_type', default='password', group=group)
|
||||
|
||||
|
||||
def get_session(group, legacy_mapping=None, legacy_auth_opts=None):
|
||||
auth = _get_auth(group, legacy_mapping, legacy_auth_opts)
|
||||
session = loading.load_session_from_conf_options(
|
||||
CONF, group, auth=auth)
|
||||
return session
|
||||
|
||||
|
||||
def _get_auth(group, legacy_mapping=None, legacy_opts=None):
|
||||
try:
|
||||
auth = loading.load_auth_from_conf_options(CONF, group)
|
||||
except exceptions.MissingRequiredOptions:
|
||||
auth = _get_legacy_auth(group, legacy_mapping, legacy_opts)
|
||||
else:
|
||||
if auth is None:
|
||||
auth = _get_legacy_auth(group, legacy_mapping, legacy_opts)
|
||||
return auth
|
||||
|
||||
|
||||
def _get_legacy_auth(group, legacy_mapping, legacy_opts):
|
||||
"""Load auth plugin from legacy options.
|
||||
|
||||
If legacy_opts is not empty, these options will be registered first.
|
||||
|
||||
legacy_mapping is a dict that maps the following keys to legacy option
|
||||
names:
|
||||
auth_url
|
||||
username
|
||||
password
|
||||
tenant_name
|
||||
"""
|
||||
LOG.warning(_LW("Group [%s]: Using legacy auth loader is deprecated. "
|
||||
"Consider specifying appropriate keystone auth plugin as "
|
||||
"'auth_type' and corresponding plugin options."), group)
|
||||
if legacy_opts:
|
||||
for opt in legacy_opts:
|
||||
try:
|
||||
CONF.register_opt(opt, group=group)
|
||||
except cfg.DuplicateOptError:
|
||||
pass
|
||||
|
||||
conf = getattr(CONF, group)
|
||||
auth_params = {a: getattr(conf, legacy_mapping[a])
|
||||
for a in legacy_mapping}
|
||||
legacy_loader = loading.get_plugin_loader('password')
|
||||
# NOTE(pas-ha) only Swift had this option, take it into account
|
||||
try:
|
||||
auth_version = conf.get('os_auth_version')
|
||||
except cfg.NoSuchOptError:
|
||||
auth_version = None
|
||||
# NOTE(pas-ha) mimic defaults of keystoneclient
|
||||
if _is_apiv3(auth_params['auth_url'], auth_version):
|
||||
auth_params.update({
|
||||
'project_domain_id': 'default',
|
||||
'user_domain_id': 'default'})
|
||||
return legacy_loader.load_from_options(**auth_params)
|
||||
|
||||
|
||||
# NOTE(pas-ha): for backward compat with legacy options loading only
|
||||
def _is_apiv3(auth_url, auth_version):
|
||||
"""Check if V3 version of API is being used or not.
|
||||
|
||||
This method inspects auth_url and auth_version, and checks whether V3
|
||||
version of the API is being used or not.
|
||||
When no auth_version is specified and auth_url is not a versioned
|
||||
endpoint, v2.0 is assumed.
|
||||
:param auth_url: a http or https url to be inspected (like
|
||||
'http://127.0.0.1:9898/').
|
||||
:param auth_version: a string containing the version (like 'v2', 'v3.0')
|
||||
or None
|
||||
:returns: True if V3 of the API is being used.
|
||||
"""
|
||||
return (auth_version in ('v3.0', '3') or
|
||||
'/v3' in parse.urlparse(auth_url).path)
|
||||
|
||||
|
||||
def add_auth_options(options, group):
|
||||
|
||||
def add_options(opts, opts_to_add):
|
||||
for new_opt in opts_to_add:
|
||||
for opt in opts:
|
||||
if opt.name == new_opt.name:
|
||||
break
|
||||
else:
|
||||
opts.append(new_opt)
|
||||
|
||||
opts = copy.deepcopy(options)
|
||||
opts.insert(0, loading.get_auth_common_conf_options()[0])
|
||||
# NOTE(dims): There are a lot of auth plugins, we just generate
|
||||
# the config options for a few common ones
|
||||
plugins = ['password', 'v2password', 'v3password']
|
||||
for name in plugins:
|
||||
plugin = loading.get_plugin_loader(name)
|
||||
add_options(opts, loading.get_auth_plugin_conf_options(plugin))
|
||||
add_options(opts, loading.get_session_conf_options())
|
||||
opts.sort(key=lambda x: x.name)
|
||||
return [(group, opts)]
|
@ -0,0 +1,115 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from keystoneauth1 import exceptions as kaexc
|
||||
from keystoneauth1 import loading as kaloading
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic_inspector.common import keystone
|
||||
from ironic_inspector.test import base
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
TESTGROUP = 'keystone_test'
|
||||
|
||||
|
||||
class KeystoneTest(base.BaseTest):
|
||||
|
||||
def setUp(self):
|
||||
super(KeystoneTest, self).setUp()
|
||||
self.cfg.conf.register_group(cfg.OptGroup(TESTGROUP))
|
||||
|
||||
def test_register_auth_opts(self):
|
||||
keystone.register_auth_opts(TESTGROUP)
|
||||
auth_opts = ['auth_type', 'auth_section']
|
||||
sess_opts = ['certfile', 'keyfile', 'insecure', 'timeout', 'cafile']
|
||||
for o in auth_opts + sess_opts:
|
||||
self.assertIn(o, self.cfg.conf[TESTGROUP])
|
||||
self.assertEqual('password', self.cfg.conf[TESTGROUP]['auth_type'])
|
||||
|
||||
@mock.patch.object(keystone, '_get_auth')
|
||||
def test_get_session(self, auth_mock):
|
||||
keystone.register_auth_opts(TESTGROUP)
|
||||
self.cfg.config(group=TESTGROUP,
|
||||
cafile='/path/to/ca/file')
|
||||
auth1 = mock.Mock()
|
||||
auth_mock.return_value = auth1
|
||||
sess = keystone.get_session(TESTGROUP)
|
||||
self.assertEqual('/path/to/ca/file', sess.verify)
|
||||
self.assertEqual(auth1, sess.auth)
|
||||
|
||||
@mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
|
||||
@mock.patch.object(keystone, '_get_legacy_auth')
|
||||
def test__get_auth(self, legacy_mock, load_mock):
|
||||
auth1 = mock.Mock()
|
||||
load_mock.side_effect = [
|
||||
auth1,
|
||||
None,
|
||||
kaexc.MissingRequiredOptions([kaloading.Opt('spam')])]
|
||||
auth2 = mock.Mock()
|
||||
legacy_mock.return_value = auth2
|
||||
self.assertEqual(auth1, keystone._get_auth(TESTGROUP))
|
||||
self.assertEqual(auth2, keystone._get_auth(TESTGROUP))
|
||||
self.assertEqual(auth2, keystone._get_auth(TESTGROUP))
|
||||
|
||||
@mock.patch('keystoneauth1.loading._plugins.identity.generic.Password.'
|
||||
'load_from_options')
|
||||
def test__get_legacy_auth(self, load_mock):
|
||||
self.cfg.register_opts(
|
||||
[cfg.StrOpt('identity_url'),
|
||||
cfg.StrOpt('old_user'),
|
||||
cfg.StrOpt('old_password')],
|
||||
group=TESTGROUP)
|
||||
self.cfg.config(group=TESTGROUP,
|
||||
identity_url='http://fake:5000/v3',
|
||||
old_password='ham',
|
||||
old_user='spam')
|
||||
options = [cfg.StrOpt('old_tenant_name', default='fake'),
|
||||
cfg.StrOpt('old_user')]
|
||||
mapping = {'username': 'old_user',
|
||||
'password': 'old_password',
|
||||
'auth_url': 'identity_url',
|
||||
'tenant_name': 'old_tenant_name'}
|
||||
|
||||
keystone._get_legacy_auth(TESTGROUP, mapping, options)
|
||||
load_mock.assert_called_once_with(username='spam',
|
||||
password='ham',
|
||||
tenant_name='fake',
|
||||
user_domain_id='default',
|
||||
project_domain_id='default',
|
||||
auth_url='http://fake:5000/v3')
|
||||
|
||||
def test__is_api_v3(self):
|
||||
cases = ((False, 'http://fake:5000', None),
|
||||
(False, 'http://fake:5000/v2.0', None),
|
||||
(True, 'http://fake:5000/v3', None),
|
||||
(True, 'http://fake:5000', '3'),
|
||||
(True, 'http://fake:5000', 'v3.0'))
|
||||
for case in cases:
|
||||
result, url, version = case
|
||||
self.assertEqual(result, keystone._is_apiv3(url, version))
|
||||
|
||||
def test_add_auth_options(self):
|
||||
group, opts = keystone.add_auth_options([], TESTGROUP)[0]
|
||||
self.assertEqual(TESTGROUP, group)
|
||||
# check that there is no duplicates
|
||||
names = {o.dest for o in opts}
|
||||
self.assertEqual(len(names), len(opts))
|
||||
# NOTE(pas-ha) checking for most standard auth and session ones only
|
||||
expected = {'timeout', 'insecure', 'cafile', 'certfile', 'keyfile',
|
||||
'auth_type', 'auth_url', 'username', 'password',
|
||||
'tenant_name', 'project_name', 'trust_id',
|
||||
'domain_id', 'user_domain_id', 'project_domain_id'}
|
||||
self.assertTrue(expected.issubset(names))
|
@ -0,0 +1,17 @@
|
||||
---
|
||||
features:
|
||||
- Ironic-Inspector is now using keystoneauth and proper auth_plugins
|
||||
instead of keystoneclient for communicating with Ironic and Swift.
|
||||
It allows to finely tune authentification for each service independently.
|
||||
For each service, the keystone session is created and reused, minimizing
|
||||
the number of authentification requests to Keystone.
|
||||
upgrade:
|
||||
- Operators are advised to specify a proper keystoneauth plugin
|
||||
and its appropriate settings in [ironic] and [swift] config sections.
|
||||
Backward compatibility with previous authentification options is included.
|
||||
Using authentification informaiton for Ironic and Swift from
|
||||
[keystone_authtoken] config section is no longer supported.
|
||||
deprecations:
|
||||
- Most of current authentification options for either Ironic or Swift are
|
||||
deprecated and will be removed in a future release. Please configure
|
||||
the keystoneauth auth plugin authentification instead.
|
Loading…
Reference in new issue