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:
Paul Bourke 2017-06-16 15:09:54 +01:00
parent ec4cfecb4b
commit 78732bf776
6 changed files with 120 additions and 1 deletions

View File

@ -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(

View File

@ -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')

View File

@ -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'
#}

View File

@ -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')

View File

@ -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.

View File

@ -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