映射组合

描述

在身份验证过程中,身份提供者 (IdP) 会向 keystone 提供关于正在进行身份验证的用户的一组用户属性。例如,在 SAML2 流程中,这些信息以 SAML 文档的形式传递给 keystone。

这些属性通常由第三方软件处理,并作为环境变量呈现给 keystone。IdP 的原始文档通常不可用于 keystone。这就是 ShibbolethMellon 实现的工作方式。

本文档中描述的映射格式将这些环境变量映射到本地 keystone 用户。映射还可以定义该用户的组成员资格以及用户可以访问的项目。

IdP 为每个协议指定一个映射。映射本身可以被不同 IdP 和协议组合多次使用。

定义

映射如下所示

{
    "rules": [
        {
            "local": [
                {
                    <user>
                    [<group>]
                    [<project>]
                }
            ],
            "remote": [
                {
                    <match>
                    [<condition>]
                }
            ]
        }
    ]
}
  • mapping: 包含规则列表的 JSON 对象。

  • rules: 映射中的一个属性,包含规则列表。

  • rule: 包含 localremote 属性以定义规则的 JSON 对象。没有显式的 rule 属性。

  • local: 包含有关将映射哪些本地属性的信息的 JSON 对象。映射引擎使用 context(如下定义)处理此信息,结果是从 keystone 的角度来看的用户表示。

    • <user>: 将映射到联合用户的本地用户。

    • <group>: (可选) 联合用户将被放置在其中的本地组。

    • <projects>: (可选) 映射到联合用户的本地项目。

    • <domain>: (可选) 映射到联合用户、项目和组的本地域。项目和组也可以通过定义自己的域来覆盖此默认域。此外,如果此配置中未定义域,则属性映射模式将使用身份提供者 OpenStack 域。

  • remote: 包含有关将映射哪些远程属性的信息的 JSON 对象。

    • <match>: 一个 JSON 对象,它告诉映射引擎哪些联合属性可用于在本地对象中进行替换。在 remote 列表中可以有一个或多个这些对象。

    • <condition>: 一个 JSON 对象,包含允许规则的条件。在 remote 列表中可以有零个或多个这些对象。

  • 直接映射: 映射引擎跟踪每个匹配项,并使其可用于本地规则进行替换。

  • assertion: IdP 提供给 keystone 的数据,用于断言有关正在进行身份验证的用户的事实(名称、组等)。在使用 SAML2 协议时,这是一个 XML 文档。

  • 映射上下文: 表示为键值对的数据,由映射引擎用于将 local 对象转换为 keystone 视角的的用户表示。映射上下文包含 keystone 进程的环境以及在处理 remote 列表时计算出的任何 direct mapping 值。

映射处理方式

通过 IdP 和协议选择映射。然后 keystone 获取映射并按顺序处理每个规则,在第一个匹配的规则之后停止。当所有条件都满足时,规则匹配。

首先,keystone 评估规则的 remote 属性中的每个条件,以查看规则是否匹配。如果匹配,keystone 会将从规则的 remote 属性捕获的每个匹配项的数据保存到有序列表中。我们称这些匹配项为 direct mappings,因为它们可以在下一步中使用。

在找到规则并存储直接映射列表后,keystone 开始处理规则的 local 属性。 local 属性中的每个对象折叠成一个 JSON 对象。例如

{
    "local": [
        {
            "user": {...}
        },
        {
            "projects": [...]
        },
    ]
}

变为

{
    "local": {
        "user": {...}
        "projects": [...]
    },
}

当相同的属性在 local 中出现多次时,第一次出现胜出

{
    "local": [
        {
            "user": {#first#}
        },
        {
            "projects": [...]
        },
        {
            "user": {#second#}
        },
    ]
}

变为

{
    "local": {
        "user": {#first#}
        "projects": [...]
    },
}

我们获取此 JSON 对象,然后递归地处理它,以便应用直接映射。这只是查找模式 {#} 并将其替换为来自直接映射列表的值。直接映射的索引从零开始。

映射规则

映射引擎

在创建联合设置之前,可以测试映射引擎。可以使用 keystone-manage mapping_engine 命令进行测试

$ keystone-manage mapping_engine --rules <file> --input <file>

注意

虽然规则文件格式为 JSON,但断言数据的输入文件格式为单独的键值对行,请参阅 keystone-manage mapping_engine –help 以获取详细信息。

映射条件

映射支持 5 种不同类型的条件

empty: 该规则与包含远程属性类型的任何声明匹配。此条件不需要指定。

any_one_of: 仅当远程属性类型中出现指定的任何字符串时,该规则才匹配。条件结果为布尔值,而不是作为输入传递的参数。

not_any_of: 如果远程属性类型中出现指定的任何字符串,则该规则不匹配。条件结果为布尔值,而不是作为输入传递的参数。

blacklist: 此规则从断言中删除所有匹配的组。它不打算用作防止用户或用户组访问服务提供商的方式。通过黑名单过滤后的输出将是断言中未列在黑名单中的所有组。

whitelist: 此规则明确指定应从断言中携带哪些组。结果是断言中和白名单中的组。

注意

emptyblacklistwhitelist 是唯一可以在直接映射 ({0}, {1} 等) 中使用的条件。

可以组合条件以创建单个规则。

映射示例

以下是所有映射规则类型的示例。

空条件

{
    "rules": [
        {
            "local": [
                {
                    "user": {
                        "name": "{0} {1}",
                        "email": "{2}"
                    },
                    "group": {
                        "name": "{3}",
                        "domain": {
                            "id": "0cd5e9"
                        }
                    }
                }
            ],
            "remote": [
                {
                    "type": "FirstName"
                },
                {
                    "type": "LastName"
                },
                {
                    "type": "Email"
                },
                {
                    "type": "OIDC_GROUPS"
                }
            ]
        }
    ]
}

注意

大括号 {} 中的数字是索引,它们按顺序映射。例如

- Mapping to user with the name matching the value in remote attribute FirstName
- Mapping to user with the name matching the value in remote attribute LastName
- Mapping to user with the email matching value in remote attribute Email
- Mapping to a group(s) with the name matching the value(s) in remote attribute OIDC_GROUPS

注意

如果未在映射中指定用户 ID 和名称,则服务器尝试直接映射 REMOTE_USER 环境变量。如果此变量也不可用,则服务器返回 HTTP 401 Unauthorized 错误。

组可以有多个值。每个值必须用 ; 分隔。示例:OIDC_GROUPS=developers;testers

其他条件

<other_condition> 如下所示中,请提供以下其中之一:any_one_ofnot_any_of

{
    "rules": [
        {
            "local": [
                {
                    "user": {
                        "name": "{0}"
                    },
                    "group": {
                        "id": "0cd5e9"
                    }
                }
            ],
            "remote": [
                {
                    "type": "UserName"
                },
                {
                    "type": "HTTP_OIDC_GROUPIDS",
                    "<other_condition>": [
                        "HTTP_OIDC_EMAIL"
                    ]
                }
            ]
        }
    ]
}

<other_condition> 如下所示中,请提供以下其中之一:blacklistwhitelist

{
    "rules": [
        {
            "local": [
                {
                    "user": {
                        "name": "{0}"
                    }
                },
                {
                    "groups": "{1}",
                    "domain": {
                        "id": "0cd5e9"
                    }
                }
            ],
            "remote": [
                {
                    "type": "UserName"
                },
                {
                    "type": "HTTP_OIDC_GROUPIDS",
                    "<other_condition>": [
                        "me@example.com"
                    ]
                }
            ]
        }
    ]
}

在上面的示例中,可以使用白名单仅将用户映射到其 HTTP_OIDC_GROUPIDS 远程属性中的几个组。

{
    "type": "HTTP_OIDC_GROUPIDS",
    "whitelist": [
        "Developers",
        "OpsTeam"
    ]
}

可以使用黑名单将用户映射到除匹配项之外的所有组

{
    "type": "HTTP_OIDC_GROUPIDS",
    "blacklist": [
        "Finance"
    ]
}

可以在任何条件中使用正则表达式以进行更灵活的匹配

{
    "type": "HTTP_OIDC_GROUPIDS",
    "whitelist": [
        ".*Team$"
    ]
}

在映射到组时,可以在本地部分提供 ID 或名称

{
    "local": [
        {
            "group": {
                "id":"0cd5e9"
            }
        }
    ]
}
{
    "local": [
        {
            "group": {
                "name": "developer_group",
                "domain": {
                    "id": "abc1234"
                }
            }
        }
    ]
}
{
    "local": [
        {
            "group": {
                "name": "developer_group",
                "domain": {
                    "name": "private_cloud"
                }
            }
        }
    ]
}

可以通过将用户的 type 属性设置为 local 并提供本地用户所属的域,将用户映射到 keystone 的身份后端中已存在的本地用户

{
    "local": [
        {
            "user": {
                "name": "local_user",
                "type": "local",
                "domain": {
                    "name": "local_domain"
                }
            }
        }
    ]
}

然后,服务器将尝试从身份后端获取用户详细信息(ID、名称、角色、组)。本地用户和域不会动态生成,因此如果它们不存在于本地身份后端中,身份验证尝试将导致 401 Unauthorized 错误。

如果省略 type 属性或将其设置为 ephemeral 并且未提供域,则用户被视为瞬态用户,并且成为身份提供者的域的成员。它不会在本地 keystone 后端中查找,因此它的所有属性都必须来自 IdP 和映射规则。

注意

Federated 是一个服务域 - 不能列出、显示、添加或删除它。在联合配置之前,无需对其执行任何操作。

输出

如果映射有效,您将收到以下输出

{
    "group_ids": "[<group-ids>]",
    "user":
        {
            "domain":
                {
                    "id": "Federated" or "<local-domain-id>"
                },
            "type": "ephemeral" or "local",
            "name": "<local-user-name>",
            "id": "<local-user-id>"
        },
    "group_names":
        [
            {
                "domain":
                    {
                        "name": "<domain-name>"
                    },
                "name":
                    {
                        "name": "[<groups-names>]"
                    }
            },
            {
                "domain":
                    {
                        "name": "<domain-name>"
                    },
                "name":
                    {
                        "name": "[<groups-names>]"
                    }
            }
        ]
}

如果映射的用户是本地用户,映射引擎将丢弃进一步的组分配并返回为用户配置的角色集。

正则表达式

可以通过指定 regex 键并将其设置为 true,在映射中使用正则表达式。

{
    "rules": [
        {
            "local": [
                {
                    "user": {
                        "name": "{0}"
                    },
                    "group": {
                        "name": "{1}",
                        "domain": {
                            "id": "abc1234"
                        }
                    }
                },
            ],
            "remote": [
                {
                    "type": "UserName"
                },
                {
                    "type": "HTTP_OIDC_GROUPIDS",
                    "any_one_of": [
                        ".*@yeah.com$"
                    ]
                    "regex": true
                },
                {
                    "type": "HTTP_OIDC_GROUPIDS",
                    "whitelist": [
                        "Project.*$"
                    ],
                    "regex": true
                 }
            ]
        }
    ]
}

这允许任何在 HTTP_OIDC_GROUPIDS 中包含键和任何值的声明的用户都被映射到 ID 为 0cd5e9 的组。此外,对于 HTTP_OIDC_GROUPIDS 声明中与字符串 Project.* 匹配的每个值,用户将被分配到该名称的项目。

条件组合

也可以完成映射条件的组合。

emptyany_one_ofnot_any_of 都可以用于同一个规则,但不能在同一个条件内重复。any_one_ofnot_any_of 在条件的范围内是互斥的。同样,whitelistblacklist 也是如此。

{
    "rules": [
        {
            "local": [
                {
                    "user": {
                        "name": "{0}"
                    },
                    "group": {
                        "id": "0cd5e9"
                    }
                },
            ],
            "remote": [
                {
                    "type": "UserName"
                },
                {
                    "type": "cn=IBM_Canada_Lab",
                    "not_any_of": [
                        ".*@naww.com$"
                    ],
                    "regex": true
                },
                {
                    "type": "cn=IBM_USA_Lab",
                    "any_one_of": [
                        ".*@yeah.com$"
                    ]
                    "regex": true
                }
            ]
        }
    ]
}

如前所述,组名称和用户也可以在本地部分提供。

这允许具有以下声明信息的任何用户被映射到 ID 为 0cd5e9 的组。

{"UserName":"<any_name>@yeah.com"}
{"cn=IBM_USA_Lab":"<any_name>@yeah.com"}
{"cn=IBM_Canada_Lab":"<any_name>@yeah.com"}

将映射以下声明

  • 包含键 UserName 的任何声明。

  • 包含键 cn=IBM_Canada_Lab 且不具有值 <any_name>@naww.com 的任何声明。

  • 包含键 cn=IBM_USA_Lab 且具有值 <any_name>@yeah.com 的任何声明。

多个规则

也可以在映射中使用多个规则。

{
    "rules": [
        {
            "local": [
                {
                    "user": {
                        "name": "{0}"
                    },
                    "group": {
                        "name": "non-contractors",
                        "domain": {
                            "id": "abc1234"
                        }
                    }
                }
            ],
            "remote": [
                {
                    "type": "UserName"
                },
                {
                    "type": "orgPersonType",
                    "not_any_of": [
                        "Contractor",
                        "SubContractor"
                    ]
                }
            ]
        },
        {
            "local": [
                {
                    "user": {
                        "name": "{0}"
                    },
                    "group": {
                        "name": "contractors",
                        "domain": {
                            "id": "abc1234"
                        }
                    }
                }
            ],
            "remote": [
                {
                    "type": "UserName"
                },
                {
                    "type": "orgPersonType",
                    "any_one_of": [
                        "Contractor",
                        "SubContractor"
                    ]
                }
            ]
        }
    ]
}

上述内容根据 orgPersonType 值分配组成员资格

  • 既不是 Contractor 也不是 SubContractor 将属于 non-contractors 组。

  • 要么 Contractor 要么 SubContractor 将属于 contractors 组。

规则是累加的,因此只有成功的规则才会授予权限。规则的所有远程条件都必须有效。

在使用多个规则时,您可以指定多个有效的用户标识,但只有第一个匹配项才会被考虑,其余项将从上到下被忽略。

由于规则是累加的,因此可以指定一个用户标识,这也可以工作。多个规则的最佳实践是为用户创建一个规则,为组创建另一个规则。以下是重复的规则示例,但具有全局用户名映射。

{
    "rules": [{
        "local": [{
            "user": {
                "id": "{0}"
            }
        }],
        "remote": [{
            "type": "UserType"
        }]
    },
    {
        "local": [{
            "group": {
                "name": "non-contractors",
                "domain": {
                    "id": "abc1234"
                }
            }
        }],
        "remote": [{
            "type": "orgPersonType",
            "not_any_of": [
                "Contractor",
                "SubContractor"
            ]
        }]
    },
    {
        "local": [{
            "group": {
                "name": "contractors",
                "domain": {
                    "id": "abc1234"
                }
            }
        }],
        "remote": [{
            "type": "orgPersonType",
            "any_one_of": [
                "Contractor",
                "SubContractor"
            ]
        }]
    }]
 }

自动配置

映射引擎具有在联合用户首次进行身份验证时帮助自动配置资源的能力。这可以使用映射引擎可以解析并最终做出决策的特定映射语法来实现。

例如,考虑以下映射

{
    "rules": [
        {
            "local": [
                {
                    "user": {
                        "name": "{0}"
                    }
                },
                {
                    "projects": [
                        {
                            "name": "Production",
                            "roles": [
                                {
                                    "name": "reader"
                                }
                            ]
                        },
                        {
                            "name": "Staging",
                            "roles": [
                                {
                                    "name": "member"
                                }
                            ]
                        },
                        {
                            "name": "Project for {0}",
                            "roles": [
                                {
                                    "name": "admin"
                                }
                            ]
                        }
                    ]
                }
            ],
            "remote": [
                {
                    "type": "UserName"
                }
            ]
        }
    ]
}

remote 部分的语义没有改变。此映射与其他示例的区别在于在 local 规则中添加了一个 projects 部分。projects 列表提供了一个联合用户将获得访问权限的项目列表。如果用户进行身份验证并且映射引擎已将断言中的值映射到 local 规则中,项目将自动创建(如果它们不存在)。

在上面的示例中,经过身份验证的联合用户将在 Production 项目上获得 reader 角色,在 Staging 项目上获得 member 角色,并且在 Project for jsmith 上获得 admin 角色。

重要的是要注意以下约束条件适用于自动配置

  • 项目是唯一将动态创建的资源。

  • 项目将在与身份提供者关联的域或通过属性映射映射的域 (federation_attribute_mapping_schema_version >= 2.0) 中创建。

  • 映射的 projects 部分还必须包含 roles 部分。

    • 部署或域中必须已经存在项目中的角色。

  • 与瞬态组成员资格不同,实际为用户创建分配。

由于创建角色通常需要在部署中的其他服务中进行策略更改,因此预计角色会提前创建。如果 SAML 断言中的属性没有更改,联合身份验证也应被视为幂等的。在上面的示例中,如果用户的名称仍然是 jsmith,那么由于身份验证不会创建任何新项目。

可以在 local 部分混合创建 groupsprojects 映射。上面示例中显示的映射在 local 规则中不包含 groups 部分。这将导致联合用户在 projects 列表中直接拥有项目的角色分配。以下示例包含由 projectsgroups 组成的 local 规则,允许直接的角色分配和组 membership。

{
    "rules": [
        {
            "local": [
                {
                    "user": {
                        "name": "{0}"
                    }
                },
                {
                    "projects": [
                        {
                            "name": "Marketing",
                            "roles": [
                                {
                                    "name": "member"
                                }
                            ]
                        },
                        {
                            "name": "Development project for {0}",
                            "roles": [
                                {
                                    "name": "admin"
                                }
                            ]
                        }
                    ]
                },
                {
                    "group": {
                        "name": "Finance",
                        "domain": {
                            "id": "6fe767"
                        }
                    }
                }
            ],
            "remote": [
                {
                    "type": "UserName"
                }
            ]
        }
    ]
}

在上面的示例中,联合用户将在 Marketing 项目上接收直接的角色分配,以及一个专门针对联合用户名称的项目。除此之外,他们还将加入 Finance 组,并接收该组在项目和域上的所有角色分配。

keystone-to-keystone

keystone-to-keystone 联合身份验证也使用映射,但有一些区别。

一个属性文件(例如,Shibboleth 实现中的 /etc/shibboleth/attribute-map.xml)用于将属性添加到映射 context。属性如下所示

<!-- example 1 from a K2k Shibboleth implementation -->
<Attribute name="openstack_user" id="openstack_user"/>
<Attribute name="openstack_user_domain" id="openstack_user_domain"/>

服务提供商必须包含如下所示的映射。openstack_useropenstack_user_domain 与身份提供商中我们拥有的属性名称匹配。它会将名称为 user1adminopenstack_domain 属性为 default 的任何用户映射到具有 ID abc1234 的组。

{
    "rules": [
        {
            "local": [
                {
                    "group": {
                        "id": "abc1234"
                    }
                }
            ],
            "remote": [
                {
                    "type": "openstack_user",
                    "any_one_of": [
                        "user1",
                        "admin"
                    ]
                },
                {
                    "type":"openstack_user_domain",
                    "any_one_of": [
                        "Default"
                    ]
                }
            ]
        }
    ]
}

keystone 用户的组也可以映射到服务提供商中的组。例如,在 Shibboleth 的属性文件中声明以下属性

<!-- example 2 from a K2k Shibboleth implementation -->
<Attribute name="openstack_user" id="openstack_user"/>
<Attribute name="openstack_groups" id="openstack_groups"/>

然后可以使用以下映射将用户的组 membership 从 keystone IdP 映射到 keystone SP 中的组

{
    "rules": [
        {
            "local":
            [
                {
                    "user":
                        {
                            "name": "{0}"
                        }
                },
                {
                    "groups": "{1}"
                }
            ],
            "remote":
            [
                {
                    "type": "openstack_user"
                },
                {
                    "type": "openstack_groups"
                }
            ]
        }
    ]
}

openstack_useropenstack_groups 将由服务提供商匹配到我们在身份提供商中拥有的属性名称。它将获取 openstack_user 属性并在断言中找到它,然后将其直接插入到映射中。身份提供商将通过组名和用户在 IdP 中所属的域名来设置 openstack_groups 的值。假设用户在 IdP 的 ‘Default’ 域中属于 ‘group1’,那么它将被映射到 SP 中具有相同名称和相同域名的组。

可以在映射中使用的可能属性是 openstack_useropenstack_user_domainopenstack_rolesopenstack_projectopenstack_project_domainopenstack_groups