Прак­тичес­ки все сов­ремен­ные сис­темы исполь­зуют кон­тей­нериза­цию. В боль­шинс­тве слу­чаев в качес­тве плат­формы кон­тей­нериза­ции слу­жит Docker. Для обес­печения информа­цион­ной безопас­ности пос­тепен­но появ­ляют­ся решения клас­са Container Security, но у команд раз­работ­ки (dev) или соп­ровож­дения (ops) не всег­да есть воз­можность их ждать, тес­тировать и встра­ивать. А коман­дам информа­цион­ной безопас­ности (sec) занимать­ся защитой нуж­но уже сей­час.

В такой ситу­ации мож­но начать с монито­рин­га информа­цион­ной безопас­ности про­исхо­дяще­го в кон­тей­нерах, а для это­го понадо­бит­ся собирать события безопас­ности, то есть, про­ще говоря, логи. И здесь нас ждет ряд побоч­ных эффектов, воз­ника­ющих из‑за эфе­мер­ной сути кон­тей­неров Docker. В этой статье погово­рим как раз об этих проб­лемах и вари­антах их решения.

 

Логирование в Docker

 

Уровни ведения журналов

Сна­чала нуж­но понять, как в Docker реали­зова­но жур­налиро­вание событий. Сущес­тву­ет два уров­ня ведения жур­налов:

  • жур­налы демона Docker;
  • жур­налы кон­тей­нер­ных при­ложе­ний.

Де­мон Docker записы­вает свои жур­налы в /var/log/syslog (в дис­три­бути­вах на осно­ве Debian) или /var/log/messages (в дис­трах, сов­мести­мых с Red Hat).

В жур­налах демона мож­но най­ти информа­цию о фун­кци­они­рова­нии Docker на уров­не плат­формы кон­тей­нериза­ции, вклю­чая события жиз­ненно­го цик­ла кон­тей­нера, нас­трой­ки сети, вхо­дящие API-зап­росы и мно­гое дру­гое.

Сю­да же вхо­дят и все жур­налы, которые отно­сят­ся к кон­крет­ному кон­тей­нер­ному при­ложе­нию. По умол­чанию при ведении жур­нала в фор­мате JSON путь к жур­налам кон­тей­нера будет сле­дующим:

/var/lib/docker/containers/<container_id>/<container_id>-json.log

При соз­дании нового кон­тей­нера ему прис­ваивает­ся уни­каль­ный ID. На хос­те, в катало­ге /var/lib/docker/containers, соз­дает­ся дирек­тория с тем же ID в качес­тве наз­вания, а внут­ри — сам файл жур­нала, наз­вание которо­го тоже сос­тоит из пре­фик­са json и ID кон­тей­нера. При уда­лении кон­тей­нера файл жур­нала уда­ляет­ся вмес­те с ним.

Эти осо­бен­ности вылива­ются в нас­тоящие проб­лемы для команд информа­цион­ной безопас­ности, пос­коль­ку меша­ют собирать события. Ведь каж­дый кон­тей­нер по умол­чанию записы­вает события в свой отдель­ный жур­нал, жур­налов получа­ется мно­го, а путь к жур­налу кон­крет­ного кон­тей­нера зависит от его ID, который прис­ваивает­ся при соз­дании кон­тей­нера и сбра­сыва­ется при уда­лении кон­тей­нера, то есть путь к жур­налу может быть раз­ным.

По­мимо ведения жур­нала в фор­мате JSON, Docker под­держи­вает и дру­гие методы монито­рин­га. За каж­дый из методов отве­чает драй­вер ведения жур­нала. Давай пос­мотрим, какими они быва­ют.

 

Драйверы ведения журналов

Драй­веры жур­налов опре­деля­ют, как жур­налы собира­ются, обра­баты­вают­ся и хра­нят­ся для каж­дого кон­тей­нера.

Кон­тей­нер отправ­ляет события на стан­дар­тный поток вывода STDOUT и поток оши­бок STDERR. Далее драй­вер ведения жур­нала счи­тыва­ет эти события, обра­баты­вает их и, нап­ример, если это драй­вер json-file, сох­раня­ет их файл локаль­но на хос­те. Если же исполь­зует­ся дру­гой драй­вер, нап­ример Syslog, дан­ные отправ­ляют­ся в ука­зан­ную точ­ку наз­начения по про­токо­лу TCP/UDP. Схе­ма с при­мера­ми информа­цион­ных потоков — ниже.

Драй­вер ведения жур­нала и его парамет­ры мож­но опре­делить как на уров­не демона Docker в кон­фиге daemon.json (парамет­ры будут рас­простра­нять­ся на все вновь соз­данные кон­тей­неры), так и на уров­не кон­крет­ного кон­тей­нера при его запус­ке, исполь­зовав docker run или ути­литу docker compose.

 

Режимы доставки журналов

Ре­жим дос­тавки жур­налов в Docker опре­деля­ет, как жур­налы переда­ются из работа­ющих кон­тей­неров в драй­вер. Сущес­тву­ет два режима такой дос­тавки: бло­киру­ющий и неб­локиру­ющий.

В бло­киру­ющем режиме при­ложе­ние отправ­ляет события непос­редс­твен­но драй­веру и может пре­рывать работу при­ложе­ния, если дос­тавка не завер­шена. Дру­гими сло­вами, если драй­вер не может спра­вить­ся с объ­емом записы­ваемых событий, то демон бло­киру­ет отправ­ку событий из кон­тей­нера, что может выз­вать задер­жки в работе самого при­ложе­ния. Но этот режим гаран­тиру­ет отправ­ку всех событий без потерь.

Бло­киру­ющий режим вклю­чен по умол­чанию для драй­веров, которые записы­вают события непос­редс­твен­но в жур­нал на локаль­ном хос­те. Нап­ример, json-file и journald.

В неб­локиру­ющем режиме события сна­чала записы­вают­ся в коль­цевой буфер в памяти, из которо­го драй­вер уже счи­тыва­ет события. Раз­мер буфера фик­сирован­ный. Если драй­вер недос­тупен, Docker не будет при­оста­нав­ливать работу при­ложе­ния и про­дол­жит напол­нение буфера. Но если буфер жур­нала будет перепол­нен, это при­ведет к потере событий. Что­бы это­го избе­жать, мож­но уве­личить мак­сималь­ный раз­мер буфера с одно­го мегабай­та (по умол­чанию) до более под­ходяще­го раз­мера с помощью парамет­ра max-buffer-size.

Неб­локиру­ющий режим исполь­зует­ся, ког­да драй­вер дол­жен уста­новить соеди­нение с уда­лен­ным сер­вером для отправ­ки событий (нап­ример, Syslog, Fluentd и тому подоб­ные решения).

Ре­жимы дос­тавки мож­но опре­делить как на уров­не демона Docker, так и на уров­не кон­крет­ного кон­тей­нера.

Ра­зоб­равшись с осно­вами жур­налиро­вания в Docker, давай под­робнее раз­берем проб­лемати­ку сбо­ра событий.

 

Проблематика

 

Сбор событий со всех контейнеров

Ес­ли сто­ит задача соб­рать события со всех кон­тей­неров Docker, то в слу­чае с умол­чатель­ным драй­вером json-file это мож­но сде­лать средс­тва­ми Rsyslog. Это стан­дар­тный метод пересы­лать события в сис­темы клас­са Log management или Security information and event management (LM/SIEM).

При­мер фай­ла с нас­трой­ками:

module(
load="imfile" PollingInterval="10")
input(type="imfile" File="/var/lib/docker/containers/*/*-json.log"
Tag="docker"
Severity="Info"
Facility="local7")
local7.* @<ip-адрес коллектора LM/SIEM-системы>:<порт>

С помощью мас­ки */*-json.log мож­но ука­зать путь к жур­налам всех кон­тей­неров, раз­верну­тых на хос­те. Но в ито­ге на сто­роне LM/SIEM-сис­темы получим общий поток событий, в котором не смо­жем отли­чить, с какого кон­тей­нера получе­но то или иное событие. А для локали­зации ИБ‑инци­ден­та это край­не важ­но, осо­бен­но если кон­тей­неров нес­коль­ко десят­ков или даже сотен.

Ни­же — при­меры двух событий, получен­ных из раз­ных кон­тей­неров, но, с каких имен­но кон­тей­неров они приш­ли, опре­делить слож­но:

<190>Apr 5 09:25:01 srv-01 docker {
"log":"2024-04-05 06:25:01.123 UTC [72]FATAL: password authentication failed for user "admin"\r\n",
"stream":"stdout",
"time":"2024-04-05T06:25:01.124670483Z"
}
<190>Apr 5 09:32:53 srv-01 docker {
"log":"::ffff:10.10.10.1 - - [05/Apr/2024:06:32:53 +0000] "POST /authenticate/login HTTP/1.1" 302 199 "http://10.10.10.170:5051/login?next=%2Fbrowser%2F" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"\n",
"stream":"stdout",
"time":"2024-04-05T06:32:53.985501905Z"
}

Ка­кие тут могут быть вари­анты решения проб­лемы?

Решение 1

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    2 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии