Adds a whitelist for endpoint catalog substitution

Change-Id: If02327d70d0143d805969fe927898f08eb84c4c2
Closes-Bug: #1354208
This commit is contained in:
David Stanek 2014-08-15 13:30:33 -05:00 committed by Tristan Cacqueray
parent d0dd958eeb
commit 52714633c9
7 changed files with 97 additions and 2 deletions

View File

@ -190,6 +190,13 @@
# template_file = default_catalog.templates # template_file = default_catalog.templates
# (Deprecated) List of possible substitutions for use in
# formatting endpoints. Use caution when modifying this list.
# It will give users with permission to create endpoints the
# ability to see those values in your configuration file. This
# option will be removed in Juno. (list value)
#endpoint_substitution_whitelist=tenant_id,user_id,public_bind_host,admin_bind_hostompute_hostompute_port,admin_port,public_port,public_endpoint,admin_endpoint
[endpoint_filter] [endpoint_filter]
# extension for creating associations between project and endpoints in order to # extension for creating associations between project and endpoints in order to
# provide a tailored catalog for project-scoped token requests. # provide a tailored catalog for project-scoped token requests.

View File

@ -19,6 +19,7 @@
from keystone.common import dependency from keystone.common import dependency
from keystone.common import manager from keystone.common import manager
from keystone.common import utils
from keystone import config from keystone import config
from keystone import exception from keystone import exception
from keystone.openstack.common import log as logging from keystone.openstack.common import log as logging
@ -30,6 +31,9 @@ LOG = logging.getLogger(__name__)
def format_url(url, data): def format_url(url, data):
"""Safely string formats a user-defined URL with the given data.""" """Safely string formats a user-defined URL with the given data."""
data = utils.WhiteListedItemFilter(
CONF.catalog.endpoint_substitution_whitelist,
data)
try: try:
result = url.replace('$(', '%(') % data result = url.replace('$(', '%(') % data
except AttributeError: except AttributeError:

View File

@ -260,8 +260,18 @@ FILE_OPTIONS = {
cfg.StrOpt('template_file', cfg.StrOpt('template_file',
default='default_catalog.templates'), default='default_catalog.templates'),
cfg.StrOpt('driver', cfg.StrOpt('driver',
default='keystone.catalog.backends.sql.Catalog')]} default='keystone.catalog.backends.sql.Catalog'),
cfg.ListOpt('endpoint_substitution_whitelist',
default=['tenant_id', 'user_id', 'public_bind_host',
'admin_bind_host', 'compute_host', 'compute_port',
'admin_port', 'public_port', 'public_endpoint',
'admin_endpoint'],
help='(Deprecated) List of possible substitutions for use '
'in formatting endpoints. Use caution when modifying '
'this list. It will give users with permission to '
'create endpoints the ability to see those values '
'in your configuration file. This option will be '
'removed in Juno.')]}
CONF = cfg.CONF CONF = cfg.CONF

View File

@ -516,3 +516,15 @@ def make_dirs(path, mode=None, user=None, group=None, log=None):
raise EnvironmentError("makedirs('%s'): %s" % (path, exc.strerror)) raise EnvironmentError("makedirs('%s'): %s" % (path, exc.strerror))
set_permissions(path, mode, user, group, log) set_permissions(path, mode, user, group, log)
class WhiteListedItemFilter(object):
def __init__(self, whitelist, data):
self._whitelist = set(whitelist or [])
self._data = data
def __getitem__(self, name):
if name not in self._whitelist:
raise KeyError
return self._data[name]

View File

View File

View File

@ -0,0 +1,62 @@
# 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 keystone.catalog import core
from keystone import config
from keystone import exception
from keystone import tests
CONF = config.CONF
class FormatUrlTests(tests.TestCase):
def setUp(self):
super(FormatUrlTests, self).setUp()
whitelist = ['host', 'port', 'part1', 'part2']
self.opt_in_group('catalog', endpoint_substitution_whitelist=whitelist)
def test_successful_formatting(self):
url_template = 'http://%(host)s:%(port)d/%(part1)s/%(part2)s'
values = {'host': 'server', 'port': 9090, 'part1': 'A', 'part2': 'B'}
actual_url = core.format_url(url_template, values)
expected_url = 'http://server:9090/A/B'
self.assertEqual(actual_url, expected_url)
def test_raises_malformed_on_missing_key(self):
self.assertRaises(exception.MalformedEndpoint,
core.format_url,
"http://%(foo)s/%(bar)s",
{"foo": "1"})
def test_raises_malformed_on_wrong_type(self):
self.assertRaises(exception.MalformedEndpoint,
core.format_url,
"http://%foo%s",
{"foo": "1"})
def test_raises_malformed_on_incomplete_format(self):
self.assertRaises(exception.MalformedEndpoint,
core.format_url,
"http://%(foo)",
{"foo": "1"})
def test_substitution_with_key_not_whitelisted(self):
url_template = 'http://%(host)s:%(port)d/%(part1)s/%(part2)s/%(part3)s'
values = {'host': 'server', 'port': 9090,
'part1': 'A', 'part2': 'B', 'part3': 'C'}
self.assertRaises(exception.MalformedEndpoint,
core.format_url,
url_template,
values)