存储 VIM 凭据到 barbican

包含您的 Launchpad 蓝图的 URL

https://blueprints.launchpad.net/tacker/+spec/encryption-with-barbican

本规范引入了一种将 VIM 凭据存储到 barbican 的方法。

问题描述

Tacker 支持使用凭据注册 VIM,这些凭据用于 NFVO 和 VNFM 在 NFVI 中操作资源。这些凭据包括用户名、密码和项目信息。出于安全考虑,我们不能显式地将明文密码存储在 tacker 数据库中。因此,我们目前使用 keystone 的 fernet 生成一个密钥来加密密码,将加密后的密钥保存到 tacker 数据库中,并将 fernet 密钥保存在本地文件系统中。

当我们需要对 VIM 进行授权时,我们通过使用本地文件系统中的 fernet 密钥解密 tacker 数据库中的密钥来检索原始密码。

问题是,如果 tacker 服务通过负载均衡器提供 API 请求,那么如果请求不由创建并存储 fernet 密钥的服务器节点处理,则操作将失败。我们需要一种在多个服务器节点之间同步密钥的可能解决方案。这增加了 tacker 管理员在扩展 tacker-server 实例时的操作复杂性。

Barbican 介绍

Barbican [1] 是一个 REST API,专为安全存储、配置和管理密钥而设计。它的目标是适用于所有环境,包括大型短暂云。

Barbican API [2] 包括以下项目

  • Secrets API。它提供对系统中存储的密钥/密钥材料的访问,包括私钥/证书/密码/SSH 密钥

  • Secret Metadata API。它允许用户能够将各种键/值对与 Secret 关联。

  • Containers API。它创建一个逻辑对象,可用于保存密钥引用。

  • ACL API。它支持对密钥和容器的访问控制。

  • Certificate Authorities API。它用作与证书颁发机构交互的接口。

  • Quotas API。它限制允许创建的资源数量。

  • Consumers API。它是一种注册作为容器感兴趣方的方式。

  • Certificates API [Pike 中已弃用]。它管理 x509 证书的生命周期,涵盖初始证书颁发、证书重新颁发、证书续订和证书撤销等操作。

  • Orders API [Pike 中已弃用]。它允许用户请求 barbican 生成密钥、创建证书和公钥/私钥对。

在 tacker vim 用例中,我们可以使用 Secrets API 来恢复 VIM 的密码。并且在未来,我们可以使用 Barbican 来支持 Tacker API 中的 TLS,新的蓝图 URL 将实现为:https://blueprints.launchpad.net/tacker/+spec/support-tls-in-api

存储密码的命令

$ 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                                                         |
+---------------+--------------------------------------------------------------+

检索密码的命令

$ openstack secret get http://192.168.80.128:9311/v1/secrets/fd44e2ed-b318-4924-a43f-afac4ba45aca --decrypt
+---------+--------+
| Field   | Value  |
+---------+--------+
| Payload | 123456 |
+---------+--------+

让我们看看其他项目如何调用 Barbican。

在 Nova 或 Cinder 中调用

Barbican 被引入到 nova 和 cinder 中以支持卷加密功能 [3] [4]。他们调用 castellan 作为 key_mamager,允许 Barbican 安全地生成、存储和呈现加密密钥。

在 Neutron-lbaas 或 Magnum 中调用

Neutron-lbaas 和 Magnum 引入 barbican 以支持 TLS [5] [6]。他们直接调用 barbicanclient 将租户的 TLS 证书存储在 barbican 安全容器中。

Castellan 介绍

Castellan [7] 是 Barbican 的库,基于配置的工作原理,提供抽象的密钥管理器。 这样,可以通过单个接口支持多种不同的管理服务。 除了密钥管理器,Castellan 还提供各种类型的密钥的基本组件(例如,非对称密钥、简单密码短语和证书)。这些基本组件与密钥管理器结合使用,以创建、存储、检索和销毁管理的密钥。

barbicanclient 与 castellan

Barbicanclient 支持 Barbican 的完整 API。 Castellan 是一个库,它调用 barbicanclient,提供一个精细的 API,并且比客户端更容易使用。

不幸的是,castellan 目前不支持密钥或容器的 ACL。 因此,在本规范中,我们将仅调用 barbicanclient,并在必要时考虑使用 castellan。

如何使用 castellan

  • 示例。创建和存储密钥。

    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)
    
  • 示例。检索密钥。

    from castellan import key_manager
    
    manager = key_manager.API()
    key = manager.get(context, stored_key_id)
    key.get_encoded()
    
  • 示例。删除密钥。

    from castellan import key_manager
    
    manager = key_manager.API()
    manager.delete(context, stored_key_id)
    

如何使用 barbicanclient

我们可以参考 castellan 了解如何调用 barbicanclient [12]

