Browse Source

Refactor Keystone client with keystoneauth

This patch does, basically, three things:

* Updates the default auth section to keystone_auth;
* Introduces keystoneauth sessions and plugins;
* Adds a deprecation warning and options when loading
legacy auth.

Config, tests and client code are also updated.

Co-Authored-By: Henrique Truta <henrique@lsd.ufcg.edu.br>
Co-Authored-By: Raildo Mascena <raildo@lsd.ufcg.edu.br>

Closes-Bug: 1496810
Closes-Bug: 1515014
Change-Id: I5c1cd24ca28d66ae7ae40e7f707b81870cf0e457
changes/27/274227/28
Paulo Ewerton 5 years ago
parent
commit
7f145e703c
7 changed files with 199 additions and 153 deletions
  1. +10
    -0
      devstack/lib/magnum
  2. +7
    -7
      magnum/common/clients.py
  3. +94
    -72
      magnum/common/keystone.py
  4. +1
    -0
      magnum/opts.py
  5. +13
    -13
      magnum/tests/unit/common/test_clients.py
  6. +73
    -61
      magnum/tests/unit/common/test_keystone.py
  7. +1
    -0
      requirements.txt

+ 10
- 0
devstack/lib/magnum View File

@ -139,12 +139,22 @@ function create_magnum_conf {
iniset $MAGNUM_CONF oslo_policy policy_file $MAGNUM_POLICY_JSON
iniset $MAGNUM_CONF keystone_auth auth_type password
iniset $MAGNUM_CONF keystone_auth username magnum
iniset $MAGNUM_CONF keystone_auth password $SERVICE_PASSWORD
iniset $MAGNUM_CONF keystone_auth project_name $SERVICE_PROJECT_NAME
iniset $MAGNUM_CONF keystone_auth project_domain_id default
iniset $MAGNUM_CONF keystone_auth user_domain_id default
# FIXME(pauloewerton): keystone_authtoken section is deprecated. Remove it
# after deprecation period.
iniset $MAGNUM_CONF keystone_authtoken admin_user magnum
iniset $MAGNUM_CONF keystone_authtoken admin_password $SERVICE_PASSWORD
iniset $MAGNUM_CONF keystone_authtoken admin_tenant_name $SERVICE_PROJECT_NAME
configure_auth_token_middleware $MAGNUM_CONF magnum $MAGNUM_AUTH_CACHE_DIR
iniset $MAGNUM_CONF keystone_auth auth_url $KEYSTONE_SERVICE_URI/v3
iniset $MAGNUM_CONF keystone_authtoken auth_uri $KEYSTONE_SERVICE_URI/v3
iniset $MAGNUM_CONF keystone_authtoken auth_version v3


+ 7
- 7
magnum/common/clients.py View File

@ -132,13 +132,13 @@ class OpenStackClients(object):
self._neutron = None
def url_for(self, **kwargs):
return self.keystone().client.service_catalog.url_for(**kwargs)
return self.keystone().session.get_endpoint(**kwargs)
def magnum_url(self):
endpoint_type = self._get_client_option('magnum', 'endpoint_type')
region_name = self._get_client_option('magnum', 'region_name')
return self.url_for(service_type='container',
endpoint_type=endpoint_type,
interface=endpoint_type,
region_name=region_name)
def cinder_region_name(self):
@ -172,7 +172,7 @@ class OpenStackClients(object):
region_name = self._get_client_option('heat', 'region_name')
heatclient_version = self._get_client_option('heat', 'api_version')
endpoint = self.url_for(service_type='orchestration',
endpoint_type=endpoint_type,
interface=endpoint_type,
region_name=region_name)
args = {
@ -199,7 +199,7 @@ class OpenStackClients(object):
region_name = self._get_client_option('glance', 'region_name')
glanceclient_version = self._get_client_option('glance', 'api_version')
endpoint = self.url_for(service_type='image',
endpoint_type=endpoint_type,
interface=endpoint_type,
region_name=region_name)
args = {
'endpoint': endpoint,
@ -220,7 +220,7 @@ class OpenStackClients(object):
endpoint_type = self._get_client_option('barbican', 'endpoint_type')
region_name = self._get_client_option('barbican', 'region_name')
endpoint = self.url_for(service_type='key-manager',
endpoint_type=endpoint_type,
interface=endpoint_type,
region_name=region_name)
session = self.keystone().session
self._barbican = barbicanclient.Client(session=session,
@ -236,7 +236,7 @@ class OpenStackClients(object):
region_name = self._get_client_option('nova', 'region_name')
novaclient_version = self._get_client_option('nova', 'api_version')
endpoint = self.url_for(service_type='compute',
endpoint_type=endpoint_type,
interface=endpoint_type,
region_name=region_name)
self._nova = novaclient.Client(novaclient_version,
auth_token=self.auth_token)
@ -250,7 +250,7 @@ class OpenStackClients(object):
endpoint_type = self._get_client_option('neutron', 'endpoint_type')
region_name = self._get_client_option('neutron', 'region_name')
endpoint = self.url_for(service_type='network',
endpoint_type=endpoint_type,
interface=endpoint_type,
region_name=region_name)
args = {


+ 94
- 72
magnum/common/keystone.py View File

@ -10,9 +10,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from keystoneclient.auth.identity import v3
from keystoneauth1.access import access as ka_access
from keystoneauth1 import exceptions as ka_exception
from keystoneauth1.identity import access as ka_access_plugin
from keystoneauth1.identity import v3 as ka_v3
from keystoneauth1 import loading as ka_loading
from keystoneauth1 import session as ka_session
import keystoneclient.exceptions as kc_exception
from keystoneclient import session
from keystoneclient.v3 import client as kc_v3
from oslo_config import cfg
from oslo_log import log as logging
@ -20,9 +24,11 @@ from oslo_log import log as logging
from magnum.common import exception
from magnum.i18n import _
from magnum.i18n import _LE
from magnum.i18n import _LW
CONF = cfg.CONF
CFG_GROUP = 'keystone_auth'
CFG_LEGACY_GROUP = 'keystone_authtoken'
LOG = logging.getLogger(__name__)
trust_opts = [
@ -39,8 +45,25 @@ trust_opts = [
'by the trustor'))
]
legacy_session_opts = {
'certfile': [cfg.DeprecatedOpt('certfile', CFG_LEGACY_GROUP)],
'keyfile': [cfg.DeprecatedOpt('keyfile', CFG_LEGACY_GROUP)],
'cafile': [cfg.DeprecatedOpt('cafile', CFG_LEGACY_GROUP)],
'insecure': [cfg.DeprecatedOpt('insecure', CFG_LEGACY_GROUP)],
'timeout': [cfg.DeprecatedOpt('timeout', CFG_LEGACY_GROUP)],
}
keystone_auth_opts = (ka_loading.get_auth_common_conf_options() +
ka_loading.get_auth_plugin_conf_options('password'))
CONF.register_opts(trust_opts, group='trust')
# FIXME(pauloewerton): remove import of authtoken group and legacy options
# after deprecation period
CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token')
ka_loading.register_auth_conf_options(CONF, CFG_GROUP)
ka_loading.register_session_conf_options(CONF, CFG_GROUP,
deprecated_opts=legacy_session_opts)
CONF.set_default('auth_type', default='password', group=CFG_GROUP)
class KeystoneClientV3(object):
@ -51,100 +74,99 @@ class KeystoneClientV3(object):
self._client = None
self._admin_client = None
self._domain_admin_client = None
self._session = None
@property
def auth_url(self):
v3_auth_url = CONF.keystone_authtoken.auth_uri.replace('v2.0', 'v3')
return v3_auth_url
# FIXME(pauloewerton): auth_url should be retrieved from keystone_auth
# section by default
return CONF[CFG_LEGACY_GROUP].auth_uri.replace('v2.0', 'v3')
@property
def auth_token(self):
return self.client.auth_token
return self.session.get_token()
@property
def session(self):
return self.client.session
if self._session:
return self._session
auth = self._get_auth()
session = self._get_session(auth)
self._session = session
return session
def _get_session(self, auth):
session = ka_loading.load_session_from_conf_options(
CONF, CFG_GROUP, auth=auth)
return session
def _get_auth(self):
if self.context.is_admin or self.context.trust_id:
try:
auth = ka_loading.load_auth_from_conf_options(CONF, CFG_GROUP)
except ka_exception.MissingRequiredOptions:
auth = self._get_legacy_auth()
elif self.context.auth_token_info:
access_info = ka_access.create(body=self.context.auth_token_info,
auth_token=self.context.auth_token)
auth = ka_access_plugin.AccessInfoPlugin(access_info)
elif self.context.auth_token:
auth = ka_v3.Token(auth_url=self.auth_url,
token=self.context.auth_token)
else:
LOG.error(_LE('Keystone API connection failed: no password, '
'trust_id or token found.'))
raise exception.AuthorizationFailure()
@property
def admin_session(self):
return self.admin_client.session
return auth
def _get_legacy_auth(self):
LOG.warning(_LW('Auth plugin and its options for service user '
'must be provided in [%(new)s] section. '
'Using values from [%(old)s] section is '
'deprecated.') % {'new': CFG_GROUP,
'old': CFG_LEGACY_GROUP})
conf = getattr(CONF, CFG_LEGACY_GROUP)
# FIXME(htruta, pauloewerton): Conductor layer does not have
# new v3 variables, such as project_name and project_domain_id.
# The use of admin_* variables is related to Identity API v2.0,
# which is now deprecated. We should also stop using hard-coded
# domain info, as well as variables that refer to `tenant`,
# as they are also v2 related.
auth = ka_v3.Password(auth_url=self.auth_url,
username=conf.admin_user,
password=conf.admin_password,
project_name=conf.admin_tenant_name,
project_domain_id='default',
user_domain_id='default')
return auth
@property
def client(self):
if self.context.is_admin:
return self.admin_client
else:
if not self._client:
self._client = self._get_ks_client()
if self._client:
return self._client
def _get_admin_credentials(self):
credentials = {
'username': CONF.keystone_authtoken.admin_user,
'password': CONF.keystone_authtoken.admin_password,
'project_name': CONF.keystone_authtoken.admin_tenant_name
}
return credentials
@property
def admin_client(self):
if not self._admin_client:
admin_credentials = self._get_admin_credentials()
self._admin_client = kc_v3.Client(auth_url=self.auth_url,
**admin_credentials)
return self._admin_client
client = kc_v3.Client(session=self.session,
trust_id=self.context.trust_id)
self._client = client
return client
@property
def domain_admin_client(self):
if not self._domain_admin_client:
auth = v3.Password(
auth = ka_v3.Password(
auth_url=self.auth_url,
user_id=CONF.trust.trustee_domain_admin_id,
domain_id=CONF.trust.trustee_domain_id,
password=CONF.trust.trustee_domain_admin_password)
sess = session.Session(auth=auth)
self._domain_admin_client = kc_v3.Client(session=sess)
session = ka_session.Session(auth=auth)
self._domain_admin_client = kc_v3.Client(session=session)
return self._domain_admin_client
@staticmethod
def _is_v2_valid(auth_token_info):
return 'access' in auth_token_info
@staticmethod
def _is_v3_valid(auth_token_info):
return 'token' in auth_token_info
def _get_ks_client(self):
kwargs = {'auth_url': self.auth_url,
'endpoint': self.auth_url}
if self.context.trust_id:
kwargs.update(self._get_admin_credentials())
kwargs['trust_id'] = self.context.trust_id
kwargs.pop('project_name')
elif self.context.auth_token_info:
kwargs['token'] = self.context.auth_token
if self._is_v2_valid(self.context.auth_token_info):
LOG.warning('Keystone v2 is deprecated.')
kwargs['auth_ref'] = self.context.auth_token_info['access']
kwargs['auth_ref']['version'] = 'v2.0'
elif self._is_v3_valid(self.context.auth_token_info):
kwargs['auth_ref'] = self.context.auth_token_info['token']
kwargs['auth_ref']['version'] = 'v3'
else:
LOG.error(_LE('Unknown version in auth_token_info'))
raise exception.AuthorizationFailure()
elif self.context.auth_token:
kwargs['token'] = self.context.auth_token
else:
LOG.error(_LE('Keystone v3 API conntection failed, no password '
'trust or auth_token'))
raise exception.AuthorizationFailure()
return kc_v3.Client(**kwargs)
def create_trust(self, trustee_user):
trustor_user_id = self.client.auth_ref.user_id
trustor_project_id = self.client.auth_ref.project_id
trustor_user_id = self.session.get_user_id()
trustor_project_id = self.session.get_project_id()
# inherit the role of the trustor, unless set CONF.trust.roles
if CONF.trust.roles:


+ 1
- 0
magnum/opts.py View File

@ -59,4 +59,5 @@ def list_opts():
local_cert_manager.local_cert_manager_opts,
)),
('baymodel', magnum.api.validation.baymodel_opts),
('keystone_auth', magnum.common.keystone.keystone_auth_opts),
]

+ 13
- 13
magnum/tests/unit/common/test_clients.py View File

@ -40,11 +40,11 @@ class ClientsTest(base.BaseTestCase):
@mock.patch.object(clients.OpenStackClients, 'keystone')
def test_url_for(self, mock_keystone):
obj = clients.OpenStackClients(None)
obj.url_for(service_type='fake_service', endpoint_type='fake_endpoint')
obj.url_for(service_type='fake_service', interface='fake_endpoint')
mock_cat = mock_keystone.return_value.client.service_catalog
mock_cat.url_for.assert_called_once_with(service_type='fake_service',
endpoint_type='fake_endpoint')
mock_endpoint = mock_keystone.return_value.session.get_endpoint
mock_endpoint.assert_called_once_with(service_type='fake_service',
interface='fake_endpoint')
@mock.patch.object(clients.OpenStackClients, 'keystone')
def test_magnum_url(self, mock_keystone):
@ -57,10 +57,10 @@ class ClientsTest(base.BaseTestCase):
obj = clients.OpenStackClients(None)
obj.magnum_url()
mock_cat = mock_keystone.return_value.client.service_catalog
mock_cat.url_for.assert_called_once_with(region_name=fake_region,
service_type='container',
endpoint_type=fake_endpoint)
mock_endpoint = mock_keystone.return_value.session.get_endpoint
mock_endpoint.assert_called_once_with(region_name=fake_region,
service_type='container',
interface=fake_endpoint)
@mock.patch.object(heatclient, 'Client')
@mock.patch.object(clients.OpenStackClients, 'url_for')
@ -82,7 +82,7 @@ class ClientsTest(base.BaseTestCase):
auth_url='keystone_url', ca_file=None, key_file=None,
password=None, insecure=False)
mock_url.assert_called_once_with(service_type='orchestration',
endpoint_type='publicURL',
interface='publicURL',
region_name=expected_region_name)
def test_clients_heat(self):
@ -139,7 +139,7 @@ class ClientsTest(base.BaseTestCase):
auth_url='keystone_url',
password=None)
mock_url.assert_called_once_with(service_type='image',
endpoint_type='publicURL',
interface='publicURL',
region_name=expected_region_name)
def test_clients_glance(self):
@ -196,7 +196,7 @@ class ClientsTest(base.BaseTestCase):
mock_keystone.assert_called_once_with()
mock_url.assert_called_once_with(service_type='key-manager',
endpoint_type='publicURL',
interface='publicURL',
region_name=expected_region_name)
def test_clients_barbican(self):
@ -251,7 +251,7 @@ class ClientsTest(base.BaseTestCase):
mock_call.assert_called_once_with(cfg.CONF.nova_client.api_version,
auth_token=con.auth_token)
mock_url.assert_called_once_with(service_type='compute',
endpoint_type='publicURL',
interface='publicURL',
region_name=expected_region_name)
def test_clients_nova(self):
@ -310,7 +310,7 @@ class ClientsTest(base.BaseTestCase):
auth_url='keystone_url',
token='3bcc3d3a03f44e3d8377f9247b0ad155')
mock_url.assert_called_once_with(service_type='network',
endpoint_type=fake_endpoint_type,
interface=fake_endpoint_type,
region_name=expected_region_name)
def test_clients_neutron(self):


+ 73
- 61
magnum/tests/unit/common/test_keystone.py View File

@ -12,10 +12,13 @@
import mock
from oslo_config import cfg
from oslo_config import fixture
cfg.CONF.import_group('keystone_authtoken',
'keystonemiddleware.auth_token')
from keystoneauth1 import exceptions as ka_exception
from keystoneauth1 import identity as ka_identity
import keystoneclient.exceptions as kc_exception
from magnum.common import exception
@ -25,79 +28,86 @@ from magnum.tests import utils
@mock.patch('keystoneclient.v3.client.Client')
class KeystoneClientTest(base.BaseTestCase):
class KeystoneClientTest(base.TestCase):
def setUp(self):
super(KeystoneClientTest, self).setUp()
dummy_url = 'http://server.test:5000/v2.0'
dummy_url = 'http://server.test:5000/v3'
self.ctx = utils.dummy_context()
self.ctx.auth_url = dummy_url
self.ctx.auth_token = 'abcd1234'
cfg.CONF.set_override('auth_uri', dummy_url,
group='keystone_authtoken')
cfg.CONF.set_override('admin_user', 'magnum',
group='keystone_authtoken')
cfg.CONF.set_override('admin_password', 'verybadpass',
group='keystone_authtoken')
cfg.CONF.set_override('admin_tenant_name', 'service',
group='keystone_authtoken')
def test_client_with_token(self, mock_ks):
plugin = keystone.ka_loading.get_plugin_loader('password')
opts = keystone.ka_loading.get_auth_plugin_conf_options(plugin)
cfg_fixture = self.useFixture(fixture.Config())
cfg_fixture.register_opts(opts, group=keystone.CFG_GROUP)
self.config(auth_type='password',
auth_url=dummy_url,
username='fake_user',
password='fake_pass',
project_name='fake_project',
group=keystone.CFG_GROUP)
self.config(auth_uri=dummy_url,
admin_user='magnum',
admin_password='varybadpass',
admin_tenant_name='service',
group=keystone.CFG_LEGACY_GROUP)
def test_client_with_password(self, mock_ks):
self.ctx.is_admin = True
ks_client = keystone.KeystoneClientV3(self.ctx)
ks_client.client
self.assertIsNotNone(ks_client._client)
mock_ks.assert_called_once_with(token='abcd1234',
auth_url='http://server.test:5000/v3',
endpoint='http://server.test:5000/v3')
def test_client_with_no_credentials(self, mock_ks):
self.ctx.auth_token = None
session = ks_client.session
auth_plugin = session.auth
mock_ks.assert_called_once_with(session=session, trust_id=None)
self.assertIsInstance(auth_plugin, ka_identity.Password)
@mock.patch('magnum.common.keystone.ka_loading')
@mock.patch('magnum.common.keystone.ka_v3')
def test_client_with_password_legacy(self, mock_v3, mock_loading, mock_ks):
self.ctx.is_admin = True
mock_loading.load_auth_from_conf_options.side_effect = \
ka_exception.MissingRequiredOptions(mock.MagicMock())
ks_client = keystone.KeystoneClientV3(self.ctx)
self.assertRaises(exception.AuthorizationFailure,
ks_client._get_ks_client)
def test_client_with_v2_auth_token_info(self, mock_ks):
self.ctx.auth_token_info = {'access': {}}
ks_client.client
session = ks_client.session
self.assertWarnsRegex(Warning,
'[keystone_authtoken] section is deprecated')
mock_v3.Password.assert_called_once_with(
auth_url='http://server.test:5000/v3', password='varybadpass',
project_domain_id='default', project_name='service',
user_domain_id='default', username='magnum')
mock_ks.assert_called_once_with(session=session, trust_id=None)
@mock.patch('magnum.common.keystone.ka_access')
def test_client_with_access_info(self, mock_access, mock_ks):
self.ctx.auth_token_info = mock.MagicMock()
ks_client = keystone.KeystoneClientV3(self.ctx)
ks_client.client
self.assertIsNotNone(ks_client._client)
mock_ks.assert_called_once_with(auth_ref={'version': 'v2.0'},
auth_url='http://server.test:5000/v3',
endpoint='http://server.test:5000/v3',
token='abcd1234')
def test_client_with_v3_auth_token_info(self, mock_ks):
self.ctx.auth_token_info = {'token': {}}
session = ks_client.session
auth_plugin = session.auth
mock_access.create.assert_called_once_with(body=mock.ANY,
auth_token='abcd1234')
mock_ks.assert_called_once_with(session=session, trust_id=None)
self.assertIsInstance(auth_plugin, ka_identity.access.AccessInfoPlugin)
@mock.patch('magnum.common.keystone.ka_v3')
def test_client_with_token(self, mock_v3, mock_ks):
ks_client = keystone.KeystoneClientV3(self.ctx)
ks_client.client
self.assertIsNotNone(ks_client._client)
mock_ks.assert_called_once_with(auth_ref={'version': 'v3'},
auth_url='http://server.test:5000/v3',
endpoint='http://server.test:5000/v3',
token='abcd1234')
def test_client_with_invalid_auth_token_info(self, mock_ks):
self.ctx.auth_token_info = {'not_this': 'urg'}
session = ks_client.session
mock_v3.Token.assert_called_once_with(
auth_url='http://server.test:5000/v3', token='abcd1234')
mock_ks.assert_called_once_with(session=session, trust_id=None)
def test_client_with_no_credentials(self, mock_ks):
self.ctx.auth_token = None
ks_client = keystone.KeystoneClientV3(self.ctx)
self.assertRaises(exception.AuthorizationFailure,
ks_client._get_ks_client)
def test_client_with_is_admin(self, mock_ks):
self.ctx.is_admin = True
ks_client = keystone.KeystoneClientV3(self.ctx)
ks_client.client
self.assertIsNone(ks_client._client)
self.assertIsNotNone(ks_client._admin_client)
mock_ks.assert_called_once_with(auth_url='http://server.test:5000/v3',
username='magnum',
password='verybadpass',
project_name='service')
ks_client._get_auth)
mock_ks.assert_not_called()
def test_delete_trust(self, mock_ks):
mock_ks.return_value.trusts.delete.return_value = None
@ -111,9 +121,10 @@ class KeystoneClientTest(base.BaseTestCase):
ks_client = keystone.KeystoneClientV3(self.ctx)
self.assertIsNone(ks_client.delete_trust(trust_id='atrust123'))
def test_create_trust_with_all_roles(self, mock_ks):
mock_ks.return_value.auth_ref.user_id = '123456'
mock_ks.return_value.auth_ref.project_id = '654321'
@mock.patch('magnum.common.keystone.ka_session.Session')
def test_create_trust_with_all_roles(self, mock_session, mock_ks):
mock_session.return_value.get_user_id.return_value = '123456'
mock_session.return_value.get_project_id.return_value = '654321'
self.ctx.roles = ['role1', 'role2']
ks_client = keystone.KeystoneClientV3(self.ctx)
@ -125,9 +136,10 @@ class KeystoneClientTest(base.BaseTestCase):
trustee_user='888888', role_names=['role1', 'role2'],
impersonation=True)
def test_create_trust_with_limit_roles(self, mock_ks):
mock_ks.return_value.auth_ref.user_id = '123456'
mock_ks.return_value.auth_ref.project_id = '654321'
@mock.patch('magnum.common.keystone.ka_session.Session')
def test_create_trust_with_limit_roles(self, mock_session, mock_ks):
mock_session.return_value.get_user_id.return_value = '123456'
mock_session.return_value.get_project_id.return_value = '654321'
self.ctx.roles = ['role1', 'role2']
ks_client = keystone.KeystoneClientV3(self.ctx)


+ 1
- 0
requirements.txt View File

@ -20,6 +20,7 @@ eventlet!=0.18.3,>=0.18.2 # MIT
greenlet>=0.3.2 # MIT
iso8601>=0.1.9 # MIT
jsonpatch>=1.1 # BSD
keystoneauth1>=2.1.0 # Apache-2.0
keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0
netaddr!=0.7.16,>=0.7.12 # BSD
oslo.concurrency>=3.5.0 # Apache-2.0


Loading…
Cancel
Save