Русский
Русский
English
Статистика
Реклама

Блог компании cross technologies

Маркирование данных как задача каждого сотрудника

12.01.2021 10:14:24 | Автор: admin

Данных становится все больше. По исследованиям International Data Corporation (международная исследовательская компания) прирост объема хранимой в электронном виде информации составляет порядка 40% в год. При этом отсутствует определенность относительно инструментов обеспечения безопасности, применяемых методик и способов защиты данных.

Откуда появилась задача классификации

Как правило, крупная организация не знает точно, где и какие данные у нее находятся и как их защищать. Проблема состоит в том, что до 90% данных в компаниях находятся неструктурированном виде. Это могут быть документы, всевозможные отчеты, презентации и т.д., располагающиеся в сетевых папках или же на локальных машинах пользователей. И, поскольку обеспечением этой самой информационной безопасности компании занимаются сотрудники службы безопасности, то им и выбирать инструменты. Часто используется какая-либо система DLP Data Leak Prevention, предотвращение утечек информации мониторинг защищенного контура на предмет сообщений, перемещения файлов на внешние носители или веб-ресурсы, и т.д. Таким способом защищается корпоративная информация в виде документов, предотвращается передача архивов персональных данных, программных исходных кодов, конструкторской документации и пр. Таким образом, для минимизации рисков перед офицерами ИБ возникают задачи, которые, как правило, включают в себя следующие пункты:

  1. Классификация информации в компании. Это нужно для понимания, какие данные имеются в компании и какие из них следует защищать.

  2. Разграничение доступа к информации. Необходимо знать, кто и с помощью чего создает данные и где они располагаются.

  3. Актуализация угроз и построение списка каналов утечки информации. Следует понимать современные каналы утечки информации, с тем, чтобы своевременно противостоять им.

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

Не знаешь, какая информация у тебя имеется не знаешь, как ее достаточно, но не избыточно защитить.

Как классифицировать данные

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

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

Далее рассматривается техническое средство: Docs Security Suite (DSS) это платформа маркирования и классификации электронных документов.

Классификация как обязанность каждого сотрудника

Рассмотрим первый подход сотрудник классифицирует документы сам. Стоит сюда добавить технические средства, которые при каждом создании/изменении документа, копировании его или печати будут требовать присвоения ему определенной категории, и задача классификации информации упрощается. Выбранная метка должна прописываться визуально в документ, а также в структуру файла.

Таким образом достигается следующее:

- Обязательное принудительное маркирование документа автором или редактором (владельцем информации);

- Учет техническими средствами электронных документов в организации;

- Экспертная классификация электронных документов на этапе создания документа;

- Разграничение доступа пользователей к документам с различными метками конфиденциальности;

- Учет и регистрация действий пользователей с документами, содержащими метки;

- Отслеживание путей развития документов, формирование дерева родительских и дочерних электронных документов;

- Вовлечение сотрудников организации в процесс информационной безопасности, как результат повышение культуры информационной безопасности в компании.

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

Что такое метка

Метка проявляет себя в визуальном виде в документе. То есть, если документ является конфиденциальным, соответствующая надпись располагается, например, на подложке текста. Располагается она, как уже упоминалось, автоматически, и удалить ее пользователю нельзя. Соответственно, каждая метка должна централизованно настраиваться, позволяя указать шрифт, цвет, размер, расположение на странице и т.д. Кстати, визуальной меткой может быть и изображение, и штрихкод.

Однако, помимо этого, в электронном документе должны сохраняться и другие данные. Если ставить задачу интеграции нашего инструмента с DLP, то визуального изображения метки будет явно недостаточно для этого. Для этого создается электронная метка дополнительные данные, которые сохраняются где-то в структуре файла документа, например в метаданных. Туда следует писать следующую информацию:

- Описание метки. Основная информация в рамках нашей системы, как раз он служит для интеграции с DLP, для контроля доступа и т.д.

- Идентификатор документа. Мы же ведем учет документов, значит каждому из них должен быть присвоен идентификатор.

- Идентификатор родительского документа. Скопировав или Сохранив как... файл мы получаем новый экземпляр, значит стоит знать его происхождение.

- Дата, время последнего открытия.

- Автор документа - идентификатор пользователя или его логин.

- Последний редактор документа - идентификатор или логин. Последние два пункта говорят о том, кто несет ответственность за присвоение той или иной метки.

- Машина, на которой были произведены последние изменения.

- Путь, по которому лежит документ.

- Хэш файла. Позволяет контролировать целостность данных в документе.

- Плюс какое-то количество другой служебной информации.

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

Однако, следует подчеркнуть: основная идея подобной автоматизации вовлечение всех сотрудников организации, работающих с документами, в процесс обеспечения информационной безопасности компании. В результате использования систем классификации данных (в моем примере DSS), рано или поздно, все файлы контуре безопасности будут иметь ту или иную метку. Закрыть или напечатать документ без установленной метки пользователю просто не будет позволено. А значит, сотрудник будет обязан выполнить эту свою обязанность классифицировать документ, который до сих пор по какой-то причине не имеет ту или иную метку.

Подробнее..

Удобное шифрование с удостоверяющим центром

25.02.2021 12:15:55 | Автор: admin

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

Наш продукт

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

Первая реализация шифрования

Изначально, когда разрабатывался продукт, для шифрования использовался симметричный алгоритм AES с использованием ключа. Данный способ не является удобным, так как нужно было всегда вводить ключ для получения доступа к документу. Также все сотрудники, которые имеют доступ к данным, должны были знать ключ для расшифровки. Такая реализация выглядит не очень удобно и небезопасно. Скорее всего, для простоты пользователи начнут использовать один ключ для шифрования всех документов, а чтобы не забыть - напишут его на стикере и приклеят к монитору. Жестко регулировать действия пользователя в данном случае - не лучшее решение. Можно заставить пользователей для каждого документа использовать уникальный ключ и с течением времени его изменять. Но тогда, при передаче документа другому пользователю, нужно будет передавать и ключ, и, наверняка, кто-нибудь при очередном обновлении ключа забудет его, и доступ к данным будет утерян. Разработка новой архитектуры, в которой учитывались бы все минусы предыдущей, напрашивалась сама собой.

Новая архитектура

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

Вы предложите решение, Вы же эксперт...

Асимметричное шифрование - это алгоритм шифрования данных, который использует уникальную пару ключей (открытый и закрытый) таким образом, что для каждого открытого ключа есть лишь один закрытый. В нашей системе, где есть сервер и множество пользователей, такая пара ключей будет у каждого. Закрытые ключи хранятся у пользователей в специальных криптоконтейнерах, к которым привязан сертификат. Сертификат, помимо всего прочего, содержит открытый ключ, и передача сертификата как раз означает передачу открытого ключа. Переходя к нашей системе - использование данного алгоритма означает, что сертификаты каждого пользователя (а точнее их открытые ключи) необходимо передать всем пользователям между собой (что уже кажется не самой лучшей идеей, если, например, таких пользователей тысячи и разбросаны они по разным концам света). Самое главное противоречие, с которым мы столкнулись - это как зашифровать данные так, чтобы расшифровать их мог любой пользователь, имеющий определенный доступ. Например, чтобы расшифровать документ пользователю X, нужно, чтобы этот документ был зашифрован именно его открытым ключом. Были озвучены самые разные варианты, порой даже безумные - можно шифровать документ в нескольких экземплярах для каждого пользователя ("самая лучшая идея", исходя из того, что пользователей может быть много).

Все размышления по этому поводу сводились к тому, что если мы не можем знать, кто будет расшифровывать документ, то и непонятно, каким открытым ключом его шифровать. Одна из первых идей, которая пришла в голову - это использовать сервер в нашей архитектуре. Каждый клиент при установке нашего продукта регистрируется на сервере, таким образом может получить от сервера его сертификат (открытый ключ) и все документы шифровать им. Далее, после передачи документа другому пользователю, тот для расшифровки документа должен был бы отправить документ на сервер, который его расшифрует и передаст обратно пользователю. Помимо основного недостатка, а именно, передача документа в открытом виде, данная схема имеет ряд других критичных недостатков, главный из которых - передача документа по сети. Документы бывают разные, и двойная передача его на сервер, а потом обратно - не самая удачная мысль. И второе - а что будет, если сервер вдруг упадет или будет недоступен, доступ к данным будет невозможен. Если первый недостаток можно было обойти, например, шифруя документ открытым ключом пользователя, который запрашивает расшифровку, то два других недостатка избежать невозможно.

Эврика!

Внимательное изучение протокола HTTPS (или SSL) показывает интересную особенность, а именно то, что хоть протокол и использует асимметричное шифрование, на самом деле применяется этот алгоритм лишь для: "выработки сессионного ключа, который, в свою очередь, используется более быстрыми алгоритмами симметричной криптографии для шифрования большого объёма данных" (http://personeltest.ru/aways/ru.wikipedia.org/wiki/SSL). Таким образом можно сказать, что 2 клиента между собой при помощи асимметричного алгоритма шифрования договариваются о симметричном ключе, который и используют в дальнейшем для симметричного шифрования данных при передаче. Это сделано для увеличения скорости работы, ведь асимметричные алгоритмы работают намного медленнее, чем симметричные. Сама идея генерации симметричного ключа в нашем случае может выглядеть так, что данные будут шифроваться все также, используя симметричное шифрование одним ключом, который сгенерирован сервером. А вот сам ключ нужно будет зашифровать, используя асимметричный алгоритм и открытый ключ пользователя, которому затем и передаст сервер этот зашифрованный ключ (каждому свой соответственно). Пользователи, получив от сервера зашифрованный ключ, расшифровывают его своим закрытым ключом и используют его для расшифровки и зашифровки данных. Таким образом мы получаем, что ключ, который используется в симметричном шифровании данных, вводит не сам пользователь, а его генерирует сервер, для каждой метки свой уникальный. Далее пользователь при запросе сервера о доступных ему метках получает вместе с ними и зашифрованный ключ. Также была решена и проблема с недоступностью сервера, все полученные данные от сервера хранятся у пользователей, в том числе и зашифрованный ключ. Соответственно, при отсутствии связи с сервером, ключ берется из хранилища, и, тем самым, доступ к зашифрованным данным не пропадает.

Итог

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

P.S.

Надеюсь этот пост кому-нибудь пригодится и поможет сэкономить время.

Подробнее..

Перехват и обработка событий в файловой системе Linux

20.01.2021 10:13:24 | Автор: admin

Введение

В предыдущей статье мы рассмотрели сборку и установку пакета на Linux системах, в которой упомянули про Linux Kernel Module (LKM) и обещали раскрыть позднее подробности о пути к нему и его создании. Ну что ж, настало его время. LKM мы выбираем тебя.

Необходимость реализации

"Windows драйвер мы заменили на Linux Kernel Module LKM" итак, вернёмся мысленно к самому началу пути. Мы имеем Windows драйвер, который обеспечивает отслеживание и перехват событий обращения к файлу. Как его перенести или чем заменить в Linux системах? Покопавшись в архитектуре, почитав про перехват и реализацию подобных технологий в Linux мы поняли, что задача абсолютно нетривиальная, содержащая кучу подводных камней.

Inotify

Закинув удочки на пару форумов, посоветовавшись с коллегами, было принято решение копать в сторону Inotify. Inotify файловый монитор, который логирует события в системе уже после того, как они произошли. Но у него есть брат fanotify. В нём мы можем добавить ограничение доступности на события открытия, копирования файла. Но нам необходимо иметь такую же возможность и для событий удаления, переименования, перемещения, а, следовательно, fanotify нам в этом не поможет. Хочу заметить, что fanotify это userspace утилита, соответственно при её использовании нет проблем с платформопереносимостью.

Virtual File System

Следующим этапом изучения стала возможность реализации перехвата обращений при помощи VFS.

После анализа VFS на основе Dtrace, eBPF и bcc, стало понятно, что при использовании данной технологии возможно выполнять мониторинг событий, происходящих в системе. В данном случае, перехват осуществляется через LKM. В рамках изучения реализации различных модулей под разные ядра выявлено следующее: перехват не всегда позволяет отследить полный путь к файлу; при перехвате обращения к файлу через открытое приложение, а не из проводника, отсутствует путь к файлу в аргументах; для каждого ядра необходима своя реализация.

Janus, SElinux и AppArmor

В ходе исследования, была найдена статья по расширению функциональности системы безопасности ядра Linux. Отсюда следует, что на рынке существует достаточное количество решений. Самым легко реализуемым является Janus. Минусом решения выступает отсутствие поддержки свежих ядер и все вышеописанные проблемы LKM хука. Реализация SELinux и AppArmor представляет квинтэссенцию всего описанного и изученного ранее. Модуль SELinux включает в себя основные компоненты: сервер безопасности; кэш вектора доступа (англ. Access Vector Cache, AVC); таблицы сетевых интерфейсов; код сигнала сетевого уведомления; свою виртуальную файловую систему (selinuxfs) и реализацию функций-перехватчиков.

Долгожданное решение

После всех этих бесконечных но, на помощь нам пришёл Хабр! Наткнувшись на статью, стало ясно, что это наш случай.

Обработка перехвата

Изучив предложенные данные по ftrace и реализации из самой статьи, сделали аналогичный LKM модуль на базе ftrace. Данная утилита, в свою очередь, работает на базе файловой системы debugfs, которая в большинстве современных дистрибутивов Linux смонтирована по умолчанию. Hook'и добавили на события к уже имеющимся clone и open: openat, rename, unlink, unlinkat. Таким образом, удалось обработать открытие, переименование, перемещение, копирование, удаление файла.

Взаимодействие

Теперь нам нужно реализовать связь между модулем ядра и приложением userspace. Для решения данной задачи существуют разные подходы, но в основном выделяют два: socket между kernel и userspace; запись/чтение в системной директории в файл.

В итоге, мы выбрали netlink socket, так как в Windows мы используем аналогичный интерфейс - FltSendMessage. Можно было использовать inet socket, но это наименее защищённое решение. Также столкнулись с такой проблемой, что на .Net Core, на которой реализовано userspace приложение, отсутствует реализация netlink.

Поэтому пришлось реализовывать динамическую библиотеку с реализацией netlink и уже её подключать в проект.

int open_netlink_connection(void){    //initialize our variables    int sock;    struct sockaddr_nl addr;    int group = NETLINK_GROUP;    //open a new socket connection    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_USERSOCK);    //if the socket failed to open,    if (sock < 0)     {        //inform the user        printf("Socket failed to initialize.\n");        //return the error value        return sock;    }    //initialize our addr structure by filling it with zeros    memset((void *) &addr, 0, sizeof(addr));    //specify the protocol family    addr.nl_family = AF_NETLINK;    //set the process id to the current process id    addr.nl_pid = getpid();    //bind the address to the socket created, and if it failed,    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0)     {        //inform the user        printf("bind < 0.\n");        //return the function with a symbolic error code        return -1;    }    //set the option so that we can receive packets whose destination    //is the group address specified (so that we can receive the message broadcasted by the kernel)    if (setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)) < 0)     {        //if it failed, inform the user        printf("setsockopt < 0\n");        //return the function with a symbolic error code        return -1;    }    //if we got thus far, then everything    //went fine. Return our socket.    return sock;}char* read_kernel_message(int sock){    //initialize the variables    //that we are going to need    struct sockaddr_nl nladdr;    struct msghdr msg;    struct iovec iov;    char* buffer[CHUNK_SIZE];    char* kernelMessage;    int ret;    memset(&msg, 0, CMSG_SPACE(MAX_PAYLOAD));    memset(&nladdr, 0, sizeof(nladdr));    memset(&iov, 0, sizeof(iov));    //specify the buffer to save the message    iov.iov_base = (void *) &buffer;    //specify the length of our buffer    iov.iov_len = sizeof(buffer);    //pass the pointer of our sockaddr structure    //that will save the source IP and port of the connection    msg.msg_name = (void *) &(dest_addr);    //give the size of our structure    msg.msg_namelen = sizeof(dest_addr);    //pass our scatter/gather I/O structure pointer    msg.msg_iov = &iov;    //we will pass only one buffer array,    //therefore we will specify that here    msg.msg_iovlen = 1;    //listen/wait for new data    ret = recvmsg(sock, &msg, 0);    //if message was received successfully,    if(ret >= 0)    {        //get the string data and save them to a local variable        char* buf = NLMSG_DATA((struct nlmsghdr *) &buffer);        //allocate memory for our kernel message        kernelMessage = (char*)malloc(CHUNK_SIZE);        //copy the kernel data to our allocated space        strcpy(kernelMessage, buf);        //return the pointer that points to the kernel data        return kernelMessage;    }        //if we got that far, reading the message failed,    //so we inform the user and return a NULL pointer    printf("Message could not received.\n");    return NULL;}int send_kernel_message(int sock, char* kernelMessage){    //initialize the variables    //that we are going to need    struct msghdr msg;    struct iovec iov;    char* buffer[CHUNK_SIZE];        int ret;    memset(&msg, 0, CMSG_SPACE(MAX_PAYLOAD));    memset(&iov, 0, sizeof(iov));    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);    nlh->nlmsg_pid = getpid();    nlh->nlmsg_flags = 0;    char buff[160];    snprintf(buff, sizeof(buff), "From:DSSAgent;Action:return;Message:%s;", kernelMessage);    strcpy(NLMSG_DATA(nlh), buff);    iov.iov_base = (void *)nlh;    iov.iov_len = nlh->nlmsg_len;    //pass the pointer of our sockaddr structure    //that will save the source IP and port of the connection    msg.msg_name = (void *) &(dest_addr);    //give the size of our structure    msg.msg_namelen = sizeof(dest_addr);    msg.msg_iov = &iov;    msg.msg_iovlen = 1;    printf("Sending message to kernel (%s)\n",(char *)NLMSG_DATA(nlh));    ret = sendmsg(sock, &msg, 0);    return ret;}int sock_netlink_connection(){sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);    if (sock_fd < 0)        return -1;    memset(&src_addr, 0, sizeof(src_addr));    src_addr.nl_family = AF_NETLINK;    src_addr.nl_pid = getpid(); /* self pid */    bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));    memset(&dest_addr, 0, sizeof(dest_addr));    dest_addr.nl_family = AF_NETLINK;    dest_addr.nl_pid = 0; /* For Linux Kernel */    dest_addr.nl_groups = 0; /* unicast */    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);    nlh->nlmsg_pid = getpid();    nlh->nlmsg_flags = 0;    strcpy(NLMSG_DATA(nlh), "From:DSSAgent;Action:hello;");    iov.iov_base = (void *)nlh;    iov.iov_len = nlh->nlmsg_len;    msg.msg_name = (void *)&dest_addr;    msg.msg_namelen = sizeof(dest_addr);    msg.msg_iov = &iov;    msg.msg_iovlen = 1;    printf("Sending message to kernel\n");    sendmsg(sock_fd, &msg, 0);    printf("Waiting for message from kernel\n");    /* Read message from kernel */    recvmsg(sock_fd, &msg, 0);    printf("Received message payload: %s\n", (char *)NLMSG_DATA(nlh));return sock_fd;}void sock_netlink_disconnection(int sock){close(sock);    free(nlh);}

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

char* get_username_by_pid(int pid){   register struct passwd *pw;  register uid_t uid;  int c;  FILE *fp;  char filename[255];  sprintf(filename, "/proc/%d/loginuid", pid);  char cc[8];    // чтение из файла  if((fp= fopen(filename, "r"))==NULL)    {        perror("Error occured while opening file");        return "";    }  // считываем, пока не дойдем до конца  while((fgets(cc, 8, fp))!=NULL) {}       fclose(fp);    uid = atoi(cc);  pw = getpwuid (uid);  if (pw)  {      return pw->pw_name;  }  else  {      return "";  }}

Доработка модуля

По итогу добавили соединение по netlink в инициализацию LKM.

static int fh_init(void){    int err;struct netlink_kernel_cfg cfg ={#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0).groups = 1,#endif.input = nl_recv_msg,};#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 36)nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32)nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, 0, nl_recv_msg, NULL, THIS_MODULE);#elsenl_sk = netlink_kernel_create(NETLINK_USER, 0, nl_recv_msg, THIS_MODULE);#endifif (!nl_sk){printk(KERN_ERR "%s Could not create netlink socket\n", __func__);return 1;}err = fh_install_hooks(hooks, ARRAY_SIZE(hooks));if (err)return err;p_list_hook_files = (tNode *)kmalloc(sizeof(tNode), GFP_KERNEL);p_list_hook_files->next = NULL;p_list_hook_files->value = 0;pr_info("module loaded\n");return 0;}module_init(fh_init);static void fh_exit(void){delete_list(p_list_hook_files);fh_remove_hooks(hooks, ARRAY_SIZE(hooks));netlink_kernel_release(nl_sk);pr_info("module unloaded\n");}module_exit(fh_exit);

Socket ожидает перехвата события обращения к файлу. Модуль, перехватывая событие, передаёт имя файла, pid и имя процесса. Userspace приложение, получая данную информацию, обрабатывает её и отвечает, что делать с файлом (блокировать или разрешать доступ). Впоследствии модуль возвращает соответствующий системный вызов.

static void send_msg_to_user(const char *msgText){int msgLen = strlen(msgText);struct sk_buff *skb = nlmsg_new(NLMSG_ALIGN(msgLen), GFP_KERNEL);if (!skb){printk(KERN_ERR "%s Allocation skb failure.\n", __func__);return;}struct nlmsghdr *nlh = nlmsg_put(skb, 0, 1, NLMSG_DONE, msgLen, 0);if (!nlh){printk(KERN_ERR "%s Create nlh failure.\n", __func__);nlmsg_free(skb);return;}NETLINK_CB(skb).dst_group = 0;strncpy(nlmsg_data(nlh), msgText, msgLen);int errorVal = nlmsg_unicast(nl_sk, skb, pid);if (errorVal < 0)printk(KERN_ERR "%s nlmsg_unicast() error: %d\n", __func__, errorVal);}static void return_msg_to_user(struct nlmsghdr *nlh){pid = nlh->nlmsg_pid;const char *msg = "Init socket from kernel";const int msg_size = strlen(msg);struct sk_buff *skb = nlmsg_new(msg_size, 0);if (!skb){printk(KERN_ERR "%s Failed to allocate new skb\n", __func__);return;}nlh = nlmsg_put(skb, 0, 0, NLMSG_DONE, msg_size, 0);NETLINK_CB(skb).dst_group = 0;strncpy(nlmsg_data(nlh), msg, msg_size);int res = nlmsg_unicast(nl_sk, skb, pid);if (res < 0)printk(KERN_ERR "%s Error while sending back to user (%i)\n", __func__, res);}

Позднее, для работы другого приложения в данный модуль, добавили возможность блокирования доступа к определённому файлу (по его полному пути) для всех процессов, кроме определённого (определяем по pid процесса).

static void parse_return_from_user(char *return_msg){char *msg = np_extract_value(return_msg, "Message", ';');const char *file_name = strsep(&msg, "|");printk(KERN_INFO "%s Name:(%s) Permiss:(%s)\n", __func__, file_name, msg);if (strstr(msg, "Deny"))reload_name_list(p_list_hook_files, file_name, Deny);elsereload_name_list(p_list_hook_files, file_name, Allow);}static void free_guards(void){// Possibly unpredictable behavior during cleaningmemset(&guards, 0, sizeof(struct process_guards));}static void change_guards(char *msg){char *path = np_extract_value(msg, "Path", ';');char *count_str = np_extract_value(msg, "Count", ';');if (path && strlen(path) && count_str && strlen(count_str)){int i, found = -1;for (i = 0; i < guards.count; ++i)if (guards.process[i].file_path && !strcmp(path, guards.process[i].file_path))found = i;guards.is_busy = 1;int count;kstrtoint(count_str, 10, &count);if (count > 0){if (found == -1){strcpy(guards.process[guards.count].file_path, path);found = guards.count;guards.count++;}for (i = 0; i < count; ++i){char buff[8];snprintf(buff, sizeof(buff), "Pid%d", i + 1);char *pid = np_extract_value(msg, buff, ';');if (pid && strlen(pid))kstrtoint(pid, 10, &guards.process[found].allow_pids[i]);elseguards.process[found].allow_pids[i] = 0;}guards.process[found].allow_pids[count] = 0;}else{if (found >= 0){for (i = found; i < guards.count - 1; ++i)guards.process[i] = guards.process[i + 1];guards.count--;}}guards.is_busy = 0;}}// Example message is "From:CryptoCli;Action:clear;" or "From:DSSAgent;Action:init;"static void nl_recv_msg(struct sk_buff *skb){printk(KERN_INFO "%s <--\n", __func__);struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;printk(KERN_INFO "%s Netlink received msg payload:%s\n", __func__, (char *)nlmsg_data(nlh));char *msg = (char *)nlmsg_data(nlh);if (msg && strlen(msg)){char *from = np_extract_value(msg, "From", ';');char *action = np_extract_value(msg, "Action", ';');if (from && strlen(from) && action && strlen(action)){if (!strcmp(from, "DSSAgent")){if (!strcmp(action, "init")){return_msg_to_user(nlh);}else if (!strcmp(action, "return")){parse_return_from_user(msg);}else{printk(KERN_ERR "%s Failed msg, \"From\" is %s and \"Action\" is %s\n", __func__, from, action);}}else if (!strcmp(from, "CryptoCli")){if (!strcmp(action, "clear")){free_guards();}else if (!strcmp(action, "change")){change_guards(msg);}else{printk(KERN_ERR "%s Failed msg, \"From\" is %s and \"Action\" is %s\n", __func__, from, action);}}else{printk(KERN_ERR "%s Failed msg, \"From\" is %s and \"Action\" is %s\n", __func__, from, action);}}else{printk(KERN_ERR "%s Failed parse msg, don`t found \"From\" and \"Action\" (%s)\n", __func__, msg);}}else{printk(KERN_ERR "%s Failed parse struct nlmsg_data, msg is empty\n", __func__);}printk(KERN_INFO "%s -->\n", __func__);}static bool check_file_access(char *fname, int processPid){if (fname && strlen(fname)){int i;for (i = 0; i < guards.count; ++i){if (!strcmp(fname, guards.process[i].file_path) && guards.process[i].allow_pids[0] != 0){int j;for (j = 0; guards.process[i].allow_pids[j] != 0; ++j)if (processPid == guards.process[i].allow_pids[j])return true;return false;}}// Not found filename in guardsif (strstr(fname, filetype)){char *processName = current->comm;printk(KERN_INFO "%s service pid = %d\n", __func__, pid);printk(KERN_INFO "%s file name = %s, process pid: %d, , process name = %s\n", __func__, fname, processPid, processName);if (processPid == pid){return true;}else{add_list(p_list_hook_files, processPid, fname, None);char *buffer = kmalloc(4096, GFP_KERNEL);sprintf(buffer, "%s|%s|%d", fname, processName, processPid);send_msg_to_user(buffer);kfree(buffer);ssleep(5);bool ret = true;if (find_list(p_list_hook_files, fname) == Deny)ret = false;delete_node(p_list_hook_files, fname);return ret;}}}return true;}

Интеграция в процесс установки

Так как первые два минуса LKM удалось преодолеть через реализацию ftrace, третий никто не отменял. Мало того, что под каждое ядро нужна сборка модуля, уже в процессе использования он может протухнуть. Было принято решение добавить его пересборку перед каждым запуском userspace приложения. В статье по сборке Linux пакетов было описано, что службу, для которой мы реализовываем обработку перехвата обращения к файлу, мы демонизировали путём добавления в system. Поэтому для демона.service добавляем два дополнительных пункта, помимо ExecStart и ExecStop будут:

ExecStartPre=/bin/sh /путь_до_расположения/prestart.shExecStopPost=/sbin/rmmod имя_модуля.ko

а в сам prestart.sh:

#!/bin/shMOD_VAL=$(lsmod | grep имя_модуля | wc -l)cd /путь_до_расположения_модуляmake cleanmake allif [ $MOD_VAL = 1 ]then    for proc in $(ps aux | grep DSS.Agent | awk '{print $2}'); do kill -9 $proc; doneelse    /sbin/insmod / путь_до_расположения_модуля/имя_модуля.kofi

Заключение

В завершение, хочется отметить: возможно, путь, по которому мы пошли, не самый красивый и элегантный, но, он содержит отработанную и проверенную логику работы на ОС Windows. Было бы полезно услышать в комментариях мнение читателей статьи. Возможно, есть более разумное решение задачи. Например, наш DevOps, в тот момент, когда мы автоматизировали сборку пакета Linux и обрабатывали/добавляли LKM, предложил реализовать логику с использованием Access Control List (ACL). Скорее всего, в дальнейшем мы займёмся переработкой нашего продукта под Linux. И, да, скоро будет новая статья, о том, как мы переносили MS Forms на Avalonia и его интеграции в Linux.

Ссылки которые нам помогли

Подробнее..

По пути в Авалонию

15.02.2021 12:06:03 | Автор: admin

Здравствуй дорогой наш читатель. Это третья статья из серии переезда нашего ПО на кроссплатформенные рельсы. Она затронет визуальную часть продукта, а именно диалоговое окно.

В рамках первой статьи упоминалось, что данное приложение вызывается через пункт ПКМ, в связи с чем были проблемы при развертывании в разных ОС. Самое интересное заключалось не только в том, как реализовать вызов визуальщины, но и в том, как без сильных потерь реализовать диалоговое окно, которое мы сможем спокойно использовать в Linux, а возможно в дальнейшем и в Windows. Для самых нетерпеливых забежим вперёд и скажем, что в данной задаче нам помог framework Avalonia. Ну, а теперь, по порядку.

Мы имеем WinForms приложение, работающее, разумеется, только в Windows. Его задача - выводить диалоговое окно с небольшим количеством контролов, фактически, главными из которых являются листбокс и кнопка.

Как раз только выходит .Net Core 3, с предположением о работе в Linux. Собираем, проверяем и ничего не получаем. В действительности в Linux не добавили визуальную часть. Ну что же, ищем другие варианты.

Самым часто встречающимся фреймворком по данному вопросу является Avalonia. Мы решили опробовать её. Взяли простейший пример, собрали в Linux, запустили и - да, диалоговое оно отобразилось. Теперь дело за малым - перенести WinForms проект на Avalonia.

В качестве проекта был выбран простой шаблон Avalonia Application. При условии, что логика и DTO уже вынесены в отдельные библиотеки, шаблон MVVM здесь был бы избыточен.

Перенос дизайна основного окна с WinForms на XAML прошёл без проблем, несмотря на то, что разработчик в знакомстве с WPF ранее замечен не был.

Логика осталась практически без изменений (BackgroundWorker, файлы, DTO...).

Реализация окон сообщений, на первый взгляд, также проблем не вызвала.

В проекте WinForms в качестве диалогового окна сообщения использовался System.Windows.Forms.MessageBox.

MessageBox.Show("Метка установлена", "Crosstech DSS",                 MessageBoxButtons.OK, MessageBoxIcon.Information);

Для нового проекта можно было использовать окно, производное от Avalonia.Controls.Window, предварительно оформленное как окно сообщения с иконками, текстом, стилями и кнопками, но...

в процессе изучения форумов выяснилось, что за нас уже подумали:). Существует проект MessageBox.Avalonia с иконками и вот этим вот всем. Проект немедленно был установлен.

Сначала это выглядело так:

private void Form_Load(object sender, EventArgs e){  ...    _backgroundWorker.DoWork += DoLoadForm;    _backgroundWorker.RunWorkerAsync();...}private void DoLoadForm(object sender, DoWorkEventArgs e){...MessageBoxHelper.ShowErrorBox("Нет разрешённых действий для данного документа. Пожалуйста, обратитесь к администратору.");...}internal static class MessageBoxHelper{  public static async void ShowBox(MessageBoxStandardParams MBSparams)    {    Action showMSBDialog = () => MessageBox.Avalonia.MessageBoxManager.GetMessageBoxStandardWindow(MBSparams).ShowDialog(new MainWindow());      await Dispatcher.UIThread.InvokeAsync(showMSBDialog);    }public static void ShowErrorBox(string message)    {    var MBSparams = new MessageBoxStandardParams      {      ButtonDefinitions = ButtonEnum.Ok,        ContentTitle = $"Ошибка доступа",        ShowInCenter = true,        ContentMessage = message,        Icon = MessageBox.Avalonia.Enums.Icon.Error,        Style = Style.Windows      };      ShowBox(MBSparams);    }...}

Окно сообщения всё-таки выводилось на экран. Для начала неплохо. Проблема была в том, что это окно сообщения существовало независимо от главного окна приложения, они все были доступны и закрывать их можно было в любом порядке, результат диалога не анализировался. Такое положение вещей нас более не устраивало, внесли небольшие изменения, на данном этапе получилось так:

private async void DoLoadForm(object sender, DoWorkEventArgs e){var rezult = await MessageBoxHelper.ShowErrorBox("Нет разрешённых действий для данного документа. Завершить работу с документом?", this);  if (rezult == MessageBox.Avalonia.Enums.ButtonResult.Yes)  {  Environment.Exit(0);  }...}internal static class MessageBoxHelper{public static async Task<ButtonResult> ShowBox(MessageBoxStandardParams MBSparams, Window owner = null)  {if (owner == null){owner = new MainWindow();}Func<Task<ButtonResult>> www = new Func<Task<ButtonResult>>(() => MessageBox.Avalonia.MessageBoxManager.GetMessageBoxStandardWindow(MBSparams).ShowDialog(owner));ButtonResult result = await Dispatcher.UIThread.InvokeAsync(www);return result;}public static async Task<ButtonResult> ShowErrorBox(string message, Window owner = null)  {  var MBSparams = new MessageBoxStandardParams    {    ButtonDefinitions = ButtonEnum.YesNo,      ContentTitle = $"Ошибка доступа",      ShowInCenter = true,      ContentMessage = message,      Icon = MessageBox.Avalonia.Enums.Icon.Error,      Style = Style.Windows     };     return await ShowBox(MBSparams, owner);   } ...}

Диалоговое окно выводится на переднем плане, не даёт переключить фокус на основное окно, возвращает результат. Уже лучше.

В процессе тестирования столкнулись с багом: на Windows 8 x86 при включенном масштабировании, наше окно отображается с чёрной рамкой. Меняли большинство параметров отображения в XAML, не помогло.

Хотим отдельно отметить прекрасную поддержку проекта Avalonia.

Ссылки на использованные источники:

Статьи на Хабре:

Подробнее..

Делаем откаты БД в msi. История про создание резервных копий и удаление БД в WixSharp

31.03.2021 14:23:38 | Автор: admin

При работе с базами данных (БД) в установщике, про который мы уже писали в прошлой статье (Пишем установщик на WixSharp. Плюшки, проблемы, возможности), в первую очередь, были реализованы проверка доступности СУБД по логину/паролю, добавление и обновление собственно БД (в нашем приложении их несколько) накатыванием миграций, a также добавление пользователей. Все это реализовано для двух СУБД Microsoft SqlServer и PostgreSql.
На первый взгляд этого достаточно, но иногда есть необходимость удалять БД и пользователей, а это влечет за собой создание резервных копий. Сразу выявили две необходимые задачи:

1.Удаление БД и пользователей при откате приложения в случае ошибки при первичной установке. При установке приложения, если возникает ошибка, происходил откат всех настроек, кроме БД. Добавленные БД и пользователи оставались. И, если при боевой эксплуатации, после серии тестирования эта ситуация непредвиденной ошибки маловероятна, то в процессе разработки и доработки установщика, ошибки возникают часто. Их, однозначно, нужно удалять.

2.Создание резервных копий (бэкапов) и удаление БД и пользователей при полном удалении приложения установщиком. Правильно ли оставлять БД после полного удаления приложения? Мы решили, что нет. Но бэкапы, конечно, сохранять нужно.

Из второго пункта возникла новая задача:

3.Создание бэкапов БД при обновлении приложения. Если мы сохраняем бэкапы при удалении, неплохо создавать их и перед обновлением, накатыванием миграций и прочими изменениями. Подстраховка еще никому не мешала. :)

Удаление БД и пользователей при откате приложения в случае ошибки при первичной установке

Если что-то пошло не так и при установке возникли ошибки, мы сразу же позаботились об удалении добавленных директорий и настроек, а также об очистке реестра. Но БД и пользователей также нужно удалять. В WixSharp для этого предусмотрен механизм роллбэка для CustomActions. Для существующего пользовательского действия нужно добавить еще один параметр - название пользовательского действия откатывающего изменения. Необходимо учесть, что данный механизм доступен только для deferred action (отложенных действий).

new ManagedAction(AddDatabaseAction, Return.check, When.After, Step.PreviousAction, Condition.NOT_Installed, DeleteAddedDatabasesAction)             {    UsesProperties = $@"{DATABASE_PROPERTIES}={database_properties}",    Execute = Execute.deferred,   ProgressText = $"Выполняется создание БД {databaseName}"              };

Тут сложностей не возникло и для каждого из СУБД было добавлено выполнение скрипта с удалением БД и пользователей, учитывая в скрипте, что в этот момент база может использоваться.

Создание бэкапов и удаление БД при полном удалении приложения установщиком

В данном случае, необходимо сохранять бэкапы БД, а затем, проводить удаление.

Пользовательское действие для создания бэкапа желательно выполнять до того, как начнут вноситься изменения установщиком, для этого предусмотрен тип immediate. В отличие от deferred, он выполняется сразу. Чтобы данное действие выполнялось только при удалении приложения, укажем условие Condition.BeingUninstalled:

new ManagedAction(BackupDatabaseAction, Return.check, When.After, Step.PreviousAction, Condition.BeingUninstalled){   Execute = Execute.immediate,   UsesProperties = DeleteAddedDatabases,   ProgressText = $"Выполняется скрипт по созданию резервных копий БД" }

Бэкапы решено было сохранять по пути, доступному текущему пользователю. Так как у нас несколько БД, группировку проводили по версии приложения. Название БД формировалось классически, с указанием имени и даты-времени создания.
\Users\{CurrentUser}\AppData\Local\{ApplicationName}\Backups\{VersionNumber}

Создаем этот путь:

Version installedVersion = session.LookupInstalledVersion();  string localUserPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); string backupPath = Path.Combine(localUserPath, "ApplicationName", "Backups", installedVersion.ToString());  Directory.CreateDirectory(backupPath);

И если для Microsoft SqlServer создание бэкапов заключалось в выполнении банального sql-скрипта:

$" USE master" +                    $" BACKUP DATABASE [{databaseName}]" +                    $" TO DISK = N'{path}'" +                    $" WITH NOFORMAT, NOINIT, " +                    $" NAME = N'{backupName}', SKIP, NOREWIND, NOUNLOAD,  STATS = 10 ";

То для PostgreSql одним скриптом не обойтись. Бэкап можно создать запуском команды через командную строку. Понадобится выполнение следующих действий:

  • Запускать pg_dump.exe из соответствующей папки PostgreSql
    C:\Program Files\PostgreSQL\{Version}\bin
    Мы не знаем какая версия установлена у заказчика (обычно в документации мы указываем версию, не ниже которой требуется), какой путь был выбран. Поэтому основной путь с указанием версии получим из реестра:

const string KEY_MASK = @"SOFTWARE\PostgreSQL\Installations\";var currentVersion = Registry.LocalMachine.OpenSubKey(KEY_MASK)?.GetSubKeyNames()[0];if (currentVersion == null){  return ActionResult.Failure;}var keyName = $@"HKEY_LOCAL_MACHINE\{KEY_MASK}{currentVersion}";var postgresPath = (string)Registry.GetValue(keyName, "Base Directory", string.Empty);
  • Проверять, добавлены ли переменные среды для PostgreSql. И в случае необходимости добавить.
    C:\Program Files\PostgreSQL\12\bin
    C:\Program Files\PostgreSQL\12\lib

    Если они отсутствуют, запуск pg_dump будет невозможен.

string binEnv = $@"{postgresPath}\bin";string path = "PATH";var scope = EnvironmentVariableTarget.User;var currentEnvironmentVariable = Environment.GetEnvironmentVariable(name, scope);if (!currentEnvironmentVariable.ToUpper().Contains(binEnv.ToUpper())){  var newEnvironmentVariable = $@"{currentEnvironmentVariable};{binEnv}";  Environment.SetEnvironmentVariable(name, newEnvironmentVariable, scope);}
  • Сформировать аргументы создания бэкапа с помощью командной строки. Здесь необходимо указать параметры подключения, имя БД и путь сохранения бэкапа. Так как ранее нам не приходилось создавать бэкапы для PostgreSql, несложный поиск в интернете показывал примерно такое решение:

    pg_dump -h {host} -p {port} -U {username} {database_name} > {backuppath}

    Если в конфиг файле pg_hba не указано для local connections безусловное подключение trust, то будет требоваться введение пароля. В данном случае, требуется добавление файла .pgpass для текущего пользователя. Тогда, можно добавить в команду атрибут -w и пароль будет считываться оттуда. Так как вновь возникает ситуация, когда мы не знаем, как это организовано у заказчика, была найдена универсальная запись, с помощью которой можно передать все параметры в рамках одной команды:

    pg_dump --dbname=postgresql://{username}:{password}@{address}:{port}
    /{databaseName}-f {backupPath}

После того, как бэкапы созданы, можно удалить БД и пользователей. Здесь будет использоваться то же пользовательское действие DeleteAddedDatabasesAction, что и для отката из пункта 1. Оно будет отложенным и будет запускаться при условии деинсталляции Condition.BeingUninstalled:

new ManagedAction(DeleteAddedDatabasesAction, Return.check, When.After, Step.PreviousAction, Condition.BeingUninstalled){  Execute = Execute.deferred,  UsesProperties = $@"{DATABASE_PROPERTIES}={database_properties}",  ProgressText = $"Выполняется удаление БД {databaseName} и роли {role}" };

Операции с БД при обновлении приложения

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

Вывод

Для PostgreSql и Microsoft SqlServer в нашем установщике удалось наладить:

  • механизм удаления БД и пользователей;

  • создание резервных копий в случае полного удаления;

  • создание резервных копий в случае обновления приложения;

  • реализацию отката добавленных БД в случае неудачной первичной установки, либо ее отмене.

    Продолжаем пилить msi ;)

Подробнее..

Сборка и установка Linux пакетов в российских сертифицированных ОС

21.12.2020 10:07:55 | Автор: admin

Введение


Ранее, в статье мы описали сборку расширений для LibreOffice. Теперь мы расскажем, как наработки были перенесены на платформу Linux, а также как решались вопросы с подготовкой пакетов для российских сертифицированных операционных системах, таких как AstraLinux, ALTLinux и RedOS.

image

Постановка задачи и первичная реализация


После успешной реализации нашего продукта DSS для платформы Windows, потребовалось перенести наработки (в том числе и расширение для LibreOffice на C++, о сборке и установке sdk которого, было рассказано ранее) на платформы семейства Linux.

Состав пакета


Соответственно необходимо определить, что мы переносим:
  • служба для связи с сервером;
  • драйвер для перехвата и обработки обращений к файлам;
  • служба для общения и обработки информации от драйвера;
  • диалоговое приложение;
  • служба шифрования;
  • расширение для LO.

Последний пункт легче всего интегрировать, так как сборка под Linux для него описана в нашей статье .
Что касается служб для связи сервером и для обработки обращений к файловой системе, то они написаны на .net core, а данный фреймворк с версии 3.0 также легко переносим на Linux.
Windows драйвер мы заменили на Linux Kernel Module (LKM), подробности по его созданию будут описаны в одной из дальнейших статей.

Службу шифрования также пришлось немного переписать, так как она реализована на C++.
Для переноса приложения, обеспечивающего нас диалоговым окном, написанного на WinForms мы использовали фреймворк Avalonia. По применению и сложностям будет создана отдельная статья. Также возникла необходимость добавить запуск данного приложения через нажатие ПКМ на определённый файл. В Ubuntu в этом помог filemanager-actions (он же в ранних версиях nautilus-actions). При помощи него можно добавить практически любой сценарий обработки ПКМ, но, повторюсь, в рамках Ubuntu(как окажется далее ещё и в AltLinux).

Сборка


Теперь, когда мы определились с содержимым, для начала соберём deb пакет.
Так как у нас есть службы их необходимо демонизировать. Для этого используем systemd.
Изначально было принято решение для сборки deb пакета использовать checkinstall. Первый пакет был собран при помощи него. Но при добавлении сборки в CI появились/возникли проблемы с окружением сборки, зависимостями и скриптами до/после установки. Поэтому было решено, что лучше это делать через fakeroot. Эти действия, по большей части, были описаны в данной статье.
Создаём отдельную директорию, содержащую инструкции для systemd, которую после перенесём в /lib/systemd/system.
Создаём директорию с содержимым, которое необходимо перенести при установке пакета.
А также создаём директорию DEBIAN, содержащую сценарии для действий перед/после установки/удаления и control, описывающий основную информацию пакета и его зависимости.
После созданного контента выполняем fakeroot dpkg-deb --build имя пакета.
В итоге на выходе мы имеем deb пакет, с содержимым.

Установка, удаление и проверка работы


Устанавливаем его командой:
sudo dpkg -i имя пакета.deb

Удаляем командами:
sudo dpkg -r имя пакета(указанное в файле control)

sudo dpkg --purge имя пакета(указанное в файле control)

При установке переносятся и запускаются 3и демона (приложения работающие фоном, аналог служб Microsoft).
Для проверки их работоспособности выполняем:
systemctl status имя демона.service

Для примера статус нашего dssservice
image

Далее началось тестирование пакета и выявление всех зависимостей, которые в процессе создания не были учтены. После успешной обработки всех зависимостей, выяснилась одна интересная деталь. Если мы хотим подключаться по rdp к машине, то данный функционал необходимо настроить, так как по дефолту, сервера для подключения по данному протоколу нет, как на Microsoft. Самым простым способом нaстройки rdp является настройка xrdp совместно с xfce4. При настройке xfce4 используется в качестве проводника Thunar и, соответственно, пункт в ПКМ, который мы добавляли через filemanager-actions, для него не добавляется. Но решение довольно быстро было найдено находясь в домашней директории проходим по следующему пути:
.config/Thunar/
и там будет лежать файл uca.xml, содержащий сценарии для ПКМ.

Разворачивание пакетов в российских сертифицированных ОС


После успешного тестирования данного пакета на Ubuntu возник вопрос о работоспособности его на других ОС использующих dpkg, как менеджер пакетов, а, соответственно, поддерживающих .deb. А, в частности, вспомнилась отечественная разработка (импортозамещение никто не отменял) AstraLinux.

AstraLinux


image

С ходу установить пакет не удалось, так как наш пакет имеет в зависимостях filemanager-actions, который мы используем для добавления пункта ПКМ в Nautilus Ubuntu. Но в AstraLinux используется файловый менеджер fly, и для добавления в него мы не будем использовать filemanager-actions, пришли к выводу, что для AstraLinux будем собирать пакет без учёта этой зависимости. А для добавления используется сценарий имя_процесса.desktop, который добавляется в /usr/share/flyfm/actions/.
Также были разрешены некоторые моменты, связанные с LKM, но их мы рассмотрим в следующей статье.

Cборка RPM


Следующей ОС стала ALTLinux. Она интересна тем, что имеет пакетный менеджер APT, но при этом вместо dpkg у неё используется rpm. А, следовательно, пора нам собрать наш пакет и под rpm.

Изначально попробовали сделать преобразование deb в rpm, как описано в этом мануале.
Alien достаточно мощная утилита и с её помощью можно достаточно просто преобразовать пакет, достаточно только следовать её подсказкам и добавить недостающее (если она об этом попросит). В итоге, при конвертации получили rpm пакет, но, при попытке его установки вылезли зависимости, ссылок на которые изначально не было (позже расскажу, в чём была изюминка). Поэтому, было принято решение собрать rpm пакет непосредственно средствами rpmbuild.

Сначала решили собирать не под ALTLinux, а под RedOs, так как со стороны бизнеса на неё более перспективные планы. RedOs основана на CentOS, поэтому сборку решили проводить в ней.
Часть с systemd остаётся без изменений, а вот Debian заменяем на файл имя_проекта.spec, который содержит в себе всю информацию и зависимости из control, сценарии для действий перед/после установки/удаления, а так же описание содержимого пакета (непосредственно пути до того, что необходимо добавить).

После создания файла выполняем:
rpmdev-setuptree
переносим .spec в rpmbuild/SPECS и выполняем:
rpmbuild --bb rpmbuild/SPECS/dssservice.spec
после чего забираем из директории rpmbuild/RPMS созданный пакет.

Пытаемся установить пакет и утыкаемся в те же самые зависимости, которые были при попытке установить конвертированный deb пакет.

Как оказалось, изюминка заключается в том, что при добавлении в rpm исполняемых файлов система думает, что, возможно, их необходимо будет пересобрать, и ставит необходимые для этого пакеты в зависимость. Чтобы такого не было необходимо в файл .spec добавить строку после описания зависимостей:
Autoreq: no

Пробуем установить и да победа, пакет корректно устанавливается.

Для установки rpm пакета используем команду:
sudo rpm -ivh "имя_пакета".rpm

Для удаления (без удаления пакетов, находящихся в зависимости):
sudo rpm -e --nodeps ""имя_пакета""


RedOs


image

Далее, необходимо разобраться с зависимостями, так как необходимые для работы наших приложений пакеты уже имеют другие названия, а также, необходимо разобраться с добавлением пункта в ПКМ.
В RedOs в качестве файлового менеджера используется nemo. Для добавления в него пункта в ПКМ необходимо создать файл имя_действия.nemo_action, в котором по аналогии с файлом .desktop (для AstraLinux) будет сценарий обработки нажатия на новый пункт меню, и переместить его в ~/.local/share/nemo/actions/, перезагрузить nemo и пункт появится.

ALTLinux


image

После успешного тестирования rpm пакета на RedOs перешли к формированию rpm пакета под
ALTLinux. По сути, необходимо скорректировать зависимости, так как для каждой оси пакеты будут иметь своё название и снова понять, как произвести добавление пункта в ПКМ. Тут нам на помощь снова пришёл filemanager-actions, через который так же можно добавить пункты в ПКМ и для Mate и Caja, которые как раз и используются в ALTLinux.
В итоге, мы собрали пакеты для основных, используемых у заказчика ОС.

Заключение


В дальнейших статьях мы расскажем, почему использовали LKM и Avalonia и какие трудности из-за этого были, а также, какие дальнейшие планы на доработку пакетов (в частности доработка UI для ввода необходимой информации) и приложений, используемых в них.

Ссылки которые нам помогли


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru