Безопасность централизованного логирования: шифрование TLS, доступ и защита от утечек секретов | AdminWiki

Безопасность централизованного логирования: шифрование TLS, доступ и защита от утечек секретов

30 сентября 2026 13 мин. чтения
Содержание статьи

Приёмник логов видит всё, что пишут ваши сервисы: токены в заголовках, строки подключения к базам, 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, но проверять нужно оба контура: агент мог отфильтровать строку, а мог и нет.

Что делать, если секрет уже попал в логи

  1. Отзовите и перевыпустите секрет. Это первое действие, все остальные шаги можно делать после.
  2. Оцените, какие системы использовали этот секрет и какие данные могли быть доступны.
  3. Удалите или замаскируйте записи в Elasticsearch и в доступных снапшотах. Помните, что удаление документа не затрагивает ILM-бэкапы и архивы на объектном хранилище.
  4. Проверьте журнал аудита на обращения к этим индексам и на необычные экспорты.
  5. Добавьте фильтр, который закрывает сценарий попадания, и тест на него.
  6. Уведомите службу безопасности, а при персональных данных - ответственного за защиту данных.

Чек-лист базовых мер безопасности для self-hosted инсталляции

Таблица для аудита стенда целиком: проверка, команда и норма. Прогоняйте её перед выводом контура в прод и после каждого крупного обновления.

ПунктКак проверитьЧто считать нормой
TLS агент - приёмникopenssl s_client -connect logstash.internal:5044 -CAfile ca.crtHandshake проходит, CN сертификата совпадает с адресом
mTLS для агентовТот же запрос без клиентского сертификатаСоединение отклонено приёмником
HTTPS на Elasticsearchcurl -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/indices401, индексы не отдаются
Аудит доступаПросмотр файла аудита 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. Прогоните чек-лист из таблицы, зафиксируйте дату проверки и повторяйте её раз в квартал: конвейер меняется, вместе с ним меняется и поверхность атаки.

Поделиться: