搜索应用程序安全性
“搜索应用”(Search Applications) 是一项 beta(测试版)功能,不建议 Elasticsearch 9.x 的新用户使用。Beta 功能可能会发生更改,且不在正式发布 (GA) 功能的支持 SLA(服务级别协议)范围内。
为搜索用例构建前端应用程序时,返回搜索结果主要有两种方法
- 客户端(用户的浏览器)向应用程序后端发出 API 请求,后端反过来向 Elasticsearch 发出请求。Elasticsearch 集群不会暴露给最终用户。
- 客户端(用户的浏览器)直接向搜索服务发出 API 请求 - 在这种情况下,客户端可以访问 Elasticsearch 集群。
本指南介绍了采用第二种方法时的最佳实践。具体来说,我们解释了如何将搜索应用程序与直接向 Search Application Search API 发出请求的前端应用配合使用。
这种方法有几个优点
- 无需在前端应用程序和 Elasticsearch 之间维护透传查询系统
- 对 Elasticsearch 的直接请求可缩短响应时间
- 查询配置在一个地方进行管理:你在 Elasticsearch 中的搜索应用程序配置
我们涵盖的内容包括
当前端应用程序能够直接向 Elasticsearch 发出 API 请求时,限制它们可以执行的操作非常重要。在我们的例子中,前端应用程序应该只能调用 Search Application Search API。为确保这一点,我们创建带有 角色限制 的 Elasticsearch API 密钥。角色限制用于指定角色在什么条件下生效。
以下 Elasticsearch API 密钥可以访问 website-product-search 搜索应用程序,但只能通过 Search Application Search API 进行访问
POST /_security/api_key
{
"name": "my-restricted-api-key",
"expiration": "7d",
"role_descriptors": {
"my-restricted-role-descriptor": {
"indices": [
{
"names": ["website-product-search"],
"privileges": ["read"]
}
],
"restriction": {
"workflows": ["search_application_query"]
}
}
}
}
indices.name必须是搜索应用程序的名称,而不是底层 Elasticsearch 索引的名称。restriction.workflows必须设置为具体值search_application_query。
指定工作流限制至关重要。如果没有这一点,Elasticsearch API 密钥可以直接调用 _search 并发出任意的 Elasticsearch 查询。在处理不受信任的客户端时,这是不安全的。
响应如下所示
{
"id": "v1CCJYkBvb5Pg9T-_JgO",
"name": "my-restricted-api-key",
"expiration": 1689156288526,
"api_key": "ztVI-1Q4RjS8qFDxAVet5w",
"encoded": "djFDQ0pZa0J2YjVQZzlULV9KZ086enRWSS0xUTRSalM4cUZEeEFWZXQ1dw"
}
然后,可以将编码后的值直接用于 Authorization 标头。以下是使用 cURL 的示例
curl -XPOST "https://:9200/_application/search_application/website-product-search/_search" \
-H "Content-Type: application/json" \
-H "Authorization: ApiKey djFDQ0pZa0J2YjVQZzlULV9KZ086enRWSS0xUTRSalM4cUZEeEFWZXQ1dw" \
-d '{
"params": {
"field_name": "color",
"field_value": "red",
"agg_size": 5
}
}'
如果不存在 expiration,默认情况下 Elasticsearch API 密钥永不过期。可以使用 使 API 密钥失效的 API 使该 API 密钥失效。
具有角色限制的 Elasticsearch API 密钥还可以使用字段和文档级安全性。这进一步限制了前端应用程序查询搜索应用程序的方式。
你的搜索应用程序使用 搜索模板 来渲染查询。模板参数传递给 Search Application Search API。对于前端应用程序或不受信任的客户端使用的 API,我们需要进行严格的参数验证。搜索应用程序定义了一个 JSON 架构(schema),用于描述 Search Application Search API 允许哪些参数。
以下示例定义了一个具有严格参数验证的搜索应用程序
PUT _application/search_application/website-product-search
{
"indices": [
"website-products"
],
"template": {
"script": {
"source": {
"query": {
"term": {
"{{field_name}}": "{{field_value}}"
}
},
"aggs": {
"color_facet": {
"terms": {
"field": "color",
"size": "{{agg_size}}"
}
}
}
},
"params": {
"field_name": "product_name",
"field_value": "hello world",
"agg_size": 5
}
},
"dictionary": {
"properties": {
"field_name": {
"type": "string",
"enum": ["name", "color", "description"]
},
"field_value": {
"type": "string"
},
"agg_size": {
"type": "integer",
"minimum": 1,
"maximum": 10
}
},
"required": [
"field_name"
],
"additionalProperties": false
}
}
}
使用该定义,Search Application Search API 将执行以下参数验证
- 它只接受
field_name、field_value和aggs_size参数 field_name仅限于取值 "name"、"color" 和 "description"agg_size定义了词条聚合的大小,其取值只能在1和10之间
使用这种方法意味着用户的浏览器可以直接向 Elasticsearch API 发出请求。Elasticsearch 支持 跨域资源共享 (CORS),但此功能默认处于禁用状态。因此,浏览器会阻止这些请求。
有两种解决方法
这是最简单的选项。通过将以下内容添加到你的 elasticsearch.yml 文件中,在 Elasticsearch 上启用 CORS
http.cors.allow-origin: "*"
# Use a specific origin value in production, like `http.cors.allow-origin: "https://<my-website-domain.example>"`
http.cors.enabled: true
http.cors.allow-credentials: true
http.cors.allow-methods: OPTIONS, POST
http.cors.allow-headers: X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept
- 仅在本地开发时使用无限制的值
如果无法在 Elasticsearch 上启用 CORS,你可以通过支持 CORS 的服务器代理请求。这比较复杂,但也是一个可行的选项。