Add encryptData yaql function
Adds a new yaql function 'encryptData' which encrypts values passed from MuranoPL UI definitions. Requires a valid secret storage backend (e.g. Barbican) to be configured via Castellan in _50_murano.py Murano will still work fine without this config but the encrypt/decrypt functions will be unavailable. Implements blueprint: allow-encrypting-of-muranopl-properties Depends-On: I09416b6d35ed2dafa823eca98262a4e23081e6eb Change-Id: Ida94093425a113b67baa05a194525e3cf4e16f79
This commit is contained in:
parent
ec4cfecb4b
commit
78732bf776
|
@ -19,6 +19,8 @@ import json
|
|||
import re
|
||||
import uuid
|
||||
|
||||
from castellan.common import exception as castellan_exception
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth import decorators as auth_dec
|
||||
|
@ -343,7 +345,14 @@ class Wizard(generic_views.PageTitleMixin, views.ModalFormMixin, LazyWizard):
|
|||
def done(self, form_list, **kwargs):
|
||||
app_name = self.storage.extra_data['app'].name
|
||||
service = tuple(form_list)[0].service
|
||||
attributes = service.extract_attributes()
|
||||
try:
|
||||
attributes = service.extract_attributes()
|
||||
except castellan_exception.KeyManagerError as e:
|
||||
msg = _("This Application requires encryption, please contact "
|
||||
"your administrator to configure this.")
|
||||
messages.error(self.request, msg)
|
||||
LOG.exception(e)
|
||||
raise
|
||||
attributes = helpers.insert_hidden_ids(attributes)
|
||||
|
||||
storage = attributes.setdefault('?', {}).setdefault(
|
||||
|
|
|
@ -22,6 +22,22 @@ from yaql.language import yaqltypes
|
|||
from muranodashboard.catalog import forms as catalog_forms
|
||||
from muranodashboard.dynamic_ui import helpers
|
||||
|
||||
from castellan.common import exception as castellan_exception
|
||||
from castellan.common.objects import opaque_data
|
||||
from castellan import key_manager
|
||||
from castellan import options
|
||||
|
||||
from keystoneauth1 import identity
|
||||
from keystoneauth1 import session
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_context import context as _oslo_context
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@specs.parameter('times', int)
|
||||
def _repeat(context, template, times):
|
||||
|
@ -102,8 +118,47 @@ def _ref(context, template_name, parameter_name=None, id_only=None):
|
|||
return data
|
||||
|
||||
|
||||
@specs.parameter('data', yaqltypes.String())
|
||||
def _encrypt_data(context, data):
|
||||
try:
|
||||
# TODO(pbourke): move auth construction into common area if it ends up
|
||||
# been required in other areas
|
||||
auth = identity.V3Password(
|
||||
auth_url=settings.KEY_MANAGER['auth_url'],
|
||||
username=settings.KEY_MANAGER['username'],
|
||||
user_domain_name=settings.KEY_MANAGER['user_domain_name'],
|
||||
password=settings.KEY_MANAGER['password'],
|
||||
project_name=settings.KEY_MANAGER['project_name'],
|
||||
project_domain_name=settings.KEY_MANAGER['project_domain_name']
|
||||
)
|
||||
except (KeyError, AttributeError) as e:
|
||||
LOG.exception(e)
|
||||
msg = ('Could not find valid key manager credentials in the '
|
||||
'murano-dashboard config. encryptData yaql function not '
|
||||
'available')
|
||||
raise castellan_exception.KeyManagerError(message_arg=msg)
|
||||
sess = session.Session(auth=auth)
|
||||
auth_context = _oslo_context.RequestContext(
|
||||
auth_token=auth.get_token(sess), tenant=auth.get_project_id(sess))
|
||||
options.set_defaults(cfg.CONF,
|
||||
auth_endpoint=settings.KEY_MANAGER['auth_url'])
|
||||
|
||||
manager = key_manager.API()
|
||||
try:
|
||||
# TODO(pbourke): while we feel opaque data should cover the most common
|
||||
# use case, we may want to add support for other secret types in the
|
||||
# future (see https://goo.gl/tZhfqe)
|
||||
stored_key_id = manager.store(auth_context,
|
||||
opaque_data.OpaqueData(data))
|
||||
except castellan_exception.KeyManagerError as e:
|
||||
LOG.exception(e)
|
||||
raise
|
||||
return stored_key_id
|
||||
|
||||
|
||||
def register(context):
|
||||
context.register_function(_repeat, 'repeat')
|
||||
context.register_function(_generate_hostname, 'generateHostname')
|
||||
context.register_function(_name, 'name')
|
||||
context.register_function(_ref, 'ref')
|
||||
context.register_function(_encrypt_data, 'encryptData')
|
||||
|
|
|
@ -47,3 +47,15 @@ HORIZON_CONFIG['legacy_static_settings'] = LEGACY_STATIC_SETTINGS
|
|||
|
||||
# from openstack_dashboard.settings import POLICY_FILES
|
||||
POLICY_FILES.update({'murano': 'murano_policy.json',})
|
||||
|
||||
# Applications using the encryptData/decryptData yaql functions will require
|
||||
# the below to be configured
|
||||
#KEY_MANAGER = {
|
||||
# 'auth_url': 'http://192.168.5.254:5000/v3',
|
||||
# 'username': 'admin',
|
||||
# 'user_domain_name': 'default',
|
||||
# 'password': 'password',
|
||||
# 'project_name': 'admin',
|
||||
# 'project_domain_name': 'default'
|
||||
#}
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ import mock
|
|||
import re
|
||||
import testtools
|
||||
|
||||
from castellan.common import exception as castellan_exception
|
||||
from castellan.common.objects import opaque_data
|
||||
|
||||
from muranodashboard.dynamic_ui import helpers
|
||||
from muranodashboard.dynamic_ui import yaql_functions
|
||||
|
||||
|
@ -89,3 +92,35 @@ class TestYAQLFunctions(testtools.TestCase):
|
|||
context = {'?service': mock_service}
|
||||
result = yaql_functions._ref(context, 'foo_template')
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch('muranodashboard.dynamic_ui.yaql_functions.settings')
|
||||
@mock.patch('muranodashboard.dynamic_ui.yaql_functions._oslo_context')
|
||||
@mock.patch('muranodashboard.dynamic_ui.yaql_functions.key_manager')
|
||||
@mock.patch('muranodashboard.dynamic_ui.yaql_functions.identity')
|
||||
def test_encrypt_data(self, mock_identity, mock_keymanager,
|
||||
mock_oslo_context, _):
|
||||
mock_service = mock.Mock(parameters={'#foo_template': 'foo_data'})
|
||||
context = {'?service': mock_service}
|
||||
secret_value = 'secret_password'
|
||||
mock_auth_context = mock.MagicMock()
|
||||
mock_oslo_context.RequestContext.return_value = mock_auth_context
|
||||
yaql_functions._encrypt_data(context, secret_value)
|
||||
mock_keymanager.API().store.assert_called_once_with(
|
||||
mock_auth_context, opaque_data.OpaqueData(secret_value))
|
||||
|
||||
def test_encrypt_data_not_configured(self):
|
||||
mock_service = mock.Mock(parameters={'#foo_template': 'foo_data'})
|
||||
context = {'?service': mock_service}
|
||||
self.assertRaises(castellan_exception.KeyManagerError,
|
||||
yaql_functions._encrypt_data, context,
|
||||
'secret_password')
|
||||
|
||||
@mock.patch('muranodashboard.dynamic_ui.yaql_functions.identity')
|
||||
@mock.patch('muranodashboard.dynamic_ui.yaql_functions.settings')
|
||||
def test_encrypt_data_badly_configured(self, mock_settings, _):
|
||||
mock_service = mock.Mock(parameters={'#foo_template': 'foo_data'})
|
||||
context = {'?service': mock_service}
|
||||
mock_settings.KEY_MANAGER = {}
|
||||
self.assertRaises(castellan_exception.KeyManagerError,
|
||||
yaql_functions._encrypt_data, context,
|
||||
'secret_password')
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Adds a new yaql function 'encryptData' which encrypts values passed from
|
||||
MuranoPL UI definitions. Requires Barbican to be configured, see
|
||||
https://docs.openstack.org/murano/latest/admin/appdev-guide/encrypting_properties.html
|
||||
for more info.
|
|
@ -12,6 +12,7 @@ python-muranoclient>=0.8.2 # Apache-2.0
|
|||
pytz>=2013.6 # MIT
|
||||
PyYAML>=3.10.0 # MIT
|
||||
yaql>=1.1.0 # Apache 2.0 License
|
||||
castellan>=0.7.0 # Apache-2.0
|
||||
|
||||
oslo.log>=3.22.0 # Apache-2.0
|
||||
semantic-version>=2.3.1 # BSD
|
||||
|
|
Loading…
Reference in New Issue