使用 Swift 作为服务数据的后端存储

背景

本节为 OpenStack 服务开发者提供关于如何在 Swift 中存储用户数据的指导。例如,用户请求 Nova 保存 VM 的快照。Nova 将请求传递给 Glance,Glance 将镜像写入 Swift 容器,作为一组对象。

在本节中,将使用以下术语和概念

  • 用户或最终用户。这是发起请求并导致 OpenStack 服务向 Swift 发出请求的人员。

  • 项目(也称为租户)。这是资源所有权的单位。虽然快照镜像或块卷备份等数据可能是由最终用户请求的结果,但实际上这些是项目数据。

  • 服务。这是最终用户使用的程序或系统。具体来说,它是任何能够接收最终用户令牌并使用 Keystone 服务验证令牌,并且需要将数据存储在 Swift 中的程序或系统。Glance 和 Cinder 是此类服务的示例。

  • 服务用户。这是已分配给服务的 Keystone 用户。这允许服务生成和使用自己的令牌,以便它可以作为自身与其他服务交互。

  • 服务项目。这是与服务关联的项目(租户)。可能有单个项目由许多服务共享,或者可能有为每个服务专门的项目。在本文档中,服务项目的主要目的是允许系统操作员为每个服务用户配置特定的角色。

替代后端存储方案

这里描述了三种方案

  • 专用服务帐户(单租户)

    您的服务拥有一个专用的服务项目(因此,一个专用的 Swift 帐户)。所有用户和项目的数据都存储在此帐户中。您的服务必须为其分配一个用户(服务用户)。当您有要代表您的用户存储的数据时,您使用服务用户凭据获取服务项目的令牌,并请求 Swift 将数据存储在服务项目中。

    使用此方案,所有用户的数据都存储在一个帐户中。这对于您的用户来说是透明的,并且由于服务用户凭据通常不与任何人共享,您的用户无法通过直接向 Swift 发出请求来访问他们的数据。但是,由于属于所有用户的数据都存储在一个帐户中,它为意外删除或服务用户凭据泄露提供了一个单一的漏洞点。

  • 多项目(多租户)

    属于某个项目的数据存储在与该项目关联的 Swift 帐户中。用户使用作用域限定为项目的令牌以正常方式向您的服务发出请求。然后,您可以使用相同的令牌将用户数据存储在项目的 Swift 帐户中。

    其结果是数据存储在多个项目(也称为租户)中。因此,此方案被称为“多租户”方案。

    使用此方案,访问由 Keystone 控制。用户必须具有允许他们执行对您的服务请求的角色。此外,他们还必须具有允许他们将数据存储在 Swift 帐户中的角色。默认情况下,admin 或 swiftoperator 角色用于此目的(特定系统可以使用其他角色名称)。如果用户没有适当的角色,当您的服务尝试访问 Swift 时,操作将失败。

    由于您正在使用用户的令牌来访问数据,因此可以推断出用户可以使用相同的令牌直接访问 Swift – 绕过您的服务。当最终用户浏览容器时,他们还会看到您的服务的容器和对象 – 并且可能潜在地删除数据。相反,没有一个帐户存储所有数据,因此凭据泄露只会影响单个项目/租户。

  • 服务前缀帐户

    属于某个项目的数据存储在与该项目关联的 Swift 帐户中。这类似于上述多项目方案。但是,Swift 帐户与用户访问的帐户不同。具体来说,它具有不同的帐户前缀。例如,对于项目 1234,用户帐户名为 AUTH_1234。您的服务使用不同的帐户,例如 SERVICE_1234。

    要访问 SERVICE_1234 帐户,您必须提供两个令牌:用户的令牌放在 X-Auth-Token 标头中。您将服务令牌放在 X-Service-Token 标头中。Swift 被配置为只有在同时提供两个令牌时才允许访问。具体来说,用户无法绕过您的服务,因为他们只有自己的令牌。相反,您的服务只有在拥有用户令牌的副本时才能访问数据 – 服务令牌本身不会授予访问权限。

    存储在服务前缀帐户中的数据无法被最终用户看到。因此,他们无法删除此数据 – 他们只能通过您的服务发出请求来访问数据。该数据也更安全。要进行未经授权的访问,某人需要破坏最终用户和您的服务用户凭据。即使这样,这只会暴露一个项目 – 而不是其他项目。

服务前缀帐户方案结合了专用服务帐户和多项目方案的特性。它具有专用服务帐户方案的私有、专用特性,但没有提供单一的攻击点。使用服务前缀帐户方案比其他方案更复杂,因此本文档更详细地描述了它。

服务前缀帐户概述

下图显示了从最终用户、到您的服务,再到 Swift 的系统流程

client
   \
    \   <request>: <path-specific-to-the-service>
     \  x-auth-token: <user-token>
      \
    SERVICE
       \
        \    PUT: /v1/SERVICE_1234/<container>/<object>
         \   x-auth-token: <user-token>
          \  x-service-token: <service-token>
           \
          Swift

