Support to use barbican to encode vim password

1. Add new option 'use_barbican' in config file [vim_keys] section,
   default value is False for Pike.
2. Use fernet to encrypt vim password, and save the fernet key into
   barbican as a secret.
3. Add new fields 'key_type', 'secret_uuid' into VimAuth.auth_cred
   json string. secret_uuid is masked in vim-show or vim-list response.
4. Set the vim's default 'shared' value to False,
   vim can only be used by who created it.
5. Add a devref to show how to test.
6. Add a release note.

Implements: blueprint encryption-with-barbican
Partial-bug: #1667652

Change-Id: I5c779041df5a08a361b9aaefac7d241369732551
This commit is contained in:
Yan Xing'an 2017-06-07 03:03:02 -07:00
parent b1b869d9e9
commit 07428d4985
25 changed files with 989 additions and 87 deletions

View File

@ -256,6 +256,9 @@ function configure_tacker {
echo "Creating bridge"
sudo ovs-vsctl --may-exist add-br ${BR_MGMT}
fi
if [[ "${USE_BARBICAN}" == "True" ]]; then
iniset $TACKER_CONF vim_keys use_barbican True
fi
_tacker_setup_rootwrap
}
@ -418,8 +421,19 @@ function tacker_register_default_vim {
cat $VIM_CONFIG_FILE
local default_vim_id
DEFAULT_VIM_NAME="VIM0"
old_project=$OS_PROJECT_NAME
old_user=$OS_USERNAME
$TOP_DIR/tools/create_userrc.sh -P -u $DEFAULT_VIM_USER -C $DEFAULT_VIM_PROJECT_NAME -p $DEFAULT_VIM_PASSWORD
echo "Switch environment openrc:"
echo $(cat $TOP_DIR/accrc/$DEFAULT_VIM_PROJECT_NAME/$DEFAULT_VIM_USER)
source $TOP_DIR/accrc/$DEFAULT_VIM_PROJECT_NAME/$DEFAULT_VIM_USER
default_vim_id=$(tacker vim-register --is-default --description "Default VIM" --config-file $VIM_CONFIG_FILE $DEFAULT_VIM_NAME -c id | grep id | awk '{print $4}')
echo "Default VIM registration done as $default_vim_id at $KEYSTONE_SERVICE_URI."
echo "Switch back to old environment openrc:"
echo $(cat $TOP_DIR/accrc/$old_project/$old_user)
source $TOP_DIR/accrc/$old_project/$old_user
echo "Update tacker/tests/etc/samples/local-vim.yaml for functional testing"
functional_vim_file="$TACKER_DIR/tacker/tests/etc/samples/local-vim.yaml"

View File

@ -50,10 +50,14 @@ NETWORK_GATEWAY=${NETWORK_GATEWAY:-15.0.0.1}
FIXED_RANGE=${FIXED_RANGE:-15.0.0.0/24}
enable_plugin heat https://git.openstack.org/openstack/heat master
enable_plugin tacker https://git.openstack.org/openstack/tacker master
enable_plugin networking-sfc git://git.openstack.org/openstack/networking-sfc master
enable_plugin barbican https://git.openstack.org/openstack/barbican
enable_plugin tacker https://git.openstack.org/openstack/tacker master
enable_service n-novnc
enable_service n-cauth
disable_service tempest
#TACKER CONFIGURATION
USE_BARBICAN=True

View File

@ -1,4 +1,6 @@
TACKER_MODE=${TACKER_MODE:-all}
USE_BARBICAN=True
if [ "${TACKER_MODE}" == "all" ]; then
# Nova
disable_service n-net

View File

@ -0,0 +1,146 @@
Barbican Guide
==============
Overview
--------
This document shows how to operate vims which use barbican to save
vim key in devstack environment.
The brief code workflow is described as following:
When creating a vim:
We use fernet to encrypt vim password, save the fernet key into barbican
as a secret, save encrypted into vim db's field **password**,
and then save the secret uuid into vim db field **secret_uuid**.
When retrieving vim password:
We use **secret_uuid** to get the fernet key from barbican, and decode with
**password** using fernet.
When deleting a vim:
We delete the secret by the **secret_uuid** in vim db from barbican.
How to test
-----------
We need enable barbican in devstack localrc file:
.. code-block:: bash
enable_plugin barbican https://git.openstack.org/openstack/barbican
enable_plugin tacker https://git.openstack.org/openstack/tacker
USE_BARBICAN=True
.. note::
Please make sure the barbican plugin is enabled before tacker plugin.
We set USE_BARBICAN=True to use barbican .
Create a vim and verify it works:
.. code-block:: bash
$ source openrc-admin.sh
$ openstack project create test
$ openstack user create --password a test
$ openstack role add --project test --user test admin
$ cat vim-test.yaml
auth_url: 'http://127.0.0.1:5000'
username: 'test'
password: 'Passw0rd'
project_name: 'test'
project_domain_name: 'Default'
user_domain_name: 'Default'
$ cat openrc-test.sh
export LC_ALL='en_US.UTF-8'
export OS_NO_CACHE='true'
export OS_USERNAME=test
export OS_PASSWORD=Passw0rd
export OS_PROJECT_NAME=test
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default
export OS_AUTH_URL=http://127.0.0.1:35357/v3
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2
export OS_NETWORK_API_VERSION=2
$ source openrc-test.sh
$ openstack secret list
$ tacker vim-register --config-file vim-test.yaml vim-test
Created a new vim:
+----------------+---------------------------------------------------------+
| Field | Value |
+----------------+---------------------------------------------------------+
| auth_cred | {"username": "test", "password": "***", "project_name": |
| | "test", "user_domain_name": "Default", "key_type": |
| | "barbican_key", "secret_uuid": "***", "auth_url": |
| | "http://127.0.0.1:5000/v3", "project_id": null, |
| | "project_domain_name": "Default"} |
| auth_url | http://127.0.0.1:5000/v3 |
| created_at | 2017-06-20 14:56:05.622612 |
| description | |
| id | 7c0b73c7-554b-46d3-a35c-c368019716a0 |
| is_default | False |
| name | vim-test |
| placement_attr | {"regions": ["RegionOne"]} |
| status | REACHABLE |
| tenant_id | 28a525feaf5e4d05b4ab9f7090837964 |
| type | openstack |
| updated_at | |
| vim_project | {"name": "test", "project_domain_name": "Default"} |
+----------------+---------------------------------------------------------+
$ openstack secret list
+-------------------------------------------+------+---------------------------+--------+-------------------------------------------+-----------+------------+-------------+------+------------+
| Secret href | Name | Created | Status | Content types | Algorithm | Bit length | Secret type | Mode | Expiration |
+-------------------------------------------+------+---------------------------+--------+-------------------------------------------+-----------+------------+-------------+------+------------+
| http://127.0.0.1:9311/v1/secrets/d379f561 | None | 2017-06-20T14:56:06+00:00 | ACTIVE | {u'default': u'application/octet-stream'} | None | None | opaque | None | None |
| -7073-40ea-822d-9d7bcb594e1a | | | | | | | | | |
+-------------------------------------------+------+---------------------------+--------+-------------------------------------------+-----------+------------+-------------+------+------------+
We can found that the **key_type** in auth_cred is **barbican_key**,
the **secret_uuid** exists with masked value, and the fernet key is
saved in barbican as a secret.
Now we create a vnf to verify it works:
.. code-block:: bash
$ tacker vnf-create --vnfd-template vnfd-sample.yaml \
--vim-name vim-test --vim-region-name RegionOne vnf-test
Created a new vnf:
+----------------+-------------------------------------------------------+
| Field | Value |
+----------------+-------------------------------------------------------+
| created_at | 2017-06-20 15:08:43.267694 |
| description | Demo example |
| error_reason | |
| id | 71d3eef7-6b53-4495-b210-78786cb28ba4 |
| instance_id | 08d0ce6f-69bc-4ff0-87b0-52686a01ce3e |
| mgmt_url | |
| name | vnf-test |
| placement_attr | {"region_name": "RegionOne", "vim_name": "vim-test"} |
| status | PENDING_CREATE |
| tenant_id | 28a525feaf5e4d05b4ab9f7090837964 |
| updated_at | |
| vim_id | 0d1e1cc4-445d-41bd-b3e9-739acb987231 |
| vnfd_id | dc68ccfd-fd7c-4ef6-8fed-f097d036c722 |
+----------------+-------------------------------------------------------+
$ tacker vnf-delete vnf-test
We can found that vnf create successfully.
Now we delete the vim to verify the secret can be deleted.
.. code-block:: bash
$ tacker vim-delete vim-test
All vim(s) deleted successfully
$ openstack secret list
We can found that the secret is deleted from barbican.

View File

@ -6,6 +6,7 @@ namespace = tacker.wsgi
namespace = tacker.service
namespace = tacker.nfvo.nfvo_plugin
namespace = tacker.nfvo.drivers.vim.openstack_driver
namespace = tacker.keymgr
namespace = tacker.vnfm.monitor
namespace = tacker.vnfm.plugin
namespace = tacker.vnfm.infra_drivers.openstack.openstack

View File

@ -0,0 +1,8 @@
---
features:
- |
Introduce barbican to save the fernet key of vim auth. Need to configure
**[vim_keys] use_barbican = True** to enable this feature.
- |
Vim's default **shared** property is changed to **False**. Vim can only be
invoked by user who creates it.

View File

@ -41,3 +41,4 @@ heat-translator>=0.4.0 # Apache-2.0
cryptography>=1.6 # BSD/Apache-2.0
paramiko>=2.0 # LGPLv2.1+
python-mistralclient>=3.1.0 # Apache-2.0
python-barbicanclient>=4.0.0 # Apache-2.0

View File

@ -71,6 +71,7 @@ oslo.config.opts =
tacker.service = tacker.service:config_opts
tacker.nfvo.nfvo_plugin = tacker.nfvo.nfvo_plugin:config_opts
tacker.nfvo.drivers.vim.openstack_driver = tacker.nfvo.drivers.vim.openstack_driver:config_opts
tacker.keymgr = tacker.keymgr:config_opts
tacker.vnfm.monitor = tacker.vnfm.monitor:config_opts
tacker.vnfm.plugin = tacker.vnfm.plugin:config_opts
tacker.vnfm.infra_drivers.openstack.openstack= tacker.vnfm.infra_drivers.openstack.openstack:config_opts

View File

@ -0,0 +1,36 @@
# Copyright 2017 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""change_vim_shared_property_to_false
Revision ID: 31acbaeb8299
Revises: e7993093baf1
Create Date: 2017-05-30 23:46:20.034085
"""
# revision identifiers, used by Alembic.
revision = '31acbaeb8299'
down_revision = 'e7993093baf1'
from alembic import op
import sqlalchemy as sa
def upgrade(active_plugins=None, options=None):
op.alter_column('vims', 'shared',
existing_type=sa.Boolean(),
server_default=sa.text('false'),
nullable=False)

View File

@ -1 +1 @@
e7993093baf1
31acbaeb8299

View File

@ -32,7 +32,7 @@ class Vim(model_base.BASE,
name = sa.Column(sa.String(255), nullable=False)
description = sa.Column(sa.Text, nullable=True)
placement_attr = sa.Column(types.Json, nullable=True)
shared = sa.Column(sa.Boolean, default=True, server_default=sql.true(
shared = sa.Column(sa.Boolean, default=False, server_default=sql.false(
), nullable=False)
is_default = sa.Column(sa.Boolean, default=False, server_default=sql.false(
), nullable=False)

35
tacker/keymgr/__init__.py Normal file
View File

@ -0,0 +1,35 @@
# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_utils import importutils
key_manager_opts = [
cfg.StrOpt('api_class',
default='tacker.keymgr.barbican_key_manager'
'.BarbicanKeyManager',
help='The full class name of the key manager API class'),
]
def config_opts():
return [('key_manager', key_manager_opts)]
def API(auth_url, configuration=None):
conf = configuration or cfg.CONF
conf.register_opts(key_manager_opts, group='key_manager')
cls = importutils.import_class(conf.key_manager.api_class)
return cls(auth_url)

View File

@ -0,0 +1,254 @@
# Copyright (c) The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Key manager implementation for Barbican
"""
from barbicanclient import client as barbican_client
from barbicanclient import exceptions as barbican_exception
from keystoneauth1 import identity
from keystoneauth1 import session
from oslo_log import log as logging
from six.moves import urllib
from tacker._i18n import _
from tacker.keymgr import exception
from tacker.keymgr import key_manager
LOG = logging.getLogger(__name__)
class BarbicanKeyManager(key_manager.KeyManager):
"""Key Manager Interface that wraps the Barbican client API."""
def __init__(self, auth_url):
self._barbican_client = None
self._base_url = None
self._auth_url = auth_url
def _get_barbican_client(self, context):
"""Creates a client to connect to the Barbican service.
:param context: the user context for authentication
:return: a Barbican Client object
:raises Forbidden: if the context is empty
:raises KeyManagerError: if context is missing tenant or tenant is
None or error occurs while creating client
"""
# Confirm context is provided, if not raise forbidden
if not context:
msg = _("User is not authorized to use key manager.")
LOG.error(msg)
raise exception.Forbidden(msg)
if self._barbican_client and self._current_context == context:
return self._barbican_client
try:
auth = self._get_keystone_auth(context)
sess = session.Session(auth=auth)
self._barbican_endpoint = self._get_barbican_endpoint(auth, sess)
self._barbican_client = barbican_client.Client(
session=sess,
endpoint=self._barbican_endpoint)
self._current_context = context
except Exception as e:
LOG.error("Error creating Barbican client: %s", e)
raise exception.KeyManagerError(reason=e)
self._base_url = self._create_base_url(auth,
sess,
self._barbican_endpoint)
return self._barbican_client
def _get_keystone_auth(self, context):
if context.__class__.__name__ is 'KeystonePassword':
return identity.Password(
auth_url=self._auth_url,
username=context.username,
password=context.password,
user_id=context.user_id,
user_domain_id=context.user_domain_id,
user_domain_name=context.user_domain_name,
trust_id=context.trust_id,
domain_id=context.domain_id,
domain_name=context.domain_name,
project_id=context.project_id,
project_name=context.project_name,
project_domain_id=context.project_domain_id,
project_domain_name=context.project_domain_name,
reauthenticate=context.reauthenticate)
elif context.__class__.__name__ is 'KeystoneToken':
return identity.Token(
auth_url=self._auth_url,
token=context.token,
trust_id=context.trust_id,
domain_id=context.domain_id,
domain_name=context.domain_name,
project_id=context.project_id,
project_name=context.project_name,
project_domain_id=context.project_domain_id,
project_domain_name=context.project_domain_name,
reauthenticate=context.reauthenticate)
# this will be kept for oslo.context compatibility until
# projects begin to use utils.credential_factory
elif (context.__class__.__name__ is 'RequestContext' or
context.__class__.__name__ is 'Context'):
return identity.Token(
auth_url=self._auth_url,
token=context.auth_token,
project_id=context.tenant)
else:
msg = _("context must be of type KeystonePassword, "
"KeystoneToken, RequestContext, or Context.")
LOG.error(msg)
raise exception.Forbidden(reason=msg)
def _get_barbican_endpoint(self, auth, sess):
service_parameters = {'service_type': 'key-manager',
'service_name': 'barbican',
'interface': 'internal'}
return auth.get_endpoint(sess, **service_parameters)
def _create_base_url(self, auth, sess, endpoint):
discovery = auth.get_discovery(sess, url=endpoint)
raw_data = discovery.raw_version_data()
if len(raw_data) == 0:
msg = _(
"Could not find discovery information for %s") % endpoint
LOG.error(msg)
raise exception.KeyManagerError(reason=msg)
latest_version = raw_data[-1]
api_version = latest_version.get('id')
base_url = urllib.parse.urljoin(endpoint, api_version)
return base_url
def store(self, context, secret, expiration=None):
"""Stores a secret with the key manager.
:param context: contains information of the user and the environment
for the request
:param secret: a secret object with unencrypted payload.
Known as "secret" to the barbicanclient api
:param expiration: the expiration time of the secret in ISO 8601
format
:returns: the UUID of the stored object
:raises KeyManagerError: if object store fails
"""
barbican_client = self._get_barbican_client(context)
try:
secret = barbican_client.secrets.create(
payload=secret,
secret_type='opaque')
secret.expiration = expiration
secret_ref = secret.store()
return self._retrieve_secret_uuid(secret_ref)
except (barbican_exception.HTTPAuthError,
barbican_exception.HTTPClientError,
barbican_exception.HTTPServerError) as e:
LOG.error("Error storing object: %s", e)
raise exception.KeyManagerError(reason=e)
def _create_secret_ref(self, object_id):
"""Creates the URL required for accessing a secret.
:param object_id: the UUID of the key to copy
:return: the URL of the requested secret
"""
if not object_id:
msg = _("Key ID is None")
raise exception.KeyManagerError(reason=msg)
base_url = self._base_url
if base_url[-1] != '/':
base_url += '/'
return urllib.parse.urljoin(base_url, "secrets/" + object_id)
def _retrieve_secret_uuid(self, secret_ref):
"""Retrieves the UUID of the secret from the secret_ref.
:param secret_ref: the href of the secret
:return: the UUID of the secret
"""
# The secret_ref is assumed to be of a form similar to
# http://host:9311/v1/secrets/d152fa13-2b41-42ca-a934-6c21566c0f40
# with the UUID at the end. This command retrieves everything
# after the last '/', which is the UUID.
return secret_ref.rpartition('/')[2]
def _is_secret_not_found_error(self, error):
if (isinstance(error, barbican_exception.HTTPClientError) and
error.status_code == 404):
return True
else:
return False
def get(self, context, managed_object_id, metadata_only=False):
"""Retrieves the specified managed object.
:param context: contains information of the user and the environment
for the request
:param managed_object_id: the UUID of the object to retrieve
:param metadata_only: whether secret data should be included
:return: ManagedObject representation of the managed object
:raises KeyManagerError: if object retrieval fails
:raises ManagedObjectNotFoundError: if object not found
"""
barbican_client = self._get_barbican_client(context)
try:
secret_ref = self._create_secret_ref(managed_object_id)
return barbican_client.secrets.get(secret_ref)
except (barbican_exception.HTTPAuthError,
barbican_exception.HTTPClientError,
barbican_exception.HTTPServerError) as e:
LOG.error("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)
def delete(self, context, managed_object_id):
"""Deletes the specified managed object.
:param context: contains information of the user and the environment
for the request
:param managed_object_id: the UUID of the object to delete
:raises KeyManagerError: if object deletion fails
:raises ManagedObjectNotFoundError: if the object could not be found
"""
barbican_client = self._get_barbican_client(context)
try:
secret_ref = self._create_secret_ref(managed_object_id)
barbican_client.secrets.delete(secret_ref)
except (barbican_exception.HTTPAuthError,
barbican_exception.HTTPClientError,
barbican_exception.HTTPServerError) as e:
LOG.error("Error deleting object: %s", e)
if self._is_secret_not_found_error(e):
raise exception.ManagedObjectNotFoundError(
uuid=managed_object_id)
else:
raise exception.KeyManagerError(reason=e)

View File

@ -0,0 +1,43 @@
# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Exception for keymgr
"""
from tacker._i18n import _
from tacker.common.exceptions import TackerException
class Forbidden(TackerException):
message = _("You are not authorized to complete this action.")
class KeyManagerError(TackerException):
message = _("Key manager error: %(reason)s")
class ManagedObjectNotFoundError(TackerException):
message = _("Key not found, uuid: %(uuid)s")
class AuthTypeInvalidError(TackerException):
message = _("Invalid auth_type was specified, auth_type: %(type)s")
class InsufficientCredentialDataError(TackerException):
message = _('Insufficient credential data was provided, either '
'"token" must be set in the passed conf, or a context '
'with an "auth_token" property must be passed.')

View File

@ -0,0 +1,87 @@
# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Key manager API
"""
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class KeyManager(object):
"""Base Key Manager Interface
A Key Manager is responsible for creating, reading, and deleting keys.
"""
@abc.abstractmethod
def __init__(self, auth_url):
"""Instantiate a KeyManager object.
Creates a KeyManager object with implementation specific details
obtained from the supplied configuration.
"""
pass
@abc.abstractmethod
def store(self, context, managed_object, expiration=None):
"""Stores a managed object with the key manager.
This method stores the specified managed object and returns its UUID
that identifies it within the key manager. If the specified context
does not permit the creation of keys, then a NotAuthorized exception
should be raised.
"""
pass
@abc.abstractmethod
def get(self, context, managed_object_id, metadata_only=False):
"""Retrieves the specified managed object.
Implementations should verify that the caller has permissions to
retrieve the managed object by checking the context object passed in
as context. If the user lacks permission then a NotAuthorized
exception is raised.
If the caller requests only metadata, then the object that is
returned will contain only the secret metadata and no secret bytes.
If the specified object does not exist, then a KeyError should be
raised. Implementations should preclude users from discerning the
UUIDs of objects that belong to other users by repeatedly calling
this method. That is, objects that belong to other users should be
considered "non-existent" and completely invisible.
"""
pass
@abc.abstractmethod
def delete(self, context, managed_object_id):
"""Deletes the specified managed object.
Implementations should verify that the caller has permission to delete
the managed object by checking the context object (context). A
NotAuthorized exception should be raised if the caller lacks
permission.
If the specified object does not exist, then a KeyError should be
raised. Implementations should preclude users from discerning the
UUIDs of objects that belong to other users by repeatedly calling this
method. That is, objects that belong to other users should be
considered "non-existent" and completely invisible.
"""
pass

View File

@ -52,7 +52,7 @@ class VimAbstractDriver(extensions.PluginInterface):
pass
@abc.abstractmethod
def deregister_vim(self, context, vim_id):
def deregister_vim(self, context, vim_obj):
"""Deregister VIM object from NFVO plugin
Cleanup VIM data and delete VIM information
@ -76,7 +76,7 @@ class VimAbstractDriver(extensions.PluginInterface):
pass
@abc.abstractmethod
def delete_vim_auth(self, vim_id):
def delete_vim_auth(self, context, vim_id, auth):
"""Delete VIM auth keys
Delete VIM sensitive information such as keys from file system or DB

View File

@ -31,6 +31,7 @@ from oslo_log import log as logging
from tacker._i18n import _
from tacker.common import log
from tacker.extensions import nfvo
from tacker.keymgr import API as KEYMGR_API
from tacker.mistral import mistral_client
from tacker.nfvo.drivers.vim import abstract_vim_driver
from tacker.nfvo.drivers.vnffg import abstract_vnffg_driver
@ -42,7 +43,12 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF
OPTS = [cfg.StrOpt('openstack', default='/etc/tacker/vim/fernet_keys',
help='Dir.path to store fernet keys.')]
help='Dir.path to store fernet keys.'),
cfg.BoolOpt('use_barbican', default=False,
help=_('Use barbican to encrypt vim password if True, '
'save vim credentials in local file system '
'if False'))
]
# same params as we used in ping monitor driver
OPENSTACK_OPTS = [
@ -186,37 +192,60 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver,
return vim_obj
@log.log
def register_vim(self, vim_obj):
def register_vim(self, context, vim_obj):
"""Validate and set VIM placements."""
if 'key_type' in vim_obj['auth_cred']:
vim_obj['auth_cred'].pop(u'key_type')
if 'secret_uuid' in vim_obj['auth_cred']:
vim_obj['auth_cred'].pop(u'secret_uuid')
ks_client = self.authenticate_vim(vim_obj)
self.discover_placement_attr(vim_obj, ks_client)
self.encode_vim_auth(vim_obj['id'], vim_obj['auth_cred'])
LOG.debug(_('VIM registration completed for %s'), vim_obj)
self.encode_vim_auth(context, vim_obj['id'], vim_obj['auth_cred'])
LOG.debug('VIM registration completed for %s', vim_obj)
@log.log
def deregister_vim(self, vim_id):
def deregister_vim(self, context, vim_obj):
"""Deregister VIM from NFVO
Delete VIM keys from file system
"""
self.delete_vim_auth(vim_id)
self.delete_vim_auth(context, vim_obj['id'], vim_obj['auth_cred'])
@log.log
def delete_vim_auth(self, vim_id):
def delete_vim_auth(self, context, vim_id, auth):
"""Delete vim information
Delete vim key stored in file system
"""
LOG.debug(_('Attempting to delete key for vim id %s'), vim_id)
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
try:
os.remove(key_file)
LOG.debug(_('VIM key deleted successfully for vim %s'), vim_id)
except OSError:
LOG.warning(_('VIM key deletion unsuccessful for vim %s'), vim_id)
Delete vim key stored in file system
"""
LOG.debug('Attempting to delete key for vim id %s', vim_id)
if auth.get('key_type') == 'barbican_key':
try:
keystone_conf = CONF.keystone_authtoken
secret_uuid = auth['secret_uuid']
keymgr_api = KEYMGR_API(keystone_conf.auth_url)
keymgr_api.delete(context, secret_uuid)
LOG.debug('VIM key deleted successfully for vim %s',
vim_id)
except Exception as ex:
LOG.warning('VIM key deletion failed for vim %s due to %s',
vim_id,
ex)
raise
else:
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
try:
os.remove(key_file)
LOG.debug('VIM key deleted successfully for vim %s',
vim_id)
except OSError:
LOG.warning('VIM key deletion failed for vim %s',
vim_id)
@log.log
def encode_vim_auth(self, vim_id, auth):
def encode_vim_auth(self, context, vim_id, auth):
"""Encode VIM credentials
Store VIM auth using fernet key encryption
@ -224,16 +253,36 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver,
fernet_key, fernet_obj = self.keystone.create_fernet_key()
encoded_auth = fernet_obj.encrypt(auth['password'].encode('utf-8'))
auth['password'] = encoded_auth
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
try:
with open(key_file, 'w') as f:
if six.PY2:
f.write(fernet_key.decode('utf-8'))
else:
f.write(fernet_key)
LOG.debug(_('VIM auth successfully stored for vim %s'), vim_id)
except IOError:
raise nfvo.VimKeyNotFoundException(vim_id=vim_id)
if CONF.vim_keys.use_barbican:
try:
keystone_conf = CONF.keystone_authtoken
keymgr_api = KEYMGR_API(keystone_conf.auth_url)
secret_uuid = keymgr_api.store(context, fernet_key)
auth['key_type'] = 'barbican_key'
auth['secret_uuid'] = secret_uuid
LOG.debug('VIM auth successfully stored for vim %s',
vim_id)
except Exception as ex:
LOG.warning('VIM key creation failed for vim %s due to %s',
vim_id,
ex)
raise
else:
auth['key_type'] = 'fernet_key'
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
try:
with open(key_file, 'w') as f:
if six.PY2:
f.write(fernet_key.decode('utf-8'))
else:
f.write(fernet_key)
LOG.debug('VIM auth successfully stored for vim %s',
vim_id)
except IOError:
raise nfvo.VimKeyNotFoundException(vim_id=vim_id)
@log.log
def get_vim_resource_id(self, vim_obj, resource_type, resource_name):

View File

@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import os
import time
import uuid
@ -38,6 +39,7 @@ from tacker.db.nfvo import ns_db
from tacker.db.nfvo import vnffg_db
from tacker.extensions import common_services as cs
from tacker.extensions import nfvo
from tacker.keymgr import API as KEYMGR_API
from tacker import manager
from tacker.nfvo.workflows.vim_monitor import vim_monitor_utils
from tacker.plugins.common import constants
@ -106,12 +108,18 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
vim_obj['id'] = str(uuid.uuid4())
vim_obj['status'] = 'PENDING'
try:
self._vim_drivers.invoke(vim_type, 'register_vim', vim_obj=vim_obj)
self._vim_drivers.invoke(vim_type,
'register_vim',
context=context,
vim_obj=vim_obj)
res = super(NfvoPlugin, self).create_vim(context, vim_obj)
except Exception:
with excutils.save_and_reraise_exception():
self._vim_drivers.invoke(vim_type, 'delete_vim_auth',
vim_id=vim_obj['id'])
self._vim_drivers.invoke(vim_type,
'delete_vim_auth',
context=context,
vim_id=vim_obj['id'],
auth=vim_obj['auth_cred'])
try:
self.monitor_vim(context, vim_obj)
@ -121,14 +129,17 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
def _get_vim(self, context, vim_id):
if not self.is_vim_still_in_use(context, vim_id):
return self.get_vim(context, vim_id)
return self.get_vim(context, vim_id, mask_password=False)
@log.log
def update_vim(self, context, vim_id, vim):
vim_obj = self._get_vim(context, vim_id)
old_vim_obj = copy.deepcopy(vim_obj)
utils.deep_update(vim_obj, vim['vim'])
vim_type = vim_obj['type']
update_args = vim['vim']
old_auth_need_delete = False
new_auth_created = False
try:
# re-register the VIM only if there is a change in password.
# auth_url of auth_cred is from vim object which
@ -138,20 +149,49 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
if 'password' in auth_cred:
vim_obj['auth_cred']['password'] = auth_cred['password']
# Notice: vim_obj may be updated in vim driver's
# register_vim method
self._vim_drivers.invoke(vim_type, 'register_vim',
self._vim_drivers.invoke(vim_type,
'register_vim',
context=context,
vim_obj=vim_obj)
return super(NfvoPlugin, self).update_vim(context, vim_id, vim_obj)
except Exception:
new_auth_created = True
# Check whether old vim's auth need to be deleted
old_key_type = old_vim_obj['auth_cred'].get('key_type')
if old_key_type == 'barbican_key':
old_auth_need_delete = True
vim_obj = super(NfvoPlugin, self).update_vim(
context, vim_id, vim_obj)
if old_auth_need_delete:
try:
self._vim_drivers.invoke(vim_type,
'delete_vim_auth',
context=context,
vim_id=old_vim_obj['id'],
auth=old_vim_obj['auth_cred'])
except Exception as ex:
LOG.warning("Fail to delete old auth for vim %s due to %s",
vim_id, ex)
return vim_obj
except Exception as ex:
LOG.debug("Got exception when update_vim %s due to %s",
vim_id, ex)
with excutils.save_and_reraise_exception():
self._vim_drivers.invoke(vim_type, 'delete_vim_auth',
vim_id=vim_obj['id'])
if new_auth_created:
# delete new-created vim auth, old auth is still used.
self._vim_drivers.invoke(vim_type,
'delete_vim_auth',
context=context,
vim_id=vim_obj['id'],
auth=vim_obj['auth_cred'])
@log.log
def delete_vim(self, context, vim_id):
vim_obj = self._get_vim(context, vim_id)
self._vim_drivers.invoke(vim_obj['type'], 'deregister_vim',
vim_id=vim_id)
self._vim_drivers.invoke(vim_obj['type'],
'deregister_vim',
context=context,
vim_obj=vim_obj)
try:
auth_dict = self.get_auth_dict(context)
vim_monitor_utils.delete_vim_monitor(context, auth_dict, vim_obj)
@ -387,24 +427,46 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
vim_obj = self.get_vim(context, vim_id['vim_id'], mask_password=False)
if vim_obj is None:
raise nfvo.VimFromVnfNotFoundException(vnf_id=vnf_id)
vim_auth = vim_obj['auth_cred']
vim_auth['password'] = self._decode_vim_auth(vim_obj['id'],
vim_auth['password'].
encode('utf-8'))
vim_auth['auth_url'] = vim_obj['auth_url']
self._build_vim_auth(context, vim_obj)
return vim_obj
def _decode_vim_auth(self, vim_id, cred):
def _build_vim_auth(self, context, vim_info):
LOG.debug('VIM id is %s', vim_info['id'])
vim_auth = vim_info['auth_cred']
vim_auth['password'] = self._decode_vim_auth(context,
vim_info['id'],
vim_auth)
vim_auth['auth_url'] = vim_info['auth_url']
# These attributes are needless for authentication
# from keystone, so we remove them.
needless_attrs = ['key_type', 'secret_uuid']
for attr in needless_attrs:
if attr in vim_auth:
vim_auth.pop(attr, None)
return vim_auth
def _decode_vim_auth(self, context, vim_id, auth):
"""Decode Vim credentials
Decrypt VIM cred. using Fernet Key
Decrypt VIM cred, get fernet Key from local_file_system or
barbican.
"""
vim_key = self._find_vim_key(vim_id)
cred = auth['password'].encode('utf-8')
if auth.get('key_type') == 'barbican_key':
keystone_conf = CONF.keystone_authtoken
secret_uuid = auth['secret_uuid']
keymgr_api = KEYMGR_API(keystone_conf.auth_url)
secret_obj = keymgr_api.get(context, secret_uuid)
vim_key = secret_obj.payload
else:
vim_key = self._find_vim_key(vim_id)
f = fernet.Fernet(vim_key)
if not f:
LOG.warning(_('Unable to decode VIM auth'))
raise nfvo.VimNotFoundException('Unable to decode VIM auth key')
raise nfvo.VimNotFoundException(
'Unable to decode VIM auth key')
return f.decrypt(cred)
@staticmethod

View File

@ -54,6 +54,8 @@ class VimTestCreate(base.BaseTackerTest):
self.verify_vim(vim_obj, data, new_name, new_desc, version)
self.verify_vim_events(vim_id, evt_constants.RES_EVT_UPDATE)
# TODO(yanxingan) Temporarily skip this case due to bug #1697818
'''
# With the updated name above, create another VIM with the
# same name and check for Duplicate name exception.
vim_arg['vim']['name'] = update_vim_arg['vim']['name']
@ -62,6 +64,7 @@ class VimTestCreate(base.BaseTackerTest):
self.client.create_vim(vim_arg)
except Exception as err:
self.assertEqual(err.message, msg)
'''
# Since there already exists a DEFAULT VM, Verify that a update
# to is_default to TRUE for another VIM raises an exception.

View File

@ -22,9 +22,16 @@ from tacker.nfvo.drivers.vim import openstack_driver
from tacker.tests.unit import base
from tacker.tests.unit.db import utils
OPTS = [cfg.StrOpt('user_domain_id', default='default', help='User Domain Id'),
cfg.StrOpt('project_domain_id', default='default', help='Project '
'Domain Id')]
OPTS = [cfg.StrOpt('user_domain_id',
default='default',
help='User Domain Id'),
cfg.StrOpt('project_domain_id',
default='default',
help='Project Domain Id'),
cfg.StrOpt('auth_url',
default='http://localhost:5000/v3',
help='Keystone endpoint')]
cfg.CONF.register_opts(OPTS, 'keystone_authtoken')
CONF = cfg.CONF
@ -37,6 +44,10 @@ class FakeNeutronClient(mock.Mock):
pass
class FakeKeymgrAPI(mock.Mock):
pass
class mock_dict(dict):
def __getattr__(self, item):
return self.get(item)
@ -51,10 +62,12 @@ class TestOpenstack_Driver(base.TestCase):
self._mock_keystone()
self.keystone.create_key_dir.return_value = 'test_keys'
self.config_fixture.config(group='vim_keys', openstack='/tmp/')
self.config_fixture.config(group='vim_keys', use_barbican=False)
self.openstack_driver = openstack_driver.OpenStack_Driver()
self.vim_obj = self.get_vim_obj()
self.auth_obj = utils.get_vim_auth_obj()
self.addCleanup(mock.patch.stopall)
self._mock_keymgr()
def _mock_keystone(self):
self.keystone = mock.Mock(wraps=FakeKeystone())
@ -63,12 +76,34 @@ class TestOpenstack_Driver(base.TestCase):
self._mock(
'tacker.vnfm.keystone.Keystone', fake_keystone)
def _mock_keymgr(self):
self.keymgr = mock.Mock(wraps=FakeKeymgrAPI())
fake_keymgr = mock.Mock()
fake_keymgr.return_value = self.keymgr
self._mock(
'tacker.keymgr.barbican_key_manager.BarbicanKeyManager',
fake_keymgr)
def get_vim_obj(self):
return {'id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff', 'type':
'openstack', 'auth_url': 'http://localhost:5000',
'auth_cred': {'username': 'test_user',
'password': 'test_password',
'user_domain_name': 'default'},
'user_domain_name': 'default',
'auth_url': 'http://localhost:5000'},
'name': 'VIM0',
'vim_project': {'name': 'test_project',
'project_domain_name': 'default'}}
def get_vim_obj_barbican(self):
return {'id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff', 'type':
'openstack', 'auth_url': 'http://localhost:5000',
'auth_cred': {'username': 'test_user',
'password': 'test_password',
'user_domain_name': 'default',
'key_type': 'barbican_key',
'secret_uuid': 'fake-secret-uuid',
'auth_url': 'http://localhost:5000'},
'name': 'VIM0',
'vim_project': {'name': 'test_project',
'project_domain_name': 'default'}}
@ -111,7 +146,7 @@ class TestOpenstack_Driver(base.TestCase):
mock_fernet_obj)
file_mock = mock.mock_open()
with mock.patch('six.moves.builtins.open', file_mock, create=True):
self.openstack_driver.register_vim(vim_obj)
self.openstack_driver.register_vim(None, vim_obj)
mock_fernet_obj.encrypt.assert_called_once_with(mock.ANY)
file_mock().write.assert_called_once_with('test_fernet_key')
@ -119,12 +154,43 @@ class TestOpenstack_Driver(base.TestCase):
@mock.patch('tacker.nfvo.drivers.vim.openstack_driver.os.path'
'.join')
def test_deregister_vim(self, mock_os_path, mock_os_remove):
vim_obj = self.get_vim_obj()
vim_id = 'my_id'
vim_obj['id'] = vim_id
file_path = CONF.vim_keys.openstack + '/' + vim_id
mock_os_path.return_value = file_path
self.openstack_driver.deregister_vim(vim_id)
self.openstack_driver.deregister_vim(None, vim_obj)
mock_os_remove.assert_called_once_with(file_path)
def test_deregister_vim_barbican(self):
self.keymgr.delete.return_value = None
vim_obj = self.get_vim_obj_barbican()
self.openstack_driver.deregister_vim(None, vim_obj)
self.keymgr.delete.assert_called_once_with(
None, 'fake-secret-uuid')
def test_encode_vim_auth_barbican(self):
self.config_fixture.config(group='vim_keys',
use_barbican=True)
fernet_attrs = {'encrypt.return_value': 'encrypted_password'}
mock_fernet_obj = mock.Mock(**fernet_attrs)
mock_fernet_key = 'test_fernet_key'
self.keymgr.store.return_value = 'fake-secret-uuid'
self.keystone.create_fernet_key.return_value = (mock_fernet_key,
mock_fernet_obj)
vim_obj = self.get_vim_obj()
self.openstack_driver.encode_vim_auth(
None, vim_obj['id'], vim_obj['auth_cred'])
self.keymgr.store.assert_called_once_with(
None, 'test_fernet_key')
mock_fernet_obj.encrypt.assert_called_once_with(mock.ANY)
self.assertEqual(vim_obj['auth_cred']['key_type'],
'barbican_key')
self.assertEqual(vim_obj['auth_cred']['secret_uuid'],
'fake-secret-uuid')
def test_register_vim_invalid_auth(self):
attrs = {'regions.list.side_effect': exceptions.Unauthorized}
self._test_register_vim_auth(attrs)
@ -139,7 +205,9 @@ class TestOpenstack_Driver(base.TestCase):
self.keystone.get_version.return_value = keystone_version
self.keystone.initialize_client.return_value = mock_ks_client
self.assertRaises(nfvo.VimUnauthorizedException,
self.openstack_driver.register_vim, self.vim_obj)
self.openstack_driver.register_vim,
None,
self.vim_obj)
mock_ks_client.regions.list.assert_called_once_with()
self.keystone.initialize_client.assert_called_once_with(
version=keystone_version, **self.auth_obj)

View File

@ -231,7 +231,31 @@ class TestNfvoPlugin(db_base.SqlTestCase):
auth_url='http://localhost:5000',
vim_project={'name': 'test_project'},
auth_cred={'username': 'test_user', 'user_domain_id': 'default',
'project_domain_id': 'default'})
'project_domain_id': 'default',
'key_type': 'fernet_key'})
session.add(vim_db)
session.add(vim_auth_db)
session.flush()
def _insert_dummy_vim_barbican(self):
session = self.context.session
vim_db = nfvo_db.Vim(
id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
name='fake_vim',
description='fake_vim_description',
type='openstack',
status='Active',
placement_attr={'regions': ['RegionOne']})
vim_auth_db = nfvo_db.VimAuth(
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
password='encrypted_pw',
auth_url='http://localhost:5000',
vim_project={'name': 'test_project'},
auth_cred={'username': 'test_user', 'user_domain_id': 'default',
'project_domain_id': 'default',
'key_type': 'barbican_key',
'secret_uuid': 'fake-secret-uuid'})
session.add(vim_db)
session.add(vim_auth_db)
session.flush()
@ -244,8 +268,9 @@ class TestNfvoPlugin(db_base.SqlTestCase):
self.context, evt_type=constants.RES_EVT_CREATE, res_id=mock.ANY,
res_state=mock.ANY, res_type=constants.RES_TYPE_VIM,
tstamp=mock.ANY)
self._driver_manager.invoke.assert_any_call(vim_type,
'register_vim', vim_obj=vim_dict['vim'])
self._driver_manager.invoke.assert_any_call(
vim_type, 'register_vim',
context=self.context, vim_obj=vim_dict['vim'])
self.assertIsNotNone(res)
self.assertEqual(SECRET_PASSWORD, res['auth_cred']['password'])
self.assertIn('id', res)
@ -255,12 +280,14 @@ class TestNfvoPlugin(db_base.SqlTestCase):
def test_delete_vim(self):
self._insert_dummy_vim()
vim_type = 'openstack'
vim_type = u'openstack'
vim_id = '6261579e-d6f3-49ad-8bc3-a9cb974778ff'
vim_obj = self.nfvo_plugin._get_vim(self.context, vim_id)
self.nfvo_plugin.delete_vim(self.context, vim_id)
self._driver_manager.invoke.assert_called_once_with(vim_type,
'deregister_vim',
vim_id=vim_id)
self._driver_manager.invoke.assert_called_once_with(
vim_type, 'deregister_vim',
context=self.context,
vim_obj=vim_obj)
self._cos_db_plugin.create_event.assert_called_with(
self.context, evt_type=constants.RES_EVT_DELETE, res_id=mock.ANY,
res_state=mock.ANY, res_type=constants.RES_TYPE_VIM,
@ -271,15 +298,52 @@ class TestNfvoPlugin(db_base.SqlTestCase):
'vim_project': {'name': 'new_project'},
'auth_cred': {'username': 'new_user',
'password': 'new_password'}}}
vim_type = 'openstack'
vim_type = u'openstack'
vim_auth_username = vim_dict['vim']['auth_cred']['username']
vim_project = vim_dict['vim']['vim_project']
self._insert_dummy_vim()
res = self.nfvo_plugin.update_vim(self.context, vim_dict['vim']['id'],
vim_dict)
self._driver_manager.invoke.assert_called_once_with(vim_type,
'register_vim',
vim_obj=mock.ANY)
vim_obj = self.nfvo_plugin._get_vim(
self.context, vim_dict['vim']['id'])
vim_obj['updated_at'] = None
self._driver_manager.invoke.assert_called_with(
vim_type, 'register_vim',
context=self.context,
vim_obj=vim_obj)
self.assertIsNotNone(res)
self.assertIn('id', res)
self.assertIn('placement_attr', res)
self.assertEqual(vim_project, res['vim_project'])
self.assertEqual(vim_auth_username, res['auth_cred']['username'])
self.assertEqual(SECRET_PASSWORD, res['auth_cred']['password'])
self.assertIn('updated_at', res)
self._cos_db_plugin.create_event.assert_called_with(
self.context, evt_type=constants.RES_EVT_UPDATE, res_id=mock.ANY,
res_state=mock.ANY, res_type=constants.RES_TYPE_VIM,
tstamp=mock.ANY)
def test_update_vim_barbican(self):
vim_dict = {'vim': {'id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'vim_project': {'name': 'new_project'},
'auth_cred': {'username': 'new_user',
'password': 'new_password'}}}
vim_type = u'openstack'
vim_auth_username = vim_dict['vim']['auth_cred']['username']
vim_project = vim_dict['vim']['vim_project']
self._insert_dummy_vim_barbican()
old_vim_obj = self.nfvo_plugin._get_vim(
self.context, vim_dict['vim']['id'])
res = self.nfvo_plugin.update_vim(self.context, vim_dict['vim']['id'],
vim_dict)
vim_obj = self.nfvo_plugin._get_vim(
self.context, vim_dict['vim']['id'])
vim_obj['updated_at'] = None
self._driver_manager.invoke.assert_called_with(
vim_type, 'delete_vim_auth',
context=self.context,
vim_id=vim_obj['id'],
auth=old_vim_obj['auth_cred'])
self.assertIsNotNone(res)
self.assertIn('id', res)
self.assertIn('placement_attr', res)

