API 验证

https://blueprints.launchpad.net/cinder/+spec/json-schema-validation

目前,Cinder 在验证请求体方面有不同的实现方式。此蓝图的目的是跟踪验证发送到 Cinder 服务器的请求体的进度,接受符合资源模式的请求,并拒绝不符合模式的请求。根据请求体的内容,请求应该被一致地接受或拒绝。

问题描述

目前 Cinder 没有一致的请求验证层。有些资源在资源控制器处验证输入,有些则在后端失败。理想情况下,Cinder 应该有一些验证机制,以捕获不允许的参数并向用户返回验证错误。

最终用户将受益于一致且有用的反馈,无论他们正在与哪个资源交互。

用例

作为用户或开发人员,我希望观察到一致的 API 验证以及传递到 Cinder API 服务器的值。

提议的变更

验证 Cinder API 的一种可能方法是使用 jsonschema,类似于 Nova、Keystone 和 Glance (https://pypi.ac.cn/project/jsonschema)。可以使用 jsonschema 验证器对象来检查每个资源是否符合该资源的适当模式。如果验证通过,则请求可以遵循现有的控制流,通过资源管理器到达后端。如果请求体参数未通过资源模式指定的验证,则服务器将返回一个包装在 HTTPBadRequest 中的验证错误。

示例:“字段‘name’的输入无效。该值为‘some invalid name value’。

每个 API 定义都应以以下方式添加

  • 在 ./cinder/api/schemas/ 下创建定义文件。

  • 每个定义应使用 JSON Schema 描述。

  • 每个定义中的参数(type、minLength 等)可以从当前验证代码、DB 模式、单元测试、Tempest 代码等定义。

关于执行此实现的一些说明

  • 可以在所有 Cinder 资源中利用常见的参数类型。例如,如下所示

    from cinder.api.validation import parameter_types
    # volume create schema
    <snip>
         create = {
            'type': 'object',
            'properties': {
                'volume': {
                    'type': 'object',
                    'properties': {
                        'size': parameter_types.positive_integer,
                        'availability_zone': parameter_types.availability_zone,
                        'source_volid': {
                            'format': 'uuid',
                        },
                        'description': parameter_types.description,
                        'multiattach': parameter_types.boolean,
                        'snapshot_id': {
                            'format': 'uuid',
                        },
                        'name': parameter_types.name,
                        'imageRef': {
                            'format': 'uuid',
                        },
                        'volume_type': {
                            'format': 'uuid',
                        },
                        'metadata': {
                            'type': 'object',
                         },
                        'consistencygroup_id': {
                            'format': 'uuid',
                        },
                    },
                'required': ['size'],
                'additionalProperties': False,
            }
            'required': ['server'],
            'additionalProperties': False,
        }
    }
    
    create_v312 = copy.deepcopy(create)
    create_v312['properties']['volume'][
        'properties']['group_id'] = {'format': 'uuid',},
    
    parameter_types.py:
    
    name = {
        'type': 'string', 'minLength': 0, 'maxLength': 255,
    }
    
    positive_integer = {
        'type': ['integer', 'string'],
        'pattern': '^[0-9]*$', 'minimum': 1, 'minLength': 1
    }
    
    description = name
    availability_zone = name
    
    # This registers a FormatChecker on the jsonschema module.
    # It might appear that nothing is using the decorated method but it gets
    # used in JSON schema validations to check uuid formatted strings.
    from oslo_utils import uuidutils
    
    @jsonschema.FormatChecker.cls_checks('uuid')
    def _validate_uuid_format(instance):
        return uuidutils.is_uuid_like(instance)
    
    boolean = {
        'type': ['boolean', 'string'],
        'enum': [True, 'True', 'TRUE', 'true', '1', 'ON', 'On', 'on',
                 'YES', 'Yes', 'yes',
                 False, 'False', 'FALSE', 'false', '0', 'OFF', 'Off', 'off',
                 'NO', 'No', 'no'],
    }
    
  • 可以使用以下装饰器在控制器层进行验证

    from cinder.api.schemas import volume
    
    @wsgi.response(http_client.ACCEPTED)
    @validation.schema(volume.create, "3.0")
    @validation.schema(volume.create_v312, "3.12")
    def create(self, req, body):
        """creates a volume.
    
         version 3.12 added groupd_id to the volume request body.
         If user has requested volume create with version v2 then the
         'validation.schema' decorator will ignore its schema validation and
         will pass the request as it is in the create method."""
    
  • 初始工作将包括捕获现有资源的 Block Storage API Spec 到一个模式中。这应该针对 API 的每个主要版本执行一次操作。这将应用于 Block Storage V3 API。

  • 在向 Cinder 添加新的扩展时,必须连同其适当的模式一起提出新的扩展。

备选方案

在 API 验证框架之前,我们需要将验证代码添加到每个 API 方法中,以临时方式进行处理。这些更改会使 API 方法代码变得混乱,并且由于验证不完整,我们需要创建多个补丁。

如果使用 JSON Schema 定义,可接受的请求格式是清晰的,并且我们将来不需要进行临时工作。

Pecan Framework: Pecan 一些项目(Ironic、Ceilometer 等)使用 Pecan/WSME 框架实现,我们可以从这些框架中自动获取 API 文档。在 WSME 实现中,开发人员应该为每个 API 定义 API 参数。Pecan 可以使 API 路由(URL、METHOD)的实现变得容易。

数据模型影响

REST API 影响

API 响应代码更改

在为它们添加模式层时,有时会发生 API 响应代码更改的情况。例如,在当前的 master 分支中,‘services’ 表在数据库表中具有最大 255 个字符的 ‘host’ 和 ‘binary’。在更新服务时,用户可以传递超过 255 个字符的 ‘host’ 和 ‘binary’,这显然会导致 404 ServiceNotFound,浪费了数据库调用。对于这种情况,我们可以仅在 ‘services’ 的模式定义中限制 ‘host’ 和 ‘binary’ 的最大长度为 255 个字符。如果用户传递超过 255 个字符,他/她将收到 400 BadRequest 响应。

API 响应错误消息

返回给用户的错误消息将发生变化。例如,在当前的 master 分支中,如果用户传递超过 255 个字符的卷名称,则 cinder-api 会返回以下错误消息

接收到无效输入:name 具有 <用户传递的实际字符数> 个字符,超过 255 个。

使用模式验证,将向用户返回以下错误消息

字段/属性 name 的输入无效。值:<用户传递的值>。‘<用户传递的值>’ 太长。

安全影响

请求验证层输出不应损害数据或向外部用户暴露私有数据。请求验证不应在成功验证时返回信息。如果请求体无效,则验证层应返回无效值和/或请求所需的值,最终用户应了解这些值。正在验证的资源的参数是公共信息,在 Block Storage API spec 中描述,不包括私有数据。如果用户的私有数据验证失败,可以在验证器的错误处理中构建一个检查,以不返回私有数据的实际值。

jsonschema 文档说明了模式和实例的安全注意事项:https://schema.json.js.cn/latest/json-schema-core.html#anchor21

更早期的输入验证将减少恶意用户输入利用安全漏洞的能力。

通知影响

其他最终用户影响

性能影响

Cinder 将需要一些性能成本来进行全面的请求参数验证,因为现在未验证的 API 参数的检查将会增加。

其他部署者影响

开发人员影响

这将要求为 Cinder 贡献新扩展的开发人员提供一个适当的模式,以表示扩展的 API。

实现

负责人

主要负责人:Dinesh_Bhor (Dinesh Bhor <dinesh.bhor@nttdata.com>)

工作项

  1. 初始验证器实现,它将包含通用的验证器代码,旨在在验证请求体的所有资源控制器之间共享。

  2. 引入现有核心 API 资源的验证模式。

  3. 引入现有 API 扩展的验证模式。

  4. 强制对提议的核心 API 添加项和扩展进行验证。

  5. 删除重复的临时验证代码。

  6. 添加相关的 API 的单元和端到端测试。

  7. 添加/更新 cinder 文档。

依赖项

cinder/api/contrib/ 下的扩展同时被 v2 和 v3 调用。因此,如果我们为 v3 添加模式验证,则需要删除控制器方法中现有的参数验证,这将再次破坏 v2 API。

解决方案

  1. 使用类似于 Nova 的 @validation.schema 装饰器为 v3 API 执行模式验证,并保留方法中的验证代码以保持 v2 的工作状态。

  2. 一旦做出停止支持 v2 的决定,我们应该删除方法中的验证代码。

测试

可以像 Nova V3 API 一样,在每个资源针对其模式进行验证时添加 Tempest 测试。这些测试应该遍历无效的请求类型。

我们可以遵循 Nova V3 API 中已经完成的一些验证工作

负面验证测试应使用 tempest.test.NegativeAutoTest

文档影响

  1. Cinder API 文档需要更新以反映 REST API 的更改。

  2. 需要更新 cinder 开发人员文档,以说明模式验证的工作方式以及如何为新的 API 添加 json 模式。

参考资料

有用的链接