Add decryptData yaql function to murano engine
Adds a new yaql function 'decryptData' which pairs with 'encryptData' on the dashboard side. Requires a valid secret storage backend (e.g. Barbican) to be configured via Castellan in murano.conf, e.g. [key_manager] auth_type = keystone_password auth_url = <auth_url> username = <username> password = <password> project_id = <project_id> user_domain_name = <user_domain_name> Murano will still work fine without this config but the encrypt/decrypt functions will be unavailable. Partially-Implements blueprint: allow-encrypting-of-muranopl-properties Depends-On: I1be3a1e11e3f4c2170062927ad359bf679eb25d9 Change-Id: I09416b6d35ed2dafa823eca98262a4e23081e6ebchanges/72/471772/13
parent
9cb54690e9
commit
9248605e67
|
@ -0,0 +1,18 @@
|
|||
Namespaces:
|
||||
=: com.paul
|
||||
std: io.murano
|
||||
res: io.murano.resources
|
||||
|
||||
Name: EncryptionDemo
|
||||
|
||||
Extends: std:Application
|
||||
|
||||
Properties:
|
||||
my_password:
|
||||
Contract: $.string()
|
||||
|
||||
Methods:
|
||||
deploy:
|
||||
Body:
|
||||
- $reporter: $this.find(std:Environment).reporter
|
||||
- $reporter.report($this, decryptData($.my_password))
|
|
@ -0,0 +1,10 @@
|
|||
Application:
|
||||
?:
|
||||
type: com.paul.EncryptionDemo
|
||||
my_password: encryptData($.instanceConfiguration.my_password)
|
||||
|
||||
Forms:
|
||||
- instanceConfiguration:
|
||||
fields:
|
||||
- name: my_password
|
||||
type: string
|
|
@ -0,0 +1,6 @@
|
|||
FullName: com.paul.EncryptionDemo
|
||||
Type: Application
|
||||
Description: Simple app to demonstrate Murano encryption
|
||||
Author: Paul Bourke
|
||||
Classes:
|
||||
com.paul.EncryptionDemo: EncryptionDemo.yaml
|
|
@ -20,4 +20,5 @@ Application Developer Guide
|
|||
use_cases
|
||||
app_development_framework
|
||||
app_debugging
|
||||
garbage_collection
|
||||
garbage_collection
|
||||
encrypting_properties
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
.. _encrypting-properties:
|
||||
|
||||
================================
|
||||
Managing Senstive Data in Murano
|
||||
================================
|
||||
|
||||
Overview
|
||||
--------
|
||||
If you are developing a Murano application that manages sensitive data such as
|
||||
passwords, user data, etc, you may want to ensure this is stored in a secure
|
||||
manner in the Murano backend.
|
||||
|
||||
Murano offers two `yaql` functions to do this, `encryptData` and
|
||||
`decryptData`.
|
||||
|
||||
.. note:: Barbican or a similar compatible secret storage backend must be
|
||||
configured to use this feature.
|
||||
|
||||
Configuring
|
||||
-----------
|
||||
Murano makes use of Castellan_ to manage encryption using a supported secret
|
||||
storage backend. As of OpenStack Pike, Barbican_ is the only supported
|
||||
backend, and hence is the one tested by the Murano community.
|
||||
|
||||
To configure Murano to use Barbican, place the following configuration into
|
||||
`murano-engine.conf`::
|
||||
|
||||
[key_manager]
|
||||
auth_type = 'keystone_password'
|
||||
auth_url = <keystone_url>
|
||||
username = <username>
|
||||
password = <password>
|
||||
project_id = <project_id>
|
||||
user_domain_name = <domain_name>
|
||||
|
||||
Similarly, place the following configuration into `_50_murano.py` to configure
|
||||
the murano-dashboard end::
|
||||
|
||||
KEY_MANAGER = {
|
||||
'auth_url': <keystone_url>/v3',
|
||||
'username': <username>,
|
||||
'user_domain_name': <domain_name>,
|
||||
'password': <password>,
|
||||
'project_name': <project_name>,
|
||||
'project_domain_name': <domain_name>
|
||||
}
|
||||
|
||||
Example
|
||||
-------
|
||||
`encryptData(foo)`: Call to encrypt string `foo` in storage. Will return a
|
||||
`uuid` which is used to retrieve the encrypted value.
|
||||
|
||||
`decryptData(foo_key)`: Call to decrypt and retrieve the value represented by
|
||||
`foo_key` from storage.
|
||||
|
||||
There is an example application available in the murano repository_.
|
||||
|
||||
.. _Castellan: https://github.com/openstack/castellan
|
||||
.. _Barbican: https://github.com/openstack/barbican
|
||||
.. _repository: https://git.openstack.org/cgit/openstack/murano/tree/contrib/packages/EncryptionDemo
|
|
@ -8,3 +8,4 @@ namespace = oslo.messaging
|
|||
namespace = oslo.middleware.cors
|
||||
namespace = oslo.policy
|
||||
namespace = oslo.service.service
|
||||
namespace = castellan.config
|
||||
|
|
|
@ -21,6 +21,7 @@ import time
|
|||
|
||||
import jsonpatch
|
||||
import jsonpointer
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import base64
|
||||
import six
|
||||
from yaql.language import specs
|
||||
|
@ -33,6 +34,11 @@ from murano.dsl import dsl
|
|||
from murano.dsl import helpers
|
||||
from murano.dsl import yaql_integration
|
||||
|
||||
from castellan.common import exception as castellan_exception
|
||||
from castellan.common import utils as castellan_utils
|
||||
from castellan import key_manager
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_random_string_counter = None
|
||||
|
||||
|
@ -203,6 +209,25 @@ def logger(context, logger_name):
|
|||
return log
|
||||
|
||||
|
||||
@specs.parameter('value', yaqltypes.String())
|
||||
@specs.extension_method
|
||||
def decrypt_data(value):
|
||||
manager = key_manager.API()
|
||||
try:
|
||||
context = castellan_utils.credential_factory(conf=cfg.CONF)
|
||||
except castellan_exception.AuthTypeInvalidError as e:
|
||||
LOG.exception(e)
|
||||
LOG.error("Castellan must be correctly configured in order to use "
|
||||
"decryptData()")
|
||||
raise
|
||||
try:
|
||||
data = manager.get(context, value).get_encoded()
|
||||
except castellan_exception.KeyManagerError as e:
|
||||
LOG.exception(e)
|
||||
raise
|
||||
return data
|
||||
|
||||
|
||||
@helpers.memoize
|
||||
def get_context(runtime_version):
|
||||
context = yaql_integration.create_empty_context()
|
||||
|
@ -213,6 +238,7 @@ def get_context(runtime_version):
|
|||
context.register_function(random_name)
|
||||
context.register_function(patch_)
|
||||
context.register_function(logger)
|
||||
context.register_function(decrypt_data, 'decryptData')
|
||||
|
||||
if runtime_version <= constants.RUNTIME_VERSION_1_1:
|
||||
context.register_function(substr)
|
||||
|
|
|
@ -318,3 +318,10 @@ Methods:
|
|||
# Thus this assignment tests the fix for bug #1597452
|
||||
- $this.target: id($newObject)
|
||||
- Return: $this.target = $newObject
|
||||
|
||||
testDecryptData:
|
||||
Arguments:
|
||||
- value:
|
||||
Contract: $.string().notNull()
|
||||
Body:
|
||||
- Return: decryptData($value)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import six
|
||||
from testtools import matchers
|
||||
from yaql.language import exceptions as yaql_exceptions
|
||||
|
@ -19,6 +20,8 @@ from yaql.language import exceptions as yaql_exceptions
|
|||
from murano.tests.unit.dsl.foundation import object_model as om
|
||||
from murano.tests.unit.dsl.foundation import test_case
|
||||
|
||||
from castellan.common import exception as castellan_exception
|
||||
|
||||
|
||||
class TestEngineYaqlFunctions(test_case.DslTestCase):
|
||||
def setUp(self):
|
||||
|
@ -249,3 +252,26 @@ class TestEngineYaqlFunctions(test_case.DslTestCase):
|
|||
|
||||
def test_new_object_assignment(self):
|
||||
self.assertTrue(self._runner.testNewObjectAssignment())
|
||||
|
||||
@mock.patch('murano.engine.system.yaql_functions.key_manager')
|
||||
@mock.patch('murano.engine.system.yaql_functions.castellan_utils')
|
||||
def test_decrypt_data(self, mock_castellan_utils, mock_key_manager):
|
||||
dummy_context = mock.MagicMock()
|
||||
mock_castellan_utils.credential_factory.return_value = dummy_context
|
||||
|
||||
encrypted_value = '91f784d0-5ef1-4b6f-9311-9b5a33d828d8'
|
||||
decrypted_value = 'secret_password'
|
||||
|
||||
mock_key_manager.API().get.return_value.get_encoded.return_value =\
|
||||
decrypted_value
|
||||
self.assertEqual(decrypted_value,
|
||||
self._runner.testDecryptData(encrypted_value))
|
||||
mock_key_manager.API().get.assert_called_once_with(dummy_context,
|
||||
encrypted_value)
|
||||
|
||||
@mock.patch('murano.engine.system.yaql_functions.LOG')
|
||||
def test_decrypt_data_not_configured(self, mock_log):
|
||||
encrypted_value = '91f784d0-5ef1-4b6f-9311-9b5a33d828d8'
|
||||
self.assertRaises(castellan_exception.AuthTypeInvalidError,
|
||||
self._runner.testDecryptData, encrypted_value)
|
||||
mock_log.error.assert_called()
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Added a new yaql function 'decryptData' which pairs with 'encryptData' on
|
||||
the dashboard side. Application authors can use these functions to secure
|
||||
sensitive input to their Murano applications such as passwords.
|
||||
|
||||
Requires a valid secret storage backend (e.g. Barbican) to be configured
|
||||
via Castellan.
|
|
@ -46,3 +46,4 @@ oslo.utils>=3.20.0 # Apache-2.0
|
|||
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
||||
oslo.log>=3.22.0 # Apache-2.0
|
||||
semantic-version>=2.3.1 # BSD
|
||||
castellan>=0.7.0 # Apache-2.0
|
||||
|
|
|
@ -61,6 +61,7 @@ oslo.config.opts =
|
|||
murano = murano.opts:list_opts
|
||||
keystone_authtoken = keystonemiddleware.opts:list_auth_token_opts
|
||||
murano.cfapi = murano.opts:list_cfapi_opts
|
||||
castellan.config = castellan.options:list_opts
|
||||
|
||||
oslo.config.opts.defaults =
|
||||
murano = murano.common.config:set_middleware_defaults
|
||||
|
|
Loading…
Reference in New Issue