encrypt vim credentials with barbican

Partial-bug: #1667652
Change-Id: I32f0c5d87c782aaaa46f1965db9bdf55cc19bae5
This commit is contained in:
Yan Xing'an 2017-03-14 08:46:20 -07:00
parent 3dee03c392
commit f2876e22c2
1 changed files with 522 additions and 0 deletions

View File

@ -0,0 +1,522 @@
..
This work is licensed under a Creative Commons Attribution 3.0 Unported
License.
http://creativecommons.org/licenses/by/3.0/legalcode
===================================
Storing VIM credentials in barbican
===================================
Include the URL of your launchpad blueprint:
https://blueprints.launchpad.net/tacker/+spec/encryption-with-barbican
This spec introduces a way of storing VIM credentials in barbican.
Problem description
===================
Tacker supports register VIM with credentials, which are used for
nfvo and vnfm to operation resources in NFVI. The credentials include
username, password, and project information. We can not explicitly
store the plain text password in tacker db for security consideration.
So currently we use keystone's fernet to generate a key to encrypt
the password, save the encrypted secret into tacker db, and save
the fernet key on local file system.
When we need the authorization to a VIM, we retrieve the original
password by decrypting the secret in tacker db with the fernet key
in local file system.
The problem is, if tacker service serves API requests through
a load balancer, then the operation will fail if the request is not
fulfilled by the server node which created and stored the fernet key.
We need a possible solution for syncing the keys across multiple
server nodes. This adds operationally complexity for tacker
administrators as they add tacker-server instances for scaling.
Barbican introduction
=====================
Barbican[1] is a REST API designed for the secure storage, provisioning and
management of secrets. It is aimed at being useful for all environments,
including large ephemeral Clouds.
The barbican API [2] includes the following items:
* Secrets API. It provides access to the secret / keying material stored
in the system, including Private Key/Certificate/Password/SSH Keys
* Secret Metadata API. It allows a user to be able to associate various
key/value pairs with a Secret.
* Containers API. It creates a logical object that can be used to
hold secret references.
* ACL API. It supports access control for secrets and containers.
* Certificate Authorities API. It is used as an interface to interact
with Certificate Authorities.
* Quotas API. It limit on the number of resources that are allowed
to be created.
* Consumers API. It is a way to register as an interested party
for a container.
* Certificates API [deprecated in Pike]. It manages the lifecycle of
x509 certificates covering operations such as initial certificate
issuance, certificate re-issuance, certificate renewal and
certificate revocation.
* Orders API [deprecated in Pike]. It allows user to request barbican
to generate a secret, create certificates and public/private key pairs.
In tacker vim use case, we can use the Secrets API to restore the password
of VIM. And in future, we can use Barbican to support TLS in Tacker API, the
new blueprint URL to be realized is:
https://blueprints.launchpad.net/tacker/+spec/support-tls-in-api
The command to store the password:
.. code-block:: console
$ source devstack/openrc admin
$ openstack secret store --name 'vim_password' --payload-content-type='text/plain' --payload="123456"
+---------------+--------------------------------------------------------------+
| Field | Value |
+---------------+--------------------------------------------------------------+
| Secret href | http://192.168.80.128:9311/v1/secrets/fd44e2ed-b318-4924 |
| | -a43f-afac4ba45aca |
| Name | vim_password |
| Created | None |
| Status | None |
| Content types | {u'default': u'text/plain'} |
| Algorithm | aes |
| Bit length | 256 |
| Secret type | opaque |
| Mode | cbc |
| Expiration | None |
+---------------+--------------------------------------------------------------+
The command to retrieve the password:
.. code-block:: console
$ openstack secret get http://192.168.80.128:9311/v1/secrets/fd44e2ed-b318-4924-a43f-afac4ba45aca --decrypt
+---------+--------+
| Field | Value |
+---------+--------+
| Payload | 123456 |
+---------+--------+
Let's look into other projects about how Barbican is invoked.
Invoked in Nova or Cinder
-------------------------
Barbican is introduced into nova and cinder to support the volume
encryption feature [3][4].
They invoke castellan as key_mamager, allowing Barbican to securely
generate, store, and present encryption keys.
Invoked in Neutron-lbaas or Magnum
----------------------------------
Neutron-lbaas and Magnum introduces barbican to support TLS [5][6].
They invoke barbicanclient directly to store tenants' TLS certificates in
barbican secure containers.
Castellan introduction
----------------------
Castellan[7] is the library of Barbican, working on the principle of providing
an abstracted key manager based on the configurations. In this manner,
several different management services can be supported through a single
interface. In addition to the key manager, Castellan also provides primitives
for various types of secrets (for example, asymmetric keys,simple
passphrases, and certificates). These primitives are used in conjunction
with the key manager to create, store, retrieve, and destroy managed secrets.
barbicanclient VS castellan
---------------------------
Barbicanclient supports full APIs of Barbican.
Castellan is a library which invokes barbicanclient, offering
an elaborate API, and more easier to use than the client.
Unfortunately castellan can not support ACL for secrets or
containers currently.
So we will invoke barbicanclient only in this spec, and may
consider to use castellan in future if necessary.
How to use castellan
--------------------
* Example. Creating and storing a key.
.. code-block:: python
from castellan.common.objects import passphrase
from castellan import key_manager
key = passphrase.Passphrase('super_secret_password')
manager = key_manager.API()
stored_key_id = manager.store(context, key)
* Example. Retrieving a key.
.. code-block:: python
from castellan import key_manager
manager = key_manager.API()
key = manager.get(context, stored_key_id)
key.get_encoded()
* Example. Deleting a key.
.. code-block:: python
from castellan import key_manager
manager = key_manager.API()
manager.delete(context, stored_key_id)
How to use barbicanclient
-------------------------
We can refer to castellan to see how to invoke barbicanclient [12]:
store secret:
.. code-block:: python
barbican_client = self._get_barbican_client(context)
try:
secret = self._get_barbican_object(barbican_client,
managed_object)
secret.expiration = expiration
secret_ref = secret.store()
return self._retrieve_secret_uuid(secret_ref)
except (barbican_exceptions.HTTPAuthError,
barbican_exceptions.HTTPClientError,
barbican_exceptions.HTTPServerError) as e:
LOG.error(_LE("Error storing object: %s"), e)
raise exception.KeyManagerError(reason=e)
get secret:
.. code-block:: python
try:
secret = self._get_secret(context, managed_object_id)
return self._get_castellan_object(secret, metadata_only)
except (barbican_exceptions.HTTPAuthError,
barbican_exceptions.HTTPClientError,
barbican_exceptions.HTTPServerError) as e:
LOG.error(_LE("Error retrieving object: %s"), e)
if self._is_secret_not_found_error(e):
raise exception.ManagedObjectNotFoundError(
uuid=managed_object_id)
else:
raise exception.KeyManagerError(reason=e)
How to generate context
-----------------------
Let's look into how to generate the context for castellan.
For security consideration, barbican need to get authorization from
the keystone. And the secrets stored in barbican is private to the operator,
the users in the same project can retrieval the secrets by default RBAC
policy.
There are two methods to generate the context.
1. Using a reserved project
Castellan Usage[8] shows a method, saving the credentials in configuration.
We can create a reserved tenant (e.g. 'tacker-vim-credential-store' or
long living existing created user), and all vims' passwords are saved and
retrieved in this tenant's domain.
.. code-block:: ini
[castellan]
auth_type = 'keystone_password'
username = 'tacker-vim-credential-store'
password = 'passw0rd1'
project_id = 'tacker-vim-credential-store'
user_domain_name = 'default'
As discussion in IRC [11], we should not do in this way.
2. Using the operator's context (who creates vim)
The default RBAC policy[9] about secrets are following:
.. code-block:: ini
"admin": "role:admin",
"observer": "role:observer",
"creator": "role:creator",
"audit": "role:audit",
"service_admin": "role:key-manager:service-admin",
"admin_or_user_does_not_work": "project_id:%(project_id)s",
"admin_or_user": "rule:admin or project_id:%(project_id)s",
"admin_or_creator": "rule:admin or rule:creator",
"all_but_audit": "rule:admin or rule:observer or rule:creator",
"all_users": "rule:admin or rule:observer or rule:creator or rule:audit or rule:service_admin",
"secret_project_match": "project:%(target.secret.project_id)s",
"secret_acl_read": "'read':%(target.secret.read)s",
"secret_private_read": "'False':%(target.secret.read_project_access)s",
"secret_creator_user": "user:%(target.secret.creator_id)s",
"secret_non_private_read": "rule:all_users and rule:secret_project_match and not rule:secret_private_read",
"secret_decrypt_non_private_read": "rule:all_but_audit and rule:secret_project_match and not rule:secret_private_read",
"secret_project_admin": "rule:admin and rule:secret_project_match",
"secret_project_creator": "rule:creator and rule:secret_project_match and rule:secret_creator_user",
"secret:decrypt": "rule:secret_decrypt_non_private_read or rule:secret_project_creator or rule:secret_project_admin or rule:secret_acl_read",
"secret:get": "rule:secret_non_private_read or rule:secret_project_creator or rule:secret_project_admin or rule:secret_acl_read",
"secret:put": "rule:admin_or_creator and rule:secret_project_match",
"secret:delete": "rule:secret_project_admin or rule:secret_project_creator",
"secrets:post": "rule:admin_or_creator",
"secrets:get": "rule:all_but_audit",
The barbican support a white-list ACL for each secret. It is not
convenient to add all projects to the ACL [10] if vim is shared.
In this method, we can not support shared vim. As result of IRC
discussion [11], in future, vim is limited to be shared with
specified projects via rbac policies, we may add these projects
into the ACL of the secret.
Transmitting encrypted password
-------------------------------
For security consideration, we need avoid sending unencrypted cleartext
password transmitting from tacker to barbican.
There are two methods:
1. use fernet to encode vim password, and save fernet key into barbican.
2. support TLS between tacker with barbican.
I suggest use method 1, just like the current vim encode way.
Proposed change
===============
We need retain current realization for a release cycle,
make it configurable, and use local file system by default.
.. code-block:: python
OPTS = [
cfg.StrOpt('use_barbican', default='no',
help=_("Use barbican to encrypt vim password if yes,
Save vim credentials in local file system if no")),
]
cfg.CONF.register_opts(OPTS, 'tacker')
We add a directory named keymgr under tacker, which
invokes the barbicanclient.
Add a class BarbicanKeyManager including following method:
.. code-block:: python
def __init__(self, configuration):
def _get_barbican_client(self, context):
"""Creates a client to connect to the Barbican service."""
def store(self, context, secret, expiration=None):
"""Stores (i.e., registers) a secret with the key manager."""
def get(self, context, managed_secret_id, metadata_only=False):
"""Retrieves the specified managed secret."""
def delete(self, context, managed_secret_id):
"""Deletes the specified managed secret."""
def create_acl(self, context, entity_ref=None, users=None,
project_access=None,
operation_type=DEFAULT_OPERATION_TYPE):
"""Creates acl for the specified managed secret."""
def get_acl(self, context, entity_ref):
"""Retrieves acl of the specified managed secret."""
In nfvo.nfvo_plugin.NfvoPlugin:
1. in create/update/delete_vim, add context into vim_obj
2. in delete_vim, invoke vim_driver with vim_obj
In nfvo.drivers.vim.openstack_driver.OpenStack_Driver:
1. __init__
initializes keymgr, loads the credentials in configuration,
self.key_manager = BarbicanKeyManager()
2. register_vim
check whether barbican is available. If no, do as before, if yes,
get original password and context from vim_obj,
use fernet to encode password which generates a fernet_key,
invoke self.key_manager.store(context, fernet_key) which returns
a secret uuid,
save the uuid into vim_obj['auth_cred']['password'],
set the vim_obj['auth_cred']['key_type'] with barbican_secret
3. deregister_vim
check whether barbican is available. If no, do as before, if yes,
replace the function parameter vim with vim_obj,
retrieve key_id from vim_obj['auth_cred']['password'],
retrieve context from vim_obj,
invoke self.key_manager.delete(context, key_id)
In vnfm.vim_client.VimClient
1. add context into _build_vim_auth parammeter list.
2. _build_vim_auth
according to the key_type in vim_info['auth_cred'],
if key_type is fernet_key, do as before, if it's barbican_secret,
retrieve key_id from vim_obj['auth_cred']['password'],
invoke BarbicanKeyManager().get(context, key_id) to decode password.
Alternatives
------------
None
Data model impact
-----------------
In current realization, the fernet-encrypted password is saved in
VimAuth.password and VimAuth.auth_cred['password'].
When using barbican, we will save the secret UUID in these fields.
A new filed will be added into VimAuth to distinguish what type of
the password, which will help to retrieve password.
Currently vim is created with shared property by default.
After we support vim rbac in future, we should support to modify the
ACL of barbican secrets.
.. code-block:: python
class Vim(model_base.BASE,
models_v1.HasId,
models_v1.HasTenant,
models_v1.Audit):
type = sa.Column(sa.String(64), nullable=False)
name = sa.Column(sa.String(255), nullable=False)
description = sa.Column(sa.Text, nullable=True)
placement_attr = sa.Column(types.Json, nullable=True)
# modify the default value to false
shared = sa.Column(sa.Boolean, default=True, server_default=sql.false(
), nullable=False)
class VimAuth(model_base.BASE, models_v1.HasId):
vim_id = sa.Column(types.Uuid, sa.ForeignKey('vims.id'),
nullable=False)
password = sa.Column(sa.String(255), nullable=False)
auth_url = sa.Column(sa.String(255), nullable=False)
vim_project = sa.Column(types.Json, nullable=False)
auth_cred = sa.Column(types.Json, nullable=False)
# 'fernet_key' or 'barbican_secret'
key_type = sa.Column(sa.String(255), nullable=False)
REST API impact
---------------
None
Security impact
---------------
None
Notifications impact
--------------------
None
Other end user impact
---------------------
None
Performance Impact
------------------
None
Other deployer impact
---------------------
We need Barbican and Castellan installed if we configure 'use_barbican'
to 'yes'.
Developer impact
----------------
None
Implementation
==============
Assignee(s)
-----------
Primary assignee:
Yan Xing an<yanxingan@cmss.chinamobile.com>
Other contributors:
None
Work Items
----------
The BP involves following tasks:
#. nfvo and vim driver with unit tests
#. functional tests
#. installation document
#. support barbican in devstack
Dependencies
============
* Barbican
Testing
=======
* FT/UT
Documentation Impact
====================
* installation document
References
==========
.. [1] https://github.com/openstack/barbican
.. [2] https://developer.openstack.org/api-guide/key-manager/
.. [3] https://github.com/openstack/nova-specs/blob/master/specs/juno/approved/encryption-with-barbican.rst
.. [4] https://review.openstack.org/#/c/106437/2/specs/juno/encryption-with-barbican.rst
.. [5] https://github.com/openstack/neutron-specs/blob/master/specs/kilo/lbaas-tls.rst
.. [6] https://github.com/openstack/magnum-specs/blob/master/specs/pre-ocata/implemented/tls-support-magnum.rst
.. [7] https://github.com/openstack/castellan
.. [8] https://docs.openstack.org/developer/castellan/usage.html
.. [9] https://github.com/openstack/barbican/blob/master/etc/barbican/policy.json
.. [10] https://developer.openstack.org/api-guide/key-manager/acls.html
.. [11] http://eavesdrop.openstack.org/meetings/tacker/2017/tacker.2017-04-05-05.30.log.html
.. [12] https://github.com/openstack/castellan/blob/master/castellan/key_manager/barbican_key_manager.py