搜索仅管理员可见的字段¶
https://blueprints.launchpad.net/searchlight/+spec/index-level-role-separation
我们的目标是允许所有字段都可以被搜索和在筛选器中可用,但仅限于对相应用户而言是合适的;因此,我们引入了基于用户是否具有管理员角色来过滤搜索结果的想法。
我们在 Liberty 版本的末尾发现的缺陷描述在 https://bugs.launchpad.net/searchlight/+bug/1504399 中,但简而言之,仅仅从结果中删除字段是不够的。通过对已知字段运行搜索并检查是否返回结果,可以“钓取”字段的值;攻击者可以使用范围或通配符查询来减少定位返回结果值的所需时间。
问题描述¶
我们希望允许插件定义字段(无论是在代码中还是在配置中),这些字段对于非管理员用户不可见,无论是在搜索结果中、在筛选器中可见还是通过搜索字段的值。管理员不应受到这些限制。
在 bug #1504399 的修复之前,Searchlight 满足了这些标准中的前两个。不幸的是,该修复(在非常紧张的时间限制下)阻止了即使是管理员搜索字段。因此,问题在于确保这些条件。
提议的变更¶
基于角色的过滤¶
此解决方案涉及两次索引,并在所有资源中添加一个可用于基于用户角色过滤搜索的字段。例如,以一个高度缩减的 Nova 服务器定义为例
{
"_id": "aaaaabbbb-1111-4444-2222-eeee",
"_type": "OS::Nova::Server",
"_source": {
"status", "ACTIVE",
"OS-EXT-ATTR-SOMETHING": "admin only data"
}
}
将其转换为两个文档,文档内容相同,但:* 仅管理员文档具有额外的字段 ‘user-role’: ‘admin’ * 用户文档具有额外的字段 ‘user-role’: ‘user’ * 用户文档不包含 OS-EXT-ATTR-SOMETHING 字段 * 每个文档的 ID 都添加了角色(111111-4444-2222-eeee:ADMIN)
索引¶
索引操作没有改变,除了需要两个操作(或一个批量操作)。对于非管理员副本,仅管理员字段将从序列化的源文档中删除
{
"_id": "aaaaabbbb-1111-4444-2222-eeee_ADMIN",
"_type": "OS::Nova::Server",
"_source": {
"status", "ACTIVE",
"OS-EXT-ATTR-SOMETHING": "admin only data",
"_searchlight_user_role": "admin"
}
},
{
"_id": "aaaaabbbb-1111-4444-2222-eeee_USER",
"_type": "OS::Nova::Server",
"_source": {
"status", "ACTIVE",
"_searchlight-user-role": "user"
}
}
此解决方案允许不需要管理员/用户分离的资源索引一个包含两种角色的单个文档
{
"_id": "abcdefa-1222",
"_type": "OS::Designate::Zone",
"_source": {
"_searchlight-user-role": ["admin", "user"]
}
}
搜索¶
服务器可以根据请求上下文,对 _searchlight-user-role 应用一个未分析的(term)过滤器
{
"query": {... },
"filter": {"term": {"_searchlight-user-role": "admin"}}
}
过滤器被缓存并且速度非常快。或者,一旦我们切换到使用别名(参见 零停机时间规范 提案),可以在别名上应用过滤器
{
"index": "searchlight-<timestamp>",
"alias": "searchlight-admin",
"filter": {"term": {"_searchlight-user-role": "admin"}}
}
搜索 API 将根据需要查询 searchlight-admin 或 searchlight-user。这有一定的先例;这是一种使数据看起来基于字段进行分段的常见方法(“每个用户一个索引” - 参考),而无需多个 lucene 索引的开销。
第二选择 - 分离索引¶
注意
最初我倾向于这个方案,但增加的维护负担让我转向了基于过滤器的分离。
另一种解决方案是为管理员和非管理员用户维护单独的索引。虽然这看起来从重复的角度来看很糟糕,但在非关系数据库中,根据您想要运行的查询类型存储信息是很常见的。这将对索引速度和数据存储产生影响,但我认为我们存储的数据量和吞吐量使得这种影响可以忽略不计。主要的缺点是增加了维护开销(至少,对于需要它的插件,至少需要两个索引)。
从技术上讲,引入一对索引并不复杂;所有写入操作都变成两个,并且搜索确定在运行之前使用哪个索引。对于用户而言,不会有任何影响(除了管理员将能够再次搜索仅管理员可见的字段)。
索引¶
可以使用动态映射模板来限制 -users 索引中的信息(该模板可以告诉 Elasticsearch 不要存储或索引与 index:no 和 include_in_all:no 匹配的字段)。结合结果过滤(或 _source 过滤或从索引文档中删除这些字段),可以满足所有三个要求。
有些插件没有仅管理员字段,这些插件可以针对相同的索引运行。然而,我认为在这种情况下,有必要使用一个单独的共享索引,因为否则查询可能会同时针对(例如)OS::Nova::Server 在两个索引中运行。例如,下面的结构假定 OS::Something::Else 不需要两个索引,并且所有数据都在用户索引中
searchlight-admin:
OS::Nova::Server
searchlight-user:
OS::Nova::Server
OS::Something::Else
对两种类型进行管理员查询必须针对两个索引运行,从而冒着 OS::Nova::Server 资源的重复结果的风险。这可能需要进一步讨论,但更安全的方法是强制所有类型都存储两次信息,或者
searchlight-admin:
OS::Nova::Server
searchlight-user:
OS::Nova::Server
searchlight-all:
OS::Something::Else
搜索¶
对于用户而言,几乎不会有任何变化。搜索代码将有一些额外的条件语句来确定使用哪个索引。如果索引同时包含管理员和非管理员数据,这将变得复杂。
备选方案¶
我了解还有另外两种替代方案。
Elasticsearch Shield。Shield 为 Elasticsearch 添加了许多功能,所有功能都旨在用于安全性和身份验证。其中一项功能(仅受 Elasticsearch 2.0 支持)是 字段级别访问控制。这要求在每个索引的基础上在配置中提供一个包含字段的列表,并且还需要启用 Shield 的身份验证(有各种可用的插件)。它为受到字段级别限制的用户禁用 _all 字段。
最重要的是,Shield 是一种商业的闭源产品,它在服务器上运行,因此能够做我们无法做的事情(因为它能够访问解析后的查询)。
修改或拒绝传入的查询。我们已经从搜索结果中为非管理员用户删除了某些字段,理论上我们可以以相同的方式限制搜索(或引发未授权异常)。虽然从表面上看这似乎很简单,但实际上很快就会变得复杂。想象一下以下针对 Nova 的受保护字段 hypervisor_id 的查询
{"query": {"term": {"hypervisor_id": "abcd1"}}} {"query": {"query_string": {"query": "hypervisor_id:abcd1"}}} {"query": {"multi_match": {"query": "abcd1", "fields": ["hypervisor_id"]}}} {"query": {"query_string": {"query": "abcd1"}}}
构造过滤器来捕获这些查询并非不可能,但会变得越来越复杂;我们需要为每种插件类型解析查询。