Create a Config object

The _conf_get ugliness in auth_token middleware has been around for a
long time now to handle the abstraction from different oslo.config
options and the paste overrides. This logic is now also being needed in
other middlewares. Extract this into a common config object that has a
better interface and is easier to work with.

Change-Id: I8b8a1427bc527e43bb1baec25a881d93df3f93cc
This commit is contained in:
Jamie Lennox 2016-05-23 10:20:43 +10:00 committed by Dolph Mathews
parent 2ab0f98a9f
commit f8c150a9cc
10 changed files with 486 additions and 402 deletions

View File

View File

@ -0,0 +1,132 @@
# 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.
from oslo_config import cfg
import six
from keystonemiddleware import exceptions
from keystonemiddleware.i18n import _
CONF = cfg.CONF
_NOT_SET = object()
def _conf_values_type_convert(group_name, all_options, conf):
"""Convert conf values into correct type."""
if not conf:
return {}
opts = {}
opt_types = {}
for group, options in all_options:
# only accept paste overrides for the primary group
if group != group_name:
continue
for o in options:
type_dest = (getattr(o, 'type', str), o.dest)
opt_types[o.dest] = type_dest
# Also add the deprecated name with the same type and dest.
for d_o in o.deprecated_opts:
opt_types[d_o.name] = type_dest
break
for k, v in six.iteritems(conf):
dest = k
try:
if v is not None:
type_, dest = opt_types[k]
v = type_(v)
except KeyError: # nosec
# This option is not known to auth_token. v is not converted.
# FIXME(jamielennox): This should probably log a warning.
pass
except ValueError as e:
raise exceptions.ConfigurationError(
_('Unable to convert the value of %(key)s option into correct '
'type: %(ex)s') % {'key': k, 'ex': e})
opts[dest] = v
return opts
class Config(object):
def __init__(self, group_name, all_options, conf):
# NOTE(wanghong): If options are set in paste file, all the option
# values passed into conf are string type. So, we should convert the
# conf value into correct type.
self.paste_overrides = _conf_values_type_convert(group_name,
all_options,
conf)
# NOTE(sileht, cdent): If we don't want to use oslo.config global
# object there are two options: set "oslo_config_project" in
# paste.ini and the middleware will load the configuration with a
# local oslo.config object or the caller which instantiates
# AuthProtocol can pass in an existing oslo.config as the
# value of the "oslo_config_config" key in conf. If both are
# set "olso_config_config" is used.
local_oslo_config = None
try:
local_oslo_config = conf['oslo_config_config']
except KeyError:
if 'oslo_config_project' in conf:
config_files = filter(None, [conf.get('oslo_config_file')])
local_oslo_config = cfg.ConfigOpts()
local_oslo_config([],
project=conf['oslo_config_project'],
default_config_files=config_files,
validate_default_values=True)
if local_oslo_config:
for group, opts in all_options:
local_oslo_config.register_opts(opts, group=group)
self.oslo_conf_obj = local_oslo_config or cfg.CONF
self.group_name = group_name
def get(self, name, group=_NOT_SET):
# try config from paste-deploy first
try:
return self.paste_overrides[name]
except KeyError:
if group is _NOT_SET:
group = self.group_name
return self.oslo_conf_obj[group][name]
@property
def project(self):
"""Determine a project name from all available config sources.
The sources are checked in the following order:
1. The paste-deploy config for auth_token middleware
2. The keystone_authtoken or base group in the project's config
3. The oslo.config CONF.project property
"""
try:
return self.get('project', group=self.group_name)
except cfg.NoSuchOptError:
try:
# CONF.project will exist only if the service uses
# oslo.config. It will only be set when the project
# calls CONF(...) and when not set oslo.config oddly
# raises a NoSuchOptError exception.
return self.oslo_conf_obj.project
except cfg.NoSuchOptError:
return None

View File

@ -218,12 +218,12 @@ from keystoneauth1 import loading
from keystoneauth1.loading import session as session_loading
from keystoneclient.common import cms
from keystoneclient import exceptions as ksc_exceptions
from oslo_config import cfg
from oslo_serialization import jsonutils
import pkg_resources
import six
import webob.dec
from keystonemiddleware._common import config
from keystonemiddleware.auth_token import _auth
from keystonemiddleware.auth_token import _base
from keystonemiddleware.auth_token import _cache
@ -233,163 +233,10 @@ from keystonemiddleware.auth_token import _request
from keystonemiddleware.auth_token import _revocations
from keystonemiddleware.auth_token import _signing_dir
from keystonemiddleware.auth_token import _user_plugin
from keystonemiddleware import opts
from keystonemiddleware.i18n import _, _LC, _LE, _LI, _LW
# NOTE(jamielennox): A number of options below are deprecated however are left
# in the list and only mentioned as deprecated in the help string. This is
# because we have to provide the same deprecation functionality for arguments
# passed in via the conf in __init__ (from paste) and there is no way to test
# that the default value was set or not in CONF.
# Also if we were to remove the options from the CONF list (as typical CONF
# deprecation works) then other projects will not be able to override the
# options via CONF.
_OPTS = [
cfg.StrOpt('auth_uri',
default=None,
# FIXME(dolph): should be default='http://127.0.0.1:5000/v2.0/',
# or (depending on client support) an unversioned, publicly
# accessible identity endpoint (see bug 1207517). Further, we
# can eliminate this configuration option in favor of pulling
# the endpoint from the service catalog that the service user
# receives (there should be an identity endpoint listed there).
# This wasn't an option originally when many auth_token
# deployments were configured with the "ADMIN" token and
# endpoint combination.
help='Complete "public" Identity API endpoint. This endpoint'
' should not be an "admin" endpoint, as it should be accessible'
' by all end users. Unauthenticated clients are redirected to'
' this endpoint to authenticate. Although this endpoint should '
' ideally be unversioned, client support in the wild varies. '
' If you\'re using a versioned v2 endpoint here, then this '
' should *not* be the same endpoint the service user utilizes '
' for validating tokens, because normal end users may not be '
' able to reach that endpoint.'),
cfg.StrOpt('auth_version',
default=None,
help='API version of the admin Identity API endpoint.'),
cfg.BoolOpt('delay_auth_decision',
default=False,
help='Do not handle authorization requests within the'
' middleware, but delegate the authorization decision to'
' downstream WSGI components.'),
cfg.IntOpt('http_connect_timeout',
default=None,
help='Request timeout value for communicating with Identity'
' API server.'),
cfg.IntOpt('http_request_max_retries',
default=3,
help='How many times are we trying to reconnect when'
' communicating with Identity API Server.'),
cfg.StrOpt('cache',
default=None,
help='Env key for the swift cache.'),
cfg.StrOpt('certfile',
help='Required if identity server requires client certificate'),
cfg.StrOpt('keyfile',
help='Required if identity server requires client certificate'),
cfg.StrOpt('cafile', default=None,
help='A PEM encoded Certificate Authority to use when '
'verifying HTTPs connections. Defaults to system CAs.'),
cfg.BoolOpt('insecure', default=False, help='Verify HTTPS connections.'),
cfg.StrOpt('region_name', default=None,
help='The region in which the identity server can be found.'),
cfg.StrOpt('signing_dir',
help='Directory used to cache files related to PKI tokens.'),
cfg.ListOpt('memcached_servers',
deprecated_name='memcache_servers',
help='Optionally specify a list of memcached server(s) to'
' use for caching. If left undefined, tokens will instead be'
' cached in-process.'),
cfg.IntOpt('token_cache_time',
default=300,
help='In order to prevent excessive effort spent validating'
' tokens, the middleware caches previously-seen tokens for a'
' configurable duration (in seconds). Set to -1 to disable'
' caching completely.'),
cfg.IntOpt('revocation_cache_time',
default=10,
help='Determines the frequency at which the list of revoked'
' tokens is retrieved from the Identity service (in seconds). A'
' high number of revocation events combined with a low cache'
' duration may significantly reduce performance. Only valid'
' for PKI tokens.'),
cfg.StrOpt('memcache_security_strategy',
default='None',
choices=('None', 'MAC', 'ENCRYPT'),
ignore_case=True,
help='(Optional) If defined, indicate whether token data'
' should be authenticated or authenticated and encrypted.'
' If MAC, token data is authenticated (with HMAC) in the cache.'
' If ENCRYPT, token data is encrypted and authenticated in the'
' cache. If the value is not one of these options or empty,'
' auth_token will raise an exception on initialization.'),
cfg.StrOpt('memcache_secret_key',
default=None,
secret=True,
help='(Optional, mandatory if memcache_security_strategy is'
' defined) This string is used for key derivation.'),
cfg.IntOpt('memcache_pool_dead_retry',
default=5 * 60,
help='(Optional) Number of seconds memcached server is'
' considered dead before it is tried again.'),
cfg.IntOpt('memcache_pool_maxsize',
default=10,
help='(Optional) Maximum total number of open connections to'
' every memcached server.'),
cfg.IntOpt('memcache_pool_socket_timeout',
default=3,
help='(Optional) Socket timeout in seconds for communicating '
'with a memcached server.'),
cfg.IntOpt('memcache_pool_unused_timeout',
default=60,
help='(Optional) Number of seconds a connection to memcached'
' is held unused in the pool before it is closed.'),
cfg.IntOpt('memcache_pool_conn_get_timeout',
default=10,
help='(Optional) Number of seconds that an operation will wait '
'to get a memcached client connection from the pool.'),
cfg.BoolOpt('memcache_use_advanced_pool',
default=False,
help='(Optional) Use the advanced (eventlet safe) memcached '
'client pool. The advanced pool will only work under '
'python 2.x.'),
cfg.BoolOpt('include_service_catalog',
default=True,
help='(Optional) Indicate whether to set the X-Service-Catalog'
' header. If False, middleware will not ask for service'
' catalog on token validation and will not set the'
' X-Service-Catalog header.'),
cfg.StrOpt('enforce_token_bind',
default='permissive',
help='Used to control the use and type of token binding. Can'
' be set to: "disabled" to not check token binding.'
' "permissive" (default) to validate binding information if the'
' bind type is of a form known to the server and ignore it if'
' not. "strict" like "permissive" but if the bind type is'
' unknown the token will be rejected. "required" any form of'
' token binding is needed to be allowed. Finally the name of a'
' binding method that must be present in tokens.'),
cfg.BoolOpt('check_revocations_for_cached', default=False,
help='If true, the revocation list will be checked for cached'
' tokens. This requires that PKI tokens are configured on the'
' identity server.'),
cfg.ListOpt('hash_algorithms', default=['md5'],
help='Hash algorithms to use for hashing PKI tokens. This may'
' be a single algorithm or multiple. The algorithms are those'
' supported by Python standard hashlib.new(). The hashes will'
' be tried in the order given, so put the preferred one first'
' for performance. The result of the first hash will be stored'
' in the cache. This will typically be set to multiple values'
' only while migrating from a less secure algorithm to a more'
' secure one. Once all the old tokens are expired this option'
' should be set to a single value for better performance.'),
]
CONF = cfg.CONF
CONF.register_opts(_OPTS, group=_base.AUTHTOKEN_GROUP)
_LOG = logging.getLogger(__name__)
_CACHE_INVALID_INDICATOR = 'invalid'
@ -410,37 +257,6 @@ def _token_is_v3(token_info):
return ('token' in token_info)
def _conf_values_type_convert(conf):
"""Convert conf values into correct type."""
if not conf:
return {}
opt_types = {}
for o in _OPTS + _auth.OPTS:
type_dest = (getattr(o, 'type', str), o.dest)
opt_types[o.dest] = type_dest
# Also add the deprecated name with the same type and dest.
for d_o in o.deprecated_opts:
opt_types[d_o.name] = type_dest
opts = {}
for k, v in six.iteritems(conf):
dest = k
try:
if v is not None:
type_, dest = opt_types[k]
v = type_(v)
except KeyError: # nosec
# This option is not known to auth_token. v is not converted.
pass
except ValueError as e:
raise ksm_exceptions.ConfigurationError(
_('Unable to convert the value of %(key)s option into correct '
'type: %(ex)s') % {'key': k, 'ex': e})
opts[dest] = v
return opts
def _get_project_version(project):
try:
return pkg_resources.get_distribution(project).version
@ -654,54 +470,25 @@ class AuthProtocol(BaseAuthProtocol):
log = logging.getLogger(conf.get('log_name', __name__))
log.info(_LI('Starting Keystone auth_token middleware'))
# NOTE(wanghong): If options are set in paste file, all the option
# values passed into conf are string type. So, we should convert the
# conf value into correct type.
self._conf = _conf_values_type_convert(conf)
# NOTE(sileht, cdent): If we don't want to use oslo.config global
# object there are two options: set "oslo_config_project" in
# paste.ini and the middleware will load the configuration with a
# local oslo.config object or the caller which instantiates
# AuthProtocol can pass in an existing oslo.config as the
# value of the "oslo_config_config" key in conf. If both are
# set "olso_config_config" is used.
self._local_oslo_config = conf.get('oslo_config_config')
if (not self._local_oslo_config) and ('oslo_config_project' in conf):
if 'oslo_config_file' in conf:
default_config_files = [conf['oslo_config_file']]
else:
default_config_files = None
self._local_oslo_config = cfg.ConfigOpts()
self._local_oslo_config(
[], project=conf['oslo_config_project'],
default_config_files=default_config_files,
validate_default_values=True)
if self._local_oslo_config:
self._local_oslo_config.register_opts(_OPTS,
group=_base.AUTHTOKEN_GROUP)
self._local_oslo_config.register_opts(_auth.OPTS,
group=_base.AUTHTOKEN_GROUP)
loading.register_auth_conf_options(self._local_oslo_config,
group=_base.AUTHTOKEN_GROUP)
self._conf = config.Config(_base.AUTHTOKEN_GROUP,
opts.list_auth_token_opts(),
conf)
super(AuthProtocol, self).__init__(
app,
log=log,
enforce_token_bind=self._conf_get('enforce_token_bind'))
enforce_token_bind=self._conf.get('enforce_token_bind'))
# delay_auth_decision means we still allow unauthenticated requests
# through and we let the downstream service make the final decision
self._delay_auth_decision = self._conf_get('delay_auth_decision')
self._include_service_catalog = self._conf_get(
self._delay_auth_decision = self._conf.get('delay_auth_decision')
self._include_service_catalog = self._conf.get(
'include_service_catalog')
self._hash_algorithms = self._conf_get('hash_algorithms')
self._hash_algorithms = self._conf.get('hash_algorithms')
self._identity_server = self._create_identity_server()
self._auth_uri = self._conf_get('auth_uri')
self._auth_uri = self._conf.get('auth_uri')
if not self._auth_uri:
self.log.warning(
_LW('Configuring auth_uri to point to the public identity '
@ -714,30 +501,21 @@ class AuthProtocol(BaseAuthProtocol):
self._auth_uri = self._identity_server.auth_uri
self._signing_directory = _signing_dir.SigningDirectory(
directory_name=self._conf_get('signing_dir'), log=self.log)
directory_name=self._conf.get('signing_dir'), log=self.log)
self._token_cache = self._token_cache_factory()
revocation_cache_timeout = datetime.timedelta(
seconds=self._conf_get('revocation_cache_time'))
seconds=self._conf.get('revocation_cache_time'))
self._revocations = _revocations.Revocations(revocation_cache_timeout,
self._signing_directory,
self._identity_server,
self._cms_verify,
self.log)
self._check_revocations_for_cached = self._conf_get(
self._check_revocations_for_cached = self._conf.get(
'check_revocations_for_cached')
def _conf_get(self, name, group=_base.AUTHTOKEN_GROUP):
# try config from paste-deploy first
if name in self._conf:
return self._conf[name]
elif self._local_oslo_config:
return self._local_oslo_config[group][name]
else:
return CONF[group][name]
def process_request(self, request):
"""Process request.
@ -994,30 +772,30 @@ class AuthProtocol(BaseAuthProtocol):
def _get_auth_plugin(self):
# NOTE(jamielennox): Ideally this would use load_from_conf_options
# however that is not possible because we have to support the override
# pattern we use in _conf_get. This function therefore does a manual
# pattern we use in _conf.get. This function therefore does a manual
# version of load_from_conf_options with the fallback plugin inline.
group = self._conf_get('auth_section') or _base.AUTHTOKEN_GROUP
group = self._conf.get('auth_section') or _base.AUTHTOKEN_GROUP
# NOTE(jamielennox): auth_plugin was deprecated to auth_type. _conf_get
# NOTE(jamielennox): auth_plugin was deprecated to auth_type. _conf.get
# doesn't handle that deprecation in the case of conf dict options so
# we have to manually check the value
plugin_name = (self._conf_get('auth_type', group=group)
or self._conf.get('auth_plugin'))
plugin_name = (self._conf.get('auth_type', group=group)
or self._conf.paste_overrides.get('auth_plugin'))
if not plugin_name:
return _auth.AuthTokenPlugin(
log=self.log,
auth_admin_prefix=self._conf_get('auth_admin_prefix',
auth_admin_prefix=self._conf.get('auth_admin_prefix',
group=group),
auth_host=self._conf_get('auth_host', group=group),
auth_port=self._conf_get('auth_port', group=group),
auth_protocol=self._conf_get('auth_protocol', group=group),
identity_uri=self._conf_get('identity_uri', group=group),
admin_token=self._conf_get('admin_token', group=group),
admin_user=self._conf_get('admin_user', group=group),
admin_password=self._conf_get('admin_password', group=group),
admin_tenant_name=self._conf_get('admin_tenant_name',
auth_host=self._conf.get('auth_host', group=group),
auth_port=self._conf.get('auth_port', group=group),
auth_protocol=self._conf.get('auth_protocol', group=group),
identity_uri=self._conf.get('identity_uri', group=group),
admin_token=self._conf.get('admin_token', group=group),
admin_user=self._conf.get('admin_user', group=group),
admin_password=self._conf.get('admin_password', group=group),
admin_tenant_name=self._conf.get('admin_tenant_name',
group=group)
)
@ -1026,39 +804,12 @@ class AuthProtocol(BaseAuthProtocol):
plugin_loader = loading.get_plugin_loader(plugin_name)
plugin_opts = loading.get_auth_plugin_conf_options(plugin_loader)
(self._local_oslo_config or CONF).register_opts(plugin_opts,
group=group)
getter = lambda opt: self._conf_get(opt.dest, group=group)
self._conf.oslo_conf_obj.register_opts(plugin_opts, group=group)
getter = lambda opt: self._conf.get(opt.dest, group=group)
return plugin_loader.load_from_options_getter(getter)
def _determine_project(self):
"""Determine a project name from all available config sources.
The sources are checked in the following order:
1. The paste-deploy config for auth_token middleware
2. The keystone_authtoken in the project's config
3. The oslo.config CONF.project property
"""
try:
return self._conf_get('project')
except cfg.NoSuchOptError:
# Prefer local oslo config object
if self._local_oslo_config:
return self._local_oslo_config.project
try:
# CONF.project will exist only if the service uses
# oslo.config. It will only be set when the project
# calls CONF(...) and when not set oslo.config oddly
# raises a NoSuchOptError exception.
return CONF.project
except cfg.NoSuchOptError:
return ''
def _build_useragent_string(self):
project = self._determine_project()
project = self._conf.project or ''
if project:
project_version = _get_project_version(project)
project = '{project}/{project_version} '.format(
@ -1074,14 +825,14 @@ class AuthProtocol(BaseAuthProtocol):
def _create_identity_server(self):
# NOTE(jamielennox): Loading Session here should be exactly the
# same as calling Session.load_from_conf_options(CONF, GROUP)
# however we can't do that because we have to use _conf_get to
# however we can't do that because we have to use _conf.get to
# support the paste.ini options.
sess = session_loading.Session().load_from_options(
cert=self._conf_get('certfile'),
key=self._conf_get('keyfile'),
cacert=self._conf_get('cafile'),
insecure=self._conf_get('insecure'),
timeout=self._conf_get('http_connect_timeout'),
cert=self._conf.get('certfile'),
key=self._conf.get('keyfile'),
cacert=self._conf.get('cafile'),
insecure=self._conf.get('insecure'),
timeout=self._conf.get('http_connect_timeout'),
user_agent=self._build_useragent_string()
)
@ -1092,10 +843,10 @@ class AuthProtocol(BaseAuthProtocol):
auth=auth_plugin,
service_type='identity',
interface='admin',
region_name=self._conf_get('region_name'),
connect_retries=self._conf_get('http_request_max_retries'))
region_name=self._conf.get('region_name'),
connect_retries=self._conf.get('http_request_max_retries'))
auth_version = self._conf_get('auth_version')
auth_version = self._conf.get('auth_version')
if auth_version is not None:
auth_version = discover.normalize_version_number(auth_version)
return _identity.IdentityServer(
@ -1105,22 +856,22 @@ class AuthProtocol(BaseAuthProtocol):
requested_auth_version=auth_version)
def _token_cache_factory(self):
security_strategy = self._conf_get('memcache_security_strategy')
security_strategy = self._conf.get('memcache_security_strategy')
cache_kwargs = dict(
cache_time=int(self._conf_get('token_cache_time')),
env_cache_name=self._conf_get('cache'),
memcached_servers=self._conf_get('memcached_servers'),
use_advanced_pool=self._conf_get('memcache_use_advanced_pool'),
dead_retry=self._conf_get('memcache_pool_dead_retry'),
maxsize=self._conf_get('memcache_pool_maxsize'),
unused_timeout=self._conf_get('memcache_pool_unused_timeout'),
conn_get_timeout=self._conf_get('memcache_pool_conn_get_timeout'),
socket_timeout=self._conf_get('memcache_pool_socket_timeout'),
cache_time=int(self._conf.get('token_cache_time')),
env_cache_name=self._conf.get('cache'),
memcached_servers=self._conf.get('memcached_servers'),
use_advanced_pool=self._conf.get('memcache_use_advanced_pool'),
dead_retry=self._conf.get('memcache_pool_dead_retry'),
maxsize=self._conf.get('memcache_pool_maxsize'),
unused_timeout=self._conf.get('memcache_pool_unused_timeout'),
conn_get_timeout=self._conf.get('memcache_pool_conn_get_timeout'),
socket_timeout=self._conf.get('memcache_pool_socket_timeout'),
)
if security_strategy.lower() != 'none':
secret_key = self._conf_get('memcache_secret_key')
secret_key = self._conf.get('memcache_secret_key')
return _cache.SecureTokenCache(self.log,
security_strategy,
secret_key,

View File

@ -14,7 +14,6 @@ import logging
from keystoneauth1 import discover
from keystoneauth1.identity import v2
from keystoneauth1 import loading
from keystoneauth1 import plugin
from keystoneauth1 import token_endpoint
from oslo_config import cfg
@ -188,5 +187,4 @@ OPTS = [
]
loading.register_auth_conf_options(cfg.CONF, _base.AUTHTOKEN_GROUP)
cfg.CONF.register_opts(OPTS, group=_base.AUTHTOKEN_GROUP)

View File

@ -10,18 +10,19 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystonemiddleware import exceptions
class InvalidToken(Exception):
ConfigurationError = exceptions.ConfigurationError
class InvalidToken(exceptions.KeystoneMiddlewareException):
pass
class ServiceError(Exception):
class ServiceError(exceptions.KeystoneMiddlewareException):
pass
class ConfigurationError(Exception):
pass
class RevocationListError(Exception):
class RevocationListError(exceptions.KeystoneMiddlewareException):
pass

View File

@ -13,14 +13,170 @@
import copy
from keystoneauth1 import loading
from oslo_config import cfg
import keystonemiddleware.auth_token
from keystonemiddleware.auth_token import _base
# NOTE(jamielennox): A number of options below are deprecated however are left
# in the list and only mentioned as deprecated in the help string. This is
# because we have to provide the same deprecation functionality for arguments
# passed in via the conf in __init__ (from paste) and there is no way to test
# that the default value was set or not in CONF.
# Also if we were to remove the options from the CONF list (as typical CONF
# deprecation works) then other projects will not be able to override the
# options via CONF.
_OPTS = [
cfg.StrOpt('auth_uri',
default=None,
# FIXME(dolph): should be default='http://127.0.0.1:5000/v2.0/',
# or (depending on client support) an unversioned, publicly
# accessible identity endpoint (see bug 1207517). Further, we
# can eliminate this configuration option in favor of pulling
# the endpoint from the service catalog that the service user
# receives (there should be an identity endpoint listed there).
# This wasn't an option originally when many auth_token
# deployments were configured with the "ADMIN" token and
# endpoint combination.
help='Complete "public" Identity API endpoint. This endpoint'
' should not be an "admin" endpoint, as it should be accessible'
' by all end users. Unauthenticated clients are redirected to'
' this endpoint to authenticate. Although this endpoint should '
' ideally be unversioned, client support in the wild varies. '
' If you\'re using a versioned v2 endpoint here, then this '
' should *not* be the same endpoint the service user utilizes '
' for validating tokens, because normal end users may not be '
' able to reach that endpoint.'),
cfg.StrOpt('auth_version',
default=None,
help='API version of the admin Identity API endpoint.'),
cfg.BoolOpt('delay_auth_decision',
default=False,
help='Do not handle authorization requests within the'
' middleware, but delegate the authorization decision to'
' downstream WSGI components.'),
cfg.IntOpt('http_connect_timeout',
default=None,
help='Request timeout value for communicating with Identity'
' API server.'),
cfg.IntOpt('http_request_max_retries',
default=3,
help='How many times are we trying to reconnect when'
' communicating with Identity API Server.'),
cfg.StrOpt('cache',
default=None,
help='Env key for the swift cache.'),
cfg.StrOpt('certfile',
help='Required if identity server requires client certificate'),
cfg.StrOpt('keyfile',
help='Required if identity server requires client certificate'),
cfg.StrOpt('cafile', default=None,
help='A PEM encoded Certificate Authority to use when '
'verifying HTTPs connections. Defaults to system CAs.'),
cfg.BoolOpt('insecure', default=False, help='Verify HTTPS connections.'),
cfg.StrOpt('region_name', default=None,
help='The region in which the identity server can be found.'),
cfg.StrOpt('signing_dir',
help='Directory used to cache files related to PKI tokens.'),
cfg.ListOpt('memcached_servers',
deprecated_name='memcache_servers',
help='Optionally specify a list of memcached server(s) to'
' use for caching. If left undefined, tokens will instead be'
' cached in-process.'),
cfg.IntOpt('token_cache_time',
default=300,
help='In order to prevent excessive effort spent validating'
' tokens, the middleware caches previously-seen tokens for a'
' configurable duration (in seconds). Set to -1 to disable'
' caching completely.'),
cfg.IntOpt('revocation_cache_time',
default=10,
help='Determines the frequency at which the list of revoked'
' tokens is retrieved from the Identity service (in seconds). A'
' high number of revocation events combined with a low cache'
' duration may significantly reduce performance. Only valid'
' for PKI tokens.'),
cfg.StrOpt('memcache_security_strategy',
default='None',
choices=('None', 'MAC', 'ENCRYPT'),
ignore_case=True,
help='(Optional) If defined, indicate whether token data'
' should be authenticated or authenticated and encrypted.'
' If MAC, token data is authenticated (with HMAC) in the cache.'
' If ENCRYPT, token data is encrypted and authenticated in the'
' cache. If the value is not one of these options or empty,'
' auth_token will raise an exception on initialization.'),
cfg.StrOpt('memcache_secret_key',
default=None,
secret=True,
help='(Optional, mandatory if memcache_security_strategy is'
' defined) This string is used for key derivation.'),
cfg.IntOpt('memcache_pool_dead_retry',
default=5 * 60,
help='(Optional) Number of seconds memcached server is'
' considered dead before it is tried again.'),
cfg.IntOpt('memcache_pool_maxsize',
default=10,
help='(Optional) Maximum total number of open connections to'
' every memcached server.'),
cfg.IntOpt('memcache_pool_socket_timeout',
default=3,
help='(Optional) Socket timeout in seconds for communicating '
'with a memcached server.'),
cfg.IntOpt('memcache_pool_unused_timeout',
default=60,
help='(Optional) Number of seconds a connection to memcached'
' is held unused in the pool before it is closed.'),
cfg.IntOpt('memcache_pool_conn_get_timeout',
default=10,
help='(Optional) Number of seconds that an operation will wait '
'to get a memcached client connection from the pool.'),
cfg.BoolOpt('memcache_use_advanced_pool',
default=False,
help='(Optional) Use the advanced (eventlet safe) memcached '
'client pool. The advanced pool will only work under '
'python 2.x.'),
cfg.BoolOpt('include_service_catalog',
default=True,
help='(Optional) Indicate whether to set the X-Service-Catalog'
' header. If False, middleware will not ask for service'
' catalog on token validation and will not set the'
' X-Service-Catalog header.'),
cfg.StrOpt('enforce_token_bind',
default='permissive',
help='Used to control the use and type of token binding. Can'
' be set to: "disabled" to not check token binding.'
' "permissive" (default) to validate binding information if the'
' bind type is of a form known to the server and ignore it if'
' not. "strict" like "permissive" but if the bind type is'
' unknown the token will be rejected. "required" any form of'
' token binding is needed to be allowed. Finally the name of a'
' binding method that must be present in tokens.'),
cfg.BoolOpt('check_revocations_for_cached', default=False,
help='If true, the revocation list will be checked for cached'
' tokens. This requires that PKI tokens are configured on the'
' identity server.'),
cfg.ListOpt('hash_algorithms', default=['md5'],
help='Hash algorithms to use for hashing PKI tokens. This may'
' be a single algorithm or multiple. The algorithms are those'
' supported by Python standard hashlib.new(). The hashes will'
' be tried in the order given, so put the preferred one first'
' for performance. The result of the first hash will be stored'
' in the cache. This will typically be set to multiple values'
' only while migrating from a less secure algorithm to a more'
' secure one. Once all the old tokens are expired this option'
' should be set to a single value for better performance.'),
]
CONF = cfg.CONF
CONF.register_opts(_OPTS, group=_base.AUTHTOKEN_GROUP)
loading.register_auth_conf_options(cfg.CONF, _base.AUTHTOKEN_GROUP)
auth_token_opts = [
(_base.AUTHTOKEN_GROUP,
keystonemiddleware.auth_token._OPTS +
loading.get_auth_common_conf_options())
(_base.AUTHTOKEN_GROUP, _OPTS + loading.get_auth_common_conf_options()),
]
__all__ = (
@ -49,7 +205,6 @@ def list_opts():
:returns: a list of (group_name, opts) tuples
"""
auth_token_opts = (keystonemiddleware.auth_token._OPTS +
loading.get_auth_common_conf_options())
auth_token_opts = (_OPTS + loading.get_auth_common_conf_options())
return [(_base.AUTHTOKEN_GROUP, copy.deepcopy(auth_token_opts))]

View File

@ -0,0 +1,19 @@
# 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.
class KeystoneMiddlewareException(Exception):
pass
class ConfigurationError(KeystoneMiddlewareException):
pass

View File

@ -20,15 +20,13 @@ import copy
from keystoneauth1 import loading
import keystonemiddleware.auth_token
from keystonemiddleware.auth_token import _auth
from keystonemiddleware.auth_token import _base
from keystonemiddleware.auth_token import _opts
auth_token_opts = [
(_base.AUTHTOKEN_GROUP,
keystonemiddleware.auth_token._OPTS +
_auth.OPTS +
loading.get_auth_common_conf_options())
_opts._OPTS + _auth.OPTS + loading.get_auth_common_conf_options())
]

View File

@ -33,7 +33,6 @@ import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from oslotest import createfile
import six
import testresources
import testtools
@ -499,7 +498,7 @@ class GeneralAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
self.assertEqual(datetime.timedelta(seconds=24),
middleware._revocations._cache_timeout)
self.assertEqual(False, middleware._include_service_catalog)
self.assertEqual('0', middleware._conf['nonexsit_option'])
self.assertEqual('0', middleware._conf.get('nonexsit_option'))
def test_deprecated_conf_values(self):
servers = 'localhost:11211'
@ -509,7 +508,7 @@ class GeneralAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
}
middleware = auth_token.AuthProtocol(self.fake_app, conf)
self.assertEqual([servers], middleware._conf_get('memcached_servers'))
self.assertEqual([servers], middleware._conf.get('memcached_servers'))
def test_conf_values_type_convert_with_wrong_value(self):
conf = {
@ -2534,86 +2533,5 @@ class TestAuthPluginUserAgentGeneration(BaseAuthTokenMiddlewareTest):
self.assertThat(sess.user_agent, matchers.StartsWith(expected_ua))
class TestAuthPluginLocalOsloConfig(BaseAuthTokenMiddlewareTest):
def setUp(self):
super(TestAuthPluginLocalOsloConfig, self).setUp()
self.project = uuid.uuid4().hex
# NOTE(cdent): The options below are selected from those
# which are statically registered by auth_token middleware
# in the 'keystone_authtoken' group. Additional options, from
# plugins, are registered dynamically so must not be used here.
self.oslo_options = {
'auth_uri': uuid.uuid4().hex,
'identity_uri': uuid.uuid4().hex,
}
self.local_oslo_config = cfg.ConfigOpts()
self.local_oslo_config.register_group(cfg.OptGroup(
name='keystone_authtoken'))
self.local_oslo_config.register_opts(auth_token._OPTS,
group='keystone_authtoken')
self.local_oslo_config.register_opts(auth_token._auth.OPTS,
group='keystone_authtoken')
for option, value in self.oslo_options.items():
self.local_oslo_config.set_override(option, value,
'keystone_authtoken')
self.local_oslo_config(args=[], project=self.project)
self.file_options = {
'auth_type': 'password',
'auth_uri': uuid.uuid4().hex,
'password': uuid.uuid4().hex,
}
content = ("[keystone_authtoken]\n"
"auth_type=%(auth_type)s\n"
"auth_uri=%(auth_uri)s\n"
"auth_url=%(auth_uri)s\n"
"password=%(password)s\n" % self.file_options)
self.conf_file_fixture = self.useFixture(
createfile.CreateFileWithContent(self.project, content))
def test_project_in_local_oslo_configuration(self):
conf = {'oslo_config_project': self.project,
'oslo_config_file': self.conf_file_fixture.path}
app = self._create_app(conf, uuid.uuid4().hex)
for option in self.file_options:
self.assertEqual(self.file_options[option],
app._conf_get(option), option)
def test_passed_oslo_configuration(self):
conf = {'oslo_config_config': self.local_oslo_config}
app = self._create_app(conf, uuid.uuid4().hex)
for option in self.oslo_options:
self.assertEqual(self.oslo_options[option],
app._conf_get(option))
def test_passed_olso_configuration_wins(self):
"""oslo_config_config has precedence over oslo_config_project."""
conf = {'oslo_config_project': self.project,
'oslo_config_config': self.local_oslo_config,
'oslo_config_file': self.conf_file_fixture.path}
app = self._create_app(conf, uuid.uuid4().hex)
for option in self.oslo_options:
self.assertEqual(self.oslo_options[option],
app._conf_get(option))
self.assertNotEqual(self.file_options['auth_uri'],
app._conf_get('auth_uri'))
def _create_app(self, conf, project_version):
fake_pkg_resources = mock.Mock()
fake_pkg_resources.get_distribution().version = project_version
body = uuid.uuid4().hex
with mock.patch('keystonemiddleware.auth_token.pkg_resources',
new=fake_pkg_resources):
# use_global_conf is poorly named. What it means is
# don't use the config created in test setUp.
return self.create_simple_middleware(body=body, conf=conf,
use_global_conf=True)
def load_tests(loader, tests, pattern):
return testresources.OptimisingTestSuite(tests)

View File

@ -0,0 +1,112 @@
# 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 uuid
import mock
from oslo_config import cfg
from oslotest import createfile
from keystonemiddleware.auth_token import _auth
from keystonemiddleware.auth_token import _opts
from keystonemiddleware.tests.unit.auth_token import base
def conf_get(app, *args, **kwargs):
return app._conf.get(*args, **kwargs)
class TestAuthPluginLocalOsloConfig(base.BaseAuthTokenTestCase):
def setUp(self):
super(TestAuthPluginLocalOsloConfig, self).setUp()
self.project = uuid.uuid4().hex
# NOTE(cdent): The options below are selected from those
# which are statically registered by auth_token middleware
# in the 'keystone_authtoken' group. Additional options, from
# plugins, are registered dynamically so must not be used here.
self.oslo_options = {
'auth_uri': uuid.uuid4().hex,
'identity_uri': uuid.uuid4().hex,
}
self.local_oslo_config = cfg.ConfigOpts()
self.local_oslo_config.register_group(
cfg.OptGroup(name='keystone_authtoken'))
self.local_oslo_config.register_opts(_opts._OPTS,
group='keystone_authtoken')
self.local_oslo_config.register_opts(_auth.OPTS,
group='keystone_authtoken')
for option, value in self.oslo_options.items():
self.local_oslo_config.set_override(option, value,
'keystone_authtoken')
self.local_oslo_config(args=[], project=self.project)
self.file_options = {
'auth_type': 'password',
'auth_uri': uuid.uuid4().hex,
'password': uuid.uuid4().hex,
}
content = ("[keystone_authtoken]\n"
"auth_type=%(auth_type)s\n"
"auth_uri=%(auth_uri)s\n"
"auth_url=%(auth_uri)s\n"
"password=%(password)s\n" % self.file_options)
self.conf_file_fixture = self.useFixture(
createfile.CreateFileWithContent(self.project, content))
def _create_app(self, conf, project_version=None):
if not project_version:
project_version = uuid.uuid4().hex
fake_pkg_resources = mock.Mock()
fake_pkg_resources.get_distribution().version = project_version
body = uuid.uuid4().hex
with mock.patch('keystonemiddleware.auth_token.pkg_resources',
new=fake_pkg_resources):
# use_global_conf is poorly named. What it means is
# don't use the config created in test setUp.
return self.create_simple_middleware(body=body, conf=conf,
use_global_conf=True)
def test_project_in_local_oslo_configuration(self):
conf = {'oslo_config_project': self.project,
'oslo_config_file': self.conf_file_fixture.path}
app = self._create_app(conf)
for option in self.file_options:
self.assertEqual(self.file_options[option],
conf_get(app, option), option)
def test_passed_oslo_configuration(self):
conf = {'oslo_config_config': self.local_oslo_config}
app = self._create_app(conf)
for option in self.oslo_options:
self.assertEqual(self.oslo_options[option],
conf_get(app, option))
def test_passed_olso_configuration_wins(self):
"""oslo_config_config has precedence over oslo_config_project."""
conf = {'oslo_config_project': self.project,
'oslo_config_config': self.local_oslo_config,
'oslo_config_file': self.conf_file_fixture.path}
app = self._create_app(conf)
for option in self.oslo_options:
self.assertEqual(self.oslo_options[option],
conf_get(app, option))
self.assertNotEqual(self.file_options['auth_uri'],
conf_get(app, 'auth_uri'))