Приёмник логов видит всё, что пишут ваши сервисы: токены в заголовках, строки подключения к базам, email пользователей, тела запросов. Один открытый Elasticsearch на порту 9200 отдаёт злоумышленнику ту же картину инфраструктуры, что и доступ к серверам-источникам. Защита централизованного логирования держится на четырёх слоях: шифрование транспорта между агентами и приёмником, аутентификация источников, ролевая модель доступа к дашбордам и маскирование чувствительных данных на этапе сбора.
Порядок работ имеет значение. Сначала закрываем транспорт и вход, потом разграничиваем доступ к индексам и пространствам, затем вычищаем секреты из самого потока данных. Если сделать наоборот, вы будете настраивать фильтры для потока, который любой может читать по HTTP.
Дальше - конфиги и команды проверки для стека Elasticsearch 8.x, Logstash, Filebeat, Kibana и Fluent Bit. Каждый блок можно копировать как есть, подставляя свои имена хостов, пути и подсети.
Почему незащищённые логи опасны для инфраструктуры
Логи пишут все компоненты: Nginx, приложения, Kubernetes, CI/CD, базы данных. Читают их тоже многие: дежурный инженер, разработчики, подрядчики, аналитики. Централизованный приёмник превращает разрозненные файлы на сотне хостов в одну точку подключения, и цена ошибки в её настройке резко растёт. Типовые топологии конвейера и варианты агентов разобраны в материале про централизованный сбор логов и выбор инструментов, где сравниваются Vector, Fluent Bit, Elasticsearch, OpenSearch и Loki.
Порты приёмников сканируются автоматически. Elasticsearch слушает 9200, Kibana - 5601, beats-вход Logstash - 5044. Инстанс без аутентификации на публичном адресе обнаруживается публичными сканерами, а дальше запросы к /_cat/indices и поиск по полю message дают злоумышленнику всё содержимое журналов без единой эксплойт-уязвимости.
Что злоумышленник может найти в логах
- Учётные данные. JWT и session ID в query string access_log Nginx, пароли в логах отладки приложений, API-ключи в заголовке Authorization.
- Персональные данные. Email, телефоны, адреса, номера документов в телах запросов и ответов, а также в трассировках ошибок валидации.
- Платёжные данные. Обычно маскированный PAN, но при логировании ответов платёжного шлюза в отладке встречается и полный номер карты.
- Внутренние идентификаторы. Строки подключения к БД с паролем, ключи шифрования, токены сервисных аккаунтов, имена бакетов и очередей.
- Карта инфраструктуры. Внутренние IP, имена хостов и namespace, версии библиотек, адреса админских эндпоинтов.
Практический пример. Nginx с форматом лога по умолчанию пишет переменную $request, куда попадает полный URI. Если приложение принимает токен в query string вида ?token=..., он оседает в access_log, уезжает через Filebeat в Elasticsearch, индексируется, попадает в ILM-снапшоты и бэкапы. Через месяц вы уже не удалите его полностью. В Kubernetes та же история с переменными окружения: приложение, которое печатает конфиг при старте, складывает секреты из Secret прямо в stdout пода, а оттуда - в приёмник.
Последствия утечки логов: компрометация, штрафы, потеря репутации
Цепочка развивается быстро. Утечка логов даёт токены и внутренние адреса, токены открывают доступ к сервисам, сервисы отдают данные клиентов, инцидент превращается в публичный. Логи CI/CD добавляют риск для цепочки поставок: deploy-токен из журнала сборки позволяет запушить свой образ в прод.
Регуляторный контур тоже давит. GDPR допускает штрафы до 20 млн евро или 4% мирового годового оборота компании, конкретный размер зависит от характера нарушения и юрисдикции. PCI DSS требует защищать журналы, содержащие данные держателей карт. В ISO 27001 ведение журналов вынесено в отдельную меру контроля (8.15), и аудитор спросит не только о факте сбора, но и о защите записей от изменения и чтения посторонними.
Экономика простая: TLS, RBAC и фильтры маскирования - это дни работы инженера. Разбор утечки с уведомлением регулятора, клиентов и партнёров занимает недели и стоит на порядки дороже.
Шифрование трафика между агентами и приёмником
Шифровать нужно каждый участок, где данные идут по сети: агент - Logstash, Logstash - Elasticsearch, Kibana - Elasticsearch, а также все обращения к API. Проверка простая: если tcpdump на интерфейсе показывает читаемые строки с токенами, шифрования нет. Развернуть сам стек с включёнными механизмами безопасности помогает гайд по развёртыванию ELK 8.x с ILM-политиками и дашбордами.
Настройка TLS для Filebeat и Logstash
Начните с собственного CA и серверного сертификата для Logstash. Для внутреннего контура самоподписанный CA удобнее публичного: вы контролируете выпуск и отзыв.
openssl req -x509 -newkey rsa:4096 -days 1825 -nodes \ -keyout ca.key -out ca.crt -subj "/CN=log-ingest-ca" openssl req -newkey rsa:4096 -nodes \ -keyout logstash.key -out logstash.csr -subj "/CN=logstash.internal" openssl x509 -req -in logstash.csr -CA ca.crt -CAkey ca.key \ -CAcreateserial -out logstash.crt -days 825 -sha256
Вход beats в Logstash включает TLS:
input {
beats {
port => 5044
ssl_enabled => true
ssl_certificate => "/etc/logstash/certs/logstash.crt"
ssl_key => "/etc/logstash/certs/logstash.key"
ssl_certificate_authorities => ["/etc/logstash/certs/ca.crt"]
}
}
На стороне агента проверка сертификата обязательна, режим verification_mode: none оставлять нельзя, он превращает TLS в декорацию:
output.logstash:
hosts: ["logstash.internal:5044"]
ssl:
certificate_authorities: ["/etc/filebeat/certs/ca.crt"]
verification_mode: full
Двусторонняя аутентификация (mTLS) для агентов
Односторонний TLS защищает от прослушивания, но не мешает отправить в приёмник поддельные логи с любого хоста, знающего адрес Logstash. mTLS закрывает эту дыру: без клиентского сертификата соединение не устанавливается.
input {
beats {
port => 5044
ssl_enabled => true
ssl_certificate => "/etc/logstash/certs/logstash.crt"
ssl_key => "/etc/logstash/certs/logstash.key"
ssl_certificate_authorities => ["/etc/logstash/certs/ca.crt"]
ssl_client_authentication => "required"
}
}
Клиентский сертификат выпускается тем же CA и кладётся на хост агента вместе с ключом:
output.logstash:
hosts: ["logstash.internal:5044"]
ssl:
certificate: "/etc/filebeat/certs/filebeat.crt"
key: "/etc/filebeat/certs/filebeat.key"
certificate_authorities: ["/etc/filebeat/certs/ca.crt"]
verification_mode: full
Сертификаты живут ограниченный срок. Держите срок 825 дней и меньше, ведите реестр выпусков и мониторинг даты истечения: openssl x509 -enddate -noout -in /etc/logstash/certs/logstash.crt. Отзыв оформляйте через отдельный CA для агентов, чтобы компрометация одного хоста не требовала перевыпуска всего парка.
Проверка шифрования: команды и инструменты
openssl s_client -connect logstash.internal:5044 -showcerts -CAfile ca.crt curl -v --cacert ca.crt https://es.internal:9200 tcpdump -i any -n -A 'tcp port 5044' -c 20 nmap --script ssl-enum-ciphers -p 5044 logstash.internal
Что считать нормой: handshake проходит с валидацией цепочки, версии TLS ниже 1.2 отклоняются, curl отдаёт 200 или 401 без редиректа на HTTP, в выводе tcpdump нет читаемых токенов, паролей и email, nmap не находит NULL- и EXPORT-шифров. Если openssl s_client без клиентского сертификата устанавливает сессию, значит mTLS на приёмнике не включён.
Аутентификация источников логов
Шифрование отвечает за конфиденциальность канала, аутентификация - за то, кто вообще имеет право в него писать. Для Elasticsearch это API-ключи с узкими правами, для сетевого уровня - белые списки подсетей и сетевые политики Kubernetes.
API-ключи и токены для агентов
Не выдавайте агенту учётную запись суперпользователя. Ключ должен разрешать запись только в свой шаблон индексов:
curl -X POST "https://es.internal:9200/_security/api_key" -u elastic \
-H 'Content-Type: application/json' -d '{
"name": "filebeat-prod-apps",
"role_descriptors": {
"logs_writer": {
"cluster": ["monitor"],
"indices": [{
"names": ["logs-app-*"],
"privileges": ["create_doc", "auto_configure"]
}]
}
}
}'
В Filebeat ключ подставляется из переменной окружения, а не хранится в открытом виде в конфиге:
output.elasticsearch:
hosts: ["https://es.internal:9200"]
api_key: "${ES_LOGS_API_KEY}"
ssl.certificate_authorities: ["/etc/filebeat/certs/ca.crt"]
Ключ живёт в Vault или в Kubernetes Secret, доступ к нему есть только у сервисной учётной записи агента. Ротируйте ключи по расписанию и при уходе сотрудника, который их видел, а старые отзывайте через API инвалидации: GET /_security/api_key покажет список активных ключей и их срок действия.
Ограничение доступа по IP и сети
Второй рубеж - сетевой. Разрешите подключение к beats-порту только подсети агентов:
iptables -A INPUT -p tcp --dport 5044 -s 10.20.0.0/24 -j ACCEPT iptables -A INPUT -p tcp --dport 5044 -j DROP nft add rule inet filter input tcp dport 5044 ip saddr != 10.20.0.0/24 drop ss -tulpn | grep -E '5044|9200|5601' iptables -L INPUT -n --line-numbers
Проверьте, что Elasticsearch и Kibana не смотрят в публичный интернет. Держите http.host и network.host на внутренних адресах, а Kibana прячьте за реверс-прокси с аутентификацией. В Kubernetes добавьте NetworkPolicy, разрешающую трафик на порт приёмника только от подов с нужной меткой. Команда ss покажет, на каких адресах реально слушают сервисы: адрес 0.0.0.0 на 9200 в продакшене означает проблему, которую надо закрыть сегодня.
Ролевая модель доступа к дашбордам и данным
Открытый приёмник - не единственный риск. Даже защищённый ELK опасен, если разработчик видит платежные логи, а подрядчик имеет право удалять индексы. Работает принцип наименьших привилегий: роль собирается под конкретную задачу, а не по должности.
Настройка RBAC в Kibana и Elasticsearch
Роль описывается через API и включает права на индексы, видимость полей и доступ к пространствам Kibana:
curl -X PUT "https://es.internal:9200/_security/role/app_dev_logs" -u elastic \
-H 'Content-Type: application/json' -d '{
"cluster": ["monitor"],
"indices": [{
"names": ["logs-app-*"],
"privileges": ["read", "view_index_metadata"],
"field_security": {
"grant": ["@timestamp", "level", "service", "message", "trace_id"],
"except": ["user.email", "http.request.headers.authorization"]
}
}],
"applications": [{
"application": "kibana-.kibana",
"privileges": ["read"],
"resources": ["space:app-dev"]
}]
}'
Field-level security вырезает поля из выдачи поиска, document-level security ограничивает набор документов запросом. Так команда приложений видит свои индексы без колонки с адресом пользователя, а служба безопасности получает роль с доступом ко всем индексам, но без права удаления и без manage. Проверяйте результат командами GET /_security/role, GET /_security/user и GET /_security/role_mapping.
Изоляция данных с помощью spaces и tenants
Kibana Spaces разделяют рабочие пространства: у команды разработки свое, у безопасников свое, с разными дашбордами и объектами. Роль получает доступ к пространству через resources: space:app-dev, и это отсекает пересечение конфигураций. Grafana решает ту же задачу организациями и папками: права viewer, editor и admin назначаются на папку, а не на весь инстанс. Как устроена такая маршрутизация данных между Loki, Elasticsearch и Grafana в продакшене, разобрано в гайде по маршрутизации логов и метрик.
Аудит доступа и мониторинг действий пользователей
Включите аудит Elasticsearch в elasticsearch.yml: параметр xpack.security.audit.enabled: true и вывод событий в отдельный файл. В журнале появятся события authentication_failed, access_denied и обращение к системным API. Настройте алерты Kibana на серию неудачных входов одного пользователя, на обращение к индексам без роли и на экспорт большого объёма документов. Аудит без алертов бесполезен: файл растёт, инцидент замечают через месяц.
Маскирование и вырезание персональных данных и секретов
Маскирование делается как можно ближе к источнику: данные, которые не ушли с хоста, нечего защищать на приёмнике. Фильтр на стороне Logstash оставляйте вторым рубежом, потому что через него проходят логи от сервисов, которых вы не контролируете. Расширенные примеры фильтров и политик собраны в руководстве по защите данных в логах.
Фильтры Logstash для маскирования
filter {
mutate {
gsub => [
"message", "(?i)password=[^&\s]+", "password=***",
"message", "(?i)(passwd|pwd)=\S+", "credential=***",
"message", "(?i)authorization:\s*bearer\s+\S+", "authorization: bearer ***",
"message", "(\d{4}) \d{4} \d{4} (\d{4})", "\1 **** **** \2",
"message", "[\w.+-]+@[\w-]+\.[\w.]+", "user@example.com"
]
}
if [http][request][headers][authorization] {
mutate { remove_field => ["[http][request][headers][authorization]"] }
}
}
Для сценариев, где regex нужно применять условно, используйте ruby-фильтр или разбор через grok: сначала извлекаете структуру, потом удаляете поле целиком через remove_field. Удаление поля надёжнее замены по regex, потому что не зависит от формата строки. Синтаксис проверяется командой logstash -f /etc/logstash/conf.d/mask.conf --config.test_and_exit.
Процессоры Filebeat и Fluent Bit для очистки логов
Filebeat умеет выкидывать поля и править сообщение до отправки:
processors:
- drop_fields:
fields: ["agent.ephemeral_id", "host.metadata", "kubernetes.labels.chart"]
ignore_missing: true
- script:
lang: javascript
source: |
function process(event) {
var msg = event.Get("message");
if (!msg) return;
event.Put("message", msg
.replace(/password=\S+/gi, "password=***")
.replace(/bearer\s+[A-Za-z0-9._-]+/gi, "bearer ***")
.replace(/[\w.+-]+@[\w-]+\.[\w.]+/g, "user@example.com"));
}
Fluent Bit легче и обычно стоит на edge-узлах и в DaemonSet. Regex-замены в modify нет, поэтому строки с секретами отбрасывают фильтром grep, а лишние поля удаляют modify:
[FILTER]
Name grep
Match *
Exclude message (?i)(password|passwd|api[_-]?key|secret)=\S+
[FILTER]
Name modify
Match *
Remove kubernetes.labels.pod-template-hash
Remove env
Комбинируйте подходы: grep отбрасывает явный мусор целиком, script и gsub вычищают секреты из полезных сообщений. После правки конфигов проверяйте их командами filebeat test config -c filebeat.yml и fluent-bit -c fluent-bit.conf --dry-run.
Тестирование фильтров маскирования
Отлаживайте regex на тестовой строке до продакшена. Прогоните сообщение через пайплайн с stdin-входом и stdout-выходом:
printf 'user=alice password=hunter2 token=eyJhbGciOi\n' | \ /usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/mask-test.conf
В выводе не должно остаться исходных значений. После каждой смены формата логов в приложении пересматривайте фильтры: новый сервис приносит новые имена полей, и старый набор правил перестаёт их покрывать. Держите папку с тестовыми сообщениями в репозитории конфигов, чтобы регресс ловился до выката.
Риски хранения токенов и паролей в логах
Даже кратковременное хранение секрета в логах создаёт долгоживущий риск. Логи копируются в снапшоты, индексируются, выгружаются в аналитику, отправляются в архив. Секрет, попавший в хранилище месяц назад, продолжает жить в бэкапах после того, как вы удалили документ. Какие события вообще стоит писать, а какие нет, разобрано в материале о критериях фильтрации логов.
Как секреты попадают в логи
- Логирование HTTP-заголовков целиком, включая Authorization и Cookie.
- Печать переменных окружения и конфигурации при старте приложения.
- SQL-запросы с параметрами в логах ORM на уровне debug.
- Трассировки исключений с телом запроса, где был пароль или токен.
- Nginx с $args в access_log, если приложение передаёт токен в query string.
- Логи CI/CD, в которых echo печатает секретную переменную, не помеченную как masked.
Обнаружение секретов в существующих логах
grep -REni '(password|passwd|api[_-]?key|bearer|secret)[=:]' /var/log/ | head -50
trufflehog filesystem /var/log --no-update
gitleaks detect --source . --report-format json
detect-secrets scan /etc/ /var/log/
curl -s -u elastic "https://es.internal:9200/logs-*/_search" \
-H 'Content-Type: application/json' \
-d '{"size":1,"query":{"query_string":{"query":"message:password OR message:bearer"}}}'
Проводите такой аудит при подключении нового сервиса и раз в квартал по всем индексам. Локальный поиск по файлам дешевле поиска по Elasticsearch, но проверять нужно оба контура: агент мог отфильтровать строку, а мог и нет.
Что делать, если секрет уже попал в логи
- Отзовите и перевыпустите секрет. Это первое действие, все остальные шаги можно делать после.
- Оцените, какие системы использовали этот секрет и какие данные могли быть доступны.
- Удалите или замаскируйте записи в Elasticsearch и в доступных снапшотах. Помните, что удаление документа не затрагивает ILM-бэкапы и архивы на объектном хранилище.
- Проверьте журнал аудита на обращения к этим индексам и на необычные экспорты.
- Добавьте фильтр, который закрывает сценарий попадания, и тест на него.
- Уведомите службу безопасности, а при персональных данных - ответственного за защиту данных.
Чек-лист базовых мер безопасности для self-hosted инсталляции
Таблица для аудита стенда целиком: проверка, команда и норма. Прогоняйте её перед выводом контура в прод и после каждого крупного обновления.
| Пункт | Как проверить | Что считать нормой |
|---|---|---|
| TLS агент - приёмник | openssl s_client -connect logstash.internal:5044 -CAfile ca.crt | Handshake проходит, CN сертификата совпадает с адресом |
| mTLS для агентов | Тот же запрос без клиентского сертификата | Соединение отклонено приёмником |
| HTTPS на Elasticsearch | curl -sI https://es.internal:9200 | Ответ 200 или 401, без переадресации на HTTP |
| Права API-ключа | GET /_security/api_key | Ключ даёт только create_doc по своему шаблону индексов |
| Сетевой доступ | nft list ruleset, iptables -L INPUT -n | Порт 5044 принимает только подсеть агентов |
| Роли и пользователи | GET /_security/role, GET /_security/user | Нет роли с wildcard * по всем индексам и правами manage |
| Анонимный доступ | curl -s -o /dev/null -w '%{http_code}' http://localhost:9200/_cat/indices | 401, индексы не отдаются |
| Аудит доступа | Просмотр файла аудита Elasticsearch | Есть события authentication_failed и access_denied |
| Фильтры маскирования | logstash -f mask.conf --config.test_and_exit и тестовое сообщение | Исходные значения пароля и токена в выводе отсутствуют |
| Секреты в логах | trufflehog filesystem /var/log --no-update | Совпадений нет |
| Срок сертификатов | openssl x509 -enddate -noout -in logstash.crt | До истечения больше 30 дней, реестр выпусков ведётся |
| Версии компонентов | curl -s https://es.internal:9200 | head | Ветка 8.x, установлен последний патч |
Шифрование и аутентификация
- TLS включён на всех участках: Filebeat - Logstash, Logstash - Elasticsearch, Kibana - Elasticsearch.
- Для агентов настроен mTLS, клиентские сертификаты выданы отдельным CA.
- Ключи и пароли лежат в Vault или Kubernetes Secret, не в конфигах и не в репозитории.
- API-ключи ограничены записью, старые отозваны, ротация запланирована.
Управление доступом и роли
- Роли собраны по командам и индексам, права manage выданы одному администратору.
- Включены field-level и document-level security для полей с персональными данными.
- Kibana Spaces или папки Grafana разделяют рабочие пространства команд.
- Аудит доступа включён, алерты на подозрительные входы настроены.
Маскирование и защита данных
- Фильтры маскирования работают на агентах, Logstash служит вторым рубежом.
- Проведён аудит существующих логов на секреты, найденные значения отозваны.
- Есть тестовые сообщения для проверки фильтров после смены формата логов.
- Определён список запрещённых к логированию полей и он зафиксирован в политике.
Обновления и мониторинг
- Обновления Elasticsearch, Logstash, Kibana и агентов ставятся по графику, патчи безопасности - вне графика.
- Срок действия сертификатов и API-ключей отслеживается, алерт за 30 дней до истечения.
- Есть дашборд по состоянию конвейера: потери событий, отказы входов, рост объёма.
- Резервные копии конфигураций и снапшотов хранятся отдельно от продовой сети.
Заключение
Начните с сетевого контура и аутентификации: закройте 9200, 5601 и 5044 от публичной сети, включите TLS и mTLS, выдайте агентам ключи с правами только на запись. Следующий шаг - роли по командам и аудит доступа, затем фильтры маскирования на агентах и в Logstash. Прогоните чек-лист из таблицы, зафиксируйте дату проверки и повторяйте её раз в квартал: конвейер меняется, вместе с ним меняется и поверхность атаки.