Merge "JSON size violation gives a bad error with nested templates"

This commit is contained in:
Jenkins 2016-02-19 07:05:18 +00:00 committed by Gerrit Code Review
commit 714627b39b
4 changed files with 44 additions and 9 deletions

View File

@ -98,6 +98,8 @@ class InstantiationData(object):
adopt_data = self.data[rpc_api.PARAM_ADOPT_STACK_DATA] adopt_data = self.data[rpc_api.PARAM_ADOPT_STACK_DATA]
try: try:
adopt_data = template_format.simple_parse(adopt_data) adopt_data = template_format.simple_parse(adopt_data)
template_format.validate_template_limit(
six.text_type(adopt_data['template']))
return adopt_data['template'] return adopt_data['template']
except (ValueError, KeyError) as ex: except (ValueError, KeyError) as ex:
err_reason = _('Invalid adopt data: %s') % ex err_reason = _('Invalid adopt data: %s') % ex
@ -105,7 +107,10 @@ class InstantiationData(object):
elif self.PARAM_TEMPLATE in self.data: elif self.PARAM_TEMPLATE in self.data:
template_data = self.data[self.PARAM_TEMPLATE] template_data = self.data[self.PARAM_TEMPLATE]
if isinstance(template_data, dict): if isinstance(template_data, dict):
template_format.validate_template_limit(six.text_type(
template_data))
return template_data return template_data
elif self.PARAM_TEMPLATE_URL in self.data: elif self.PARAM_TEMPLATE_URL in self.data:
url = self.data[self.PARAM_TEMPLATE_URL] url = self.data[self.PARAM_TEMPLATE_URL]
LOG.debug('TemplateUrl %s' % url) LOG.debug('TemplateUrl %s' % url)

View File

@ -22,8 +22,6 @@ import yaml
from heat.common import exception from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
cfg.CONF.import_opt('max_template_size', 'heat.common.config')
if hasattr(yaml, 'CSafeLoader'): if hasattr(yaml, 'CSafeLoader'):
yaml_loader = yaml.CSafeLoader yaml_loader = yaml.CSafeLoader
else: else:
@ -39,6 +37,7 @@ def _construct_yaml_str(self, node):
# Override the default string handling function # Override the default string handling function
# to always return unicode objects # to always return unicode objects
return self.construct_scalar(node) return self.construct_scalar(node)
yaml_loader.add_constructor(u'tag:yaml.org,2002:str', _construct_yaml_str) yaml_loader.add_constructor(u'tag:yaml.org,2002:str', _construct_yaml_str)
# Unquoted dates like 2013-05-23 in yaml files get loaded as objects of type # Unquoted dates like 2013-05-23 in yaml files get loaded as objects of type
# datetime.data which causes problems in API layer when being processed by # datetime.data which causes problems in API layer when being processed by
@ -75,16 +74,31 @@ def simple_parse(tmpl_str):
return tpl return tpl
def validate_template_limit(contain_str):
"""Validate limit for the template.
Check if the contain exceeds allowed size range.
"""
if len(contain_str) > cfg.CONF.max_template_size:
msg = _("Template size (%(actual_len)s bytes) exceeds maximum "
"allowed size (%(limit)s bytes)."
) % {'actual_len': len(contain_str),
'limit': cfg.CONF.max_template_size}
raise exception.RequestLimitExceeded(message=msg)
def parse(tmpl_str): def parse(tmpl_str):
"""Takes a string and returns a dict containing the parsed structure. """Takes a string and returns a dict containing the parsed structure.
This includes determination of whether the string is using the This includes determination of whether the string is using the
JSON or YAML format. JSON or YAML format.
""" """
if len(tmpl_str) > cfg.CONF.max_template_size:
msg = (_('Template exceeds maximum allowed size (%s bytes)') % # TODO(ricolin): Move this validation to api side.
cfg.CONF.max_template_size) # Validate nested stack template.
raise exception.RequestLimitExceeded(message=msg) validate_template_limit(six.text_type(tmpl_str))
tpl = simple_parse(tmpl_str) tpl = simple_parse(tmpl_str)
# Looking for supported version keys in the loaded template # Looking for supported version keys in the loaded template
if not ('HeatTemplateFormatVersion' in tpl if not ('HeatTemplateFormatVersion' in tpl

View File

@ -130,6 +130,20 @@ blarg: wibble
data = stacks.InstantiationData(body) data = stacks.InstantiationData(body)
self.assertRaises(webob.exc.HTTPBadRequest, data.template) self.assertRaises(webob.exc.HTTPBadRequest, data.template)
def test_template_exceeds_max_template_size(self):
cfg.CONF.set_override('max_template_size', 10)
template = json.dumps(['a'] * cfg.CONF.max_template_size)
body = {'template': template}
data = stacks.InstantiationData(body)
error = self.assertRaises(heat_exc.RequestLimitExceeded,
data.template)
msg = ('Request limit exceeded: Template size (%(actual_len)s '
'bytes) exceeds maximum allowed size (%(limit)s bytes).') % {
'actual_len': len(str(template)),
'limit': cfg.CONF.max_template_size}
self.assertEqual(msg, six.text_type(error))
def test_parameters(self): def test_parameters(self):
params = {'foo': 'bar', 'blarg': 'wibble'} params = {'foo': 'bar', 'blarg': 'wibble'}
body = {'parameters': params, body = {'parameters': params,

View File

@ -97,7 +97,7 @@ class YamlMinimalTest(common.HeatTestCase):
def test_long_yaml(self): def test_long_yaml(self):
template = {'HeatTemplateFormatVersion': '2012-12-12'} template = {'HeatTemplateFormatVersion': '2012-12-12'}
config.cfg.CONF.set_override('max_template_size', 1024) config.cfg.CONF.set_override('max_template_size', 10)
template['Resources'] = ['a'] * int( template['Resources'] = ['a'] * int(
config.cfg.CONF.max_template_size / 3) config.cfg.CONF.max_template_size / 3)
limit = config.cfg.CONF.max_template_size limit = config.cfg.CONF.max_template_size
@ -105,8 +105,10 @@ class YamlMinimalTest(common.HeatTestCase):
self.assertTrue(len(long_yaml) > limit) self.assertTrue(len(long_yaml) > limit)
ex = self.assertRaises(exception.RequestLimitExceeded, ex = self.assertRaises(exception.RequestLimitExceeded,
template_format.parse, long_yaml) template_format.parse, long_yaml)
msg = ('Request limit exceeded: Template exceeds maximum allowed size ' msg = ('Request limit exceeded: Template size (%(actual_len)s '
'(1024 bytes)') 'bytes) exceeds maximum allowed size (%(limit)s bytes).') % {
'actual_len': len(str(long_yaml)),
'limit': config.cfg.CONF.max_template_size}
self.assertEqual(msg, six.text_type(ex)) self.assertEqual(msg, six.text_type(ex))
def test_parse_no_version_format(self): def test_parse_no_version_format(self):