Keystone 架构

许多设计假设在大多数部署中,认证后端将是现有用户系统前面的封装器。

服务

Keystone 被组织为一组内部服务,通过一个或多个端点暴露。其中许多服务被前端以组合的方式使用。例如,一个认证调用将使用身份服务验证用户/项目凭证,并在成功后,使用令牌服务创建并返回一个令牌。

身份认证

身份服务提供认证凭证验证以及关于 用户 的数据。在基本情况下,这些数据由身份服务管理,使其也能处理与这些数据相关的所有 CRUD 操作。在更复杂的情况下,数据而是由权威后端服务管理。一个例子是当身份服务充当 LDAP 的前端时。在这种情况下,LDAP 服务器是事实来源,身份服务的作用是准确地传递该信息。

用户

用户 代表一个单独的 API 消费者。用户本身必须属于特定的域,因此所有用户名 是全局唯一的,而仅在其域内唯一。

组织

是一个容器,代表一组用户。组本身必须属于特定的域,因此所有组名 是全局唯一的,而仅在其域内唯一。

资源

资源服务提供关于 项目 的数据。

项目

项目 代表 OpenStack 中的基本 所有权 单位,即 OpenStack 中的所有资源都应属于特定的项目。项目本身必须属于特定的域,因此所有项目名称 是全局唯一的,而仅在其域内唯一。如果项目的域未指定,则将其添加到默认域。

领域

是项目、用户和组的高级容器。每个都属于且仅属于一个域。每个域定义一个 API 可见名称属性存在的命名空间。Keystone 提供一个默认域,恰如其分地命名为“Default”。

在 Identity v3 API 中,属性的唯一性如下

  • 域名称。在所有域中全局唯一。

  • 角色名称。在其所属域内唯一。

  • 用户名。在其所属域内唯一。

  • 项目名称。在其所属域内唯一。

  • 组名称。在其所属域内唯一。

由于其容器架构,域可以用作委派 OpenStack 资源管理的一种方式。如果授予了适当的分配,域中的用户仍然可以访问另一个域中的资源。

分配

分配服务提供关于 角色角色分配 的数据。

角色

角色 决定了最终用户可以获得的授权级别。角色可以在域级别或项目级别授予。角色可以分配给单个用户或组。角色名称在其所属域内唯一。

角色分配

一个 3 元组,包含一个 角色、一个 资源 和一个 身份

令牌

令牌服务验证和管理用于在用户的凭证已经过验证后,对请求进行身份验证的令牌。

目录

目录服务提供一个端点注册表,用于端点发现。

应用程序构建

Keystone 是多个服务的 HTTP 前端。自 Rocky 版本以来,Keystone 使用 Flask-RESTful 库为这些服务提供 REST API 接口。

Keystone 在 keystone.server.flask.common 中定义了与 Flask-RESTful 相关的函数。Keystone 创建 API 资源,这些资源继承自类 keystone.server.flask.common.ResourceBase 并暴露支持的 HTTP 方法 GET、PUT、POST、PATCH 和 DELETE 的方法。例如,User 资源如下所示

class UserResource(ks_flask.ResourceBase):
    collection_key = 'users'
    member_key = 'user'
    get_member_from_driver = PROVIDERS.deferred_provider_lookup(
        api='identity_api', method='get_user')

    def get(self, user_id=None):
        """Get a user resource or list users.
        GET/HEAD /v3/users
        GET/HEAD /v3/users/{user_id}
        """
        ...

    def post(self):
        """Create a user.
        POST /v3/users
        """
        ...

class UserChangePasswordResource(ks_flask.ResourceBase):
    @ks_flask.unenforced_api
     def post(self, user_id):
         ...

每个 API 资源的路由由继承自 keystone.server.flask.common.APIBase 的类定义。

class UserAPI(ks_flask.APIBase):
    _name = 'users'
    _import_name = __name__
    resources = [UserResource]
    resource_mapping = [
        ks_flask.construct_resource_map(
            resource=UserChangePasswordResource,
            url='/users/<string:user_id>/password',
            resource_kwargs={},
            rel='user_change_password',
            path_vars={'user_id': json_home.Parameters.USER_ID}
        ),
     ...

keystone.server.flask.common.APIBase 中的 _add_resources()_add_mapped_resources() 方法将资源与 API 绑定。在每个 API 中,加载一个或多个管理器(例如,参见 keystone.catalog.core.Manager),这些是薄封装类,它们根据 keystone 配置加载适当的服务驱动程序。

服务后端

每个服务都可以配置为使用后端,以允许 keystone 适应各种环境和需求。每个服务的后端在 keystone.conf 文件中定义,键为 driver,位于与每个服务关联的组下。

在每个后端存在一个通用类,为任何实现提供抽象基类,标识预期的服务实现。抽象基类存储在服务的 backends 目录中,作为 base.py。服务的相应驱动程序是

如果您为 keystone 的一个服务实现后端驱动程序,则预计您将从这些类继承。

模板化后端

主要设计用于 keystone 项目中的服务目录周围的常见用例,模板化后端是一个目录后端,它只是扩展预配置的模板以提供目录数据。

示例 paste.deploy 配置(使用 $ 代替 % 以避免 ConfigParser 的插值)

[DEFAULT]
catalog.RegionOne.identity.publicURL = https://:$(public_port)s/v3
catalog.RegionOne.identity.adminURL = https://:$(public_port)s/v3
catalog.RegionOne.identity.internalURL = https://:$(public_port)s/v3
catalog.RegionOne.identity.name = 'Identity Service'

数据模型

Keystone 从一开始就被设计成易于适应多种后端风格。因此,许多方法和数据类型会乐于接受比它们知道的更多的数据,并将它们传递给后端。

有几种主要数据类型

  • 用户:具有帐户凭证,与一个或多个项目或域关联

  • :一组用户,与一个或多个项目或域关联

  • 项目:OpenStack 中的所有权单位,包含一个或多个用户

  • :OpenStack 中的所有权单位,包含用户、组和项目

  • 角色:与许多用户-项目对关联的一流元数据。

  • 令牌:用于在用户的凭证已经过验证后对请求进行身份验证的标识凭证。

  • Extras:与用户-项目对关联的键值元数据桶。

  • 规则:描述执行操作的一组要求。

虽然通用数据模型允许用户和组与项目和域之间存在多对多关系;但实际的后端实现会采取不同程度的优势来利用这种功能。

CRUD 方法

虽然预计任何大型公司的“真实”部署都将在其现有用户系统中管理其用户和组,但为了开发和测试的目的,提供了各种 CRUD 操作。

CRUD 被视为核心功能集的一个扩展或附加功能,即后端不需要支持它。预计对于不支持 CRUD 操作的服务,后端将引发 keystone.exception.NotImplemented

授权方法(策略)

系统中的各种组件需要不同的操作允许基于用户是否被授权执行该操作。

对于 keystone 而言,正在检查的授权级别只有几个

  • 要求执行用户被认为是管理员。

  • 要求执行用户与正在引用的用户匹配。

希望使用策略引擎的其他系统需要额外的检查风格,并可能编写完全自定义的后端。默认情况下,keystone 利用在 oslo.policy 中维护的策略执行。

规则

给定要检查的匹配列表,只需验证凭证是否包含这些匹配项。例如

credentials = {'user_id': 'foo', 'is_admin': 1, 'roles': ['nova:netadmin']}

# An admin only call:
policy_api.enforce(('is_admin:1',), credentials)

# An admin or owner call:
policy_api.enforce(('is_admin:1', 'user_id:foo'), credentials)

# A netadmin call:
policy_api.enforce(('roles:nova:netadmin',), credentials)

凭证通常由 Identity API 的“extras”部分中的用户元数据构建。因此,将“角色”添加到用户意味着将角色添加到用户元数据。

能力 RBAC

(尚未实现。)

另一种授权方法是基于操作的,将角色映射到允许该角色执行哪些功能。例如

credentials = {'user_id': 'foo', 'is_admin': 1, 'roles': ['nova:netadmin']}

# add a policy
policy_api.add_policy('action:nova:add_network', ('roles:nova:netadmin',))

policy_api.enforce(('action:nova:add_network',), credentials)

在后端中,这将查找策略“action:nova:add_network”,然后执行实际上是对凭证的“简单匹配”风格的匹配。

身份验证方法

Keystone 提供了几个继承自 keystone.auth.plugins.base 的身份验证插件。以下是可用插件的列表。

在最基本的插件 password 中,需要两部分信息才能使用 keystone 进行身份验证,一部分 资源 信息(项目、域或系统),另一部分 身份 信息。

以以下 POST 数据为例

{
    "auth": {
        "identity": {
            "methods": [
                "password"
            ],
            "password": {
                "user": {
                    "id": "0ca8f6",
                    "password": "secretsecret"
                }
            }
        },
        "scope": {
            "project": {
                "id": "263fd9"
            }
        }
    }
}

用户(ID 为 0ca8f6)正在尝试检索一个作用域为项目(ID 为 263fd9)的令牌。

要使用名称而不是 ID 执行相同的调用,现在我们需要提供有关域的信息。这是因为用户名仅在其给定的域内唯一,但用户 ID 应该在整个部署中唯一。因此,身份验证请求如下所示

{
    "auth": {
        "identity": {
            "methods": [
                "password"
            ],
            "password": {
                "user": {
                    "domain": {
                        "name": "acme"
                    }
                    "name": "userA",
                    "password": "secretsecret"
                }
            }
        },
        "scope": {
            "project": {
                "domain": {
                    "id": "1789d1"
                },
                "name": "project-x"
            }
        }
    }
}

对于用户和项目部分,我们必须提供域 ID 或域名称,才能正确确定正确的用户和项目。

或者,如果我们要将其表示为命令行环境变量,它将是

$ export OS_PROJECT_DOMAIN_ID=1789d1
$ export OS_USER_DOMAIN_NAME=acme
$ export OS_USERNAME=userA
$ export OS_PASSWORD=secretsecret
$ export OS_PROJECT_NAME=project-x

请注意,用户尝试访问的项目必须与用户位于同一域中。

什么是作用域?

作用域是一个含义过载的术语。

参考上述身份验证,作用域指的是 POST 数据中决定用户想要访问哪个 资源(项目、域或系统)的部分。

参考令牌,作用域指的是令牌的有效性,即:项目作用域 令牌仅对最初授予它的项目有用。 域作用域 令牌可用于执行与域相关的函数。 系统作用域 令牌仅对与整个部署交互的 API 有用。

参考用户、组和项目,作用域通常指的是实体所属的域。例如:域 X 中的用户作用域为域 X。