Интеграция мессенджера с DLP-системой InfoWatch Traffic Monitor

В этой статье мы расскажем, как внешние приложения и сервисы могут взаимодействовать с DLP-системой InfoWatch Traffic Monitor через API. API-интерфейс в InfoWatch Traffic Monitor разработан уже давно, но в последний год интерес к нему со стороны сторонних разработчиков значительно вырос. Интеграторы и вендоры обычно хотят интегрировать DLP-системы с нуля в ИТ-ландшафт организации, импортозаместить прежние иностранные системы, расширить количество контролируемых DLP-системой каналов и аккумулировать информацию из нескольких систем в одной. Мы анализируем кросс-продуктовые сценарии и видим, что API является наиболее эффективным и современным средством решения подобных задач. Поэтому мы решили рассказать про наш API-интерфейс и продемонстрировать его работу на практических примерах.

Сценариев интеграции довольно много, поэтому мы рассмотрим самую популярную задачу — отправку событий из корпоративного мессенджера в DLP InfoWatch Traffic Monitor на контентный анализ. Для пущей сложности мы взяли самописный мессенджер Stack chat, а еще решили отправлять в DLP не только переписку, но и пересылаемые корпоративные файлы. Не секрет, что корпоративный мессенджер — это основное оперативное средство коммуникации любой динамичной организации. А благодаря своим интеграциям со средствами телефонии, сетевыми каталогами, CRM и корпоративными порталами, он же — канал утечки конфиденциальной информации.

Далее мы расскажем, как сделать плагин, выписать для него лицензию, выбрать или собрать утилиту для отправки в API. Приведем примеры отправки синтетических событий и кода, а также опишем результат.

Плагин

Для интеграции с DLP InfoWatch Traffic Monitor нужно всего 3 вещи:

  • Плагин. По сути, это просто zip-архив с manifest-файлом и иконками, загружаемый в систему. Он позволяет регистрировать новые неизвестные ранее типы событий. Что регистрировать, от какого источника и с какими атрибутами, вы вольны описать в плагине. InfoWatch предоставляет типовые плагины под разные классы событий, например…
  • Лицензия. По ряду полей совпадает с плагином. Сопоставляет поля и проверяет, может ли DLP загружать данный тип событий в соответствии с лицензированием.
  • Протокол связи. Способ отправки события из ПО в API DLP-системы. В IW API за получение событий отвечает Push API. Он реализован с использованием библиотеки Apache thrift. Thrift — это фреймворк для работы с клиентской и серверной частью. Он формирует и преобразует события под наш API.

Нам необходимо написать и зарегистрировать плагин в InfoWatch Traffic Monitor. Сам плагин несложный, в минимальном виде это json-файл манифеста (utf-8 без BOM) в zip-архиве. В нашем случае это будет манифест и иконка для событий. Для примера иконку позаимствуем из пака иконок FatCow (www.fatcow.com/free-icons). В итоговом виде архив будет содержать:

Для примеров плагина и событий используем данные:

Название компании: MY_COMPANY

Домен: mycompany

Сайт: mycompany.com

Сотрудники:

  1. Николай Смирнов
  • Телефон: +1234567890
  • Логин: smirnov
  1. Валерия Иванова
  • Телефон: +4926666
  • Логин: viva
  • Страничка в телефонной книге компании: phones.mycompany.com/viva
  1. Петр Петров
  • Телефон: +777555

Архив с примерами Push API идет вместе с поставкой InfoWatch Traffic Monitor. В этом архиве есть каталог pushapi_example\pushapi\plugin, в котором лежит json-схема манифеста и архив с примером. Если написать манифест с тестовыми данными выше, то получим:

{
    "PLUGIN_ID": "244A143F356345D8853D430739DF8B6E244A143F",
    "DISPLAY_NAME": "Демонстрационный адаптер",
    "DESCRIPTION": "Демонстрация событий сообщений чата и пересылки файла",
    "VERSION": "1.0.0",
    "IS_SYSTEM": false,
    "VENDOR": "MY_COMPANY",
	"ADDS_SERVICES": {
		"SERVICE_TYPE": [{
			"SERVICE_MNEMO": "im_stack",
			"DATA_CLASS": ["kChat"],
			"ICON": "icon/stack.png",
			"LOCALE": {
				"rus": "Stack чат",
				"eng": "Stack chat"
			}
		}]
	},
	"PATTERN_SEARCH_LICENSE": "{\"operator\": \"and\", \"conditions\": [{\"common_name\": \"MY_COMPANY\"},{\"object_type\": \"im_stack\"}]}",
    "LICENSE": [ ]
}

