Запуск приложения в отдельных виртуальных машинах с помощью AppVM

Запуск приложения в отдельных виртуальных машинах

Если вы действительно заботититесь о безопасности, то некоторые приложения есть смысл запускать в виртуализации. Обычно это создает массу проблем: держать три десятка виртуалок неудобно и остается соблазн «по-быстрому» что-то запустить на хосте. Мне удалось обойти часть неудобств. Данное решение я назвал AppVM — оно работает в Linux и позволяет запускать приложения с GUI и шейрить файлы с основной системой; оверхеды при этом минимальны. Как я этого добился? Сейчас расскажу.

Я очень люблю Qubes. Если вы с ним не знакомы, поясню: это дистрибутив, построенный на базе гипервизора Xen, который дает возможность создавать домены — честные виртуальные окружения, внутри которых приложения работают в изоляции. Вы можете создавать домены на базе разных дистрибутивов (образов), и они смогут общаться между собой: например, шейрить файлы. В Qubes доменов может быть сколько угодно: work, persoanl, vault — создавайте и называйте как хотите. Звучит неплохо, но есть ли альтернативы и нужны ли они?

Если бы не Xen (вместо гипервизора :)) и отсутствие возможности использовать созданные его разработчиками утилиты вне окружения дистрибутива, то я бы им даже пользовался. Но, на мой взгляд, Xen не подходит хотя бы тем, что в отличие от KVM он не в ядре в апстриме. Предлагать кому-то устанавливать Xen, только чтобы воспользоваться каким-то инструментом, — не самое большое удовольствие. В Qubes самом по себе тоже ничего плохого нет, но его нужно устанавливать и настраивать. Гораздо проще начать встраивать все хорошее из Qubes к себе в уютную Gentoo, чем ломать годами отлаженную систему.

Однако идея безопасности через виртуализацию (security through virtualization) меня по-прежнему привлекала, поэтому что-то нужно было с этим делать.

Зачем запускать приложения в отдельных виртуальных машинах?

Конечно же, для повышения безопасности! Чтобы минимизировать риски заражения через ненадежные программы. Например, вы знали, что любое приложение, которые вы запускаетеу себя, имеет доступ ко всем вашим авторизациям в Chrome на всех сайтах и может слить ваши сессии и выполнить любые действия на любых сайтах от вашего имени? Или получить доступ к твоим файлам. Вот этого я и постарался избежать.

Я покажу, как запускать любые, даже GUI-приложения в виртуальной машине, а также расшаривать между ними файлы и буфер обмена. Последнее — это, конечно, в теории небезопасно, но жить без этого будет сложновато.

Чтобы реализовать такую схему работы, я написал инструмент AppVM и сейчас покажу, как с ним обращаться.

Запуск приложения в отдельных виртуальных машинах
Запущенные в AppVM приложения с GUI. Удобный менеджер запущенных приложений с информацией о потребляемой памяти

Если вы не знакомы с Nix, то будет полезно прочитать cheatsheet, который поможет понять, каким образом ведется работа в системе. А для тех, кто хочет попробовать NixOS (дистрибутив) или Nix (пакетный менеджер, которым можно пользоваться на любом дистрибутиве), будет полезно прочитать другую статью на той же вики.

Из чего строим

Изначально я видел решение в том, чтобы «собрать виртуальную машину для каждого приложения». Обернувшись блогами, как теплым клетчатым пледом, я начал искать готовые скрипты. Но все они мне показались весьма непривлекательными ― за исключением примера, в котором использовался NixOS. Так и появилась первая версия AppVM, которая, по сути, была набором скриптов для установочного диска.

А как же другие системы изоляции?

В чем преимущество по сравнению с использованием SELinux или AppArmor, а быть может, еще и Firejail или bubblewrap? В лени. Виртуальные машины не только дают безопасность, их еще и проще использовать. Вам не нужно ограничивать доступ к определенным ресурсам системы, если их просто нет.

Предельно простой конфигурационный файл Nix определил систему:

Решаем вопрос с GUI

Использовать виртуализацию для консольных приложений, конечно, здорово, но мне бы хотелось в первую очередь запускать GUI-приложения. Например, недоверенные PDF — вы же прекрасно знаете, как дыряв формат PDF и сколько хитрых сплоитов можно через него провернуть, чтобы исполнить код на целевой системе. Без поддержки GUI смысл AppVM минимален.

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

Почему нельзя работать вообще без оконного менеджера на голых «Иксах»? Потому, что многие приложения хотят показывать модальные и дочерние окна. Без хоть какого-нибудь оконного менеджера они будут работать некорректно.

В качестве временного решения я воспользовался xmonad, задействовав важнейшую фичу каждого тайлового оконного менеджера ― возможность разворачивать окно на весь экран. А после я осознал, что xmonad настолько незаметен в системе (потребляет всего 5 Мбайт с конфигом AppVM), что разумнее воспользоваться им, а функциональность при необходимости расширить.

Почему xmonad

В работе я пробовал много оконных менеджеров, но в итоге использую только xmonad, неизменно с середины 2012 года. За это время я не столкнулся ни с одним багом, ни разу не было падения или каких-либо проблем, мешающих работать. В основе этой программы — всего три тысячи строк на Haskell, а если убрать комменты, останется около двух. Для сравнения i3wm — около 40 тысяч строк на С, а mutter из проекта GNOME — 150 тысяч строк. Легковесность победила!

В итоге определенный конфигурационный файл Nix собирался в ISO:

После чего запускался с помощью QEMU:

Это работало, и это было хорошо. Но недостаточно. Проблема в том, что каждое приложение требовало включать в ISO все системные библиотеки и файлы, — когда используется одно или два приложения, это еще приемлемо, но если их пять-семь, то уже не доставляет.

Решение не заставило себя ждать, и оно при этом уже было частью проекта Nix:

Выглядит как то, что мне и было нужно!

Как реализовать общее дисковое пространство, которое будет доступно для чтения и хостовой системе, и гостевой? Я для этого использовал VirtFS (Plan 9 folder sharing over Virtio, между прочим) и пробросил через директорию /nix/store.

Скучный 0day с VM escape, который я так и не зарепортил

Немного опробовав готовое решение в деле, я начал адаптировать его к своим хотелкам и попутно нашел скучный «0day» — возможность выйти за пределы VM. Как видно в описании модуля, Nix store (суть которого — директория /nix) расшаривается с гостевой ОС. Тут-то собака и оказалась зарыта.

Можно было бы подумать: «Ага! security_model=none!», но нет. Они забыли добавить опцию readonly, тем самым сведя выход из виртуальной машины всего к двум действиям:

Подробнее об опциях VirtFS можно прочитать в вики QEMU.

Как хранить данные?

Вернемся к нашим костылям реализации. На данный момент у нас уже есть виртуальная машина, в которой запускается нужное нам приложение и которая использует директорию /nix/store, проброшенную с хоста. Таким образом, виртуальная машина почти не занимает места на жестком диске. Nix store, безусловно, занимает весьма много (так как содержит все основные системные пакеты), но эту проблему простым образом уже не решить. Благо в одном экземпляре это не составляет проблемы.

У меня в качестве хранилища файлов конфигурации использовался диск qcow2, подключенный в режиме writeback. Туда и записывалась разница между исходной запущенной системой и внесенными изменениями. Проблема такого подхода проявилась очень быстро, а если точнее ― после первого же обновления: при использовании дисков в режиме «писать только разницу» обновить базовую систему не получится, так как она в этом случае будет другой.

Тогда я принял решение пробрасывать через VirtFS директорию гостя /home/user на хост точно так же, как и /nix/store. Таким образом получилось и хранилище файлов, и одновременно shared directory.

Подключаем libvirt и разбираемся с правами

Тут стало понятно, что запуск QEMU из терминала недостаточно удобен, так как приложения «теряются». Их либо приходилось убивать руками (звучит пугающе, я знаю), либо нужно писать обертку, которая будет следить за ними. Это привело меня к libvirt.

