为 Barbican 资源添加配额支持

https://blueprints.launchpad.net/barbican/+spec/quota-support-on-barbican-resources

Barbican REST API 对每个项目允许的资源数量没有任何上限。这可能导致资源爆炸式增长。此蓝图提出了一种指定和强制执行项目配额的方法。配额是运营限制,旨在优化云资源。

问题描述

以下是一些可能影响 Barbican 服务器正常运行的场景

  • 客户端可能会为单个项目发出数千个创建密钥的请求。这可能会在处理时间和消耗的磁盘空间方面使 Barbican 服务器不堪重负

  • 如果一个有缺陷的客户端脚本失控并尝试创建一个没有关联密钥的通用类型容器,它可能会迅速填满 Barbican 数据库!

  • 如果用户创建了大量项目,并且进一步为这些项目创建了大量的 Barbican 资源,这可能会影响其他真正的用户

注意:如果 Keystone 可以强制用户可以创建的项目数量上限,则可以避免最后一点。因此,目前 Barbican 将其视为较低优先级。此规范旨在首先实现项目级别的配额。预计后续规范将添加对用户级别配额的支持。

这类似于 nova 和 cinder 服务执行的配额强制执行。

提议的变更

为所有 Barbican 资源引入配额。配额应具有无限制的默认值,以确保与当前不支持配额的代码的向后兼容性。以下资源将具有配额支持

  • 密钥

  • 订单

  • 容器

  • 消费者

注意

此提案是 Nova 和 Cinder 执行的配额实现的一个更简单的子集。Barbican 没有可预留的资源,因此配额要简单得多,因为没有使用跟踪和预留。此外,此规范不涵盖项目-用户级别配额强制执行。

*强制执行配额:*

Barbican API 控制器将使用资源创建方法的配额逻辑进行更新。实施后,配额检查将按以下方式为资源创建请求工作

  1. 从身份验证上下文中检索项目的配额。如果未设置项目配额,则使用默认配额

  2. 获取上下文项目的资源计数。注意:当前,已过期或已删除的密钥不会从数据库中硬删除,而是软删除,因此它们仍然在使用 Barbican 中的资源,并会被计入项目的配额。但是,这取决于部署。部署可能有一个在一段时间后硬删除此类资源的过程

  3. 如果计数等于或超过配额,则拒绝请求并显示以下错误消息:HTTP 403 Forbidden {“error”: “<project_id> 的配额已超出。仅允许 <count> 个 <resource>” }

  4. 继续资源创建

更新 Barbican 配置文件以包含配额限制的以下部分

# ====================== Quota Options ===============================

[quotas]
# For each resource, the default maximum number that can be used for
# a project is set below.  This value can be overridden for each
# project through the API.  A negative value means no limit.  A zero
# value effectively disables creation of the resource.

# default number of secrets allowed per project
quota_secrets = -1

# default number of orders allowed per project
quota_orders = -1

# default number of containers allowed per project
quota_containers = -1

# default number of consumers allowed per project
quota_consumers = -1

一个 >=0 的数字表示该资源的上限,负值表示无限制。值为零表示该资源的最大实例数为零,从而有效地禁用该实体的创建。为了确保向后兼容性,为默认值提供的默认值将为 -1(无限制),适用于每个资源。

虽然这些通用配额适用于所有项目,但还将支持定义和强制执行每个项目的配额。配额的强制执行优先级为:[项目配额] => [默认配额]

其中,“项目配额” - 这些配额直接与特定的项目 ID 相关联。对这些配额的任何更改仅会影响该项目。

“默认配额” - 如果未指定项目配额,则使用默认配额。这些通常是配置文件中提供的值。

默认配额存储在配置文件中(如上所示),但项目配额存储在数据库中。

还将实现一个 Barbican 管理员的 REST API,用于配额 CRUD 操作。非管理员用户将提供一个 REST API,以获取他们对各种资源的有效配额。将使用 Keystone RBAC 检查来确定调用者是否具有执行这些操作所需的角色。

配额管理应由服务级别管理员负责,而不是项目级别管理员。在当前的 Barbican 实现中,有四个用户角色:管理员、创建者、观察者和审计员。所有这些角色都是项目级别的。因此,为了实现适当的基于角色的访问控制以进行配额管理,将创建一个新的角色。该角色的名称将是“key-manager:service-admin”。

备选方案

曾尝试创建一个包含所有 OpenStack 项目配额支持的 oslo.common 库。第一次提交到 oslo,但已被弃用,并且没有被任何项目采用。第二次尝试已经开始,但已暂停,目前没有重新启动的计划。没有其他通用的 OpenStack 库实现 Barbican 可以采用的配额。

配额配置和逻辑将通过查看其他 OpenStack 项目(如 nova、cinder 和 neutron)执行的配额实现来确定。将通过使用类似于 Nova 的实现中的 API 和逻辑,同时删除不必要的功能(如可插拔后端驱动程序和资源预留)为 Barbican 开发一个简化的实现。

另一种选择是 Rackspace 的 Kevin Mitchell 发起的一项倡议 https://wiki.openstack.org/wiki/Boson。但是,Nova 和 Cinder 的设计更适合 Barbican。

除了上述四个资源之外,此规范的先前版本描述了实施 transport_keys 配额支持。基于 transport_keys 的当前实现,这是不可能的,因为它们是每插件定义的,而不是每项目定义的。项目管理员根本不应该管理 transport_keys。

决定管理项目配额所需的角色名称是“key-manager:service-admin”。这预示着未来的一项更改,即所有 Barbican 角色都可能使用 key-manager 命名空间定义。其他各种角色名称(“service-admin”、“barbican-admin”、“cloud-admin”)也是可能的替代方案。

数据模型影响

将添加以下新的数据模型

  • ProjectQuota

    表示项目的配额覆盖。

    如果对于给定的项目 ID 没有行,则使用部署的默认值。如果资源的配额值为 null,则使用部署的该资源的默认值。如果资源的配额值为 0,则禁用该资源的创建。如果资源的配额值为 -1,则不通过配额强制执行逻辑限制该资源的创建。

    Schema:(表名:project_quotas

    • project_id:String(36) ForeignKey projects.id, nullable=False

    • secrets:Integer, nullable=True

    • orders:Integer, nullable=True

    • containers:Integer, nullable=True

    • consumers:Integer, nullable=True

    约束:project_id 必须是唯一的

    project_id 必须存在于 projects.id 中

  • 现有模型的更改

此添加不会影响现有模型。但是,需要调查是否需要构建新的索引来加速资源消耗查找。

REST API 影响

将实施以下新的 REST API 来管理配额 CRUD 操作。请注意,除了第一个 GET API 之外,所有其他 API 都需要调用者具有“key-manager:service-admin”角色。

  • 获取有效配额(任何 Barbican 用户)

    • 返回调用者针对指定项目的有效资源配额。如果没有项目特定的配额,则返回部署的默认资源限制。

    • GET /v1/quotas

    • 正常的 http 响应代码(s) 200 OK

    • 预期的错误 http 响应代码

      • 401 Unauthorized - 如果 auth token 不存在或无效。

        如果使用未经验证的上下文,并且请求中没有 X-Project-Id 标头。

    • 所需的请求标头

      X-Auth-Token,如果使用 keystone auth

      X-Project-Id,如果使用未经验证的上下文

    • 参数

    • 如果允许,则为 body 数据定义 JSON schema

    • 如果存在,则为响应数据定义 JSON schema

      示例

      {
        'type': 'object',
        'properties': {
            'quotas': {
              'type': 'object',
              'properties': {
                'secrets': {'type':'integer'}
                'orders': {'type':'integer'},
                'containers': {'type':'integer'},
                'consumers': {'type':'integer'}
               },
              'additionalProperties': False
            }
        },
        'additionalProperties': False
      }
      
      • 示例 1

        A non-admin user checking the resource quotas using a token scoped to a
        particular project
        
        Request:
        
          GET /v1/quotas
        
          X-Auth-Token:<token>
        
        Response:
        
          200 OK
        
          Content-Type: application/json
        
          {
            "quotas": {
              "secrets": 10,
              "orders": 20,
              "containers": 10,
              "consumers": -1
            }
          }
        
  • 列出所有项目配额(仅限 service-admin)

    • 列出所有用户所有项目的已配置项目级别资源配额。如果项目没有项目特定的配额配置,则该项目不包含在返回的列表中。如果项目仅对某些资源的配额进行了项目特定的配置,则此调用将为该项目中没有配置值的那些资源返回 null。返回的列表将按创建日期排序,并支持标准的 limit/offset 分页。

      标准分页支持包括在适用时在响应主体中添加三个字段。

      • “total”:显示 project-quotas 记录的数量

      • “next”:提供指向记录下一页的 URL

      • “prev”:提供指向记录上一页的 URL

    • GET /v1/project-quotas?limit=x&offset=y (仅限 service-admin)

    • 正常的 http 响应代码(s) 200 OK

    • 预期的错误 http 响应代码

      • 401 Unauthorized - 如果 auth token 不存在或无效。

        如果使用未经验证的上下文,并且请求中没有 X-Project-Id 标头。

    • 所需的请求标头

      X-Auth-Token,如果使用 keystone auth

    • 参数

      limit(可选),整数,检索的记录的最大数量 offset(可选),整数,要跳过的记录数

    • 如果允许,则为 body 数据定义 JSON schema

    • 如果存在,则为响应数据定义 JSON schema

      示例

      {
        'type': 'object',
        'properties': {
            'project_quotas': {
              'type': 'array'
              'items': {
                'type': 'object',
                'properties': {
                   'project_id': {'type':'string'},
                   'project_quotas': {
                        'type':'object',
                        'properties': {
                           'secrets': {'type': 'integer'},
                           'orders': {'type': 'integer'},
                           'containers': {'type': 'integer'},
                           'consumers': {'type': 'integer'}
                        }
                   }
                 }
               }
              }
           },
        'additionalProperties': False
      }
      
      • 示例 1

        A service-admin user listing all the project quotas
        
        Request:
        
          GET /v1/project-quotas
        
          X-Auth-Token:<token>
        
        Response:
        
          200 OK
        
          Content-Type: application/json
        
          {
            "project_quotas": [
              {
                "project_id": "1234",
                "project_quotas": {
                     "secrets": 2000,
                     "orders": 0,
                     "containers": -1,
                     "consumers": null
                 }
              },
              {
                "project_id": "5678",
                "project_quotas": {
                     "secrets": 200,
                     "orders": 100,
                     "containers": -1,
                     "consumers": null
                 }
              },
            ],
            "total" : 30,
          }
        
      • 示例 2

        A service-admin user listing all the project quotas with paging
        
        Request:
        
          GET /v1/project-quotas?limit=2&offset=6
        
          X-Auth-Token:<token>
        
        Response:
        
          200 OK
        
          Content-Type: application/json
        
          {
            "project_quotas": [
              {
                "project_id": "1234",
                "project_quotas": {
                     "secrets": 2000,
                     "orders": 0,
                     "containers": -1,
                     "consumers": null
                 }
              },
              {
                "project_id": "5678",
                "project_quotas": {
                     "secrets": 200,
                     "orders": 100,
                     "containers": -1,
                     "consumers": null
                 }
              },
            ],
            "total" : 30,
            "next": "https://:9311/v1/project_quotas?limit=2&offset=8",
            "prev": "https://:9311/v1/project_quotas?limit=2&offset=4"
          }
        
  • 获取特定项目的配额(仅限 service-admin)

    • 返回为指定项目配置的一组资源配额。如果未配置项目特定的配额值(或者项目不存在),则 API 响应为 Not Found。如果项目仅对某些资源的配额进行了项目特定的配置,则此调用将为该项目中没有配置值的那些资源返回 null。

    • GET /v1/project-quotas/{project_id}

    • 正常的 http 响应代码(s) 200 OK

    • 预期的错误 http 响应代码

      • 401 Unauthorized - 如果 auth token 不存在或无效。

        如果使用未经验证的上下文,并且请求中没有 X-Project-Id 标头。

      • 404 Not Found - 如果没有要删除的 project quota 设置

        对于指定的项目。

    • 所需的请求标头

      X-Auth-Token,如果使用 keystone auth

      X-Project-Id,如果使用未经验证的上下文

    • 如果允许,则主体数据的 JSON schema 定义 None

    • 如果存在,则为响应数据定义 JSON schema

      {
        'type': 'object',
        'properties': {
             'project_quotas': {
                'type':'object',
                'properties': {
                  'secrets': {'type': 'integer'},
                  'orders': {'type': 'integer'},
                  'containers': {'type': 'integer'},
                  'consumers': {'type': 'integer'}
                }
           }
        },
        'additionalProperties': False
      }
      
      • 示例

        Request:
        
          GET /v1/project-quotas/1234
        
          X-Auth-Token:<token>
        
        Response:
        
          200 OK
        
          Content-Type: application/json
        
          {
            "project_quotas": {
              "secrets": 10,
              "orders": 20,
              "containers": -1,
              "consumers": 10
            }
          }
        
  • 更新/设置特定项目的配额(仅限 service-admin)

    • 创建或更新指定项目的已配置资源配额。不必为所有 Barbican 资源指定限制。如果未指定资源的某个值,则将使用该资源的默认限制。如果指定的项目以前不为 Barbican 所知,则将在 projects 表中创建一个新条目。

    • PUT /v1/project-quotas/{project_id}

    • 正常的 http 响应代码

      204 No Content

    • 预期的错误 http 响应代码

      • 401 Unauthorized - 如果 auth token 不存在或无效。

        如果使用未经验证的上下文,并且请求中没有 X-Project-Id 标头。

      • 400 Bad Request - 如果请求有效负载不符合 schema

    • 所需的请求标头

      X-Auth-Token,如果使用 keystone auth

      X-Project-Id,如果使用未经验证的上下文

      Content-Type,application/json

    • 如果允许,则为 body 数据定义 JSON schema

      {
        'type': 'object',
        'properties': {
           'project_quotas': {
                'type':'object',
                'properties': {
                   'secrets': {'type': 'integer'},
                   'orders': {'type': 'integer'},
                   'containers': {'type': 'integer'},
                   'consumers': {'type': 'integer'}
                }
           }
       },
       'additionalProperties': False
      }
      
    • 如果存在,则响应数据的 JSON schema 定义:: None

      • 示例

        Request:
        
          PUT /v1/project-quotas/1234
        
          X-Auth-Token:<token>
        
          Body::
        
            {
              "project_quotas": {
                "secrets": 50,
                "orders": 10,
                "containers": 20
              }
            }
        
        
        Response:
        
          204 OK
        
  • 删除特定项目的配额(仅限 service-admin)

    • 删除指定项目的已配置资源配额。此调用成功后,用户后续调用列出有效配额时将返回默认资源配额。

    • DELETE v1/project-quotas/{project_id}

    • 参数 None

    • 正常的 http 响应代码(s) 204 No Content

    • 预期的错误 http 响应代码

      • 401 Unauthorized - 如果 auth token 不存在或无效。

        如果使用未经验证的上下文,并且请求中没有 X-Project-Id 标头。

      • 404 Not Found - 如果没有要删除的 project quota 设置

        对于指定的项目或 Barbican 不知道该项目。

    • 所需的请求标头

      X-Auth-Token,如果使用 keystone auth

      X-Project-Id,如果使用未经验证的上下文

    • 参数

    • 如果允许,则为 body 数据定义 JSON schema

    • 如果存在,则为响应数据定义 JSON schema

  • 示例 1

    Request:
    
      DELETE v1/project-quotas/1234
    
      X-Auth-Token:<token>
    
    
    Response:
    
      204 No Content
    
  • 策略更改

    对于所有仅限 service-admin 的 API,调用者预计将具有 barbican key-manager:service-admin 角色。将在 Barbican policy.json 文件中添加对此的检查。

实施并强制执行后,所有 Barbican 资源创建 API 都可以将新的错误消息返回给客户端,如果请求超过了允许的配额限制。

示例

Request::

  POST /v1/secrets

  X-Auth-Token: <token>

  Content-Type: application/json

  {
    # payload to create secret
  }

Response::

  403 Forbidden

  Retry-After: 0

  Content-Type: application/json

 {
  "error": "Quota exceeded for <project-id>. Only <count> <resource>s
            are allowed"
 }
  • 类 Quotas

    此规范未涉及类级别配额。需要另一个规范来涵盖数据模型影响和相关的 CRUD 操作的 REST API。

安全影响

通知与审计影响

其他最终用户影响

Barbican 客户端(python-barbicanclient)必须增强以使用上述 Quota REST API。应支持以下场景。

普通非管理员 barbican 用户可以发出的配额命令

  • 列出所有配额

    barbican quota show

只有 barbican service-admin 才能发出的配额命令

  • 列出适用于所有新项目的默认配额

    barbican quota show

  • 列出特定项目的配额

    barbican quota show –project_id <project>

  • 更新特定项目的配额

    barbican quota update –project_id <project> –secrets 50 –orders 10

  • 删除项目的配额

    barbican quota delete –project_id <project>

性能影响

待定

其他部署者影响

将通过新的 Alembic 版本文件添加引入的新数据模型。如果自动迁移已关闭,则必须手动运行 db 迁移工具才能使更改生效。

开发人员影响

与 Barbican API/客户端集成的开发人员现在需要处理服务器可能返回配额违规错误的这种情况

实现

负责人

Dave McCowan (dave-mccowan) 将领导代码的实施。

主要负责人

<dave-mccowan>

其他指派人

工作项

  • 配额数据库提供程序源代码

  • 数据模型添加

  • Alembic 迁移版本脚本

  • 包含配额部分的更新默认配置文件

  • python-barbicanclient 增强功能以支持配额操作

  • 新的单元测试以测试与配额相关的源代码更改

  • 更新现有的资源单元测试以处理配额违规错误

  • 功能测试

依赖项

待定

测试

需要添加新的单元测试和功能测试。

文档影响

  • 必须记录一个关于配额的新部分

  • 需要更新现有的资源 API 文档,其中包含与配额违规相关的特定错误

参考资料

待定