JSON size violation gives a bad error with nested templates

This patch propose a normalized error message for both template
validation and JSON validation.

Also Add template validation in api template parser to prevent huge template
(besides nested stacks) pass to engine.

Change-Id: If62621d7a6f44100a158974f0fc9c3ddf58d8d99
Depends-On: I981d477c96345e68baa5a3d96c6671ad242dbb63
Closes-Bug: 1499379
This commit is contained in:
ricolin 2015-12-29 15:32:05 +08:00
parent bb894cde1c
commit fc1c513d35
4 changed files with 44 additions and 9 deletions

View File

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

View File

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

View File

@ -130,6 +130,20 @@ blarg: wibble
data = stacks.InstantiationData(body)
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):
params = {'foo': 'bar', 'blarg': 'wibble'}
body = {'parameters': params,

View File

@ -97,7 +97,7 @@ class YamlMinimalTest(common.HeatTestCase):
def test_long_yaml(self):
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(
config.cfg.CONF.max_template_size / 3)
limit = config.cfg.CONF.max_template_size
@ -105,8 +105,10 @@ class YamlMinimalTest(common.HeatTestCase):
self.assertTrue(len(long_yaml) > limit)
ex = self.assertRaises(exception.RequestLimitExceeded,
template_format.parse, long_yaml)
msg = ('Request limit exceeded: Template exceeds maximum allowed size '
'(1024 bytes)')
msg = ('Request limit exceeded: Template size (%(actual_len)s '
'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))
def test_parse_no_version_format(self):