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

编辑

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

编辑

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

在此示例中,我们将

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

设置连接器以同步具有访问控制的数据

编辑

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

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

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

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

Elasticsearch 索引概述

编辑

当 SharePoint Online 连接器设置好并且您已开始同步内容时,该连接器将创建两个单独的 Elasticsearch 索引

  • 一个 内容 索引,其中包含 SharePoint Online 中的可搜索数据。我们将使用此索引来创建我们的搜索应用程序。
  • 一个 访问控制 索引,其中包含有权访问 SharePoint Online 的每个用户的访问控制数据。它将被命名为 .search-acl-filter-<您的索引名称>,其中 <您的索引名称> 是您选择的索引名称。例如,名为 search-sharepoint 的索引将具有 ACL 过滤器索引 .search-acl-filter-search-sharepoint。我们将使用此索引创建控制对内容索引访问的 Elasticsearch API 密钥。

创建搜索应用程序

编辑

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

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

  1. 从主菜单导航到 搜索 > 搜索应用程序,或使用 全局搜索字段
  2. 选择 创建
  3. 命名搜索应用程序。
  4. 选择 SharePoint Online 连接器使用的 索引
  5. 选择 创建

或者,您可以使用 放置搜索应用程序 API。

创建 Elasticsearch API 密钥

编辑

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

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

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

此文档包含 Elasticsearch 查询,该查询描述了用户 [email protected] 有权访问哪些文档。访问控制信息存储在 access_control 字段中。在这种情况下,用户只能访问 _allow_access_control 字段中包含 "[email protected]""Engineering Members" 的文档。

query 字段包含我们将用于创建 Elasticsearch API 密钥的 DLS 查询。该密钥将确保查询仅限于 [email protected] 有权访问的文档。

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

resp = client.security.create_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": [
                                    "[email protected]",
                                    "Engineering Members"
                                ]
                            },
                            "source": "\n              {\n                \"bool\": {\n                  \"should\": [\n                    {\n                      \"bool\": {\n                        \"must_not\": {\n                          \"exists\": {\n                            \"field\": \"_allow_access_control\"\n                          }\n                        }\n                      }\n                    },\n                    {\n                      \"terms\": {\n                        \"_allow_access_control.enum\": {{#toJson}}access_control{{/toJson}}\n                      }\n                    }\n                  ]\n                }\n              }\n              "
                        }
                    }
                }
            ],
            "restriction": {
                "workflows": [
                    "search_application_query"
                ]
            }
        }
    },
)
print(resp)
const response = await client.security.createApiKey({
  name: "john-api-key",
  expiration: "1d",
  role_descriptors: {
    "sharepoint-online-role": {
      index: [
        {
          names: ["sharepoint-search-application"],
          privileges: ["read"],
          query: {
            template: {
              params: {
                access_control: ["[email protected]", "Engineering Members"],
              },
              source:
                '\n              {\n                "bool": {\n                  "should": [\n                    {\n                      "bool": {\n                        "must_not": {\n                          "exists": {\n                            "field": "_allow_access_control"\n                          }\n                        }\n                      }\n                    },\n                    {\n                      "terms": {\n                        "_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}\n                      }\n                    }\n                  ]\n                }\n              }\n              ',
            },
          },
        },
      ],
      restriction: {
        workflows: ["search_application_query"],
      },
    },
  },
});
console.log(response);
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": [
                  "[email protected]",
                  "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 密钥。

查询多个索引
编辑

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

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

假设我们想创建一个 API 密钥,该密钥结合了以下用户身份

GET .search-acl-filter-source1
{
  "_id": "[email protected]",
  "identity": {
      "username": "example username",
      "email": "[email protected]"
   },
   "query": {
        "template": {
            "params": {
                "access_control": [
                    "[email protected]",
                    "source1-user-group"]
            }
        },
        "source": "..."
    }
}
GET .search-acl-filter-source2
{
  "_id": "[email protected]",
  "identity": {
      "username": "example username",
      "email": "[email protected]"
   },
   "query": {
        "template": {
            "params": {
                "access_control": [
                    "[email protected]",
                    "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: "[email protected]",
  indices: "source1,source2",
  expiration: "1d",
  metadata: {
    application: "my-search-app",
    namespace: "dev",
    foo: "bar",
  },
}).then((encodedKey) => console.log(encodedKey));

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

前端应用程序中的实现

编辑

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

  1. 用户登录您的应用程序。
  2. 您的应用程序使用 创建 API 密钥 API 生成 Elasticsearch API 密钥。
  3. encoded 字段返回到前端应用程序。
  4. 当用户搜索文档时,前端应用程序将 encoded 字段传递到您的搜索应用程序的 _search 端点。例如,您可以使用 搜索应用程序客户端使用 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 密钥将永不过期。

工作流指导

编辑

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

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

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

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

后续步骤

编辑

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

了解更多

编辑