禁止删除共享锁

包含您的 Launchpad 蓝图的 URL

https://blueprints.launchpad.net/manila/+spec/allow-locking-shares-against-deletion

默认的 RBAC 允许非读取者项目用户在项目的命名空间下创建和删除共享。删除共享可能很危险,我们期望用户在发起此操作之前谨慎行事。共享文件系统 (Manila) API 确保在继续删除之前满足一些先决条件。一个理想的先决条件是检查共享是否正在被客户端工作负载主动使用。然而,这种检查并不容易执行,因为它无法以一致的方式在 Manila 支持的所有网络附加存储 (NAS) 协议或存储系统后端中可靠地实现。换句话说,Manila 不知道有多少客户端已挂载共享,或者数据是否正在主动读取或写入共享。因此,需要一种安全机制来防止意外后果。

本规范提出一个新的先决条件,允许任何非读取者项目用户在项目的命名空间下创建一个删除锁,以防止共享被删除。该删除锁可以由创建该锁的用户或具有特权的用户移除。

问题描述

共享文件系统由网络文件服务器提供服务,允许多个并发客户端连接、读取和写入。在 OpenStack 中,Manila 共享由属于该共享的项目用户集体拥有。项目中的用户可以删除其他用户正在主动使用的共享,并且 Manila API 没有提供任何方式来指示或协调删除之前的通信。

此外,作为协议的一部分,NAS 客户端针对轻微的网络中断和服务器端故障进行了加固,在一定容忍度范围内。如果服务器一段时间内无响应,客户端可以等待,填充其写入缓存或重试读取,直到成功或容忍度到期。在最常见的情况下,客户端可以被指示无限期等待(“硬挂载”)。因此,长时间的服务器故障可能对客户端造成灾难性影响。如果在挂载的文件系统在服务器上被删除,客户端通常会执行相同的等待行为,并可能在此过程中变得无响应。

用例

最近的使用案例是 OpenStack Compute 功能,该功能允许用户通过 VirtIOFS [1] 将其共享挂载到虚拟机。借助此功能,计算主机可以将挂载的网络文件系统连接到一个或多个客户机。如果共享在挂载时被删除,计算主机将被破坏。这将禁用主机上的所有虚拟机,而不仅仅是使用通过 VirtIOFS 使用共享的虚拟机。因此,虽然 Compute 服务会协调挂载,但用户删除基础共享的操作可能会导致共享基础设施崩溃。

提议的变更

用户将能够锁定项目中的任何共享。可以对共享放置多个锁。除非所有锁都已移除,否则无法删除、取消管理或软删除共享。只有创建锁的用户或管理员用户才能移除给定的锁。如果用户尝试锁定先前为其特定资源操作锁定的共享,API 将不会重新创建锁。锁记录可以使用新的锁原因进行更新。

服务用户(使用 X-Service-Tokenservice 角色)可以代表普通用户在共享上创建锁。在这种情况下,Manila 也会记录锁用户上下文。因此,即使使用用户的令牌,如果提供了服务令牌,用户创建的锁也无法被用户移除。只有服务用户或具有“admin”角色的用户才能移除或更新锁。

此功能的实现将包括用于未来可扩展性的泛化。锁 API 将接受资源 ID、资源类型和必须锁定的资源操作。在 2023.2 Bobcat 发布周期中,我们将仅实现共享的删除锁。

备选方案

共享可以具有“使用中”状态,该状态可以防止不利的操作。访问规则的存在可以允许共享过渡到此“使用中”状态。但是,此方法的问题是用户可以在删除共享之前耗尽访问规则。这提供了一个两步删除过程,确保操作是故意的。但是,在上述用例中,它无法保护 OpenStack Compute 服务资源免于失去共享。

数据模型影响

将引入一个新的表

+-------------------+----------------------------------+----------+----------+
|       Field       |               Type               | Nullable | Default  |
+-------------------+----------------------------------+----------+----------+
| ID                | varchar(36)                      | NO       | NULL     |
| USER_ID           | varchar(36)                      | YES      | NULL     |
| PROJECT_ID        | varchar(36)                      | YES      | NULL     |
| RESOURCE_ACTION   | Enum('delete', ..)               | YES      | 'delete' |
| RESOURCE_TYPE     | Enum('share', ..)                | YES      | NULL     |
| RESOURCE_ID       | varchar(36)                      | NO       | NULL     |
| LOCK_USER_CONTEXT | Enum('user', 'service', 'admin') | YES      | NULL     |
| LOCK_REASON       | varchar(1023)                    | YES      | NULL     |
| DELETED           | varchar(36)                      | YES      | 'false'  |
| CREATED_AT        | datetime(6)                      | YES      | NULL     |
| DELETED_AT        | datetime(6)                      | YES      | NULL     |
| UPDATED_AT        | datetime(6)                      | YES      | NULL     |
+-------------------+----------------------------------+----------+----------+

