--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openstack/heat/patches/04-launchpad-1496277.patch Fri May 20 17:42:29 2016 -0400
@@ -0,0 +1,236 @@
+This fix will be included in future 2015.1.3 (Kilo) and 5.0.1 (Liberty)
+releases.
+
+From fa19a617a79fd1cb0d892bb8ea87c4b9f6398c34 Mon Sep 17 00:00:00 2001
+From: Zane Bitter <[email protected]>
+Date: Tue, 24 Nov 2015 12:29:38 -0500
+Subject: Load template files only from their known source
+
+Modify get_class to ensure that user-defined resources cannot result in
+reads from the local filesystem. Only resources defined by the operator
+in the global environment should read local files.
+
+To make this work, this patch also adds a separate
+get_class_to_instantiate() method to the Environment.
+
+We were previously using get_class for two different purposes - to get a
+resource plugin on which we could perform introspection to obtain the
+properties and attributes schema, and to get a resource plugin we could
+instantiate to create a Resource object. These are both the same except in
+the case of a TemplateResource, where having two different use cases for
+the same piece of code was adding considerable extra complexity. Combining
+the use cases in this way also made the error handling confusing (leading
+to bug 1518458).
+
+This change separates out the two cases.
+
+Change-Id: I845e7d23c73242a4a4c9c40599690ab705c75caa
+Closes-Bug: #1496277
+Related-Bug: #1447194
+Related-Bug: #1518458
+Related-Bug: #1508115
+(cherry picked from commit 06a713c4456203cd561f16721dc8ac3bcbb37a3
+ and 26e6d5f6d776c1027c4f27058767952a58d15e25)
+---
+
+--- heat-2015.1.2/heat/engine/environment.py.~1~ 2015-10-13 09:51:53.000000000 -0700
++++ heat-2015.1.2/heat/engine/environment.py 2016-01-25 20:50:15.096875593 -0800
+@@ -112,6 +112,12 @@ class ResourceInfo(object):
+ def matches(self, resource_type):
+ return False
+
++ def get_class(self):
++ raise NotImplemented
++
++ def get_class_to_instantiate(self):
++ return self.get_class()
++
+ def __str__(self):
+ return '[%s](User:%s) %s -> %s' % (self.description,
+ self.user_resource,
+@@ -140,10 +146,20 @@ class TemplateResourceInfo(ResourceInfo)
+
+ def get_class(self):
+ from heat.engine.resources import template_resource
++ if self.user_resource:
++ allowed_schemes = template_resource.REMOTE_SCHEMES
++ else:
++ allowed_schemes = template_resource.LOCAL_SCHEMES
++ data = template_resource.TemplateResource.get_template_file(
++ self.template_name,
++ allowed_schemes)
+ env = self.registry.environment
+- return template_resource.generate_class(str(self.name),
+- self.template_name,
+- env)
++ return template_resource.generate_class_from_template(str(self.name),
++ data, env)
++
++ def get_class_to_instantiate(self):
++ from heat.engine.resources import template_resource
++ return template_resource.TemplateResource
+
+
+ class MapResourceInfo(ResourceInfo):
+@@ -398,6 +414,13 @@ class ResourceRegistry(object):
+ return match
+
+ def get_class(self, resource_type, resource_name=None):
++ info = self.get_resource_info(resource_type,
++ resource_name=resource_name)
++ if info is None:
++ raise exception.ResourceTypeNotFound(type_name=resource_type)
++ return info.get_class()
++
++ def get_class_to_instantiate(self, resource_type, resource_name=None):
+ if resource_type == "":
+ msg = _('Resource "%s" has no type') % resource_name
+ raise exception.StackValidationFailed(message=msg)
+@@ -414,7 +437,7 @@ class ResourceRegistry(object):
+ if info is None:
+ msg = _("Unknown resource Type : %s") % resource_type
+ raise exception.StackValidationFailed(message=msg)
+- return info.get_class()
++ return info.get_class_to_instantiate()
+
+ def as_dict(self):
+ """Return user resources in a dict format."""
+@@ -521,6 +544,10 @@ class Environment(object):
+ def get_class(self, resource_type, resource_name=None):
+ return self.registry.get_class(resource_type, resource_name)
+
++ def get_class_to_instantiate(self, resource_type, resource_name=None):
++ return self.registry.get_class_to_instantiate(resource_type,
++ resource_name)
++
+ def get_types(self, support_status=None):
+ return self.registry.get_types(support_status)
+
+--- heat-2015.1.2/heat/engine/resource.py.~1~ 2015-10-13 09:51:53.000000000 -0700
++++ heat-2015.1.2/heat/engine/resource.py 2016-01-25 20:50:15.097540727 -0800
+@@ -137,14 +137,11 @@ class Resource(object):
+ # Call is already for a subclass, so pass it through
+ ResourceClass = cls
+ else:
+- from heat.engine.resources import template_resource
+-
+ registry = stack.env.registry
+- try:
+- ResourceClass = registry.get_class(definition.resource_type,
+- resource_name=name)
+- except exception.NotFound:
+- ResourceClass = template_resource.TemplateResource
++ ResourceClass = registry.get_class_to_instantiate(
++ definition.resource_type,
++ resource_name=name)
++
+ assert issubclass(ResourceClass, Resource)
+
+ return super(Resource, cls).__new__(ResourceClass)
+--- heat-2015.1.2/heat/engine/resources/openstack/heat/resource_group.py.~1~ 2015-10-13 09:51:53.000000000 -0700
++++ heat-2015.1.2/heat/engine/resources/openstack/heat/resource_group.py 2016-01-25 20:50:15.098020940 -0800
+@@ -193,11 +193,7 @@ class ResourceGroup(stack_resource.Stack
+ val_templ = template.Template(test_tmpl)
+ res_def = val_templ.resource_definitions(self.stack)["0"]
+ # make sure we can resolve the nested resource type
+- try:
+- self.stack.env.get_class(res_def.resource_type)
+- except exception.NotFound:
+- # its a template resource
+- pass
++ self.stack.env.get_class_to_instantiate(res_def.resource_type)
+
+ try:
+ name = "%s-%s" % (self.stack.name, self.name)
+--- heat-2015.1.2/heat/engine/resources/template_resource.py.~1~ 2015-10-13 09:51:53.000000000 -0700
++++ heat-2015.1.2/heat/engine/resources/template_resource.py 2016-01-25 20:50:15.098440251 -0800
+@@ -26,8 +26,11 @@ from heat.engine.resources import stack_
+ from heat.engine import template
+
+
+-def generate_class(name, template_name, env):
+- data = TemplateResource.get_template_file(template_name, ('file',))
++REMOTE_SCHEMES = ('http', 'https')
++LOCAL_SCHEMES = ('file',)
++
++
++def generate_class_from_template(name, data, env):
+ tmpl = template.Template(template_format.parse(data))
+ props, attrs = TemplateResource.get_schemas(tmpl, env.param_defaults)
+ cls = type(name, (TemplateResource,),
+@@ -74,9 +77,9 @@ class TemplateResource(stack_resource.St
+ self.template_name = tri.template_name
+ self.resource_type = tri.name
+ if tri.user_resource:
+- self.allowed_schemes = ('http', 'https')
++ self.allowed_schemes = REMOTE_SCHEMES
+ else:
+- self.allowed_schemes = ('http', 'https', 'file')
++ self.allowed_schemes = REMOTE_SCHEMES + LOCAL_SCHEMES
+
+ return tri
+
+--- heat-2015.1.2/heat/engine/service.py.~1~ 2015-10-13 09:51:53.000000000 -0700
++++ heat-2015.1.2/heat/engine/service.py 2016-01-25 20:50:15.099200696 -0800
+@@ -978,8 +978,6 @@ class EngineService(service.Service):
+ """
+ try:
+ resource_class = resources.global_env().get_class(type_name)
+- except exception.StackValidationFailed:
+- raise exception.ResourceTypeNotFound(type_name=type_name)
+ except exception.NotFound as ex:
+ raise exception.StackValidationFailed(message=ex.message)
+
+@@ -1010,8 +1008,6 @@ class EngineService(service.Service):
+ try:
+ return resources.global_env().get_class(
+ type_name).resource_to_template(type_name)
+- except exception.StackValidationFailed:
+- raise exception.ResourceTypeNotFound(type_name=type_name)
+ except exception.NotFound as ex:
+ raise exception.StackValidationFailed(message=ex.message)
+
+--- heat-2015.1.2/heat/tests/test_provider_template.py.~1~ 2015-10-13 09:51:54.000000000 -0700
++++ heat-2015.1.2/heat/tests/test_provider_template.py 2016-01-25 20:50:15.099763200 -0800
+@@ -613,7 +613,11 @@ class ProviderTemplateTest(common.HeatTe
+
+ env_str = {'resource_registry': {'resources': {'fred': {
+ "OS::ResourceType": test_templ_name}}}}
+- env = environment.Environment(env_str)
++ global_env = environment.Environment({}, user_env=False)
++ global_env.load(env_str)
++ with mock.patch('heat.engine.resources._environment',
++ global_env):
++ env = environment.Environment({})
+ cls = env.get_class('OS::ResourceType', 'fred')
+ self.assertNotEqual(template_resource.TemplateResource, cls)
+ self.assertTrue(issubclass(cls, template_resource.TemplateResource))
+@@ -640,10 +644,6 @@ class ProviderTemplateTest(common.HeatTe
+ self.assertTrue(test_templ, "Empty test template")
+ self.m.StubOutWithMock(urlfetch, "get")
+ urlfetch.get(test_templ_name,
+- allowed_schemes=('file',)
+- ).AndRaise(urlfetch.URLFetchError(
+- _('Failed to retrieve template')))
+- urlfetch.get(test_templ_name,
+ allowed_schemes=('http', 'https')).AndReturn(test_templ)
+ parsed_test_templ = template_format.parse(test_templ)
+ self.m.ReplayAll()
+--- heat-2015.1.2/heat/tests/test_resource.py.~1~ 2015-10-13 09:51:54.000000000 -0700
++++ heat-2015.1.2/heat/tests/test_resource.py 2016-01-25 20:50:15.100592773 -0800
+@@ -67,12 +67,13 @@ class ResourceTest(common.HeatTestCase):
+ self.patch('heat.engine.resource.warnings')
+
+ def test_get_class_ok(self):
+- cls = resources.global_env().get_class('GenericResourceType')
++ cls = resources.global_env().get_class_to_instantiate(
++ 'GenericResourceType')
+ self.assertEqual(generic_rsrc.GenericResource, cls)
+
+ def test_get_class_noexist(self):
+ self.assertRaises(exception.StackValidationFailed,
+- resources.global_env().get_class,
++ resources.global_env().get_class_to_instantiate,
+ 'NoExistResourceType')
+
+ def test_resource_new_ok(self):