Использование приложений поиска с недоверенными клиентами
При разработке фронтенд-приложения для использования случаев поиска существуют два основных подхода к возврату результатов поиска:
- Клиент (браузер пользователя) отправляет запросы API к бэкенду приложения, который в свою очередь отправляет запрос в Elasticsearch. Кластер Elasticsearch недоступен для конечного пользователя.
- Клиент (браузер пользователя) отправляет запросы API напрямую к службе поиска — в этом случае кластер Elasticsearch доступен для клиента.
Это руководство описывает лучшие практики при использовании второго подхода. В частности, мы объясним, как использовать приложения поиска с фронтенд-приложениями, которые отправляют прямые запросы к API поиска приложения поиска.
Этот подход имеет несколько преимуществ:
- Нет необходимости поддерживать систему передачи запросов между фронтенд-приложениями и Elasticsearch
- Прямые запросы в Elasticsearch приводят к более быстрым откликам
- Настройка запроса управляема в одном месте: в вашей конфигурации приложения поиска в Elasticsearch
Мы рассмотрим:
Использование API-ключей Elasticsearch с ограничениями ролей
Когда фронтенд-приложения могут отправлять прямые запросы API в Elasticsearch, важно ограничить операции, которые они могут выполнять. В нашем случае, фронтенд-приложения должны иметь возможность вызывать только API поиска приложения поиска. Для обеспечения этого мы создадим API-ключи Elasticsearch с ограничениями ролей. Ограничение роли используется для указания условий, при которых роль должна быть эффективной.
Следующий API-ключ Elasticsearch имеет доступ к приложению поиска website-product-search, только через API поиска приложения поиска:
resp = client.security.create_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"
]
}
}
},
)
print(resp) const response = await client.security.createApiKey({
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"],
},
},
},
});
console.log(response); 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"]
}
}
}
} |
| |
|
|
Критически важно указать ограничение рабочего процесса. Без этого API-ключ Elasticsearch может напрямую вызывать _search и отправлять произвольные запросы Elasticsearch. Это небезопасно при работе с недоверенными клиентами.
Ответ будет выглядеть следующим образом:
{
"id": "v1CCJYkBvb5Pg9T-_JgO",
"name": "my-restricted-api-key",
"expiration": 1689156288526,
"api_key": "ztVI-1Q4RjS8qFDxAVet5w",
"encoded": "djFDQ0pZa0J2YjVQZzlULV9KZ086enRWSS0xUTRSalM4cUZEeEFWZXQ1dw"
} Закодированное значение можно затем использовать напрямую в заголовке Authorization. Вот пример использования cURL:
curl -XPOST "http://localhost: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 отсутствует, по умолчанию API-ключи Elasticsearch никогда не истекают. API-ключ может быть аннулирован с помощью API аннулирования API-ключа.
API-ключи Elasticsearch с ограничениями ролей также могут использовать безопасность на уровне полей и документов. Это дополнительно ограничивает то, как фронтенд-приложения обращаются к приложению поиска.
Валидация параметров с приложениями поиска
Ваши приложения поиска используют шаблоны поиска для рендеринга запросов. Параметры шаблона передаются в API поиска приложения поиска. В случае API, используемых фронтенд-приложениями или недоверенными клиентами, нам необходима строгая валидация параметров. Приложения поиска определяют JSON-схему, которая описывает, какие параметры допускаются API поиска приложения поиска.
Следующий пример определяет приложение поиска со строгой валидацией параметров:
resp = client.search_application.put(
name="website-product-search",
search_application={
"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
}
}
},
)
print(resp) const response = await client.searchApplication.put({
name: "website-product-search",
search_application: {
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,
},
},
},
});
console.log(response); 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
}
}
} Используя это определение, API поиска приложения поиска выполняет следующую валидацию параметров:
- Он принимает только параметры
field_name,field_valueиaggs_size -
field_nameограничено только значениями "name", "color" и "description" -
agg_sizeопределяет размер агрегации термов, и он может принимать только значения между1и10
Работа с CORS
Использование этого подхода означает, что браузер вашего пользователя будет отправлять запросы к API Elasticsearch напрямую. Elasticsearch поддерживает Cross-Origin Resource Sharing (CORS), но эта функция по умолчанию отключена. Поэтому браузер заблокирует эти запросы.
Существует два обходных пути для этого:
Включить CORS в Elasticsearch
Это самый простой вариант. Включите CORS в Elasticsearch, добавив следующее в ваш файл elasticsearch.yml:
http.cors.allow-origin: "*" # Only use unrestricted value for local development # 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
В Elastic Cloud вы можете сделать это, изменив настройки пользователя Elasticsearch.
- Из меню развертывания перейдите на страницу Изменить.
- В разделе Elasticsearch выберите Управление настройками пользователей и расширениями.
- Обновите настройки пользователя с помощью вышеприведенной конфигурации.
- Выберите Сохранить изменения.
Проксировать запрос через сервер, поддерживающий CORS
Если вы не можете включить CORS в Elasticsearch, вы можете проксировать запрос через сервер, поддерживающий CORS. Это сложнее, но это жизнеспособный вариант.
Дополнительная информация
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-application-security.html