diff --git a/etc/heat/heat.conf.sample b/etc/heat/heat.conf.sample index d8f238fd9..50867b806 100644 --- a/etc/heat/heat.conf.sample +++ b/etc/heat/heat.conf.sample @@ -75,6 +75,15 @@ #auth_encryption_key=notgood but just long enough i think +# +# Options defined in heat.common.wsgi +# + +# Maximum raw byte size of JSON request body. Should be larger +# than max_template_size. (integer value) +#max_json_body_size=1048576 + + # # Options defined in heat.db.api # diff --git a/heat/common/exception.py b/heat/common/exception.py index fcaff0a99..bc175e809 100644 --- a/heat/common/exception.py +++ b/heat/common/exception.py @@ -330,3 +330,7 @@ class StackRecursionLimitReached(HeatException): def __init__(self, recursion_depth): self.message = self.message % recursion_depth super(StackRecursionLimitReached, self).__init__() + + +class RequestLimitExceeded(HeatException): + message = _('Request limit exceeded: %(message)s') diff --git a/heat/common/wsgi.py b/heat/common/wsgi.py index f50c521d8..08386ed62 100644 --- a/heat/common/wsgi.py +++ b/heat/common/wsgi.py @@ -137,6 +137,12 @@ cfg.CONF.register_group(api_cw_group) cfg.CONF.register_opts(api_cw_opts, group=api_cw_group) +json_size_opt = cfg.IntOpt('max_json_body_size', + default=1048576, + help='Maximum raw byte size of JSON request body.' + ' Should be larger than max_template_size.') +cfg.CONF.register_opt(json_size_opt) + class WritableLogger(object): """A thin wrapper that responds to `write` and logs.""" @@ -524,6 +530,12 @@ class JSONRequestDeserializer(object): def from_json(self, datastring): try: + if len(datastring) > cfg.CONF.max_json_body_size: + msg = _('JSON body size (%(len)s bytes) exceeds maximum ' + 'allowed size (%(limit)s bytes).') % \ + {'len': len(datastring), + 'limit': cfg.CONF.max_json_body_size} + raise exception.RequestLimitExceeded(message=msg) return json.loads(datastring) except ValueError as ex: raise webob.exc.HTTPBadRequest(str(ex)) @@ -638,11 +650,10 @@ class Resource(object): # ContentType=JSON results in a JSON serialized response... content_type = request.params.get("ContentType") - deserialized_request = self.dispatch(self.deserializer, - action, request) - action_args.update(deserialized_request) - try: + deserialized_request = self.dispatch(self.deserializer, + action, request) + action_args.update(deserialized_request) action_result = self.dispatch(self.controller, action, request, **action_args) except TypeError as err: diff --git a/heat/tests/test_wsgi.py b/heat/tests/test_wsgi.py index 01d947725..52783d982 100644 --- a/heat/tests/test_wsgi.py +++ b/heat/tests/test_wsgi.py @@ -17,6 +17,8 @@ import datetime +import json +from oslo.config import cfg import stubout import webob @@ -380,3 +382,15 @@ class JSONRequestDeserializerTest(HeatTestCase): actual = wsgi.JSONRequestDeserializer().default(request) expected = {"body": {"key": "value"}} self.assertEqual(actual, expected) + + def test_from_json_exceeds_max_json_mb(self): + cfg.CONF.set_override('max_json_body_size', 10) + body = json.dumps(['a'] * cfg.CONF.max_json_body_size) + self.assertTrue(len(body) > cfg.CONF.max_json_body_size) + error = self.assertRaises(exception.RequestLimitExceeded, + wsgi.JSONRequestDeserializer().from_json, + body) + msg = 'Request limit exceeded: JSON body size ' + \ + '(%s bytes) exceeds maximum allowed size (%s bytes).' % \ + (len(body), cfg.CONF.max_json_body_size) + self.assertEqual(msg, str(error))