Для тех, кто не сталкивался с libvirt, поясню, что это такое. Эта библиотека обеспечивает простую возможность управления виртуальными машинами как из терминала, так и из GUI (virt-manager) и при этом поддерживает разные системы виртуализации (как слой абстракции). Одна из полезных особенностей — то, что при использовании с QEMU/KVM libvirt в отличие от VirtualBox не требует установки дополнительных драйверов.

Существует возможность конвертировать командную строку QEMU в описание libvirt — это значительно облегчило мне переход. Проблемы проявились позже из-за того, что QEMU начал запускаться от другого пользователя.

Сначала я пытался решить проблему стандартными методами, с помощью ACL и бита sgid (если установить его на директорию, то можно автоматически присваивать права доступа на созданные файлы владельцу директории). Но к сожалению, QEMU создавал новые файлы на хостовой системе с правами 00 для group и other, что мешает использовать все перечисленное выше (или я в чем-то ошибся, не дойдя до решения).

Вопреки моим ожиданиям, suid на директорию работает иным образом. Подробнее об этом можете почитать в документации проекта GNU для coreutils.

В итоге проще оказалось просто сменить пользователя libvirt на своего, тем самым потеряв немного в безопасности, но приобретя в юзабилити. Я просто поменял в /etc/libvirt/qemu.conf пользователя, от имени которого запускаются виртуальные машины.

Как аллоцировать память

Мне оставалось еще решить проблему с потреблением памяти. Статическое выделение слабо подходит для большого количества приложений, а проект по автоматическому изменению памяти Automatic Ballooning, к сожалению, с 2013 года не продвигался. В апстриме этого нет.

Вооружившись изолентой и костылями, я написал свою реализацию — безусловно, временную (хотя все временное имеет свойство становиться постоянным). Сложно ее назвать элегантной, но она, как ни странно, работает. Идея до безумия проста и состоит в создании задачи cron в гостевой системе.

Аналогичную задачу создаем и на хосте:

Это решение забирает текущее количество памяти из виртуальной машины, добавляет к этому значению 20%, после чего устанавливает полученное значение как размер памяти виртуальной машины. В Linux есть поддержка изменения размера памяти подобным образом — динамически. Решение, конечно, далеко от идеала, но оно позволяет нам относительно комфортно работать.

Оставалась проблема с автоматическим изменением разрешения экрана, но ее решить на данный момент так и не удалось. Если вы сделаете это быстрее меня, то буду рад получить ваш pull request!

Что получилось

В итоге получилось очень простое решение. Вот что нужно сделать для старта.

Установите пакетный менеджер Nix:

Укажите, что libvirt должен работать из-под нашего пользователя — это нужно для доступа к расшаренным с виртуалкой файлам:

[/crayon]
Установите сам AppVM:

Обновите:

Сгенерируйте разрешение. По умолчанию используется 1920 x 1080; если нужно другое, задайте его в appvm/nix/monitor.nix:

Запускайте Chrome:

Подробнее смотрите в README.md из репозитория AppVM.

Подытожу преимущества этого подхода:

  • AppVM использует QEMU;
  • для всех VM используется одна директория /nix, и каждое последующее приложение в VM не занимает значительного пространства на диске;
  • для управления виртуальными машинами используется libvirt, для отображения — virt-viewer;
  • есть базовая реализация перераспределения памяти на основе memory balloon, что позволяет уменьшить потребление памяти.

В текущий момент проект имеет ограничение: не поддерживается автоматическое изменение разрешения экрана внутри виртуальной машины. При этом используется автоматическое масштабирование в virt-manager.

В общем, ставьте звездочки репозиторию, подписывайтесь на GitHub, и, возможно, когда-то кто-то обратится ко мне за рекламной интеграцией в коммиты. Тогда-то наконец я смогу зарабатывать на open source & free software!

Понравилась статья? Поделиться с друзьями:
Добавить комментарий