Merge "Encrypt Heat template hidden parameters"

This commit is contained in:
Jenkins 2015-05-15 07:51:28 +00:00 committed by Gerrit Code Review
commit f1f5c8081c
11 changed files with 152 additions and 11 deletions

View File

@ -216,8 +216,12 @@ engine_opts = [
' set to a list of tuples,'
' (stackresourcename, stackname) with list[0] being'
' (None, rootstackname), and heat_resource_name will'
' be set to the resource\'s name.'))]
' be set to the resource\'s name.')),
cfg.BoolOpt('encrypt_parameters_and_properties',
default=False,
help=_('Encrypt template parameters that were marked as'
' hidden and also all the resource properties before'
' storing them in database.'))]
rpc_opts = [
cfg.StrOpt('host',

View File

@ -16,9 +16,11 @@ from heat.common import template_format
SECTIONS = (
PARAMETERS, RESOURCE_REGISTRY, PARAMETER_DEFAULTS
PARAMETERS, RESOURCE_REGISTRY, PARAMETER_DEFAULTS,
ENCRYPTED_PARAM_NAMES
) = (
'parameters', 'resource_registry', 'parameter_defaults'
'parameters', 'resource_registry', 'parameter_defaults',
'encrypted_param_names'
)
@ -50,4 +52,7 @@ def default_for_missing(env):
"""Checks a parsed environment for missing sections."""
for param in SECTIONS:
if param not in env:
env[param] = {}
if param == ENCRYPTED_PARAM_NAMES:
env[param] = []
else:
env[param] = {}

View File

@ -472,6 +472,8 @@ class Environment(object):
else:
self.param_defaults = {}
self.encrypted_param_names = env.get(env_fmt.ENCRYPTED_PARAM_NAMES, [])
if env_fmt.PARAMETERS in env:
self.params = env[env_fmt.PARAMETERS]
else:
@ -504,7 +506,8 @@ class Environment(object):
"""Get the environment as a dict, ready for storing in the db."""
return {env_fmt.RESOURCE_REGISTRY: self.registry.as_dict(),
env_fmt.PARAMETERS: self.params,
env_fmt.PARAMETER_DEFAULTS: self.param_defaults}
env_fmt.PARAMETER_DEFAULTS: self.param_defaults,
env_fmt.ENCRYPTED_PARAM_NAMES: self.encrypted_param_names}
def register_class(self, resource_type, resource_class):
self.registry.register_class(resource_type, resource_class)

View File

@ -429,7 +429,6 @@ class Stack(collections.Mapping):
status, action and status_reason.
- We sometimes only want the DB attributes.
"""
stack = {
'owner_id': self.owner_id,
'username': self.username,
@ -471,6 +470,7 @@ class Stack(collections.Mapping):
s['backup'] = backup
s['updated_at'] = self.updated_time
if self.t.id is None:
stack_object.Stack.encrypt_hidden_parameters(self.t)
s['raw_template_id'] = self.t.store(self.context)
else:
s['raw_template_id'] = self.t.id

View File

@ -17,9 +17,13 @@
RawTemplate object
"""
from oslo_config import cfg
from oslo_utils import encodeutils
from oslo_versionedobjects import base
from oslo_versionedobjects import fields
from heat.common import crypt
from heat.common import environment_format as env_fmt
from heat.db import api as db_api
from heat.objects import fields as heat_fields
@ -41,6 +45,17 @@ class RawTemplate(
def _from_db_object(context, tpl, db_tpl):
for field in tpl.fields:
tpl[field] = db_tpl[field]
# If any of the parameters were encrypted, then decrypt them
parameters = tpl.environment[env_fmt.PARAMETERS]
encrypted_param_names = tpl.environment[env_fmt.ENCRYPTED_PARAM_NAMES]
for param_name in encrypted_param_names:
decrypt_function_name = parameters[param_name][0]
decrypt_function = getattr(crypt, decrypt_function_name)
decrypted_val = decrypt_function(parameters[param_name][1])
parameters[param_name] = encodeutils.safe_decode(decrypted_val)
tpl.environment[env_fmt.PARAMETERS] = parameters
tpl._context = context
tpl.obj_reset_changes()
return tpl
@ -51,6 +66,17 @@ class RawTemplate(
raw_template = cls._from_db_object(context, cls(), raw_template_db)
return raw_template
@classmethod
def encrypt_hidden_parameters(cls, tmpl):
if cfg.CONF.encrypt_parameters_and_properties:
for param_name, param in tmpl.env.params.items():
if not tmpl.param_schemata()[param_name].hidden:
continue
clear_text_val = tmpl.env.params.get(param_name)
encoded_val = encodeutils.safe_encode(clear_text_val)
tmpl.env.params[param_name] = crypt.encrypt(encoded_val)
tmpl.env.encrypted_param_names.append(param_name)
@classmethod
def create(cls, context, values):
return db_api.raw_template_create(context, values)

View File

@ -164,3 +164,7 @@ class Stack(
self,
db_stack
)
@classmethod
def encrypt_hidden_parameters(cls, tmpl):
raw_template.RawTemplate.encrypt_hidden_parameters(tmpl)

View File

@ -166,6 +166,7 @@ blarg: wibble
def test_parameters(self):
params = {'foo': 'bar', 'blarg': 'wibble'}
body = {'parameters': params,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}}
data = stacks.InstantiationData(body)
@ -182,6 +183,7 @@ blarg: wibble
'environment': {'parameters': {'blarg': 'wibble'}}}
expect = {'parameters': {'blarg': 'wibble',
'foo': 'bar'},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}}
data = stacks.InstantiationData(body)
@ -197,6 +199,7 @@ blarg: wibble
expect = {'parameters': {'blarg': 'wibble',
'foo': 'bar',
'tester': 'Yes'},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}}
data = stacks.InstantiationData(body)
@ -212,8 +215,8 @@ blarg: wibble
env = {'foo': 'bar', 'blarg': 'wibble'}
body = {'not the environment': env}
data = stacks.InstantiationData(body)
self.assertEqual({'parameters': {}, 'parameter_defaults': {},
'resource_registry': {}},
self.assertEqual({'parameters': {}, 'encrypted_param_names': [],
'parameter_defaults': {}, 'resource_registry': {}},
data.environment())
def test_args(self):
@ -744,6 +747,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': identity.stack_name,
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -806,6 +810,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': identity.stack_name,
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -894,6 +899,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': identity.stack_name,
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {'my.yaml': 'This is the file contents.'},
@ -938,6 +944,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -955,6 +962,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -972,6 +980,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -1026,6 +1035,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -1107,6 +1117,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -1507,6 +1518,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -1543,6 +1555,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {u'parameters': parameters,
u'encrypted_param_names': [],
u'parameter_defaults': {},
u'resource_registry': {}},
'files': {},
@ -1627,6 +1640,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': {},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -1663,6 +1677,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -1725,6 +1740,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': {},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -1765,6 +1781,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
@ -1903,6 +1920,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
('validate_template',
{'template': template,
'params': {'parameters': {},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}}})
).AndReturn(engine_response)
@ -1927,6 +1945,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
('validate_template',
{'template': template,
'params': {'parameters': {},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}}})
).AndReturn({'Error': 'fubar'})