事件和操作的顺序如下

  • 请求到达您的服务

  • <user-token> 由 keystonemiddleware.auth_token 中间件验证。用户的角色用于确定用户是否可以执行请求。有关身份验证系统的技术信息,请参阅 身份验证系统

  • 作为此请求的一部分,您的服务需要访问 Swift(无论是写入还是读取容器或对象)。在此示例中,您希望对 <container>/<object> 执行 PUT。

  • 在 wsgi 环境中,auth_token 模块将填充 HTTP_X_SERVICE_CATALOG 项。这列出了 Swift 端点和帐户。这类似于 https://<netloc>/v1/AUTH_1234,其中 AUTH_ 是一个前缀,1234 是项目 ID。

  • AUTH_ 是默认值。但是,您的系统可以使用不同的前缀。要确定实际的前缀,请在帐户名称中搜索第一个下划线(‘_’)字符。如果帐户名称中没有下划线字符,则表示没有前缀。

  • 您的服务应该有一个配置参数,提供用于在 Swift 中存储数据的适当前缀。下面将对此进行进一步讨论,但现在假设前缀是 SERVICE_

  • 将前缀(AUTH_ 在上述示例中)替换为 SERVICE_,因此访问该对象的完整 URL 变为 https://<netloc>/v1/SERVICE_1234/<container>/<object>。

  • 使用此 URL 向 Swift 发出请求。在 X-Auth-Token 标头中放置 <user-token> 的副本。在 X-Service-Token 标头中,放置您的服务令牌。如果您使用 python-swiftclient,您可以这样做

    • 将 URL 放在 preauthurl 参数中

    • 将 <user-token> 放在 preauthtoken 参数中

    • 将 X-Service-Token 添加到 headers 参数中

使用 HTTP_X_SERVICE_CATALOG 获取 Swift 帐户名称

auth_token 中间件在验证用户的令牌时,会将 wsgi 环境填充信息。HTTP_X_SERVICE_CATALOG 项是一个 JSON 字符串,其中包含 OpenStack 端点的详细信息。对于 Swift,它还包含项目的 Swift 帐户名称。这是一个 Swift 目录条目的示例

"serviceCatalog": [
    ...
    {
        ....
        "type": "object-store",
        "endpoints": [
           ...
           {
               ...
               "publicURL": "https://<netloc>/v1/AUTH_1234",
               "region": "<region-name>"
               ...
           }
           ...
     ...
     }
}

要获取最终用户的帐户

  • 查找 typeobject-store 的条目

  • 如果有多个区域,将有多个端点。使用适当的区域名称并选择 publicURL 项。

  • Swift 帐户名称是路径中的最后一个项目(在此示例中为“AUTH_1234”)。

获取服务令牌

服务令牌与任何其他令牌没有区别,并且使用用户凭据和项目以通常的方式从 Keystone 请求。核心要求是您的服务用户必须具有适当的角色。实际上

  • 您的服务必须为其分配一个用户(服务用户)。

  • 您的服务必须为其分配一个项目(服务项目)。

  • 服务用户必须在服务项目上具有一个角色。此角色与任何常规最终用户角色不同。

  • 使用的角色必须是 /etc/swift/proxy-server.conf 中配置的角色。这是 <prefix>_service_roles 选项。在此示例中,该角色是 service 角色

    [keystoneauth]
    reseller_prefix = AUTH_, SERVICE_
    SERVICE_service_role = service
    

service 角色应仅授予 OpenStack 服务。不应将其授予用户。

单个或多个服务前缀?

本文档中使用的大多数示例都使用了单个前缀。前缀 SERVICE 已被使用。通过使用单个前缀,操作员允许所有 OpenStack 服务共享与给定项目关联的相同帐户。对于受良好保护的私有防火墙网络的测试系统或部署,这是合适的。

但是,如果一个服务受到破坏,该服务可以访问另一个服务创建的数据。为了防止这种情况,可以使用多个服务前缀。这还需要操作员配置多个服务角色。例如,在具有 Glance 和 Cinder 的系统中,可以使用以下 Swift 配置

[keystoneauth]
reseller_prefix = AUTH_, IMAGE_, BLOCK_
IMAGE_service_roles = image_service
BLOCK_service_roles = block_service

Glance 的服务用户将被授予其服务项目上的 image_service 角色,Cinder 服务用户将被授予其项目上的 block_service 角色。在这种方案中,如果 Cinder 服务受到破坏,它将无法访问任何 Glance 数据。

容器命名

有可能使用单个服务前缀,因此容器名称应使用唯一的字符串进行前缀,以防止名称冲突。我们建议您使用服务类型字段(如服务目录中所用)。例如,Glance 服务将使用“image”作为前缀。