该表将协助存储锁记录,并在创建、更新和移除锁时进行操作。数据库迁移将创建此表,不包含初始数据。LOCK_USER_CONTEXT 字段将允许通过枚举“user”、“service”和“admin”。RESOURCE_TYPERESOURCE_ACTION 字段也将限制为受支持的资源类型和资源操作的枚举常量。

REST API 影响

使用资源锁端点和方法的 API 仅将在新的 API 微版本中可用。但是,如果存在资源锁,则无法通过使用旧的 API 微版本来绕过它们,以执行它们所阻止的操作。

在特定操作上创建资源锁:

POST /v2/resource-locks

正常的 http 响应代码

  • 200 - 锁创建成功

预期的 http 错误代码

  • 401 - 未授权;用户未进行身份验证

  • 400 - 请求错误

  • 400 - 资源上的操作未识别

  • 400 - 未识别的资源(项目命名空间中没有此类资源)

  • 403 - 禁止;用户被策略禁止

  • 404 - API 在微版本中不存在

请求示例

{
    'resource_lock': {
        'resource_action': 'delete',
        'resource_type': 'share',
        'resource_id': 'a448e0d2-7501-4b99-a447-1b89e3961e39',
        'lock_reason': 'share is used by audit team'
    }
}

响应示例

{
    'resource_lock': {
        'id': 'be0871e8-742e-4c19-8567-7016fa0e2235',
        'user_id': 'cec1dd3e297b45348228f4fc3f5dba38',
        'project_id': '2e47ac4e2cf04a5b8b8509de8177d65d',
        'resource_action': 'delete',
        'resource_type': 'share',
        'resource_id': 'a448e0d2-7501-4b99-a447-1b89e3961e39',
        'lock_reason': 'share is used by audit team',
        'created_at': '2023-04-28T09:49:58-05:00',
        'updated_at': None
    }
}

更新资源锁:

PUT /v2/resource-locks/{id}

可更新的字段包括“resource_action”和“lock_reason”。“lock_reason”可以在更新时被设置为 null。根据默认 RBAC 策略,只有创建锁的用户或具有“admin”角色的用户才能更新锁。

正常的 http 响应代码

  • 200 - 锁更新成功

预期的 http 错误代码

  • 401 - 未授权;用户未进行身份验证

  • 400 - 请求错误

  • 400 - 资源上的操作未识别

  • 403 - 禁止;用户被策略禁止

  • 404 - API 在微版本中不存在

  • 404 - 锁在项目命名空间中不存在

请求示例

{
    'resource_lock': {
        'lock_reason': 'share will be used by audit team until 2024'
    }
}

响应示例

{
    'resource_lock': {
        'id': 'be0871e8-742e-4c19-8567-7016fa0e2235',
        'user_id': 'cec1dd3e297b45348228f4fc3f5dba38',
        'project_id': '2e47ac4e2cf04a5b8b8509de8177d65d',
        'resource_action': 'delete',
        'resource_type': 'share',
        'resource_id': 'a448e0d2-7501-4b99-a447-1b89e3961e39',
        'lock_reason': 'share will be used by audit team until 2024',
        'created_at': '2023-04-28T09:49:58.231919',
        'updated_at': '2023-04-28T20:01:13.12106'
    }
}

删除资源锁:

DELETE /v2/resource-locks/{id}

根据默认 RBAC 策略,只有创建锁的用户或具有“admin”角色的用户才能删除锁。

正常的 http 响应代码

  • 204 - 锁删除成功

预期的 http 错误代码

  • 401 - 未授权;用户未进行身份验证

  • 400 - 请求错误

  • 403 - 禁止;用户被策略禁止

  • 404 - API 在微版本中不存在

  • 404 - 锁在项目命名空间中不存在

请求和响应不包含任何数据

列出资源锁:

GET /v2/resource-locks?{queries}

查询将允许使用精确和不精确(“created_since”、“created_before”)属性进行过滤。根据默认 RBAC 策略,只有具有“admin”角色的用户才能使用“project_id”或“all_projects”进行查询。

正常的 http 响应代码

  • 200 - 项目命名空间中的锁列表

