Add a new crypt method using cryptography
This updates the default crypt method to use the cryptography module instead of the oslo crypto utils module. It also refactors decrypt to remove some duplication. Change-Id: Ie24aebcb3080725c250a4f3ba726b23a9c995965 Closes-Bug: #1468025
This commit is contained in:
parent
e6ecedca1a
commit
d8f3ef910c
@ -12,13 +12,19 @@
|
||||
# under the License.
|
||||
|
||||
import base64
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from cryptography import fernet
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import encodeutils
|
||||
|
||||
from heat.common.i18n import _
|
||||
from heat.common.i18n import _LW
|
||||
from heat.openstack.common.crypto import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
auth_opts = [
|
||||
cfg.StrOpt('auth_encryption_key',
|
||||
@ -32,36 +38,52 @@ auth_opts = [
|
||||
cfg.CONF.register_opts(auth_opts)
|
||||
|
||||
|
||||
def encrypt(auth_info, encryption_key=None):
|
||||
if auth_info is None:
|
||||
def encrypt(value, encryption_key=None):
|
||||
if value is None:
|
||||
return None, None
|
||||
|
||||
encryption_key = get_valid_encryption_key(encryption_key)
|
||||
sym = utils.SymmetricCrypto()
|
||||
res = sym.encrypt(encryption_key,
|
||||
auth_info, b64encode=True)
|
||||
return 'oslo_decrypt_v1', res
|
||||
sym = fernet.Fernet(encryption_key.encode('base64'))
|
||||
res = sym.encrypt(encodeutils.safe_encode(value))
|
||||
return 'cryptography_decrypt_v1', res
|
||||
|
||||
|
||||
def oslo_decrypt_v1(auth_info, encryption_key=None):
|
||||
if auth_info is None:
|
||||
def decrypt(method, data, encryption_key=None):
|
||||
if method is None or data is None:
|
||||
return None
|
||||
decryptor = getattr(sys.modules[__name__], method)
|
||||
value = decryptor(data, encryption_key)
|
||||
if value is not None:
|
||||
try:
|
||||
return encodeutils.safe_decode(value, 'utf-8')
|
||||
except UnicodeDecodeError as ex:
|
||||
# if the incorrect encryption_key was used then we can get
|
||||
# total gibberish here and safe_decode() will freak out.
|
||||
LOG.warn(_LW("Couldn't decrypt parameters %s"), ex)
|
||||
|
||||
|
||||
def oslo_decrypt_v1(value, encryption_key=None):
|
||||
encryption_key = get_valid_encryption_key(encryption_key)
|
||||
sym = utils.SymmetricCrypto()
|
||||
return sym.decrypt(encryption_key,
|
||||
auth_info, b64decode=True)
|
||||
value, b64decode=True)
|
||||
|
||||
|
||||
def cryptography_decrypt_v1(value, encryption_key=None):
|
||||
encryption_key = get_valid_encryption_key(encryption_key)
|
||||
sym = fernet.Fernet(encryption_key.encode('base64'))
|
||||
return sym.decrypt(encodeutils.safe_encode(value))
|
||||
|
||||
|
||||
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]
|
||||
encryption_key = encryption_key[:32]
|
||||
return encryption_key
|
||||
|
||||
|
||||
def heat_decrypt(auth_info, encryption_key=None):
|
||||
def heat_decrypt(value, 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
|
||||
@ -70,11 +92,8 @@ def heat_decrypt(auth_info, encryption_key=None):
|
||||
function must still exist. So whilst it may seem that this function
|
||||
is not referenced, it will be referenced from the database.
|
||||
"""
|
||||
if auth_info is None:
|
||||
return None
|
||||
|
||||
encryption_key = get_valid_encryption_key(encryption_key)
|
||||
auth = base64.b64decode(auth_info)
|
||||
auth = base64.b64decode(value)
|
||||
iv = auth[:AES.block_size]
|
||||
cipher = AES.new(encryption_key, AES.MODE_CFB, iv)
|
||||
res = cipher.decrypt(auth[AES.block_size:])
|
||||
|
@ -18,8 +18,6 @@ import sys
|
||||
from oslo_config import cfg
|
||||
from oslo_db.sqlalchemy import session as db_session
|
||||
from oslo_db.sqlalchemy import utils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import timeutils
|
||||
import osprofiler.sqlalchemy
|
||||
import six
|
||||
@ -31,7 +29,6 @@ from sqlalchemy.orm import session as orm_session
|
||||
from heat.common import crypt
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.common.i18n import _LW
|
||||
from heat.db.sqlalchemy import filters as db_filters
|
||||
from heat.db.sqlalchemy import migration
|
||||
from heat.db.sqlalchemy import models
|
||||
@ -42,8 +39,6 @@ CONF.import_opt('hidden_stack_tags', 'heat.common.config')
|
||||
CONF.import_opt('max_events_per_stack', 'heat.common.config')
|
||||
CONF.import_group('profiler', 'heat.common.config')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_facade = None
|
||||
|
||||
|
||||
@ -197,7 +192,7 @@ def resource_data_get_all(resource, data=None):
|
||||
|
||||
for res in data:
|
||||
if res.redact:
|
||||
ret[res.key] = _decrypt(res.value, res.decrypt_method)
|
||||
ret[res.key] = crypt.decrypt(res.decrypt_method, res.value)
|
||||
else:
|
||||
ret[res.key] = res.value
|
||||
return ret
|
||||
@ -211,7 +206,7 @@ def resource_data_get(resource, key):
|
||||
resource.id,
|
||||
key)
|
||||
if result.redact:
|
||||
return _decrypt(result.value, result.decrypt_method)
|
||||
return crypt.decrypt(result.decrypt_method, result.value)
|
||||
return result.value
|
||||
|
||||
|
||||
@ -245,22 +240,6 @@ def stack_tags_get(context, stack_id):
|
||||
return result or None
|
||||
|
||||
|
||||
def _encrypt(value):
|
||||
if value is not None:
|
||||
return crypt.encrypt(value.encode('utf-8'))
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def _decrypt(enc_value, method):
|
||||
if method is None:
|
||||
return None
|
||||
decryptor = getattr(crypt, method)
|
||||
value = decryptor(enc_value)
|
||||
if value is not None:
|
||||
return six.text_type(value, 'utf-8')
|
||||
|
||||
|
||||
def resource_data_get_by_key(context, resource_id, key):
|
||||
"""Looks up resource_data by resource_id and key. Does not unencrypt
|
||||
resource_data.
|
||||
@ -277,7 +256,7 @@ def resource_data_get_by_key(context, resource_id, key):
|
||||
def resource_data_set(resource, key, value, redact=False):
|
||||
"""Save resource's key/value pair to database."""
|
||||
if redact:
|
||||
method, value = _encrypt(value)
|
||||
method, value = crypt.encrypt(value)
|
||||
else:
|
||||
method = ''
|
||||
try:
|
||||
@ -623,7 +602,7 @@ def user_creds_create(context):
|
||||
values = context.to_dict()
|
||||
user_creds_ref = models.UserCreds()
|
||||
if values.get('trust_id'):
|
||||
method, trust_id = _encrypt(values.get('trust_id'))
|
||||
method, trust_id = crypt.encrypt(values.get('trust_id'))
|
||||
user_creds_ref.trust_id = trust_id
|
||||
user_creds_ref.decrypt_method = method
|
||||
user_creds_ref.trustor_user_id = values.get('trustor_user_id')
|
||||
@ -635,7 +614,7 @@ def user_creds_create(context):
|
||||
user_creds_ref.region_name = values.get('region_name')
|
||||
else:
|
||||
user_creds_ref.update(values)
|
||||
method, password = _encrypt(values['password'])
|
||||
method, password = crypt.encrypt(values['password'])
|
||||
if len(six.text_type(password)) > 255:
|
||||
raise exception.Error(_("Length of OS_PASSWORD after encryption"
|
||||
" exceeds Heat limit (255 chars)"))
|
||||
@ -653,8 +632,10 @@ def user_creds_get(user_creds_id):
|
||||
# or it can be committed back to the DB in decrypted form
|
||||
result = dict(db_result)
|
||||
del result['decrypt_method']
|
||||
result['password'] = _decrypt(result['password'], db_result.decrypt_method)
|
||||
result['trust_id'] = _decrypt(result['trust_id'], db_result.decrypt_method)
|
||||
result['password'] = crypt.decrypt(
|
||||
db_result.decrypt_method, result['password'])
|
||||
result['trust_id'] = crypt.decrypt(
|
||||
db_result.decrypt_method, result['trust_id'])
|
||||
return result
|
||||
|
||||
|
||||
@ -1159,8 +1140,7 @@ def db_encrypt_parameters_and_properties(ctxt, encryption_key):
|
||||
except KeyError:
|
||||
param_val = param.default
|
||||
|
||||
encoded_val = encodeutils.safe_encode(param_val)
|
||||
encrypted_val = crypt.encrypt(encoded_val, encryption_key)
|
||||
encrypted_val = crypt.encrypt(param_val, encryption_key)
|
||||
raw_template.environment['parameters'][param_name] = \
|
||||
encrypted_val
|
||||
encrypted_params.append(param_name)
|
||||
@ -1183,18 +1163,10 @@ def db_decrypt_parameters_and_properties(ctxt, encryption_key):
|
||||
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)
|
||||
try:
|
||||
parameters[param_name] = encodeutils.safe_decode(
|
||||
decrypted_val)
|
||||
except UnicodeDecodeError as ex:
|
||||
# if the incorrect encryption_key was used then we can get
|
||||
# total gibberish here and safe_decode() will freak out.
|
||||
LOG.warn(_LW("Couldn't decrypt parameters %s"), ex)
|
||||
parameters[param_name] = ""
|
||||
method, value = parameters[param_name]
|
||||
decrypted_val = crypt.decrypt(method, value, encryption_key)
|
||||
parameters[param_name] = decrypted_val
|
||||
|
||||
environment = raw_template.environment.copy()
|
||||
environment['encrypted_param_names'] = []
|
||||
raw_template_update(ctxt, raw_template.id,
|
||||
|
@ -20,7 +20,6 @@ RawTemplate object
|
||||
import copy
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_versionedobjects import base
|
||||
from oslo_versionedobjects import fields
|
||||
|
||||
@ -56,10 +55,9 @@ class RawTemplate(
|
||||
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)
|
||||
method, value = parameters[param_name]
|
||||
decrypted_val = crypt.decrypt(method, value)
|
||||
parameters[param_name] = decrypted_val
|
||||
tpl.environment[env_fmt.PARAMETERS] = parameters
|
||||
|
||||
tpl._context = context
|
||||
@ -78,8 +76,7 @@ class RawTemplate(
|
||||
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.params[param_name] = crypt.encrypt(clear_text_val)
|
||||
tmpl.env.encrypted_param_names.append(param_name)
|
||||
|
||||
@classmethod
|
||||
|
@ -20,7 +20,6 @@ Resource object
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_versionedobjects import base
|
||||
from oslo_versionedobjects import fields
|
||||
import six
|
||||
@ -85,9 +84,8 @@ class Resource(
|
||||
if resource.properties_data_encrypted and resource.properties_data:
|
||||
properties_data = {}
|
||||
for prop_name, prop_value in resource.properties_data.items():
|
||||
decrypt_function_name = prop_value[0]
|
||||
decrypt_function = getattr(crypt, decrypt_function_name, None)
|
||||
decrypted_value = decrypt_function(prop_value[1])
|
||||
method, value = prop_value
|
||||
decrypted_value = crypt.decrypt(method, value)
|
||||
prop_string = jsonutils.loads(decrypted_value)
|
||||
properties_data[prop_name] = prop_string
|
||||
resource.properties_data = properties_data
|
||||
@ -185,8 +183,7 @@ class Resource(
|
||||
result = {}
|
||||
for prop_name, prop_value in data.items():
|
||||
prop_string = jsonutils.dumps(prop_value)
|
||||
encoded_value = encodeutils.safe_encode(prop_string)
|
||||
encrypted_value = crypt.encrypt(encoded_value)
|
||||
encrypted_value = crypt.encrypt(prop_string)
|
||||
result[prop_name] = encrypted_value
|
||||
return (True, result)
|
||||
return (False, data)
|
||||
|
@ -22,6 +22,7 @@ from oslo_utils import timeutils
|
||||
import six
|
||||
|
||||
from heat.common import context
|
||||
from heat.common import crypt
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.db.sqlalchemy import api as db_api
|
||||
@ -1500,8 +1501,8 @@ class DBAPIUserCredsTest(common.HeatTestCase):
|
||||
trustor_user_id='trustor_id')
|
||||
self.assertIsNotNone(user_creds.id)
|
||||
self.assertEqual('test_trust_id',
|
||||
db_api._decrypt(user_creds.trust_id,
|
||||
user_creds.decrypt_method))
|
||||
crypt.decrypt(user_creds.decrypt_method,
|
||||
user_creds.trust_id))
|
||||
self.assertEqual('trustor_id', user_creds.trustor_user_id)
|
||||
self.assertIsNone(user_creds.username)
|
||||
self.assertIsNone(user_creds.password)
|
||||
@ -1512,14 +1513,14 @@ class DBAPIUserCredsTest(common.HeatTestCase):
|
||||
user_creds = create_user_creds(self.ctx)
|
||||
self.assertIsNotNone(user_creds.id)
|
||||
self.assertEqual(self.ctx.password,
|
||||
db_api._decrypt(user_creds.password,
|
||||
user_creds.decrypt_method))
|
||||
crypt.decrypt(user_creds.decrypt_method,
|
||||
user_creds.password))
|
||||
|
||||
def test_user_creds_get(self):
|
||||
user_creds = create_user_creds(self.ctx)
|
||||
ret_user_creds = db_api.user_creds_get(user_creds.id)
|
||||
self.assertEqual(db_api._decrypt(user_creds.password,
|
||||
user_creds.decrypt_method),
|
||||
self.assertEqual(crypt.decrypt(user_creds.decrypt_method,
|
||||
user_creds.password),
|
||||
ret_user_creds['password'])
|
||||
|
||||
def test_user_creds_get_noexist(self):
|
||||
@ -2731,8 +2732,9 @@ class DBAPICryptParamsPropsTest(common.HeatTestCase):
|
||||
self.ctx, cfg.CONF.auth_encryption_key)
|
||||
|
||||
env = session.query(models.RawTemplate).all()[0].environment
|
||||
self.assertEqual('oslo_decrypt_v1',
|
||||
self.assertEqual('cryptography_decrypt_v1',
|
||||
env['parameters']['param2'][0])
|
||||
encrypt_value = env['parameters']['param2'][1]
|
||||
|
||||
db_api.db_decrypt_parameters_and_properties(
|
||||
self.ctx, cfg.CONF.auth_encryption_key)
|
||||
@ -2742,9 +2744,13 @@ class DBAPICryptParamsPropsTest(common.HeatTestCase):
|
||||
|
||||
# Use a different encryption key to decrypt
|
||||
db_api.db_encrypt_parameters_and_properties(
|
||||
self.ctx, cfg.CONF.auth_encryption_key)
|
||||
self.ctx, '774c15be099ea74123a9b9592ff12680')
|
||||
env = session.query(models.RawTemplate).all()[0].environment
|
||||
self.assertNotEqual(encrypt_value,
|
||||
env['parameters']['param2'][1])
|
||||
|
||||
db_api.db_decrypt_parameters_and_properties(
|
||||
self.ctx, '774c15be099ea74123a9b9592ff12680')
|
||||
|
||||
env = session.query(models.RawTemplate).all()[0].environment
|
||||
self.assertNotEqual('bar', env['parameters']['param2'])
|
||||
self.assertEqual('bar', env['parameters']['param2'])
|
||||
|
@ -1987,7 +1987,7 @@ class StackTest(common.HeatTestCase):
|
||||
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.assertEqual('cryptography_decrypt_v1', db_params['param2'][0])
|
||||
self.assertIsNotNone(db_params['param2'][1])
|
||||
|
||||
# Verify that loaded stack has decrypted paramters
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
pbr<2.0,>=0.11
|
||||
Babel>=1.3
|
||||
cryptography>=0.8.2 # Apache-2.0
|
||||
dogpile.cache>=0.5.3
|
||||
eventlet>=0.17.4
|
||||
greenlet>=0.3.2
|
||||
|
Loading…
Reference in New Issue
Block a user