Support for encrypt/decrypt parameters in heat-manage
Adding support to encrypt/decrypt parameters through heat-manage command. Change-Id: I2cd7b8837156ebe892d591f48ad2b1c6ecca5f9d Implements: blueprint encrypt-hidden-parameters Co-Authored-By: Jason Dunsmore <jasondunsmore@gmail.com>
This commit is contained in:
parent
89d4206954
commit
654032459c
@ -87,6 +87,18 @@ def purge_deleted():
|
||||
utils.purge_deleted(CONF.command.age, CONF.command.granularity)
|
||||
|
||||
|
||||
def do_crypt_parameters_and_properties():
|
||||
"""
|
||||
Encrypt or decrypt template hidden parameters and resource properties data.
|
||||
"""
|
||||
ctxt = context.get_admin_context()
|
||||
prev_encryption_key = CONF.command.previous_encryption_key
|
||||
if CONF.command.crypt_operation == "encrypt":
|
||||
utils.encrypt_parameters_and_properties(ctxt, prev_encryption_key)
|
||||
elif CONF.command.crypt_operation == "decrypt":
|
||||
utils.decrypt_parameters_and_properties(ctxt, prev_encryption_key)
|
||||
|
||||
|
||||
def add_command_parsers(subparsers):
|
||||
parser = subparsers.add_parser('db_version')
|
||||
parser.set_defaults(func=do_db_version)
|
||||
@ -105,6 +117,18 @@ def add_command_parsers(subparsers):
|
||||
choices=['days', 'hours', 'minutes', 'seconds'],
|
||||
help=_('Granularity to use for age argument, defaults to days.'))
|
||||
|
||||
parser = subparsers.add_parser('update_params')
|
||||
parser.set_defaults(func=do_crypt_parameters_and_properties)
|
||||
parser.add_argument('crypt_operation',
|
||||
nargs='?',
|
||||
choices=['encrypt', 'decrypt'],
|
||||
help=_('Valid values are encrypt or decrypt.'))
|
||||
parser.add_argument('previous_encryption_key',
|
||||
nargs='?',
|
||||
default=None,
|
||||
help=_('Provide old encryption key. New encryption'
|
||||
' key would be used from config file.'))
|
||||
|
||||
ServiceManageCommand.add_service_parsers(subparsers)
|
||||
|
||||
command_opt = cfg.SubCommandOpt('command',
|
||||
|
@ -32,24 +32,36 @@ auth_opts = [
|
||||
cfg.CONF.register_opts(auth_opts)
|
||||
|
||||
|
||||
def encrypt(auth_info):
|
||||
def encrypt(auth_info, encryption_key=None):
|
||||
if auth_info is None:
|
||||
return None, None
|
||||
|
||||
encryption_key = get_valid_encryption_key(encryption_key)
|
||||
sym = utils.SymmetricCrypto()
|
||||
res = sym.encrypt(cfg.CONF.auth_encryption_key[:32],
|
||||
res = sym.encrypt(encryption_key,
|
||||
auth_info, b64encode=True)
|
||||
return 'oslo_decrypt_v1', res
|
||||
|
||||
|
||||
def oslo_decrypt_v1(auth_info):
|
||||
def oslo_decrypt_v1(auth_info, encryption_key=None):
|
||||
if auth_info is None:
|
||||
return None
|
||||
|
||||
encryption_key = get_valid_encryption_key(encryption_key)
|
||||
sym = utils.SymmetricCrypto()
|
||||
return sym.decrypt(cfg.CONF.auth_encryption_key[:32],
|
||||
return sym.decrypt(encryption_key,
|
||||
auth_info, b64decode=True)
|
||||
|
||||
|
||||
def heat_decrypt(auth_info):
|
||||
def get_valid_encryption_key(encryption_key):
|
||||
if encryption_key is None:
|
||||
encryption_key = cfg.CONF.auth_encryption_key[:32]
|
||||
else:
|
||||
encryption_key = encryption_key[0:32]
|
||||
return encryption_key
|
||||
|
||||
|
||||
def heat_decrypt(auth_info, encryption_key=None):
|
||||
"""Decrypt function for data that has been encrypted using an older
|
||||
version of Heat.
|
||||
Note: the encrypt function returns the function that is needed to
|
||||
@ -60,9 +72,11 @@ def heat_decrypt(auth_info):
|
||||
"""
|
||||
if auth_info is None:
|
||||
return None
|
||||
|
||||
encryption_key = get_valid_encryption_key(encryption_key)
|
||||
auth = base64.b64decode(auth_info)
|
||||
iv = auth[:AES.block_size]
|
||||
cipher = AES.new(cfg.CONF.auth_encryption_key[:32], AES.MODE_CFB, iv)
|
||||
cipher = AES.new(encryption_key, AES.MODE_CFB, iv)
|
||||
res = cipher.decrypt(auth[AES.block_size:])
|
||||
return res
|
||||
|
||||
|
@ -18,6 +18,7 @@ import sys
|
||||
from oslo_config import cfg
|
||||
from oslo_db.sqlalchemy import session as db_session
|
||||
from oslo_db.sqlalchemy import utils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
import osprofiler.sqlalchemy
|
||||
import six
|
||||
@ -1088,3 +1089,56 @@ def db_sync(engine, version=None):
|
||||
def db_version(engine):
|
||||
"""Display the current database version."""
|
||||
return migration.db_version(engine)
|
||||
|
||||
|
||||
def db_encrypt_parameters_and_properties(ctxt, encryption_key):
|
||||
from heat.engine import template
|
||||
session = get_session()
|
||||
session.begin()
|
||||
|
||||
raw_templates = session.query(models.RawTemplate).all()
|
||||
|
||||
for raw_template in raw_templates:
|
||||
tmpl = template.Template.load(ctxt, raw_template.id, raw_template)
|
||||
|
||||
encrypted_params = []
|
||||
for param_name, param in tmpl.param_schemata().items():
|
||||
if (param_name in encrypted_params) or (not param.hidden):
|
||||
continue
|
||||
|
||||
try:
|
||||
param_val = raw_template.environment['parameters'][
|
||||
param_name]
|
||||
except KeyError:
|
||||
param_val = param.default
|
||||
|
||||
encoded_val = encodeutils.safe_encode(param_val)
|
||||
encrypted_val = crypt.encrypt(encoded_val, encryption_key)
|
||||
raw_template.environment['parameters'][param_name] = \
|
||||
encrypted_val
|
||||
encrypted_params.append(param_name)
|
||||
|
||||
raw_template.environment['encrypted_param_names'] = \
|
||||
encrypted_params
|
||||
|
||||
session.commit()
|
||||
|
||||
|
||||
def db_decrypt_parameters_and_properties(ctxt, encryption_key):
|
||||
session = get_session()
|
||||
session.begin()
|
||||
raw_templates = session.query(models.RawTemplate).all()
|
||||
|
||||
for raw_template in raw_templates:
|
||||
parameters = raw_template.environment['parameters']
|
||||
encrypted_params = raw_template.environment[
|
||||
'encrypted_param_names']
|
||||
for param_name in encrypted_params:
|
||||
decrypt_function_name = parameters[param_name][0]
|
||||
decrypt_function = getattr(crypt, decrypt_function_name)
|
||||
decrypted_val = decrypt_function(parameters[param_name][1],
|
||||
encryption_key)
|
||||
parameters[param_name] = encodeutils.safe_decode(decrypted_val)
|
||||
raw_template.environment['encrypted_param_names'] = []
|
||||
|
||||
session.commit()
|
||||
|
@ -45,3 +45,11 @@ IMPL = LazyPluggable('backend',
|
||||
|
||||
def purge_deleted(age, granularity='days'):
|
||||
IMPL.purge_deleted(age, granularity)
|
||||
|
||||
|
||||
def encrypt_parameters_and_properties(ctxt, encryption_key):
|
||||
IMPL.db_encrypt_parameters_and_properties(ctxt, encryption_key)
|
||||
|
||||
|
||||
def decrypt_parameters_and_properties(ctxt, encryption_key):
|
||||
IMPL.db_decrypt_parameters_and_properties(ctxt, encryption_key)
|
||||
|
@ -25,6 +25,7 @@ from heat.common import context
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.db.sqlalchemy import api as db_api
|
||||
from heat.db.sqlalchemy import models
|
||||
from heat.engine.clients.os import glance
|
||||
from heat.engine.clients.os import nova
|
||||
from heat.engine import environment
|
||||
@ -2602,3 +2603,57 @@ class DBAPISyncPointTest(common.HeatTestCase):
|
||||
self.ctx, self.stack.id, self.stack.current_traversal, True
|
||||
)
|
||||
self.assertEqual(None, ret_sync_point_stack)
|
||||
|
||||
|
||||
class DBAPICryptParamsPropsTest(common.HeatTestCase):
|
||||
def setUp(self):
|
||||
super(DBAPICryptParamsPropsTest, self).setUp()
|
||||
self.ctx = utils.dummy_context()
|
||||
t = 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
|
||||
''')
|
||||
template = {
|
||||
'template': t,
|
||||
'files': {'foo': 'bar'},
|
||||
'environment': {'parameters': {'param1': 'foo',
|
||||
'param2': 'bar'}}}
|
||||
self.template = db_api.raw_template_create(self.ctx, template)
|
||||
|
||||
def test_db_encrypt_decrypt(self):
|
||||
session = db_api.get_session()
|
||||
|
||||
env = session.query(models.RawTemplate).all()[0].environment
|
||||
self.assertEqual('bar', env['parameters']['param2'])
|
||||
|
||||
db_api.db_encrypt_parameters_and_properties(
|
||||
self.ctx, cfg.CONF.auth_encryption_key)
|
||||
|
||||
env = session.query(models.RawTemplate).all()[0].environment
|
||||
self.assertEqual('oslo_decrypt_v1',
|
||||
env['parameters']['param2'][0])
|
||||
|
||||
db_api.db_decrypt_parameters_and_properties(
|
||||
self.ctx, cfg.CONF.auth_encryption_key)
|
||||
|
||||
env = session.query(models.RawTemplate).all()[0].environment
|
||||
self.assertEqual('bar', env['parameters']['param2'])
|
||||
|
||||
# Use a different encryption key to decrypt
|
||||
db_api.db_encrypt_parameters_and_properties(
|
||||
self.ctx, cfg.CONF.auth_encryption_key)
|
||||
db_api.db_decrypt_parameters_and_properties(
|
||||
self.ctx, '774c15be099ea74123a9b9592ff12680')
|
||||
|
||||
env = session.query(models.RawTemplate).all()[0].environment
|
||||
self.assertNotEqual('bar', env['parameters']['param2'])
|
||||
|
Loading…
Reference in New Issue
Block a user