预期的 http 错误代码

  • 401 - 未授权;用户未进行身份验证

  • 403 - 禁止;用户被策略禁止

  • 404 - API 在微版本中不存在

响应示例

{
    'resource_locks': [
        {
            'id': 'be0871e8-742e-4c19-8567-7016fa0e2235',
            'user_id': 'cec1dd3e297b45348228f4fc3f5dba38',
            'project_id': '2e47ac4e2cf04a5b8b8509de8177d65d',
            'resource_action': 'delete',
            'resource_type': 'share',
            'resource_id': 'a448e0d2-7501-4b99-a447-1b89e3961e39',
            'lock_reason': 'share will be used by audit team until 2024'
        },
        {
            'id': '4945b04e-cdda-4308-9cfd-1483e7f9dd8c',
            'user_id': '80b789450540431db23575b333059ca8',
            'project_id': '2e47ac4e2cf04a5b8b8509de8177d65d',
            'resource_action': 'shrink',
            'resource_type': 'share',
            'resource_id': '4227fbd2-7f55-4ff4-9239-2cfc700d9fdf',
            'lock_reason': 'space is reserved for in place snapshots'
        }
    ]
}

显示锁:

GET /v2/resource-locks/{id}

正常的 http 响应代码

  • 200 - 项目命名空间中锁的详细信息

预期的 http 错误代码

  • 401 - 未授权;用户未进行身份验证

  • 403 - 禁止;用户被策略禁止

  • 404 - API 在微版本中不存在

  • 404 - 锁在项目命名空间中不存在

响应示例

{
    'resource_lock': {
        'id': 'be0871e8-742e-4c19-8567-7016fa0e2235',
        'user_id': 'cec1dd3e297b45348228f4fc3f5dba38',
        'project_id': '2e47ac4e2cf04a5b8b8509de8177d65d',
        'resource_action': 'delete',
        'resource_type': 'share',
        'resource_id': 'a448e0d2-7501-4b99-a447-1b89e3961e39',
        'lock_reason': 'share will be used by audit team until 2024',
        'created_at': '2023-04-28T09:49:58.231919',
        'updated_at': '2023-04-28T20:01:13.12106'
    }
}

删除具有锁的共享:

DELETE /v2/shares/{id}

正常的 http 响应代码

  • 202 - 没有锁存在,并且所有其他先决条件允许,已接受

预期的 http 错误代码

  • 401 - 未授权;用户未进行身份验证

  • 403 - 禁止;用户被策略禁止

  • 404 - API 在微版本中不存在

  • 404 - 共享在项目命名空间中不存在

  • 409 - 共享删除先决条件失败,可能存在锁

“delete”锁也会以类似的方式防止共享的软删除或取消管理操作。

将引入新的 RBAC 策略

"""Policy defaults that are used in specific policies below:"""

RULE_ADMIN = "role:admin"
RULE_SERVICE = "role:service"
RULE_ADMIN_OR_SERVICE = f'({RULE_ADMIN}) or ({RULE_SERVICE})'
PROJECT_MEMBER = "rule:project-member"
PROJECT_READER = "rule:project-reader"
PROJECT_OWNER_USER = "rule:project-owner-user"

ADMIN_OR_SERVICE_OR_PROJECT_MEMBER = f'({RULE_ADMIN_OR_SERVICE}) or ({PROJECT_MEMBER})'
ADMIN_OR_SERVICE_OR_PROJECT_MEMBER = f'({RULE_ADMIN_OR_SERVICE}) or ({PROJECT_READER})'
ADMIN_OR_SERVICE_OR_PROJECT_OWNER_USER = f'({RULE_ADMIN_OR_SERVICE}) or ({PROJECT_OWNER_USER})'

rules = [
    policy.RuleDefault(
        name='project-member',
        check_str='role:member and '
                  'project_id:%(project_id)s',
        description='Project scoped Member',
        scope_types=['project']),
    policy.RuleDefault(
        name='project-reader',
        check_str='role:reader and '
                  'project_id:%(project_id)s',
        description='Project scoped Reader',
        scope_types=['project']),
    policy.RuleDefault(
        name='project-owner-user',
        check_str='role:member and '
                  'project_id:%(project_id)s and '
                  'user_id:%(user_id)s',
        description='Project scoped Member who owns a resource',
        scope_types=['project']),
]
  • 创建锁

policy.DocumentedRuleDefault(
     name= 'resource_locks:create',
     check_str=ADMIN_OR_SERVICE_OR_PROJECT_MEMBER,
     scope_types=['project'],
     description="Create a resource lock.",
     operations=[
         {
             'method': 'POST',
             'path': '/resource-locks',
         },
     ],
 )
  • 更新锁