View File

@ -38,6 +38,7 @@ class EnvironmentTest(common.HeatTestCase):
def test_load_old_parameters(self):
old = {u'a': u'ff', u'b': u'ss'}
expected = {u'parameters': old,
u'encrypted_param_names': [],
u'parameter_defaults': {},
u'resource_registry': {u'resources': {}}}
env = environment.Environment(old)
@ -45,6 +46,7 @@ class EnvironmentTest(common.HeatTestCase):
def test_load_new_env(self):
new_env = {u'parameters': {u'a': u'ff', u'b': u'ss'},
u'encrypted_param_names': [],
u'parameter_defaults': {u'ff': 'new_def'},
u'resource_registry': {u'OS::Food': u'fruity.yaml',
u'resources': {}}}
@ -56,6 +58,7 @@ class EnvironmentTest(common.HeatTestCase):
prev_params = {'foo': 'bar', 'tester': 'Yes'}
params = {}
expected = {'parameters': prev_params,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
prev_env = environment.Environment(
@ -70,6 +73,7 @@ class EnvironmentTest(common.HeatTestCase):
prev_params = {'foo': 'bar', 'tester': 'Yes'}
params = {'tester': 'patched'}
expected = {'parameters': {'foo': 'bar', 'tester': 'patched'},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
prev_env = environment.Environment(
@ -85,6 +89,7 @@ class EnvironmentTest(common.HeatTestCase):
'another_tester': 'Yes'}
params = {'tester': 'patched'}
expected = {'parameters': {'foo': 'bar', 'tester': 'patched'},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
prev_env = environment.Environment(
@ -99,6 +104,7 @@ class EnvironmentTest(common.HeatTestCase):
prev_params = {'foo': 'bar', 'tester': 'Yes'}
params = {}
expected = {'parameters': {'foo': 'bar'},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
prev_env = environment.Environment(
@ -487,6 +493,7 @@ class ChildEnvTest(common.HeatTestCase):
new_params = {'foo': 'bar', 'tester': 'Yes'}
penv = environment.Environment()
expected = {'parameters': new_params,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
cenv = environment.get_child_environment(penv, new_params)
@ -496,6 +503,7 @@ class ChildEnvTest(common.HeatTestCase):
new_params = {'parameters': {'foo': 'bar', 'tester': 'Yes'}}
penv = environment.Environment()
expected = {'parameter_defaults': {},
'encrypted_param_names': [],
'resource_registry': {'resources': {}}}
expected.update(new_params)
cenv = environment.get_child_environment(penv, new_params)
@ -506,6 +514,7 @@ class ChildEnvTest(common.HeatTestCase):
parent_params = {'parameters': {'gone': 'hopefully'}}
penv = environment.Environment(env=parent_params)
expected = {'parameter_defaults': {},
'encrypted_param_names': [],
'resource_registry': {'resources': {}}}
expected.update(new_params)
cenv = environment.get_child_environment(penv, new_params)

View File

@ -24,6 +24,7 @@ class YamlEnvironmentTest(common.HeatTestCase):
yaml1 = ''
yaml2 = '''
parameters: {}
encrypted_param_names: []
parameter_defaults: {}
resource_registry: {}
'''

View File

@ -24,6 +24,7 @@ import six
from heat.common import context
from heat.common import exception
from heat.common import template_format
from heat.db import api as db_api
from heat.engine.clients.os import keystone
from heat.engine.clients.os import nova
from heat.engine import environment
@ -1965,6 +1966,73 @@ class StackTest(common.HeatTestCase):
# resource state.
self.assertFalse(mock_drg.called)
def test_encrypt_parameters_false_parameters_stored_plaintext(self):
'''
Test stack loading with disabled parameter value validation.
'''
tmpl = template_format.parse('''
heat_template_version: 2013-05-23
parameters:
param1:
type: string
description: value1.
param2:
type: string
description: value2.
hidden: true
resources:
a_resource:
type: GenericResourceType
''')
env1 = environment.Environment({'param1': 'foo', 'param2': 'bar'})
self.stack = stack.Stack(self.ctx, 'test',
template.Template(tmpl, env=env1))
cfg.CONF.set_override('encrypt_parameters_and_properties', False)
# Verify that hidden parameters stored in plain text
self.stack.store()
db_stack = stack_object.Stack.get_by_id(self.ctx, self.stack.id)
params = db_stack.raw_template.environment['parameters']
self.assertEqual('foo', params['param1'])
self.assertEqual('bar', params['param2'])
def test_parameters_stored_encrypted_decrypted_on_load(self):
'''
Test stack loading with disabled parameter value validation.
'''
tmpl = template_format.parse('''
heat_template_version: 2013-05-23
parameters:
param1:
type: string
description: value1.
param2:
type: string
description: value2.
hidden: true
resources:
a_resource:
type: GenericResourceType
''')
env1 = environment.Environment({'param1': 'foo', 'param2': 'bar'})
self.stack = stack.Stack(self.ctx, 'test',
template.Template(tmpl, env=env1))
cfg.CONF.set_override('encrypt_parameters_and_properties', True)
# Verify that hidden parameters are stored encrypted
self.stack.store()
db_tpl = db_api.raw_template_get(self.ctx, self.stack.t.id)
db_params = db_tpl.environment['parameters']
self.assertEqual('foo', db_params['param1'])
self.assertEqual('oslo_decrypt_v1', db_params['param2'][0])
self.assertIsNotNone(db_params['param2'][1])
# Verify that loaded stack has decrypted paramters
loaded_stack = stack.Stack.load(self.ctx, stack_id=self.stack.id)
params = loaded_stack.t.env.params
self.assertEqual('foo', params.get('param1'))
self.assertEqual('bar', params.get('param2'))
class StackKwargsForCloningTest(common.HeatTestCase):
scenarios = [

View File

@ -754,7 +754,8 @@ class WithTemplateTest(StackResourceBaseTest):
def test_create_with_template(self):
child_env = {'parameter_defaults': {},
'parameters': self.params,
'resource_registry': {'resources': {}}}
'resource_registry': {'resources': {}},
'encrypted_param_names': []}
self.parent_resource.child_params = mock.Mock(
return_value=self.params)
res_name = self.parent_resource.physical_resource_name()
@ -788,7 +789,8 @@ class WithTemplateTest(StackResourceBaseTest):
child_env = {'parameter_defaults': {},
'parameters': self.params,
'resource_registry': {'resources': {}}}
'resource_registry': {'resources': {}},
'encrypted_param_names': []}
self.parent_resource.child_params = mock.Mock(
return_value=self.params)
rpcc = mock.Mock()