View File

@ -333,6 +333,9 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
driver_name, 'create', plugin=self,
context=context, vnf=vnf_dict, auth_attr=vim_auth)
except Exception:
LOG.debug('Fail to create vnf %s in infra_driver, '
'so delete this vnf',
vnf_dict['id'])
with excutils.save_and_reraise_exception():
self.delete_vnf(context, vnf_id)

View File

@ -60,7 +60,8 @@ class VNFActionRespawn(abstract_action.AbstractPolicyAction):
'instance_id']
def _fetch_vim(vim_uuid):
return vim_client.VimClient().get_vim(context, vim_uuid)
vim_res = vim_client.VimClient().get_vim(context, vim_uuid)
return vim_res
def _delete_heat_stack(vim_auth):
placement_attr = vnf_dict.get('placement_attr', {})
@ -72,7 +73,7 @@ class VNFActionRespawn(abstract_action.AbstractPolicyAction):
'instance_id'])
_log_monitor_events(context, vnf_dict, "ActionRespawnHeat invoked")
def _respin_vnf():
def _respawn_vnf():
update_vnf_dict = plugin.create_vnf_sync(context, vnf_dict)
LOG.info(_('respawned new vnf %s'), update_vnf_dict['id'])
plugin.config_vnf(context, update_vnf_dict)
@ -84,11 +85,11 @@ class VNFActionRespawn(abstract_action.AbstractPolicyAction):
if vnf_dict['attributes'].get('monitoring_policy'):
plugin._vnf_monitor.mark_dead(vnf_dict['id'])
_delete_heat_stack(vim_res['vim_auth'])
updated_vnf = _respin_vnf()
updated_vnf = _respawn_vnf()
plugin.add_vnf_to_monitor(context, updated_vnf)
LOG.debug(_("VNF %s added to monitor thread"), updated_vnf[
'id'])
if vnf_dict['attributes'].get('alarming_policy'):
_delete_heat_stack(vim_res['vim_auth'])
vnf_dict['attributes'].pop('alarming_policy')
_respin_vnf()
_respawn_vnf()

