Плагин http-scraping
Введение
Плагин http-scraping
позволяет отправлять HTTP-запросы на различные хосты и создавать событие на основе возвращаемого ответа. Если сравнивать этот плагин с функционалом HTTP-коллектора, то можно представить их как подходы pull и push.
"Push"-подход HTTP-коллектора работает следующим образом: источник, у которого есть событие, сам посылает его на коллектор (т.е. событие "проталкивается" на коллектор).
"Pull"-подход плагина http-scraping
же работает иначе: источник, у которого есть событие, отдает его по HTTP. Коллектор с плагином делает запрос на сервер-источник, чтобы это событие получить (т.е. событие "извлекается" коллектором).
http-scraping.md
Помимо создания событий на основе тела ответа данный плагин также может быть полезен, чтобы отслеживать задержку ответа либо следить за состоянием TLS-сертификатов сервера.
Конфигурация
Конфигурация плагина состоит из объекта http-scraping
, в котором имеется два поля:
- targets - список целей для HTTP-запросов. Я бы сравнил их с scrape_config в Prometheus. Их структура будет описана далее.
- include-response-headers - список заголовков ответа, которые будет включены в сформированное событие.
Вложенность в объект http-scraping
требуется для идентификации плагина.
Конфигурация цели включает в себя множество полей. Рассмотрим их:
- headers - заголовки запроса в виде словаря строка-строка.
- query - query-параметры URL, в конфигурации задаются как словарь, где ключом является строка, а значением - либо строка, либо массив строк.
- url - URL запроса.
- body - тело запроса.
- method - метод запроса. По умолчанию равен GET.
- username - имя пользователя для Basic Auth.
- password - пароль пользователя для Basic Auth.
- bearer-token-file - путь к файлу с токеном для авторизации (для авторизации на основе токенов)
- tls - параметры TLS. Схожи с параметрами в конфигурационных файлах сервисов Комрад с единственным исключением: вместо того, чтобы указывать, выключен ли TLS (
disable: false
), здесь указывается, включен ли он (enable: true
). - period - частота запросов в виде
time.Duration
. Задается строкой по типу "2s", "5h" и т.п. - timeout - таймаут для запроса, задается в виде
time.Duration
- deduplication - подход к дедупликации событий. Рассмотрим его позже.
- pagination - блок продвинутой логики (в основном для пагинации).
Пример конфигурации:
http-scraping:
include-response-headers:
- Date
- Expires
targets:
- headers:
Cookie: "session=session_key"
query:
plain_param: param
list_param:
- param1
- param2
url: example.com/api/path
body: '{"hello": "world"}'
method: POST
tls:
enabled: true
TrustedCA: "/path/to/cert"
system-pool: true
period: 15s
timeout: 3m
deduplication: none
pagination:
type: json
transforms:
- set:
target: url.params.page
value: "{{ persistent.page }}"
persistent_values:
page:
value: last_response.body.page
Дедупликация событий
Для дедупликации событий в цели можно использовать параметр deduplication
в конфигурации одной цели. Основной механизм работы дедупликации - плагин запоминает определенный параметр ответа цели, который в дальнейшем сравнивается с аналогичным параметром нового ответа. Если они совпадают - новое событие не создается. Далее перечислены возможные стратегии дедупликации:
Стратегия | Принцип работы |
---|---|
none | Дедупликация отсутствует |
body | События дедуплицируются на основе тела ответа |
response_code | События дедуплицируются на основе кода состояния ответа |
error | События дедуплицируются на основе произошедшей (или нет) ошибки |
Например, мы дедуплицируем события на основе кода состояния ответа. Допустим, у нас следующая последовательность кодов ответа: 200 500 500 200 200. Тогда в результате получим 3 события: 200 500 200, т.к. вторые (и так далее) события убираются.
Запросы с учетом пагинации
Плагин предоставляет несколько мощных инструментов, позволяющих добавлять достаточно сложную логику в создание запросов. Источников вдохновения служил модуль HTTP JSON в filebeat.
Эти инструменты описываются в блоке pagination
конфигурации.
В данный момент есть только два типа пагинации: отсутствующая пагинация - none
(по умолчанию), и пагинация json
на основе JSON.
Среди инструментов па гинации json
:
- модификация запроса на основе шаблонов Liquid перед его отправкой
- модификация запросов для пагинации и их отправка в рамках одной "сессии сбора" (т.е. несколько запросов идут подряд, без интервала)
- сохранение значений на диске
Перед тем, как рассматривать параметры конфигурации, необходимо рассмотреть то, как работает пагинация json
. Для этого используем диаграмму последовательностей:
Цикл запросов:
-
0: (при запуске плагина) Извлечение с диска или создание долговечных переменных, создание шаблона запроса (на базе основной конфигурации http-scraping)
-
1-2: Сброс переменных цикла, применение трансформаций
transforms
к шаблону запроса и переменным цикла. Если произойдет провал применения трансформаций - выход из цикла. -
3-4: Создание запроса на основе шаблона, передача основному http-scraping, выполнение запроса клиентом и создание события на основе ответа. Если на данном этапе произойдет провал запроса либо ответ будет указывать на неудачное исполнение (код статуса ответа - ошибка клиента или сервера), то это приведет к выходу из текущего цикла запросов.
-
5-6: Применение трансформаций
after_response_transforms
для изменения запроса и переменных цикла. Если произойдет провал применения трансформаций - выход из цикла. -
7-8: Применение блока
persistent_values
- перевычисление значений долговечных переменных и их сохранение. -
9-10: Применение трансформаций пагинации
pagination_transforms
. Если произойдет провал либо блокpagination_transforms
пустой - выход из цикла. Иначе - возвращение на шаг 3.
Контекст трансформаций (состояние пагинации)
Блок пагинации сохраняет состояние, которое можно применять в трансформациях. В состоянии (или, иначе, контексте трансформаций) на разных этапах можно встретить следующие элементы:
persistent
- долговечные переменные, сохраняющиеся между циклами запросов и даже запусками коллектора.vars
- переменные текущего цикла запросов, сбрасываются при каждом цикле.last_response.url.value
- полный URL запроса, связанного с последним успешным ответомlast_response.url.params
- отображение (map) для параметров (query) URL запроса. Если говорить точнее, то это Go-типurl.Values
last_response.body
- тело последнего успешного ответа (JSON)last_response.page
- номер "страницы" последнего успешного ответа, т.е. порядковый номер ответа (начиная с нуля) в цикле запросов.last_response.header
- заголовки последнего успешного ответа (в виде отображения (map)).url
- URL запроса в виде Go-ти паurl.URL
.body
- тело запроса (JSON).header
- заголовки запроса.
Все значения (кроме persistent
и vars
) сохраняются на протяжении всего времени работы плагина (т.е. до его выключения или выключения коллектора).
Трансформации
В конфигурации описывается три блока трансформаций - transforms
, after_response_transforms
и pagination_transforms
. То, когда они применяются, описано выше. Обсудим здесь то, как они применяются.
Каждая транформация относится к одному из трех типов:
set
- установить значение.append
- добавить значение к массиву.delete
- удалить значение.
set
Трансформация set
устанавливает значение в некотором элементе (теле, заголовках и т.д.), переопределяя старое, если есть. В конфигурации данной трансформации содержатся следующие поля:
target
- целевое поле, куда устанавливается значение.value
- Liquid шаблон, возвращающий при примении значение.default
- Liquid шаблон, используемый при ошибки применения шаблонаvalue
fail_on_template_error
- вызывать провал при ошибочном применении шаблона, что приводит к выходу из цикла запросов. Если установлено вfalse
, то при провале изменения не применятся.value_type
- тип значения (string
,int
илиjson
).
append
Трансформация append
добавляет значение к массиву (или создает массив из одного элемента, если его не было). Поля:
target
- целевое поле, куда добавляется значение.value
- Liquid шаблон, возвращающий при примении значение.default
- Liquid шаблон, используемый при ошибки применения шаблонаvalue
fail_on_template_error
- вызывать провал при ошибочном применении шаблона, что приводит к выходу из цикла запросов. Если установлено вfalse
, то при провале изменения не применятся.value_type
- тип значения (string
,int
илиjson
).
delete
Трансформация delete
удаляет заданное поле. Параметры:
target
- удаляемое поле.
Шаблоны
В модуле пагинации для шаблонов используется язык Liquid, аналогично шаблонам уведомлений электронной почты. Помимо стандартных фильтров, также определены дополнительные:
Фильтр | Описание | Аргументы | Возвращаемое значение |
---|---|---|---|
toUnixMilliseconds | Конвертирует время (time.Time) в целое число, представляющее количество миллисекунд в формате Unix | time.Time | int64 |
unixMillisecondsToDate | Конвертирует целое число, представляющее количество миллисекунд в формате Unix, во время (time.Time) | int64 | time.Time |
toUTC | Меняет часовой пояс времени на UTC (соответственно корректирует само время, чтобы оно указывало на тот же момент времени) | time.Time | time.Time |
Конфигурация
Конфигурация зависит от типа пагинации, которая определяется полем type
(возможные значения: none
, json
). Поскольку в настоящее время поддерживается только пагинация на основе JSON, дальше будет описание полей для данного типа.
transforms
В этом блоке перечисляются трансформации, применяемые только в начале цикла запросов.
Поля для чтения: last_response.*
(если это не самый первый цикл), persistent.*
, header.*
, url.*
, body.*
.
Поля для записи: header.*
, url.*
, body.*
, vars.*
.
В целом данный блок нужен для модификации запроса или создания переменных (например, на основе долговечных переменных). Поскольку в начале каждого цикла запросов недолговечные переменные обнуляются, читать их смысла нет.
Пример:
transforms:
- set:
target: url.params.since
value: "{% if persistent.since %}{{ persistent.since }}{% endif %}"
after_response_transforms
В этом блоке перечисляются трансформации, применяемые после каждого ответа. Этот блок полезен, чтобы установить значения в переменные и дальше использовать их при установке долговечных переменных.
Поля для чтения: last_response.*
, persistent.*
, vars.*
, header.*
, url.*
, body.*
.
Поля для записи: header.*
, url.*
, body.*
, vars.*
.
Пример:
after_response_transforms:
- append:
target: vars.array
value: "{{ last_response.body.value }}"
persistent_values
В блоке persistent_values
указываются долговечные переменные. Этот блок представлен в виде словаря "имя переменной - конфигурация".
Конфигурация каждой переменной содержи т следующие поля:
value
- Liquid шаблон, возвращающий при примении значение.default
- Liquid шаблон, используемый при ошибки применения шаблонаvalue
ignore_empty_values
- булево значение, определяющее то, менять ли значение переменной, если результатом шаблона является пустое значение (т.е., еслиignore_empty_values: true
, то при возвращении шаблоном пустого значение старое значение переменной не переопределяется).
Пример:
persistent_values:
page:
value: "{{ url.params.page | plus: 1 }}"
ignore_empty_values: true
key:
value: "{{ last_response.body.key }}"
pagination_transforms
Блок pagination_transforms
содержит трансформации, применяющиеся для создания следую щего запроса в цикле (для получения следующей страницы). Этот блок примечателен тем, что с его помощью можно управлять пагинацией и циклом запросов в целом - если любая трансформация в этот блоке вернет ошибку, то текущий цикл завершится.
Поля для чтения: last_response.*
, persistent.*
, vars.*
, header.*
, url.*
, body.*
.
Поля для записи: header.*
, url.*
, body.*
, vars.*
.
Пример:
pagination_transforms:
- set:
target: url.params.page
value: "{% if last_response.body.has_more_pages %}{{ url.params.page | plus: 1}}{% endif %}"
fail_on_template_error: true
Парсинг ответа
Плагин парсит множество полей, связанных с HTTP-соединением. В таблице ниже перечислены извлекаемые поля:
Поле | Значение | Когда добавляется к событию |
---|---|---|
ECS.Destination.Address | Адрес сервера | При создании подключения (обычно TCP) |
ECS.Destination.Domain | Домен сервера | При начале DNS запроса |
ECS.Destination.IP | IP сервера | В конце DNS запроса |
ECS.Event.Duration | Длительность запроса | После получения ответа |
ECS.Event.Start | Время начала запроса | После получения ответа |
ECS.Event.End | Время конца запроса (время получения ответа) | После получения ответа |
ECS.HTTP.ResponseBodyContent | Тело ответа | После получения ответа |
ECS.HTTP.ResponseBodyBytes | Размер тела ответа в байтах | После получения ответа |
ECS.HTTP.Version | Версия HTTP | После получения ответа |
ECS.HTTP.Header.* | Значения заголовков, заданных в поле include-response-headers | После получения ответа |
ECS.TLS.Version | Версия TLS | После получения ответа |
ECS.TLS.Cipher | Алгоритм шифрования TLS | После получения ответа |
ECS.TLS.Resumed | Было ли соединение TLS возобновлено | После получения ответа |
ECS.TLS.Established | Было ли соединение TLS успешно установлено | После получения ответа |
ECS.TLS.ServerIssuer | Издат ель сертификата сервера | После получения ответа либо при неудачном рукопожатии TLS |
ECS.TLS.ServerNotAfter | Время окончания сертификата сервера | После получения ответа либо при неудачном рукопожатии TLS |
ECS.TLS.ServerNotBefore | Время начала действия сертификата сервера | После получения ответа либо при неудачном рукопожатии TLS |
ECS.TLS.ServerSubject | Субъект сертификата сервера | После получения ответа либо при неудачном рукопожатии TLS |
Примеры
Проверка здоровья сервиса
В разработки сервисов есть паттерн "проверки здоровья" (healthcheck), помогающий узнать, работает ли сервис исправно или есть какие-то неполадки. Для этого в HTTP API добавляется дополнительный маршрут (обычно, е го называют /health
), который в нормальной ситуации возвращает код состояния 200 и какую-то информацию (например, о состоянии системы). Если информации нет, можно возвращать просто какой-либо текст (например, текст кода состояния).
Можем использовать плагин http-scraping
для того, чтобы создавать события, когда сервис не здоров. В качестве подопытного используем сам Комрад. Для сбора информации о здоровье используем следующий конфиг:
http-scraping:
targets:
- url: 'https://search-komrad.etecs.ru/api/health'
method: GET
tls:
enabled: true
TrustedCA: "{путь к CA сертификату}"
system-pool: true
period: 30s
timeout: 15s
deduplication: response_code
Чтобы отфильтровывать события с успешным кодом возврата, можем использовать плагин if
:
processors:
- module: deny
cel:
- event.vars("ECS.HTTP.ResponseStatusCode") == 200
Поскольку скорее всего search-komrad работает, то событий мы получить не должны. Чтобы их получить, можем попробовать добавить опечатку. Например, поменять health
на healthx
. Тогда получим одно (т.к. добавлена дедупликация на основе кода статуса) событие с текстом 404 page not found
, что указывает нам на отсутствие страницы и нашу опечатку.
Обнаружение истечения срока действия TLS-сертификата
Плагин http-scraping
можно использовать для обнаружения истекших сертификатов. Для этих целей можно использовать любой URL, например, для проверки здоровья из предыдущего примера.
Для создания сервера с истекшим (или, точнее, близким к истеканию - 1 день до конца) сертификатом я сгенерил следующий набор с помощью openssl
:
- Закрытый ключ корневого сертификата
- Корневой сертификат
- Закрытый ключ сертификата сервера
- Сертификат сервера
Также это можно сделать с помощью komrad-cli
.
Далее я создал простой сервер, который запускается с созданным сертификатом и ключ и обслуживает только один endpoint - /health
.
Чтобы извлекать из этого сервера события, используем следующую конфигурацию:
http-scraping:
targets:
- url: 'https://localhost:33333/health'
method: GET
tls:
enabled: true
TrustedCA: "{путь к сгенерированному корневому сертификату}"
period: 15s
timeout: 5s
После запуска плагина будем получать события, где в поле ECS.TLS.ServerNotAfter
будет указана дата окончания действия сертификата. Можем использовать ее для фильтрации и корреляции. Например, можем использовать плагин deny
для отсечения событий с долгоживущими сертификатами:
processors:
- module: deny
cel:
- event.vars("ECS.TLS.ServerNotAfter") != null && (event.vars("ECS.TLS.ServerNotAfter") - now() > duration("72h"))
- module: assign
assign:
- target: CertIsGoingToBeExpired
cel: "true"
В данном плагине deny отсеиваются все события, оставшийся срок действия сертификата которых превышает три дня. Также с помощью модуля assign
добавляется поле, указывающее на скорое истечение сертификата.
После применения плагинов http-scraping
и deny
мы будем получать только те события от http-scraping
, в которых срок действия сертификата подходит к концу.
Отслеживание высокой задержки HTTP-запросов
С помощью плагина http-scraping
совместно с deny
или assign
можно отслеживать высокую задержку HTTP-ответа от сервера. Для этого в конфигурации необходимо "нацелить" http-scraping
на нужный URL. Например:
http-scraping:
targets:
- url: http://localhost:44444/
method: POST
body: Hello HTTP body!
period: 20s
В данном случае я написал простой сервер, который при запросе на "/" с методом POST засыпает на две секунды, а затем возвращает тело запроса в ответе. Чтобы отследить высокую задержку, можно написать еще один плагин. Я решил использовать assign
, чтобы обогатить событие новым полем:
processors:
- module: assign
assign:
- target: HighLatency
cel: event.vars("ECS.Event.Duration") != null && event.vars("ECS.Event.Duration") > duration("1s")
Этот плагин записывает в поле HighLatency
то, длился ли запрос больше одной секунды.