Лицензирование

Вторым шагом не забудем выписать нужную лицензию. Пример лицензии:

«protocol»: «*» — лучше указывать «*» а не none. Хотя возможны оба варианта, в случае «*» в событиях протокол можно не указывать в атрибутах «}»

Есть еще одна тонкость — в файле лицензии используется именование объектов InfoWatch Traffic Monitor, а в манифесте JSON именование объектов в нотации PushApi.

У разработчиков часто возникает вопрос — как назвать сервис в плагине и почему он отличается от класса?

В InfoWatch исторически сервис — это именованные типы / группы классов, или то, как указывать те типы классов, которые используются. Например, имя сервиса может быть — Мой любимый сервис. В нем можно описать те классы, которые в нем присутствуют.

Классы — это поддерживаемые типы данных. Класс описывает только те форматы событий, которые поддерживает Traffic Monitor.

Варианты:

")" — собственные типы данных DLP из них сделать нельзя

Важно корректно интерпретировать наши сущности. В манифесте есть обязательные корневые поля. Пример:

  "required": ["PLUGIN_ID", "DISPLAY_NAME", "VERSION", "VENDOR", "LICENSE"],

PLUGIN_ID — уникальный идентификатор плагина в InfoWatch Traffic Monitor размерностью от 32х до 40 символов. Поддерживаются цифры 0-9 и буквы A-F

Если лицензия в плагине есть, то указывается:

"LICENSE": [
    {
      "PATH": "license/example.license"
    }
  ]

Для корректного отображения лицензирования плагина в интерфейсе InfoWatch Traffic Monitor необходимо указать в плагине, как искать лицензию:

"LICENSE": [
    {
      "PATH": "license/example.license"
    }
  ]

После загрузки лицензии и плагина в советующий раздел InfoWatch Traffic Monitor, плагин отображает зарегистрированный тип события.

Кроме того, в разделе «События» при создании запросов уже можно указать тип зарегистрированного события. Для нашего примера:

Как протестировать отправку событий с помощью PushAPI util

В полях хорошо зарекомендовала себя уже скомпилированная тестовая thrift-утилита в поставке опциональных DLP-пакетов — PushAPI util. Недавно появился ее Windows-вариант. Утилиту на первом этапе используют многие, с нее можно начинать разработку. Она простая и доступная.

Другие варианты:

  1. Взять готовую скомпилированную thrift-утилиту, например, из наших пакетов QAtools.
  2. Собрать самостоятельно.

Thrift поддерживает C#, C++, Cappuccino, Cocoa, Delphi, Erlang, Go, Haskell, Java, OCaml, Perl, PHP, Python, Ruby, Rust, Smalltalk и JavaScript.

Под  C#, C++, Go, Python у InfoWatch уже есть примеры сборки. Для Python 3 потребуется еще одна библиотека github.com/Thriftpy/thriftpy2

Пример отправки в мессенджер:

pushapi-util \
 --host localhost \
 --token vykmjv5ek15tyueosq42 \
 --id acme \
 --class imchat \
 --service im_acme \
 --sender im_acme:Acme_sender_key \
 --receiver im_acme:Acme_receiver_key \
 --mes im_acme:Acme_sender_key,"Acme sender message for identification" \
 --mes im_acme:Acme_receiver_key,"Acme receiver message for identification2"

Пример передачи файла:

pushapi-util \
 --host localhost \
--token vykmjv5ek15tyueosq42 \
 --id acme \
 --class fileexch \
 --service acme_cloud_storage \
 --evtattr acme_cloud_header:"My extend field" \
 --sender auth:test@host \
 --receiver res:acmecloud.com,res_destination_url:"catalog\catalog\pushapi-util" \
 --data file:/opt/iw/tm5/bin/pushapi-util,destination_file_path:"Send file here"

Здесь мы видим следующие атрибуты:

  • --host — адрес источника событий.
  • --token — автоматически генерируется при установке плагина. Его можно скопировать из веб-консоли DLP (Управление — плагины).
  • --class — класс события, который вы хотите присвоить (в утилите наименования классов также отличны от манифеста pushApi);
  • --service — а это сервис;
  • --evtattr — возможность задавать атрибуты событиям в консоли, по которым можно их искать группировать и применять политики;
  • --sender — адрес отправителя, по которому произойдет идентификация и приклеится карточка сотрудника из AD;
  • --receiver — здесь ресурс и каталог для загрузки файла;
  • --data — файл-вложение, передаваемый в событии.

Как только мы понимаем, какие классы и с какими параметрами нам нужны для разработки, и умеем с ними работать, мы определяемся с деталями и можем дополнить плагин, указав:

  • как назвать сервис;
  • как описать классы, которые в нем присутствуют;
  • как описать manifes-файл, содержащий все это.

Разработка алгоритма для формирования и отправки событий (C#)

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

Если мы заглянем в файл с описанием pushAPI-протокола (pushapi_example\pushapi\pushapi.thrift), в нем будет описание всех необходимых методов протокола и требуемых объектов.

Сам алгоритм вызовов методов протокола pushAPI для отправки события в InfoWatch Тraffic Мonitor можно представить следующими вызовами:

  • VerifyCredentials(Credentials(или компании, Токен авторизации)) — Данный вызов не обязателен, он лишь проверяет, что в InfoWatch Traffic Monitor есть зарегистрированный плагин с нужным Vendor, и существует указанный токен). Никакие лицензии при вызове данного метода InfoWatch Traffic Monitor не проверяет.
  • eventId = BeginEvent(thriftEvent, Credentials(или компании, Токен авторизации)) — Вызов метода сообщает InfoWatch Traffic Monitor о начале передачи события. При этом объект thriftEvent представляет собой заполненное описание события. При вызове данного метода InfoWatch Traffic Monitor проверяет предоставленные учетные данные (Credentials) и наличие необходимой лицензии.

    Если отправляемое событие в InfoWatch Traffic Monitor содержит дополнительные бинарные данные (например, сопутствующая теневая копия), то необходимо вызвать:

    - streamId = BeginStream(eventId, data.Id) — Начать передачу экземпляра данных события. Где eventId — идентификатор события полученного в результате вызова BeginEvent, а data.Id идентификатор объекта данных в thriftEvent. Указание идентификатора объекта данных необходимо, т.к. в событии может содержаться более одного файла теневой копии

    - SendStreamData(eventId, streamId, buffer) — Передать кусок данных события. Данный метод допускает множественные вызовы, до тех пор, пока нужный файл теневой копии не будет полностью передан в InfoWatch Traffic Monitor;

    - EndStream(eventId, streamId) — закончить передачу указанного потока данных события.

  • EndEvent(event_id, abort: false) — Закончить обработку события. Если флажок abort выставлен, то событие не попадает в БД и не обрабатывается дальше.

А для любителей кода, это же — на C#

public async Task<bool> SendEvent(Event thriftEvent, Dictionary<int, byte[]> someData)
{
    long event_id = 0;
    bool abort = true;
    try
    {
        event_id = await _client.BeginEventAsync(thriftEvent, _credentials);
        if (someData != null)
        foreach (var data in someData)
        {
            long stream_id = await _client.BeginStreamAsync(event_id, data.Key);
            using (var dataReader = new MemoryStream(data.Value))
            {
                byte[][] dataBuffer = new byte[2][]
                {
                    new byte[64 * 1024],
                    new byte[64 * 1024]
                };
                int bufferIndex = 0; // 0 или 1
                int realRead;
                Task sendTask = null;
                while ((realRead = dataReader.Read(dataBuffer[bufferIndex], 0, dataBuffer[bufferIndex].Length)) > 0)
                {
                    if (dataBuffer[bufferIndex].Length != realRead)
                    {
                        Array.Resize(ref dataBuffer[bufferIndex], realRead);
                    }
                    if (sendTask != null)
                    {
                        await sendTask;
                    }
                    sendTask = _client.SendStreamDataAsync(event_id, stream_id, dataBuffer[bufferIndex]);
                    bufferIndex = bufferIndex == 0 ? 1 : 0;
                }
                if (sendTask != null)
                {
                    await sendTask;
                }
            }
            await _client.EndStreamAsync(event_id, stream_id);
        }
        abort = false;
    }
    finally
    {
        if (event_id > 0) // как минимум, отработал BeginEvent
        {
            try
            {
                await _client.EndEventAsync(event_id, abort); // Событие обязательно должно быть закрыто
            }
            catch
            {
                // тут подавим для демо
            }
        }
    }
    return !abort;
}

Заранее прошу прощения за костыль с «Dictionary<int, byte[]> someData» в  целях  небольшого упрощения кода.

С отправкой разобрались, а теперь перейдем к заполнению структуры события. Но прежде чем положим пальцы на клавиатуру, для большей визуализации данных представим его в виде json.

А теперь заполним то же самое, но уже программно:

public async Task<bool> SendChatEvent()
{
    Event thriftEvent = new Event
    {
        Evt_class = EventClass.kChat,
        Evt_service = "im_stack",
        Evt_attributes = new List<InfoWatch.Pushapi.Thrift.Attribute>
            {
                new InfoWatch.Pushapi.Thrift.Attribute("capture_ts", DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture)),
                new InfoWatch.Pushapi.Thrift.Attribute("capture_server_ip", HostHelper.LocalIPAddress),
                new InfoWatch.Pushapi.Thrift.Attribute("capture_server_fqdn", HostHelper.HostName),
            },
        Evt_senders = new List<InfoWatch.Pushapi.Thrift.Identity>
            {
                new Identity
                {
                    Identity_id = 1,
                    Identity_type = IdentityItemType.kPerson,
                    Identity_contacts = new List<InfoWatch.Pushapi.Thrift.Attribute>
                        {
                            new InfoWatch.Pushapi.Thrift.Attribute("phone", "+1234567890"),
                            new InfoWatch.Pushapi.Thrift.Attribute("auth", "smirnov@mycompany"),
                        },
                },
                new Identity
                {
                    Identity_id = 2,
                    Identity_type = IdentityItemType.kPerson,
                    Identity_contacts = new List<InfoWatch.Pushapi.Thrift.Attribute>(), // при использовании contacts_with_meta, сам contacts хоть пустым, но должен присутствовать
                    Identity_contacts_with_meta = new List<ContactWithMeta>
                        {
                            new ContactWithMeta()
                            {
                                Contact = new InfoWatch.Pushapi.Thrift.Attribute("phone","+4926666"),
                                Meta = "{ \"display_name\": \"Валерия Иванова\", \"login\": \"viva\", \"url\": \"https://phones.mycompany.com/viva\" }",
                            }
                        },
                    Identity_attributes = null,
                }
            },
        Evt_receivers = new List<InfoWatch.Pushapi.Thrift.Identity>
            {
                new Identity
                {
                    Identity_id = 3, // обратим внимание что пользователь уже есть в отправителях, но тут это новый объект со своим ID
                    Identity_type = IdentityItemType.kPerson,
                    Identity_contacts = new List<InfoWatch.Pushapi.Thrift.Attribute>
                        {
                            new InfoWatch.Pushapi.Thrift.Attribute("phone", "+1234567890"),
                            new InfoWatch.Pushapi.Thrift.Attribute("auth", "smirnov@mycompany"),
                        },
                },
                new Identity
                {
                    Identity_id = 4,
                    Identity_type = IdentityItemType.kPerson,
                    Identity_contacts = new List<InfoWatch.Pushapi.Thrift.Attribute>
                        {
                            new InfoWatch.Pushapi.Thrift.Attribute("phone", "+777555"),
                        },
                }
            },
        Evt_data = new List<InfoWatch.Pushapi.Thrift.EventData>(), // DATA должен присутствовать всегда, даже если в событии только передача сообщений
        Evt_messages = new List<InfoWatch.Pushapi.Thrift.ChatMessage>
            {
                new ChatMessage
                {
                    Mes_data_id = 5,
                    Sender_id = 1,
                    Sent_time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz"),
                    Utf8_text = "Привет. Как дела?"
                },
                new ChatMessage
                {
                    Mes_data_id = 6,
                    Sender_id = 2,
                    Sent_time = DateTime.Now.AddSeconds(1).ToString("yyyy-MM-ddTHH:mm:sszzz"),
                    Utf8_text = "Привет. Спасибо хорошо!"
                },
                new ChatMessage
                {
                    Mes_data_id = 7,
                    Sender_id = 1,
                    Sent_time = DateTime.Now.AddSeconds(2).ToString("yyyy-MM-ddTHH:mm:sszzz"),
                    Utf8_text = "Тогда улыбнись на камеру"
                },
                new ChatMessage
                {
                    Mes_data_id = 8,
                    Sender_id = 2,
                    Sent_time = DateTime.Now.AddSeconds(3).ToString("yyyy-MM-ddTHH:mm:sszzz"),
                    Utf8_text = ":-)"
                },
            },
        Evt_links = null,
        Evt_source = null,
        Evt_destination = null,
    };
    return await SendEvent(thriftEvent, null);
}

После вызова данного метода в InfoWatch Traffic Monitor появится наше событие:

Cразу заполним и сгенерируем  второе событие — передача файла.

public async Task<bool> SendFileEvent()
{
    Event thriftEvent = new Event
    {
        Evt_class = EventClass.kFileExchange,
        Evt_service = "im_stack",
        Evt_attributes = new List<InfoWatch.Pushapi.Thrift.Attribute>
        {
            new InfoWatch.Pushapi.Thrift.Attribute("capture_ts", DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture)),
            new InfoWatch.Pushapi.Thrift.Attribute("capture_server_ip", HostHelper.LocalIPAddress),
            new InfoWatch.Pushapi.Thrift.Attribute("capture_server_fqdn", HostHelper.HostName),
        },
        Evt_senders = new List<InfoWatch.Pushapi.Thrift.Identity>
            {
                new Identity
                {
                    Identity_id = 1,
                    Identity_type = IdentityItemType.kPerson,
                    Identity_contacts = new List<InfoWatch.Pushapi.Thrift.Attribute>(), // при использовании contacts_with_meta, сам contacts хоть пустым, но должен присутствовать
                    Identity_contacts_with_meta = new List<ContactWithMeta>
                        {
                            new ContactWithMeta()
                            {
                                Contact = new InfoWatch.Pushapi.Thrift.Attribute("phone","+4926666"),
                                Meta = "{ \"display_name\": \"Валерия Иванова\", \"login\": \"viva\", \"url\": \"https://phones.mycompany.com/viva\" }",
                            }
                        },
                    Identity_attributes = null,
                }
            },
        Evt_receivers = new List<InfoWatch.Pushapi.Thrift.Identity>
            {
                new Identity
                {
                    Identity_id = 2,
                    Identity_type = IdentityItemType.kPerson,
                    Identity_contacts = new List<InfoWatch.Pushapi.Thrift.Attribute>
                        {
                            new InfoWatch.Pushapi.Thrift.Attribute("phone", "+777555"),
                        },
                }
            },
        Evt_data = new List<InfoWatch.Pushapi.Thrift.EventData>
        {
            new EventData()
            {
                Data_id = 3, // не забываем, что идентификация объектов во всем событии сквозная
                Data_attributes = new List<InfoWatch.Pushapi.Thrift.Attribute>
                    {
                        new InfoWatch.Pushapi.Thrift.Attribute("filename", "somefile.txt")
                    },
            }
        },
        Evt_messages = null,
        Evt_links = null,
        Evt_source = null,
        Evt_destination = null,
    };
    // смоделируем сами данные в памяти, чтобы не возиться с файлами
    Dictionary<int, byte[]> someData = new()
    {
        // идентификатор тот же, что и в событии
        { 3, System.Text.Encoding.Unicode.GetBytes("Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it?") }
    };
    return await SendEvent(thriftEvent, someData);
}

Результаты

А теперь посмотрим на наше событие в интерфейсе InfoWatch Traffic Monitor.

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

Дополнительно стоит отметить, что в тестовом InfoWatch Traffic Monitor, на момент получения событий не содержится каких-либо персон, поэтому контакты не проходят идентификацию и не привязываются к каким-либо пользователям.

С помощью плагина можно зарегистрировать собственные контакты (например, если у пользователей системы заказчика есть какие-либо собственные уникальные идентификаторы). Но в подавляющем большинстве случаев этого не требуется, т.к. в InfoWatch Traffic Monitor есть достаточное количество предустановленных типов:

Немного возвращаясь в начало примера, вспомним, что в плагин мы с вами добавляли иконку. А вот, где она отображаются в событии:

Заключение

В статье мы рассмотрели создание плагина и загрузки валидной лицензии, генерацию синтетических событий в систему с целью проверки, а также написание собственного приложения для отправки событий заданной структуры. На выходе мы получили полноценно структурированное событие с идентификацией и атрибутами для поиска и анализа. Теперь вся переписка в компании под контролем. При этом API позволил сделать универсальное и простое решение, сэкономив ресурсы разработки.

Данный пример является только частью API DLP InfoWatch Traffic Monitor, предназначенной для приема сторонних событий. Про иные задачи и способы их решения в DLP Traffic Monitor расскажем в следующий раз.

Подпишитесь на рассылку INFOWATCH
l.12-.057c.834-.407 1.663-.812 2.53-1.211a42.414 42.414 0 0 1 3.345-1.374c2.478-.867 5.078-1.427 7.788-1.427 2.715 0 5.318.56 7.786 1.427z" transform="translate(-128 -243)"/>