文档级安全

edit

文档级安全(DLS)使您能够根据用户和组的权限限制对Elasticsearch索引中文档的访问。 这样可以确保搜索结果仅返回用户根据其权限授权的信息。

可用性与前提条件

edit

Elastic 连接器中对 DLS 的支持是在版本 8.9.0 中引入的。

此功能处于测试版,可能会发生变化。 设计和代码不如正式发布的功能成熟,并且是按原样提供的,不提供任何保证。 测试版功能不受正式发布功能的SLA支持。

此功能并非适用于所有 Elastic 订阅级别。 请参阅 Elastic CloudElastic Stack 的订阅页面。

使用以下 Elastic 连接器时,默认提供 DLS:

请注意,我们的独立产品(App Search 和 Workplace Search)不使用此功能。 Workplace Search 有自己的权限管理系统。

了解更多

edit

DLS 文档:

DLS 的工作原理

edit

文档级安全(DLS)使您能够在文档级别控制对内容的访问。 索引中的每个文档的访问权限可以独立管理,基于允许查看它的身份(如用户名、电子邮件、组等)。

此功能借助由连接器索引到与标准内容索引相关联的隐藏Elasticsearch索引中的特殊访问控制文档来实现。 如果您的内容文档具有与访问控制文档中定义的标准相匹配的访问控制字段,Elasticsearch将对连接器同步的文档应用DLS。

核心概念

edit

在非常高的层次上,有两个关键组件能够通过连接器实现文档级安全:

  • 访问控制文档:这些文档定义了从您的第三方来源获取的文档的访问控制策略。 它们位于一个隐藏的索引中,命名为以下模式:.search-acl-filter-。 有关更多详细信息和示例,请参阅访问控制文档
  • 包含访问控制字段的内容文档:包含从第三方来源同步内容的文档必须具有与访问控制文档中定义的标准匹配的访问控制字段。 这些文档位于以下模式命名的索引中:search-

    • 如果内容文档没有访问控制字段,则任何人都可以查看它。
    • 如果访问控制字段存在但为,则没有任何身份可以访问,文档将实际上不可见。

      查看内容文档了解更多详情。

启用DLS

edit

要启用DLS,您需要执行以下步骤:

  1. 首先在连接器配置中为您的连接器启用DLS
  2. 运行一次访问控制同步。
  3. 这将创建一个以前缀为.search-acl-filter-的隐藏访问控制索引。例如,如果您将连接器索引命名为search-sharepoint,则访问控制索引将被命名为.search-acl-filter-search-sharepoint
  4. 隐藏索引上的访问控制文档定义了哪些身份可以查看具有访问控制字段的文档。
  5. 访问控制文档使用搜索模板来定义如何根据身份过滤搜索结果。
  6. 安排定期的访问控制同步,以更新隐藏索引中的访问控制文档。

请注意以下关于内容文档和同步的细节:

  1. 请记住,为了使DLS正常工作,您的内容文档必须具有与访问控制文档中定义的标准相匹配的访问控制字段。 内容文档包含您的用户将搜索的实际内容。 如果内容文档没有访问控制字段,则任何人都可以查看它。
  2. 当用户搜索内容时,访问控制文档决定用户可以查看哪些内容。
  3. 搜索时,没有_allow_access_control字段或_allow_access_control.enum中具有允许值的文档将包含在搜索结果中。确定文档是否启用了访问控制的逻辑基于_allow_access_control*字段的存在或值。
  4. 运行内容同步以将您的第三方数据源同步到Elasticsearch。 这些文档中的特定字段(或字段)与访问控制文档中的查询参数相关联,从而启用文档级安全(DLS)。

您必须在运行首次内容同步之前为您的连接器启用DLS。如果您已经运行了内容同步,您需要删除索引上的所有文档,启用DLS,并运行新的内容同步。

索引时的DLS

edit
访问控制文档
edit

这些文档定义了索引到 Elasticsearch 中的数据的访问控制策略。 一个访问控制文档的示例如下:

{
  "_id": "example.user@example.com",
  "identity": {
      "username": "example username",
      "email": "example.user@example.com"
   },
   "query": {
        "template": {
            "params": {
                "access_control": [
                    "example.user@example.com",
                    "example group",
                    "example username"]
            }
        },
        "source": "..."
    }
}

在这个例子中,身份对象指定了此文档所涉及的用户的身份。 query 对象随后使用一个模板列出了构成此身份的访问控制策略的参数。 它还包含查询 source,该查询将指定一个查询以获取身份有权访问的所有内容文档。 _id 可以是,例如,用户的电子邮件地址或用户名。 identity 的确切内容和结构取决于相应的实现。

内容文档
edit

内容文档包含来自您的第三方源的实际数据。 这些文档中的特定字段(或字段)与访问控制文档中的查询参数相关联,从而实现文档级安全(DLS)。 请注意,用于实现DLS的字段名称在不同的连接器之间可能会有所不同。 在下面的示例中,我们将使用字段 _allow_access_control 来指定用户身份的访问控制。

{
  "_id": "some-unique-id",
  "key-1": "value-1",
  "key-2": "value-2",
  "key-3": "value-3",
  "_allow_access_control": [
    "example.user@example.com",
    "example group",
    "example username"
  ]
}
访问控制同步与内容同步
edit

将文档摄取到 Elasticsearch 索引中的过程称为同步。 DLS 使用两种类型的同步进行管理:

  • 内容同步: 将内容导入以 search- 开头的索引中。
  • 访问控制同步: 单独的额外同步,将访问控制文档导入以 .search-acl-filter- 开头的索引中。

在同步过程中,连接器根据文档的类型(内容或访问控制)将其摄取到相关索引中。 访问控制文档决定了内容文档的访问控制策略。

通过利用DLS,您可以确保您的Elasticsearch数据根据访问控制文档中定义的权限安全地提供给正确的用户或组。

搜索时的DLS

edit
身份何时被允许查看内容文档
edit

如果用户的访问控制文档中至少有一个访问控制元素与文档的_allow_access_control字段中的项目匹配,则用户可以查看该文档。

示例
edit

本节说明用户何时可以根据访问控制访问某些文档。

一个访问控制文档:

{
  "_id": "example.user@example.com",
  "identity": {
      "username": "example username",
      "email": "example.user@example.com"
   },
   "query": {
        "template": {
            "params": {
                "access_control": [
                    "example.user@example.com",
                    "example group",
                    "example username"]
            }
        },
        "source": "..."
    }
}

让我们看看以下哪些示例文档可以访问这些权限,以及为什么。

{
  "_id": "some-unique-id-1",
  "_allow_access_control": [
    "example.user@example.com",
    "example group",
    "example username"
  ]
}

用户 example username 将有权访问此文档,因为他属于相应的组,并且他的用户名和电子邮件地址也明确包含在 _allow_access_control 中。

{
  "_id": "some-unique-id-2",
  "_allow_access_control": [
    "example group"
  ]
}

用户 example username 也将有权访问此文档,因为他们属于 example group

{
  "_id": "some-unique-id-3",
  "_allow_access_control": [
    "another.user@example.com"
  ]
}

用户 example username 将无法访问此文档,因为他们的电子邮件与 another.user@example.com 不匹配。

{
  "_id": "some-unique-id-4",
  "_allow_access_control": []
}

没有人能够访问此文档,因为 _allow_access_control 字段为空。

查询多个索引
edit

本节说明如何定义一个Elasticsearch API密钥,该密钥对启用了DLS的多个索引具有受限的读取访问权限。

用户可能拥有多个身份,这些身份定义了他们可以读取哪些文档。 我们可以为每个用户有权访问的索引定义一个带有角色描述符的Elasticsearch API密钥。

示例
edit

让我们假设我们想要创建一个结合以下用户身份的API密钥:

GET .search-acl-filter-source1
{
  "_id": "example.user@example.com",
  "identity": {
      "username": "example username",
      "email": "example.user@example.com"
   },
   "query": {
        "template": {
            "params": {
                "access_control": [
                    "example.user@example.com",
                    "source1-user-group"]
            }
        },
        "source": "..."
    }
}
GET .search-acl-filter-source2
{
  "_id": "example.user@example.com",
  "identity": {
      "username": "example username",
      "email": "example.user@example.com"
   },
   "query": {
        "template": {
            "params": {
                "access_control": [
                    "example.user@example.com",
                    "source2-user-group"]
            }
        },
        "source": "..."
    }
}

.search-acl-filter-source1.search-acl-filter-source2 定义了 source1source2 的访问控制身份。

您可以使用如下API调用来创建一个Elasticsearch API密钥:

POST /_security/api_key
{
  "name": "my-api-key",
  "role_descriptors": {
    "role-source1": {
      "indices": [
        {
          "names": ["source1"],
          "privileges": ["read"],
          "query": {
            "template": {
                "params": {
                    "access_control": [
                        "example.user@example.com",
                        "source1-user-group"]
                }
            },
            "source": "..."
          }
        }
      ]
    },
    "role-source2": {
      "indices": [
        {
          "names": ["source2"],
          "privileges": ["read"],
          "query": {
            "template": {
                "params": {
                    "access_control": [
                        "example.user@example.com",
                        "source2-user-group"]
                }
            },
            "source": "..."
          }
        }
      ]
    }
  }
}
工作流程指南
edit

我们建议依赖连接器访问控制同步来自动化并保持文档与原始内容源的用户权限更改同步。

在创建Elasticsearch API密钥时,考虑设置一个过期时间。当过期时间未设置时,Elasticsearch API将永不过期。

可以使用Invalidate API Key API使API密钥失效。 此外,如果用户的权限发生变化,您需要更新或重新创建Elasticsearch API密钥。

了解更多
edit

在搜索应用程序中利用连接器的文档级安全性

edit

本指南解释了在构建搜索应用程序时,如何确保通过Elastic连接器摄取的文档的文档级安全(DLS)。

在这个示例中我们将:

  • 设置SharePoint Online连接器以从SharePoint Online获取数据
  • 使用SharePoint Online连接器创建的Elasticsearch索引设置一个搜索应用程序
  • 创建带有DLS和工作流限制的Elasticsearch API密钥,以查询您的搜索应用程序
  • 构建一个搜索体验,使经过身份验证的用户可以搜索连接器获取的数据

设置连接器以同步数据并进行访问控制

edit

您可以在 Elastic Cloud(原生)或自托管部署(自托管连接器)中运行 SharePoint Online 连接器。 请参阅 SharePoint Online 连接器 以了解如何设置 SharePoint Online 连接器并启用 DLS。

要运行自托管连接器,除了您的 Elastic 部署之外,您还需要运行 连接器服务。 有关如何设置自托管连接器并运行连接器服务的详细信息,请参阅 自托管连接器

本指南假设您已经有一个满足运行连接器服务前提条件的Elastic部署。 如果您没有Elastic部署,请注册一个免费的Elastic Cloud试用版

我们在这个具体的例子中使用了SharePoint Online连接器。 有关支持DLS的连接器列表,请参阅文档级安全(DLS)

Elasticsearch 索引概览

edit

当SharePoint Online连接器设置完成并开始同步内容时,连接器将创建两个独立的Elasticsearch索引:

  • A content index that holds the searchable data in SharePoint Online. We’ll use this index to create our search application.
  • An access control index that includes access control data for each user that has access to SharePoint Online. It will be named .search-acl-filter-<your index name>, where <your index name> is the index name you chose. For example, an index named search-sharepoint would have the ACL filter index .search-acl-filter-search-sharepoint. We’ll use this index to create Elasticsearch API keys that control access to the content index.

创建一个搜索应用程序

edit

为了为我们的 SharePoint Online 数据构建搜索体验,我们需要创建一个搜索应用程序。

按照以下步骤在 Kibana UI 中创建搜索应用程序:

  1. 导航到 搜索 > 搜索应用程序
  2. 选择 创建
  3. 命名 搜索应用程序。
  4. 选择 SharePoint Online 连接器使用的 索引
  5. 选择 创建

或者,您可以使用Put Search Application API。

创建 Elasticsearch API 密钥

edit

接下来,我们需要创建 Elasticsearch API 密钥,以限制查询仅限于搜索应用程序。 这些限制将确保用户只能查询他们有权访问的文档。 要创建此 API 密钥,我们将利用连接器创建的访问控制索引中的信息。

访问控制索引将包含类似于以下示例的文档:

{
  "_index": ".search-acl-filter-search-sharepoint",
  "_id": "john@example.co",
  "_version": 1,
  "_seq_no": 0,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "identity": {
      "email": "john@example.co",
      "access_control": [
        "john@example.co",
        "Engineering Members"
      ]
    },
    "query": {
      "template": {
        "params": {
          "access_control": [
            "john@example.co",
            "Engineering Members"
            ]
        },
        "source": """
        {
          "bool": {
            "should": [
              {
                "bool": {
                  "must_not": {
                    "exists": {
                      "field": "_allow_access_control"
                    }
                  }
                }
              },
              {
                "terms": {
                  "_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}
                }
              }
            ]
          }
        }
        """
      }
    }
  }
}

本文档包含描述用户john@example.com可以访问哪些文档的Elasticsearch查询。 访问控制信息存储在access_control字段中。 在这种情况下,用户只能访问在_allow_access_control字段中包含"john@example.co""Engineering Members"的文档。

The query field contains the DLS query we will use to create an Elasticsearch API key. That key will ensure queries are restricted to the documents john@example.com has access to.

要创建API密钥,我们将使用创建API密钥 API。 API调用将如下所示:

POST /_security/api_key
{
  "name": "john-api-key",
  "expiration": "1d",
  "role_descriptors": {
    "sharepoint-online-role": {
      "index": [
        {
          "names": [
            "sharepoint-search-application"
          ],
          "privileges": [
            "read"
          ],
          "query": {
            "template": {
              "params": {
                "access_control": [
                  "john@example.co",
                  "Engineering Members"
                  ]
              },
              "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"
        ]
      }
    }
  }
}

响应将会是这样的:

{
  "id": "0rCD3i-MjKsw4g9BpRIBa",
  "name": "john-api-key",
  "expiration": 1687881715555,
  "api_key": "zTxre9L6TcmRIgd2NgLCRg",
  "encoded": "Qk05dy1JZ0JhRDNyNGpLQ3MwUmk6elRzdGU5QjZUY21SSWdkMldnQ1RMZw=="
}

字段 api_key 包含可以用于查询带有适当DLS限制的搜索应用程序的API密钥。

查询多个索引
edit

本节介绍如何生成API密钥以查询包含多个索引的搜索应用程序,这些索引中的文档由具有DLS的连接器引入。

用户可能拥有多个身份,这些身份定义了他们可以读取哪些文档。在这种情况下,我们希望创建一个单一的Elasticsearch API密钥,该密钥只能用于查询该用户有权访问的文档。

让我们假设我们想要创建一个结合以下用户身份的API密钥:

GET .search-acl-filter-source1
{
  "_id": "example.user@example.com",
  "identity": {
      "username": "example username",
      "email": "example.user@example.com"
   },
   "query": {
        "template": {
            "params": {
                "access_control": [
                    "example.user@example.com",
                    "source1-user-group"]
            }
        },
        "source": "..."
    }
}
GET .search-acl-filter-source2
{
  "_id": "example.user@example.com",
  "identity": {
      "username": "example username",
      "email": "example.user@example.com"
   },
   "query": {
        "template": {
            "params": {
                "access_control": [
                    "example.user@example.com",
                    "source2-user-group"]
            }
        },
        "source": "..."
    }
}

.search-acl-filter-source1.search-acl-filter-source2 定义了 source1source2 的访问控制身份。

以下脚本示例了如何生成结合多个用户身份的Elasticsearch API密钥:

require("dotenv").config();
const axios = require("axios");

// Elasticsearch URL and creds retrieved from environment variables
const ELASTICSEARCH_URL = process.env.ELASTICSEARCH_URL;
const ELASTICSEARCH_USER = process.env.ELASTICSEARCH_USER;
const ELASTICSEARCH_PASSWORD = process.env.ELASTICSEARCH_PASSWORD;

const config = {
  auth: {
    username: ELASTICSEARCH_USER,
    password: ELASTICSEARCH_PASSWORD,
  },
  headers: {
    "Content-Type": "application/json",
  },
};

async function createApiKey({
  searchApplication,
  userId,
  indices = "",
  metadata,
  expiration = "1d"
}) {
  try {
    const indices = indices.split(",");

    let combinedQuery = { bool: { should: [] } };

    for (const index of indices) {
      const aclsIndex = `.search-acl-filter-${index}`;
      const response = await axios.get(
        `${ELASTICSEARCH_URL}/${aclsIndex}/_doc/${userId}`,
        config
      );
      combinedQuery.bool.should.push({
        bool: {
          must: [
            {
              term: {
                "_index": index,
              },
            },
            response.data._source.query.source,
          ],
        },
      });
    }

    if (!metadata || Object.keys(metadata).length === 0) {
      metadata = { created_by: "create-api-key" };
    }

    const apiKeyBody = {
      name: userId,
      expiration,
      role_descriptors: {
        [`${searchApplication}-role`]: {
          index: [
            {
              names: [searchApplication],
              privileges: ["read"],
              query: combinedQuery,
            },
          ],
          restriction: {
            workflows: ["search_application_query"],
          },
        },
      },
      metadata,
    };

    const apiKeyResponse = await axios.post(
      `${ELASTICSEARCH_URL}/_security/api_key`,
      apiKeyBody,
      config
    );

    console.log(apiKeyResponse.data);
    return apiKeyResponse.data.encoded;
  } catch (error) {
    console.log(error)
  }
}

// example usage:
createApiKey({
  searchApplication: "my-search-app",
  userId: "example.user@example.com",
  indices: "source1,source2",
  expiration: "1d",
  metadata: {
    application: "my-search-app",
    namespace: "dev",
    foo: "bar",
  },
}).then((encodedKey) => console.log(encodedKey));

该示例将多个身份合并为一个角色描述符。这是因为Elasticsearch API密钥只有在具有单一角色描述符时才能使用角色限制。

在前端应用中的实现

edit

如果您正在构建一个前端应用程序,请使用encoded字段将API密钥传递到前端。 您的应用程序随后可以使用API密钥来查询搜索应用程序。 工作流程大致如下:

  1. 用户登录到您的应用程序。
  2. 您的应用程序使用创建API密钥API生成一个Elasticsearch API密钥。
  3. encoded字段返回给前端应用程序。
  4. 当用户搜索文档时,前端应用程序将encoded字段传递给您的搜索应用程序的_search端点。 例如,您可以使用Search Application客户端使用API密钥进行实际查询:

    const client = SearchApplicationClient(applicationName, endpoint, apiKey, params);

以下是此工作流在序列图中的样子:

DLS API key and search application client workflow

在为查询搜索应用程序创建Elasticsearch API密钥时,您必须包含search_application_query限制。这将确保API密钥只能访问搜索应用程序搜索API。

我们建议在创建 Elasticsearch API 密钥时始终设置一个 expiration 时间。当未设置 expiration 时,Elasticsearch API 将永不过期。

工作流程指导

edit

我们建议依赖连接器访问控制同步来自动化并保持文档与原始内容源的用户权限更改同步。

在这个工作流程中,您需要在应用程序的后端处理Elasticsearch API密钥的生成,以响应浏览器登录。

一旦密钥生成,后端还需要将该密钥返回给客户端(浏览器),以便在后续搜索请求中用于您的搜索应用程序。

可以使用Invalidate API Key API使API密钥失效。 此外,如果用户的权限发生变化,您需要更新或重新创建Elasticsearch API密钥。

下一步

edit

了解如何使用搜索应用程序客户端来查询您的搜索应用程序。 请参阅搜索应用程序客户端

了解更多

edit