作者:来自 Elastic Sean Story
你的企业很可能淹没在内部数据中。
你拥有问题跟踪、笔记记录、会议记录、维基页面、视频录制、聊天以及即时消息和私信。 并且不要忘记电子邮件!
难怪如此多的企业都在尝试创造工作场所搜索体验 - 为员工提供集中、一站式的内部信息搜索服务。
通过 Elastic 的连接器(connectors)目录,这相对容易做到。 但是,当你将所有数据编入索引并准备好进行搜索后,如何确保其安全? 毕竟,苔丝(来自工程部门)不应该查看鲍勃(来自人力资源部门)关于绩效评估的笔记。 你如何确保访问此统一搜索栏的每个单独用户都只能获得他们有权查看的数据的独特视图?
进入文档级安全性 (document level security - DLS)。
关注 Elasticsearch 一段时间的人可能已经意识到,DLS 已经成为 Elasticsearch 的一项功能相当长一段时间了。 它是用户授权(user authorization)这一更大主题的一部分,而且确实非常简单。 你将元数据嵌入到 Elasticsearch 文档中,然后创建一个 Elasticsearch 查询,根据描述用户授权的文档元数据进行过滤。 该查询用于创建 Elasticsearch 角色。
在查询时,当搜索用户进行身份验证时,他们的角色(如果有)将被识别,并且嵌入的查询过滤器(如果有)将应用于他们的搜索。
让我们看一个简单的例子。 假设我们有两个文档:
PUT example/_doc/1
{
"my-data": true,
"text": "This data is mine"
}
PUT example/_doc/2
{
"my-data": false,
"text": "This data belongs to someone else"
}
仅获取我的数据的查询是:
GET example/_search
{
"query": {
"term": {
"my-data": {
"value": true
}
}
}
}
该查询可以嵌入到角色中,例如:
POST /_security/role/my_role
{
"indices": [
{
"names": [ "example" ],
"privileges": ["read"],
"query": {
"term": {
"my-data": {
"value": true
}
}
}
}
]
}
注意:我们必须为我们的 Elasticsearch 集群配置安全才可以顺利地运行上面的命令。
因此,如果我的用户被分配角色 my_role,如果我这样做:
GET example/_search
我们可以使用如下的命令来进行验证:
curl -k -XGET -u liuxg:password "https://localhost:9200/example/_search" -H "kbn-xsrf: reporting"
从上面的输出中,我们可以看到只有文档 1 被搜索到。文档 2 没有被搜索到。
虽然这个例子在理论上很简单,但它有相对较多的移动部件。
最后一项尤其困难。 当你企业中的人员加入、离开、切换团队或晋升时,这需要进行更改 - 可能会更改你的(元)数据和你的角色。 如果你添加支持共享或访问编辑的数据源,你肯定需要确保你的(元)数据保持最新。
连接器文档级安全性基于 Elasticsearch DLS 构建。 对于许多连接器来说,这包括同步相关元数据和角色描述符以支持 DLS。 这会导致内容索引中的文档自动包含元数据(通常在 _allow_access_control 字段中)来描述有权搜索该文档的人员/组,以及特殊 .search-acl-filter-
你可以在此处找到哪些连接器具有可用的 DLS。 在本博客中,我们将引用一个使用 Sharepoint Online 连接器的示例应用程序。 这是我们启用 DLS 的第一个连接器,但该示例可以轻松适应任何启用 DLS 的连接器。
如果你的连接器符合条件,并且你拥有 Platinum+ Elasticsearch 许可证,则可以通过连接器配置页面上的开关启用 DLS。
从那里开始,只需运行完全同步和访问控制同步,Elasticsearch 将获得所需的所有数据。
然后什么?
一旦 Elasticsearch 拥有角色描述符和文档数据以及这些角色过滤器的足够元数据,你就可以构建安全的搜索体验。
我们已经构建了一个示例知识搜索应用程序,我们将在本博客中使用它,欢迎你查看其源代码。 但是,我们确实想强调,这只是一个示例 - 它尚未准备好在生产中自行运行。 请运用良好的判断力,不要运行你未阅读或不理解的代码。
该应用程序的架构非常简单。
它由 Flask 后端和 React 前端组成。 后端配置了环境变量以与 Elasticsearch 建立连接。
export ELASTICSEARCH_URL=...
export ELASTIC_USERNAME=...
export ELASTIC_PASSWORD=...
使用此连接,后端提供三个端点:
如上所述,该示例不应在生产中使用。 差距包括:
下面我们链接到使用 DLS 实现搜索所需的关键代码片段。
代码链接
identity = elasticsearch_client.get(
index=identities_index, id=persona)
permissions = identity["_source"]["query"]["template"]["params"][
"access_control"
]
role_descriptor = {
"dls-role": {
"cluster": ["all"],
"indices": [
{
"names": [search_app_name],
"privileges": ["read"],
"query": {
"template": {
"params": {"access_control": permissions},
"source": """{
"bool": {
"should": [
{
"bool": {
"must_not": {
"exists": {
"field": "_allow_access_control"
}
}
}
},
{
"terms": {
"_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}
}
}
]
}
}""",
}
},
}
],
"restriction": {"workflows": ["search_application_query"]},
}
}
你可能会注意到,此角色描述符中的查询模板比本博客前面提供的简单示例要复杂得多。 这个查询做了几件事:
代码链接
api_key = elasticsearch_client.security.create_api_key(
name=search_app_name+"-internal-knowledge-search-example-"+persona, expiration="1h", role_descriptors=role_descriptor)
return {"api_key": api_key['encoded']}
代码链接
const apiKey = searchPersonaAPIKey;
const client = SearchApplicationClient(
appName,
searchEndpoint,
apiKey,
{
facets: {
description: {
type: "text",
},
},
},
{
disableCache: true,
}
);
const sortArray = Object.values(sorts).map((sort) => ({
[sort.title]: sort.sortDirection,
}));
const rawResults = await client()
.query(query)
.setSort(sortArray)
.setPageSize(10)
.addParameter("indices", indexFilter)
.search();
const searchResults = rawResults.hits.hits.map((hit: any) => {
return mapHitToSearchResult(hit);
});
原文:Adding Document Level Security (DLS) to your Internal Knowledge Search — Elastic Search Labs