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: I09416b6d35ed2dafa823eca98262a4e23081e6eb
This commit is contained in:
Paul Bourke 2017-06-16 17:24:00 +01:00
parent 9cb54690e9
commit 9248605e67
12 changed files with 167 additions and 1 deletions

View File

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

View File

@ -0,0 +1,10 @@
Application:
?:
type: com.paul.EncryptionDemo
my_password: encryptData($.instanceConfiguration.my_password)
Forms:
- instanceConfiguration:
fields:
- name: my_password
type: string

View File

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

View File

@ -20,4 +20,5 @@ Application Developer Guide
use_cases
app_development_framework
app_debugging
garbage_collection
garbage_collection
encrypting_properties

View File

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

View File

@ -8,3 +8,4 @@ namespace = oslo.messaging
namespace = oslo.middleware.cors
namespace = oslo.policy
namespace = oslo.service.service
namespace = castellan.config

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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