policy.DocumentedRuleDefault(
     name= 'resource_locks:update',
     check_str=ADMIN_OR_SERVICE_OR_PROJECT_OWNER_USER,
     scope_types=['project'],
     description="Update a resource lock.",
     operations=[
         {
             'method': 'PUT',
             'path': '/resource-locks/{id}',
         },
     ],
 )
  • 删除锁

policy.DocumentedRuleDefault(
     name= 'resource_locks:delete',
     check_str=ADMIN_OR_SERVICE_OR_PROJECT_OWNER_USER,
     scope_types=['project'],
     description="Delete a resource lock.",
     operations=[
         {
             'method': 'DELETE',
             'path': '/resource-locks/{id}',
         },
     ],
 )
  • 列出锁

policy.DocumentedRuleDefault(
     name= 'resource_locks:index',
     check_str=ADMIN_OR_SERVICE_OR_PROJECT_READER,
     scope_types=['project'],
     description="List all resource locks.",
     operations=[
         {
             'method': 'GET',
             'path': '/resource-locks?{queries}',
         },
     ],
 )
  • 使用项目查询列出锁

policy.DocumentedRuleDefault(
     name= 'resource_locks:get_all_projects',
     check_str=RULE_ADMIN,
     scope_types=['project'],
     description="Get resource locks across projects.",
     operations=[
         {
             'method': 'GET',
             'path': '/resource-locks?all_projects=1&project_id={project_id}',
         },
     ],
 )
  • 获取锁

policy.DocumentedRuleDefault(
     name= 'resource_locks:get',
     check_str=ADMIN_OR_SERVICE_OR_PROJECT_READER,
     scope_types=['project'],
     description="Get details about a resource lock.",
     operations=[
         {
             'method': 'GET',
             'path': '/resource-locks/{id}',
         },
     ],
 )

驱动程序影响

无。这是一个仅限 API 的功能。

安全影响

默认 RBAC 策略将允许具有“admin”角色的用户创建、查看或删除用户锁。假定“admin”角色授予操作员用户。如果需要代表用户创建锁的服务或应用程序,建议将服务或应用程序配置为使用具有“service”角色而不是“admin”角色的用户。

没有进一步的安全影响,无论是正面还是负面。

通知影响

将为各自的操作发出“lock.create”和“lock.delete”通知事件。

其他最终用户影响

将在 OpenStackClient (python-manilaclient 插件) 和 OpenStack Dashboard (manila-ui 插件) 中引入用户界面改进。OpenStackClient 的添加将伴随 manilaclientopenstacksdk 接口

  • 创建资源锁

openstack share lock create <resource_id> \
  [--resource-action <resource_action>] \
  [--resource-type <resource_type>] \
  [--reason <lock_reason>}]

“resource-action”默认为“delete”。

  • 更新资源锁

openstack share lock update <id> \
  [--resource-action <resource_action>] \
  [--reason <lock_reason>}]
  • 删除资源锁

openstack share lock delete <id>
  • 列出资源锁

openstack share lock list
  • 显示资源锁

openstack share lock show <id>

性能影响

由于我们正在引入共享删除(或取消管理/软删除)的新先决条件,因此由于额外的查找,共享删除 API 将遭受性能下降。即使在未使用锁的环境中,也无法避免此查找。我们将通过使用适当的索引来优化查询。将来,随着更多资源和资源操作使用这种方法,我们将影响这些 API 的现有性能。这是功能权衡。

其他部署者影响

无。

开发人员影响

在定义或操作操作时,考虑通过此接口允许锁。

实现

负责人

主要负责人

gouthamr

工作项

  • Manila API 变更

  • manilaclient、openstackclient、manila UI 中的支持

  • openstacksdk 中的支持

  • 使用 manila-tempest-plugin 的 e2e 测试

  • API 参考、用户和管理员文档

依赖项

  • 此功能不依赖于其他工作,但是,Nova 中的 VirtIOFS 集成工作需要此功能。

测试

将添加新测试来创建锁、列出锁、显示锁、删除锁。测试用例将涵盖多个锁的使用,并涉及请求和响应模式和代码的验证。RBAC 策略也将通过 tempest 进行测试。

文档影响

API 参考将随着 API 变更进行更新。用户和管理员文档将随着各自存储库中的 UX 变更而更新。

参考资料

[1] VirtIOFS 规范

[2] 2023.2 Bobcat PTG 讨论