diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 407bb49f..64d74a5c 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -28,6 +28,7 @@ EC2API_CONF_DIR=${EC2API_CONF_DIR:-/etc/ec2api} EC2API_CONF_FILE=${EC2API_CONF_DIR}/ec2api.conf EC2API_DEBUG=${EC2API_DEBUG:-True} EC2API_STATE_PATH=${EC2API_STATE_PATH:=$DATA_DIR/ec2api} +EC2API_AUTH_CACHE_DIR=${EC2API_AUTH_CACHE_DIR:-/var/cache/ec2api} EC2API_SERVICE_PORT=${EC2API_SERVICE_PORT:-8788} EC2API_S3_SERVICE_PORT=${EC2API_S3_SERVICE_PORT:-3334} @@ -178,12 +179,9 @@ function configure_ec2api { # ec2api Api Configuration #------------------------- - iniset $EC2API_CONF_FILE DEFAULT admin_tenant_name $SERVICE_TENANT_NAME - iniset $EC2API_CONF_FILE DEFAULT admin_user $EC2API_ADMIN_USER - iniset $EC2API_CONF_FILE DEFAULT admin_password $SERVICE_PASSWORD + configure_auth_token_middleware $EC2API_CONF_FILE $EC2API_ADMIN_USER $EC2API_AUTH_CACHE_DIR iniset $EC2API_CONF_FILE DEFAULT ec2api_workers "$API_WORKERS" - iniset $EC2API_CONF_FILE DEFAULT keystone_url "$KEYSTONE_SERVICE_URI" iniset $EC2API_CONF_FILE DEFAULT keystone_ec2_tokens_url "$KEYSTONE_SERVICE_URI_V3/ec2tokens" iniset $EC2API_CONF_FILE DEFAULT region_list "$REGION_NAME" diff --git a/ec2api/api/__init__.py b/ec2api/api/__init__.py index 06fe8936..d783b47b 100644 --- a/ec2api/api/__init__.py +++ b/ec2api/api/__init__.py @@ -47,6 +47,10 @@ LOG = logging.getLogger(__name__) ec2_opts = [ cfg.StrOpt('keystone_url', default='http://localhost:5000/', + deprecated_for_removal=True, + deprecated_reason='code was switched to common section ' + '"keystone_authtoken"', + deprecated_since='Newton', help='URL for getting admin session.'), cfg.StrOpt('keystone_ec2_tokens_url', default='http://localhost:5000/v3/ec2tokens', diff --git a/ec2api/clients.py b/ec2api/clients.py index 93345534..6703dea9 100644 --- a/ec2api/clients.py +++ b/ec2api/clients.py @@ -14,6 +14,7 @@ from cinderclient import client as cinderclient from glanceclient import client as glanceclient +from keystoneauth1 import loading as ks_loading from keystoneclient.auth.identity.generic import password as keystone_auth from keystoneclient import client as keystoneclient from keystoneclient import session as keystone_session @@ -31,8 +32,16 @@ logger = logging.getLogger(__name__) ec2_opts = [ cfg.BoolOpt('ssl_insecure', default=False, + deprecated_for_removal=True, + deprecated_reason='code was switched to common section ' + '"keystone_authtoken"', + deprecated_since='Newton', help="Verify HTTPS connections."), cfg.StrOpt('ssl_ca_file', + deprecated_for_removal=True, + deprecated_reason='code was switched to common section ' + '"keystone_authtoken"', + deprecated_since='Newton', help="CA certificate file to use to verify " "connecting clients"), cfg.StrOpt('nova_service_type', @@ -45,20 +54,34 @@ ec2_opts = [ default='volumev2', help='Service type of Volume API, registered in Keystone ' 'catalog.'), - # TODO(andrey-mp): keystone v3 allows to pass domain_name - # or domain_id to auth. This code should support this feature. cfg.StrOpt('admin_user', + deprecated_for_removal=True, + deprecated_reason='code was switched to common section ' + '"keystone_authtoken"', + deprecated_since='Newton', help=_("Admin user to access specific cloud resourses")), cfg.StrOpt('admin_password', + deprecated_for_removal=True, + deprecated_reason='code was switched to common section ' + '"keystone_authtoken"', + deprecated_since='Newton', help=_("Admin password"), secret=True), cfg.StrOpt('admin_tenant_name', + deprecated_for_removal=True, + deprecated_reason='code was switched to common section ' + '"keystone_authtoken"', + deprecated_since='Newton', help=_("Admin tenant name")), ] CONF = cfg.CONF CONF.register_opts(ec2_opts) +GROUP_AUTHTOKEN = 'keystone_authtoken' +ks_loading.register_session_conf_options(CONF, GROUP_AUTHTOKEN) +ks_loading.register_auth_conf_options(CONF, GROUP_AUTHTOKEN) + # Nova API version with microversions support REQUIRED_NOVA_API_VERSION = '2.1' @@ -106,8 +129,8 @@ def cinder(context): def keystone(context): - auth_url = context.session.get_endpoint(service_type='identity') - return keystoneclient.Client(auth_url=auth_url, + url = context.session.get_endpoint(service_type='identity') + return keystoneclient.Client(auth_url=url, session=context.session) @@ -202,27 +225,41 @@ class _rpc_RequestContextSerializer(messaging.NoOpSerializer): _admin_session = None +def get_session_from_deprecated(): + auth = keystone_auth.Password( + username=CONF.admin_user, + password=CONF.admin_password, + project_name=CONF.admin_tenant_name, + tenant_name=CONF.admin_tenant_name, + auth_url=CONF.keystone_url, + ) + params = {'auth': auth} + update_request_params_with_ssl(params) + return keystone_session.Session(**params) + + def get_os_admin_session(): """Create a context to interact with OpenStack as an administrator.""" # NOTE(ft): this is a singletone because keystone's session looks thread # safe for both regular and token renewal requests global _admin_session if not _admin_session: - auth = keystone_auth.Password( - username=CONF.admin_user, - password=CONF.admin_password, - project_name=CONF.admin_tenant_name, - tenant_name=CONF.admin_tenant_name, - auth_url=CONF.keystone_url, - ) - params = {'auth': auth} - update_request_params_with_ssl(params) - _admin_session = keystone_session.Session(**params) + if not CONF[GROUP_AUTHTOKEN].auth_type: + _admin_session = get_session_from_deprecated() + else: + auth_plugin = ks_loading.load_auth_from_conf_options( + CONF, GROUP_AUTHTOKEN) + _admin_session = ks_loading.load_session_from_conf_options( + CONF, GROUP_AUTHTOKEN, auth=auth_plugin) return _admin_session def update_request_params_with_ssl(params): - verify = CONF.ssl_ca_file or not CONF.ssl_insecure + if not CONF[GROUP_AUTHTOKEN].auth_type: + verify = CONF.ssl_ca_file or not CONF.ssl_insecure + else: + verify = (CONF[GROUP_AUTHTOKEN].cafile or + not CONF[GROUP_AUTHTOKEN].insecure) if verify is not True: params['verify'] = verify diff --git a/ec2api/opts.py b/ec2api/opts.py index d8014e39..775078a6 100644 --- a/ec2api/opts.py +++ b/ec2api/opts.py @@ -11,6 +11,10 @@ # limitations under the License. import itertools +import operator + +from keystoneauth1 import loading as ks_loading +from oslo_config import cfg import ec2api.clients import ec2api.db.api @@ -21,6 +25,9 @@ import ec2api.utils import ec2api.wsgi +CONF = cfg.CONF + + def list_opts(): return [ ('DEFAULT', @@ -34,3 +41,20 @@ def list_opts(): ec2api.wsgi.wsgi_opts, )), ] + + +GROUP_AUTHTOKEN = 'keystone_authtoken' + + +def list_auth_opts(): + opt_list = ks_loading.register_session_conf_options(CONF, GROUP_AUTHTOKEN) + opt_list.insert(0, ks_loading.get_auth_common_conf_options()[0]) + # NOTE(mhickey): 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: + for plugin_option in ks_loading.get_auth_plugin_conf_options(name): + if all(option.name != plugin_option.name for option in opt_list): + opt_list.append(plugin_option) + opt_list.sort(key=operator.attrgetter('name')) + return [(GROUP_AUTHTOKEN, opt_list)] diff --git a/ec2api/tests/unit/test_context.py b/ec2api/tests/unit/test_context.py index adc4ae99..a007e2f7 100644 --- a/ec2api/tests/unit/test_context.py +++ b/ec2api/tests/unit/test_context.py @@ -20,22 +20,59 @@ from oslo_config import fixture as config_fixture from oslo_context import context from oslotest import base as test_base +from ec2api import clients from ec2api import context as ec2_context + cfg.CONF.import_opt('keystone_url', 'ec2api.api') +GROUP_AUTHTOKEN = 'keystone_authtoken' class ContextTestCase(test_base.BaseTestCase): - def setUp(self): - super(ContextTestCase, self).setUp() + @mock.patch('keystoneauth1.loading.load_auth_from_conf_options') + @mock.patch('keystoneauth1.loading.load_session_from_conf_options') + def test_get_os_admin_context(self, session, auth): conf = config_fixture.Config() + clients._admin_session = None + conf.config(auth_type='fake', group=GROUP_AUTHTOKEN) + + imp.reload(ec2_context) + # NOTE(ft): initialize a regular context to populate oslo_context's + # local storage to prevent admin context to populate it. + # Used to implicitly validate overwrite=False argument of the call + # RequestContext constructor from inside get_os_admin_context + if not context.get_current(): + ec2_context.RequestContext(None, None) + + ctx = ec2_context.get_os_admin_context() + conf = cfg.CONF + auth.assert_called_once_with(conf, GROUP_AUTHTOKEN) + auth_plugin = auth.return_value + session.assert_called_once_with(conf, GROUP_AUTHTOKEN, + auth=auth_plugin) + self.assertIsNone(ctx.user_id) + self.assertIsNone(ctx.project_id) + self.assertIsNone(ctx.auth_token) + self.assertEqual([], ctx.service_catalog) + self.assertTrue(ctx.is_os_admin) + self.assertIsNotNone(ctx.session) + self.assertIsNotNone(ctx.session.auth) + self.assertNotEqual(context.get_current(), ctx) + + session.reset_mock() + ec2_context.get_os_admin_context() + self.assertFalse(session.called) + + @mock.patch('keystoneclient.auth.identity.generic.password.Password') + def test_get_os_admin_context_deprecated(self, password_plugin): + conf = config_fixture.Config() + clients._admin_session = None + conf.config(auth_type=None, group=GROUP_AUTHTOKEN) conf.config(admin_user='admin', admin_password='password', admin_tenant_name='service') - @mock.patch('keystoneclient.auth.identity.generic.password.Password') - def test_get_os_admin_context(self, password_plugin): imp.reload(ec2_context) # NOTE(ft): initialize a regular context to populate oslo_context's # local storage to prevent admin context to populate it. diff --git a/etc/ec2api/ec2api-config-generator.conf b/etc/ec2api/ec2api-config-generator.conf index a302613b..825566ed 100644 --- a/etc/ec2api/ec2api-config-generator.conf +++ b/etc/ec2api/ec2api-config-generator.conf @@ -2,6 +2,7 @@ output_file = etc/ec2api/ec2api.conf.sample wrap_width = 79 namespace = ec2api +namespace = keystoneauth1 namespace = ec2api.api namespace = ec2api.metadata namespace = ec2api.s3 diff --git a/install.sh b/install.sh index 31fc7813..00359dbc 100755 --- a/install.sh +++ b/install.sh @@ -4,6 +4,8 @@ SERVICE_USERNAME=ec2api SERVICE_PASSWORD=ec2api SERVICE_TENANT=service +# this domain name will be used for project and user +SERVICE_DOMAIN_NAME=Default EC2API_PORT=8788 CONNECTION="mysql://ec2api:ec2api@127.0.0.1/ec2api?charset=utf8" LOG_DIR=/var/log/ec2api @@ -267,15 +269,22 @@ iniset $CONF_FILE DEFAULT api_paste_config $APIPASTE_FILE iniset $CONF_FILE DEFAULT logging_context_format_string "%(asctime)s.%(msecs)03d %(levelname)s %(name)s [%(request_id)s %(user_name)s %(project_name)s] %(instance)s%(message)s" iniset $CONF_FILE DEFAULT log_dir "$LOG_DIR" iniset $CONF_FILE DEFAULT verbose True -iniset $CONF_FILE DEFAULT keystone_url "$OS_AUTH_URL" iniset $CONF_FILE DEFAULT keystone_ec2_tokens_url "$OS_AUTH_URL/v3/ec2tokens" iniset $CONF_FILE database connection "$CONNECTION" iniset $CONF_FILE DEFAULT full_vpc_support "$VPC_SUPPORT" iniset $CONF_FILE DEFAULT external_network "$EXTERNAL_NETWORK" -iniset $CONF_FILE DEFAULT admin_user $SERVICE_USERNAME -iniset $CONF_FILE DEFAULT admin_password $SERVICE_PASSWORD -iniset $CONF_FILE DEFAULT admin_tenant_name $SERVICE_TENANT +GROUP_AUTHTOKEN="keystone_authtoken" +iniset $CONF_FILE $GROUP_AUTHTOKEN signing_dir "$AUTH_CACHE_DIR" +iniset $CONF_FILE $GROUP_AUTHTOKEN auth_uri "$OS_AUTH_URL" +iniset $CONF_FILE $GROUP_AUTHTOKEN auth_url "$OS_AUTH_URL" +iniset $CONF_FILE $GROUP_AUTHTOKEN username $SERVICE_USERNAME +iniset $CONF_FILE $GROUP_AUTHTOKEN password $SERVICE_PASSWORD +iniset $CONF_FILE $GROUP_AUTHTOKEN project_name $SERVICE_TENANT +iniset $CONF_FILE $GROUP_AUTHTOKEN project_domain_name $SERVICE_DOMAIN_NAME +iniset $CONF_FILE $GROUP_AUTHTOKEN user_domain_name $SERVICE_DOMAIN_NAME +iniset $CONF_FILE $GROUP_AUTHTOKEN auth_type password + if [[ -f "$NOVA_CONF" ]]; then # NOTE(ft): use swift instead internal s3 server if enabled diff --git a/requirements.txt b/requirements.txt index 7da8cf8a..1a79c3e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ oslo.utils>=3.16.0 # Apache-2.0 Paste # MIT PasteDeploy>=1.5.0 # MIT pbr>=1.6 # Apache-2.0 +keystoneauth1>=2.10.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0 python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index e9d31225..d5ea4568 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ setup-hooks = oslo.config.opts = ec2api = ec2api.opts:list_opts ec2api.api = ec2api.api.opts:list_opts + keystoneauth1 = ec2api.opts:list_auth_opts ec2api.metadata = ec2api.metadata.opts:list_opts ec2api.s3 = ec2api.s3.opts:list_opts