存储密钥

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)

获取密钥

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)

如何生成上下文

让我们看看如何为 castellan 生成上下文。

出于安全考虑,barbican 需要从 keystone 获取授权。 存储在 barbican 中的密钥对操作员是私有的,默认 RBAC 策略允许同一项目中的用户检索密钥。

有两种方法可以生成上下文。

  1. 使用保留项目

Castellan Usage [8] 显示了一种方法,将凭据保存在配置中。 我们可以创建一个保留的租户(例如“tacker-vim-credential-store”或长期存在的现有用户),并将所有 vim 的密码保存在此租户的域中保存和检索。

[castellan]
auth_type = 'keystone_password'
username = 'tacker-vim-credential-store'
password = 'passw0rd1'
project_id = 'tacker-vim-credential-store'
user_domain_name = 'default'

正如 IRC [11] 中的讨论,我们不应该这样做。

  1. 使用操作员的上下文(创建 vim 的人)

默认 RBAC 策略 [9] 关于密钥如下

"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",

Barbican 支持每个密钥的白名单 ACL。 如果 vim 是共享的,则将所有项目添加到 ACL [10] 不方便。

在这种方法中,我们无法支持共享 vim。 作为 IRC 讨论的结果 [11],在未来,vim 仅限于通过 rbac 策略与指定的项目共享,我们可以将这些项目添加到密钥的 ACL 中。

传输加密密码

出于安全考虑,我们需要避免将未加密的明文密码从 tacker 发送到 barbican。

有两种方法:1. 使用 fernet 对 vim 密码进行编码,并将 fernet 密钥存储到 barbican 中。 2. 支持 tacker 与 barbican 之间的 TLS。 我建议使用方法 1,就像当前的 vim 编码方式一样。

提议的变更

我们需要保留当前实现一个发布周期,使其可配置,并默认使用本地文件系统。

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

我们在 tacker 下添加一个名为 keymgr 的目录,该目录调用 barbicanclient。 添加一个类 BarbicanKeyManager,包括以下方法

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."""
在 nfvo.nfvo_plugin.NfvoPlugin 中
  1. 在 create/update/delete_vim 中,将上下文添加到 vim_obj 中

  2. 在 delete_vim 中,使用 vim_obj 调用 vim_driver

在 nfvo.drivers.vim.openstack_driver.OpenStack_Driver 中

1. __init__ 初始化 keymgr,加载配置中的凭据,self.key_manager = BarbicanKeyManager()

2. register_vim 检查 barbican 是否可用。 如果没有,则像以前一样执行,如果有,则从 vim_obj 获取原始密码和上下文,使用 fernet 对密码进行编码,生成一个 fernet_key,调用 self.key_manager.store(context, fernet_key),这将返回一个 secret uuid,将 uuid 保存到 vim_obj[‘auth_cred’][‘password’] 中,将 vim_obj[‘auth_cred’][‘key_type’] 设置为 barbican_secret

3. deregister_vim 检查 barbican 是否可用。 如果没有,则像以前一样执行,如果有,则将 vim 作为函数参数替换为 vim_obj,从 vim_obj[‘auth_cred’][‘password’] 中检索 key_id,从 vim_obj 中检索上下文,调用 self.key_manager.delete(context, key_id)

在 vnfm.vim_client.VimClient 中

  1. 将上下文添加到 _build_vim_auth 参数列表中。

2. _build_vim_auth 根据 vim_info[‘auth_cred’] 中的 key_type,如果 key_type 是 fernet_key,则像以前一样执行,如果它是 barbican_secret,则从 vim_obj[‘auth_cred’][‘password’] 中检索 key_id,调用 BarbicanKeyManager().get(context, key_id) 来解码密码。

备选方案

数据模型影响

在当前实现中,fernet 加密的密码保存在 VimAuth.password 和 VimAuth.auth_cred[‘password’] 中。 使用 barbican 时,我们将 secret UUID 保存在这些字段中。 将添加一个新字段到 VimAuth 中以区分密码的类型,这将有助于检索密码。

目前 vim 默认使用共享属性创建。 在我们将来支持 vim rbac 之后,我们应该支持修改 barbican 密钥的 ACL。

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 影响

安全影响

通知影响

其他最终用户影响

性能影响

其他部署者影响

如果我们将“use_barbican”配置为“yes”,则需要安装 Barbican 和 Castellan。

开发人员影响

实现

负责人

主要负责人

Yan Xing an<yanxingan@cmss.chinamobile.com>

其他贡献者

工作项

BP 涉及以下任务

  1. nfvo 和 vim 驱动程序以及单元测试

  2. 功能测试

  3. 安装文档

  4. 在 devstack 中支持 barbican

依赖项

  • Barbican

测试

  • FT/UT

文档影响

  • 安装文档

参考资料