View File

@ -20,6 +20,7 @@ from oslo_config import cfg
from oslo_log import log as logging
from tacker.extensions import nfvo
from tacker.keymgr import API as KEYMGR_API
from tacker import manager
from tacker.plugins.common import constants
@ -42,7 +43,8 @@ class VimClient(object):
'VIM information'))
try:
vim_info = nfvo_plugin.get_default_vim(context)
except Exception:
except Exception as ex:
LOG.debug('Fail to get default vim due to %s', ex)
raise nfvo.VimDefaultNotDefined()
else:
try:
@ -55,7 +57,7 @@ class VimClient(object):
['regions'], region_name):
raise nfvo.VimRegionNotFoundException(region_name=region_name)
vim_auth = self._build_vim_auth(vim_info)
vim_auth = self._build_vim_auth(context, vim_info)
vim_res = {'vim_auth': vim_auth, 'vim_id': vim_info['id'],
'vim_name': vim_info.get('name', vim_info['id']),
'vim_type': vim_info['type']}
@ -65,26 +67,43 @@ class VimClient(object):
def region_valid(vim_regions, region_name):
return region_name in vim_regions
def _build_vim_auth(self, vim_info):
def _build_vim_auth(self, context, vim_info):
LOG.debug('VIM id is %s', vim_info['id'])
vim_auth = vim_info['auth_cred']
vim_auth['password'] = self._decode_vim_auth(vim_info['id'],
vim_auth[
'password'].encode(
'utf-8'))
vim_auth['password'] = self._decode_vim_auth(context,
vim_info['id'],
vim_auth)
vim_auth['auth_url'] = vim_info['auth_url']
# These attributes are needless for authentication
# from keystone, so we remove them.
needless_attrs = ['key_type', 'secret_uuid']
for attr in needless_attrs:
if attr in vim_auth:
vim_auth.pop(attr, None)
return vim_auth
def _decode_vim_auth(self, vim_id, cred):
def _decode_vim_auth(self, context, vim_id, auth):
"""Decode Vim credentials
Decrypt VIM cred. using Fernet Key
Decrypt VIM cred, get fernet Key from local_file_system or
barbican.
"""
vim_key = self._find_vim_key(vim_id)
cred = auth['password'].encode('utf-8')
if auth.get('key_type') == 'barbican_key':
keystone_conf = CONF.keystone_authtoken
secret_uuid = auth['secret_uuid']
keymgr_api = KEYMGR_API(keystone_conf.auth_url)
secret_obj = keymgr_api.get(context, secret_uuid)
vim_key = secret_obj.payload
else:
vim_key = self._find_vim_key(vim_id)
f = fernet.Fernet(vim_key)
if not f:
LOG.warning(_('Unable to decode VIM auth'))
raise nfvo.VimNotFoundException('Unable to decode VIM auth key')
raise nfvo.VimNotFoundException(
'Unable to decode VIM auth key')
return f.decrypt(cred)
@staticmethod

View File

@ -20,6 +20,7 @@ os-api-ref>=1.0.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
WebTest>=2.0 # MIT
python-barbicanclient>=4.0.0 # Apache-2.0
# releasenotes
reno!=2.3.1,>=1.8.0 # Apache-2.0