Применимо к редакции: МСВСфера 9 (ФСТЭК)

14.13. Реализация ролевой модели управления доступом

14.13.1. Введение

В этом разделе описывается способ реализации ролевой модели управления доступом к функциям гипервизора.

Для средства виртуализации ОС должны быть реализованы четыре роли:

  • разработчик виртуальной машины;

  • администратор безопасности средства виртуализации;

  • администратор средства виртуализации;

  • администратор виртуальной машины.

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

14.13.2. Роль администратора средства виртуализации

Роль администратора средства виртуализации должна позволять:

  • создавать учётные записи пользователей средства виртуализации;

  • управлять учётными записями пользователей средства виртуализации;

  • назначать права доступа пользователям средства виртуализации к виртуальным машинам;

  • создавать и удалять виртуальное оборудование средства виртуализации;

  • изменять конфигурации виртуального оборудования средства виртуализации;

  • управлять доступом виртуальных машин к физическому и виртуальному оборудованию;

  • управлять квотами доступа виртуальных машин к физическому и виртуальному оборудованию;

  • управлять перемещением виртуальных машин;

  • удалять виртуальные машины;

  • запускать и останавливать виртуальные машины;

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

Назовём соответствующую группу пользователей libvirt-admin и создадим её:

$ sudo groupadd --system libvirt-admin

Для работы с гипервизором libvirt используются системные учётные записи, соответственно, для реализации первых двух функций потребуется предоставить группе право на выполнение следующих команд:

  • /usr/sbin/useradd — добавляет новую учётную запись пользователя, позволяет назначить пользователю группы и установить пароль;

  • /usr/sbin/usermod — позволяет модифицировать учётную запись пользователя: заблокировать её, изменить домашний каталог, командную оболочку, изменить набор пользовательских групп и т.д.;

  • /usr/sbin/userdel — удаляет учётную запись пользователя;

  • /usr/bin/passwd — устанавливает пароль для учётной записи пользователя;

  • /usr/bin/gpasswd — позволяет управлять группами пользователей: добавлять или удалять участников, ограничить доступ к группе и т.д.;

  • /usr/bin/chage — устанавливает срок действия учётной записи и пароля пользователя.

Для предоставления группе libvirt-admin прав на запуск данных команд с правами администратора будет использоваться утилита sudo. Создайте файл /etc/sudoers.d/libvirt-fstec следующего содержания:

# запретить участникам группы libvirt-admin вызов любых команд
%libvirt-admin      ALL=(ALL)       !ALL

# разрешить участникам группы libvirt-admin вызов перечисленных команд. При
# этом команда sudo попросит ввести пароль текущего пользователя для
# авторизации.
%libvirt-admin ALL=/usr/sbin/useradd, /usr/sbin/usermod, \
/usr/sbin/userdel, /usr/bin/passwd, /usr/bin/gpasswd, /usr/bin/chage

# если требуется использовать перечисленные команды без пароля, то вместо
# предыдущей строки используйте следующую:
# %libvirt-admin      ALL=NOPASSWD: /usr/sbin/useradd,
# /usr/sbin/usermod, /usr/sbin/userdel, /usr/bin/passwd, /usr/bin/gpasswd, /usr/bin/chage

Установите для созданного файла соответствующие права доступа и владельца:

$ sudo chown root:root /etc/sudoers.d/libvirt-fstec
$ sudo chmod 440 /etc/sudoers.d/libvirt-fstec

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

// объявляем переменную, значением которой будет название группы пользователей,
// для которой реализуется роль администратора средства виртуализации
libvirtAdminGroup = 'libvirt-admin';


// метод addRule объекта polkit добавляет новое правило в политику управления
// доступом.

// Правила реализуются в виде функций, принимающих два аргумента:
//   action — действие, для которого выполняется проверка;
//   subject — информация о процессе, который запрашивает разрешение на
//       выполнение действия. Среди прочей информации объект subject содержит
//       идентификатор процесса (PID), имя пользователя, от которого был запущен
//       процесс и список групп, в которые входит этот пользователь.

// По результатам проверки функция должна вернуть одно из следующих значений:
//   polkit.Result.YES — разрешить доступ;
//   polkit.Result.NO — запретить доступ;
//   polkit.Result.AUTH_SELF — запросить авторизацию от имени пользователя,
//       запустившего сессию, и предоставить доступ в случае успеха;
//   polkit.Result.AUTH_SELF_KEEP — то же самое, что и AUTH_SELF, но авторизация
//       сохраняется (кешируется) на короткий промежуток времени;
//   polkit.Result.AUTH_ADMIN — запросить авторизацию от имени пользователя с
//       правами администратора и предоставить доступ в случае успеха;
//   polkit.Result.AUTH_ADMIN_KEEP — аналогично AUTH_ADMIN, но авторизация
//       сохраняется (кешируется) на короткий промежуток времени;
//   polkit.Result.NOT_HANDLED — указывает на то, что данная функция не
//       осуществляет запрошенную проверку. В таком случае polkit запустит
//       проверку следующего правила. Это также является поведением по
//       умолчанию, если функция не вернула ничего.
polkit.addRule(function(action, subject) {
    // действие org.libvirt.unix.manage запрашивается при подключении к
    // локальному Unix сокету гипервизора libvirt
    if (action.id == 'org.libvirt.unix.manage') {
        // разрешить доступ без пароля для всех пользователей, включённых в
        // группу администраторов средства виртуализации
        if (subject.isInGroup(libvirtAdminGroup)) {
            return polkit.Result.YES;
        }
        // запретить доступ для всех остальных пользователей
        else {
            return polkit.Result.NO;
        }
    }
    // данное правило обрабатывает только события, связанные с API-вызовами
    // гипервизора libvirt
    else if (action.id.indexOf('org.libvirt.api.') != 0) {
        return polkit.Result.NOT_HANDLED;
    }

     // разрешить выполнение любого действия org.libvirt.api.* пользователям в
    // группе администраторов средства виртуализации
    if (subject.isInGroup(libvirtAdminGroup)) {
        return polkit.Result.YES;
    }

    // запретить выполнение действий всем остальным пользователям
    return polkit.Result.NO;
});

Сохраните код политики в файл /etc/polkit-1/rules.d/99-libvirt-fstec.rules и установите для него корректные права:

$ sudo chown root:root /etc/polkit-1/rules.d/99-libvirt-fstec.rules
$ sudo chmod 644 /etc/polkit-1/rules.d/99-libvirt-fstec.rules

Служба polkit автоматически загрузит и применит обновлённые правила.

В совокупности с правилами для sudo данная политика позволяет роли администратора средства виртуализации выполнять все требуемые операции за исключением предоставления прав доступа пользователям средства виртуализации к виртуальным машинам — подход к реализации этого требования будет описан в конце этой главы.

Для назначения роли администратора средства виртуализации необходимо добавить пользователя в ранее созданную группу libvirt-admin:

# замените user на реальное имя пользователя
$ sudo gpasswd -a user libvirt-admin

Если пользователь уже вошёл в систему, то необходимо либо выйти и зайти заново, либо выполнить от его имени команду:

$ newgrp libvirt-admin

После этого пользователь получит соответствующие роли полномочия. В следующих разделах политика доступа к API гипервизора будет доработана.

14.13.3. Роль администратора виртуальной машины

Роль администратора виртуальной машины должна позволять осуществлять доступ пользователя средства виртуализации к виртуальной машине посредством интерфейса средства виртуализации.

По сути это означает возможность подключения к графической или терминальной сессии виртуальной машины используя утилиты virt-manager, virsh console или virt-viewer.

Назовём соответствующую группу пользователей libvirt-user и создадим её:

$ sudo groupadd --system libvirt-user

Для подключения к сессии виртуальной машины команды virt-manager и virsh console используют API гипервизора libvirt, ниже представлен минимально необходимый набор разрешений:

  • connect.getattr — подключение к API гипервизора и получение информации о системе;

  • connect.read — получение информации об узле гипервизора;

  • connect.search-domains — получение списка доступных виртуальных машин (используется утилитой virt-manager и командой virsh list);

  • domain.getattr — получение списка виртуальных машин;

  • domain.read — получение информации о виртуальной машине;

  • domain.read-secure — чтение настроек виртуальной машины, связанных с безопасностью (используется утилитой virt-manager);

  • domain.open-device — подключение к каналам, последовательным и параллельным портам виртуальной машины. Данное разрешение требуется для подключения к последовательному терминалу (serial console).

  • domain.open-graphics — подключение к «аппаратному» графическому терминалу виртуальной машины (virt-manager --show-domain-console);

для предоставления этих разрешений группе libvirt-user потребуется внести соответствующие изменения в политику polkit:

libvirtAdminGroup = 'libvirt-admin';

// объявляем переменную, значением которой будет название группы пользователей,
// для которой реализуется роль администратора (пользователя) виртуальных машин
libvirtUserGroup = 'libvirt-user';

// список разрешений libvirt API для роли пользователя виртуальных машин
libvirtUserActions = [
    'connect.getattr',
    'connect.read',
    'connect.search-domains',
    'domain.getattr',
    'domain.read',
    'domain.read-secure',
    'domain.open-device',
    'domain.open-graphics'
];

polkit.addRule(function(action, subject) {
    if (action.id == 'org.libvirt.unix.manage') {
        // разрешить подключение к API гипервизора через Unix сокет группам,
        // указанным в переменных libvirtAdminGroup и libvirtUserGroup
        if (subject.isInGroup(libvirtAdminGroup)
            || subject.isInGroup(libvirtUserGroup)) {
            return polkit.Result.YES;
        } else {
            return polkit.Result.NO;
        }
    } else if (action.id.indexOf('org.libvirt.api.') != 0) {
        return polkit.Result.NOT_HANDLED;
    }

    // заменить префикс libvirt API org.libvirt.api на пустую строку чтобы не
    // приходилось его дублировать в списке разрешённых действий. Таким образом
    // действие org.libvirt.api.connect.read преобразуется в connect.read.
    var api = action.id.replace('org.libvirt.api.', '');

    if (subject.isInGroup(libvirtAdminGroup)) {
        return polkit.Result.YES;
    }
    // разрешить действие пользователю из группы libvirtUserGroup, если это
    // действие перечислено в списке libvirtUserActions
    else if (subject.isInGroup(libvirtUserGroup)
             && libvirtUserActions.includes(api)) {
        return polkit.Result.YES;
    }

    return polkit.Result.NO;
});

Кроме команд virt-manager и virsh console для подключения к виртуальным машинам также может использоваться команда virt-viewer, которая, в отличии от двух других, подключается к виртуальной машине по протоколам VNC/SPICE без использования API libvirt. Соответственно, для ограничения возможности такого подключения администратор средства виртуализации должен либо отключить поддержку протоколов VNC/SPICE, либо установить пароль для подключения и предоставить его только тем пользователям, которые должны иметь возможность такого подключения.

14.13.4. Роль разработчика виртуальной машины

Роль разработчика виртуальной машины должна позволять:

  • создавать виртуальные машины;

  • изменять конфигурации виртуальных машин.

Назовём соответствующую данной роли группу пользователей libvirt-vm-dev и создадим её:

$ sudo groupadd --system libvirt-vm-dev

Как уже было рассмотрено ранее, основным способом создания виртуальных машин в среде виртуализации МСВСфера ОС является использование команды virt-install.

Полный список доступных для использования в политиках polkit разрешений libvirt доступен в разделе «14.12.3. Объекты и разрешения libvirt», а ниже представлен минимальный набор разрешений, необходимый для корректной работы команды virt-install:

  • connect.getattr — подключение к API гипервизора и получение информации о системе;

  • connect.read — получение информации об узле гипервизора;

  • connect.search-storage-pools — получение списка доступных пулов хранения;

  • domain.getattr — получение списка виртуальных машин;

  • domain.read — получение информации о виртуальной машине;

  • domain.save — изменение конфигурационного файла виртуальной машины;

  • domain.start — запуск виртуальной машины;

  • domain.write — изменение виртуальной машины;

  • network.getattr — получение списка сетей;

  • network.read — чтение настроек сети;

  • network-port.create — создание сетевого порта;

  • network-port.delete — удаление сетевого порта;

  • network-port.read — получение информации о сетевом порте;

  • storage-pool.getattr — получение списка пулов хранения;

  • storage-pool.read — получение информации о пуле хранения;

  • storage-pool.refresh — обновление списка томов хранения в пуле;

  • storage-pool.search-storage-vols — получение списка томов хранения в пуле;

  • storage-vol.create — создание тома хранения;

  • storage-vol.getattr — получение списка томов в пуле хранения;

  • storage-vol.read — получение информации о пуле хранения.

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

  • будет невозможно использовать графическую утилиту для создания и настройки виртуальных машин virt-manager;

  • функции выключения и перезагрузки виртуальной машины будут недоступны;

  • в случае ошибки при создании виртуальной машины утилита virt-install не сможет автоматически удалить созданный в процессе том хранения.

Для решения вышеперечисленных проблем рекомендуется добавить следующие разрешения роли разработчика виртуальной машины:

  • domain.init-control — перезагрузка или выключение виртуальной машины;

  • domain.read-secure — чтение настроек виртуальной машины, связанных с безопасностью (используется утилитой virt-manager);

  • domain.open-device — подключение к каналам, последовательным и параллельным портам виртуальной машины. Данное разрешение требуется для подключения к последовательному терминалу (serial console).

  • domain.open-graphics — подключение к «аппаратному» графическому терминалу виртуальной машины (virt-manager --show-domain-console);

  • connect.search-domains — получение списка доступных виртуальных машин (используется утилитой virt-manager и командой virsh list);

  • connect.search-networks — получение списка доступных сетей (функция доступных сетей в virt-manager и работа команды virsh net-list);

  • storage-vol.delete — удаление тома хранения. Является необходимым для автоматической очистки, если операция по созданию виртуальной машины завершилась с ошибкой.

Создав группу пользователей и определившись с необходимыми разрешениями для доступа к API libvirt, доработаем политику polkit чтобы предоставить необходимые полномочия группе:

libvirtAdminGroup = 'libvirt-admin';

// объявляем переменную, значением которой будет название группы пользователей,
// для которой реализуется роль разработчика виртуальных машин
libvirtVMDevGroup = 'libvirt-vm-dev';

libvirtUserGroup = 'libvirt-user';

libvirtUserActions = [
    'connect.getattr',
    'connect.read',
    'connect.search-domains',
    'domain.getattr',
    'domain.read',
    'domain.read-secure',
    'domain.open-device',
    'domain.open-graphics'
];

// список разрешений libvirt API для роли разработчика виртуальных машин. За
// основу берётся список разрешений для пользователя чтобы избежать
// дублирования кода
libvirtVMDevActions = libvirtUserActions.concat([
    'connect.search-storage-pools',
    'connect.search-networks',
    'domain.init-control',
    'domain.save',
    'domain.start',
    'domain.write',
    'network.getattr',
    'network.read',
    'network-port.create',
    'network-port.delete',
    'network-port.read',
    'storage-pool.getattr',
    'storage-pool.read',
    'storage-pool.refresh',
    'storage-pool.search-storage-vols',
    'storage-vol.create',
    'storage-vol.delete',
    'storage-vol.getattr',
    'storage-vol.read'
]);

polkit.addRule(function(action, subject) {
    if (action.id == 'org.libvirt.unix.manage') {
        // разрешить подключение к API гипервизора через Unix сокет группам,
        // указанным в переменных libvirtAdminGroup, libvirtVMDevGroup и
        // libvirtUserGroup
        if (subject.isInGroup(libvirtAdminGroup)
            || subject.isInGroup(libvirtVMDevGroup)
            || subject.isInGroup(libvirtUserGroup)) {
            return polkit.Result.YES;
        } else {
            return polkit.Result.NO;
        }
    } else if (action.id.indexOf('org.libvirt.api.') != 0) {
        return polkit.Result.NOT_HANDLED;
    }

    var api = action.id.replace('org.libvirt.api.', '');

    if (subject.isInGroup(libvirtAdminGroup)) {
        return polkit.Result.YES;
    }
    // разрешить действие пользователю из группы libvirtVMDevGroup если это
    // действие перечислено в списке libvirtVMDevActions
    else if (subject.isInGroup(libvirtVMDevGroup)
             && libvirtVMDevActions.includes(api)) {
        return polkit.Result.YES;
    }
    else if (subject.isInGroup(libvirtUserGroup)
             && libvirtUserActions.includes(api)) {
        return polkit.Result.YES;
    }

    return polkit.Result.NO;
});

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

14.13.5. Роль администратора безопасности средства виртуализации

Роль администратора безопасности средства виртуализации должна позволять:

  • иметь доступ на чтение к журналу событий безопасности средства виртуализации;

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

Для работы с журналом событий безопасности пользователю достаточно иметь права на запуск следующих утилит от имени суперпользователя:

  • /usr/sbin/ausearch — утилита для поиска событий в журналах безопасности службы auditd;

  • /usr/sbin/aureport — утилита для построения отчётов о событиях безопасности на основе журналов безопасности службы auditd.

Документация по использованию данных утилит доступна в руководстве администратора МСВСфера ОС в главе «logging-security».

Как и в случае с ролью администратора средства виртуализации, для предоставления прав на запуск этих команд с полномочиями администратора будет реализован через утилиту sudo.

Группа пользователей, выполняющих роль администратора безопасности средства виртуализации будет называться libvirt-sec-admin, создадим её:

$ sudo groupadd --system libvirt-sec-admin

Далее, добавьте в файл /etc/sudoers.d/libvirt-fstec следующие настройки (вероятно, вам потребуется использовать либо редактор visudo, либо временно изменить права доступа к файлу, поскольку ранее мы установили их в режим только для чтения владельцем и группой — 440):

# запретить участникам группы libvirt-sec-admin вызов любых команд
%libvirt-sec-admin      ALL=(ALL)       !ALL

# разрешить участникам группы libvirt-sec-admin вызов команд /usr/sbin/ausearch
# и /usr/sbin/aureport. При этом команда sudo попросит ввести пароль текущего
# пользователя для авторизации.
%libvirt-sec-admin      ALL=/usr/sbin/ausearch, /usr/sbin/aureport

# если требуется использовать команды /usr/sbin/ausearch и /usr/sbin/aureport
# без пароля, то вместо предыдущей строки используйте следующую:
# %libvirt-sec-admin      ALL=NOPASSWD: /usr/sbin/ausearch, /usr/sbin/aureport

Если для редактирования вы изменяли права доступа к файлу, то восстановите исходные значения:

$ sudo chown root:root /etc/sudoers.d/libvirt-fstec
$ sudo chmod 440 /etc/sudoers.d/libvirt-fstec

Далее, добавьте пользователя, который будет выполнять роль администратора безопасности средства виртуализации в группу libvirt-sec-admin:

# замените "user" на реальное имя пользователя
$ sudo gpasswd -a user libvirt-sec-admin

Если пользователь уже вошёл в систему, то необходимо либо выйти и зайти заново, либо выполнить от его имени команду:

$ newgrp libvirt-sec-admin

Теперь пользователь сможет работать с журналом безопасности используя команды sudo ausearch и sudo aureport.

Если вы хотите предоставить группе libvirt-sec-admin права на чтение файлов журналов службы аудита напрямую, то в конфигурационном файле /etc/audit/auditd.conf необходимо установить значение опции log_group равным имени группы:

log_group = libvirt-sec-admin

И подать службе auditd сигнал перечитать конфигурационный файл:

$ sudo auditctl --signal reload

После этого права на файлы журналов в каталоге /var/log/audit поменяются с -rw-------. 1 root root на -rw-r-----. 1 root libvirt-sec-admin:

$ sudo ls -la /var/log/audit/
drwxr-x---.  2 root libvirt-sec-admin    4096 окт 15 03:30 .
drwxr-xr-x. 16 root root                 4096 ноя 20 11:16 ..
-rw-r-----.  1 root libvirt-sec-admin 5994208 ноя 20 18:59 audit.log

Таким образом пользователи, входящие в группу libvirt-sec-admin, получат возможность выполнять чтение файлов журналов аудита.

Если вам требуется предоставить пользователю доступ к журналам systemd, допустим, для просмотра логов Polkit или гипервизора Libvirt, то вы можете добавить пользователя в системную группу systemd-journal, которая предоставляет такую возможность:

# замените "user" на реальное имя пользователя
$ sudo gpasswd -a user systemd-journal

После этого пользователь получит возможность просматривать системные журналы с помощью команды journalctl.

14.13.6. Регистрация событий запуска и остановки виртуальных машин

Одной из важных особенностей гипервизора libvirt, работающего в системном режиме (см. «14.3.3. Системный режим»), является то, что в системном журнале событий безопасности службы auditd инициатором всех событий, связанных с запуском или остановом виртуальных машин, является пользователь root:

type=VIRT_CONTROL msg=audit(1733316312.117:1249): pid=10098 \
uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:virtd_t:s0-s0:c0.c1023 \
msg='virt=kvm op=stop reason=shutdown vm="msvsphere-9-server" \
uuid=9faaa743-e098-4925-8c4e-8854b88e7a25 vm-pid=0 exe="/usr/sbin/virtqemud" \
hostname=? addr=? terminal=? res=success'

Соответственно, из системного журнала невозможно узнать какой конкретно пользователь изменил состояние виртуальной машины.

Однако, для решения этой задачи можно применить политику polkit. Реализуем следующую функцию, которая будет выводить название и идентификатор виртуальной машины, а также имя пользователя, который вызвал изменение состояния виртуальной машины через API:

function logVMActions(action, subject, api) {
    var vmName = action.lookup('domain_name');
    var vmUUID = action.lookup('domain_uuid');

    var actionText;

    switch(api) {
    case 'domain.delete':
        actionText = 'deletion';
        break;
    case 'domain.init-control':
        actionText = 'reboot or shutdown';
        break;
    case 'domain.start':
    case 'domain.stop':
        actionText = api.split('.')[1];
        break;
    case 'domain.save':
    case 'domain.write':
        actionText = 'modification';
        break;
    }

    if (actionText) {
        polkit.log(`virtual machine "${vmName}" (UUID="${vmUUID}") ${actionText} initiated by "${subject.user}" user`);
    }
}

И добавим вызов этой функции перед возвратом из функции, осуществляющей проверку прав доступа:

polkit.addRule(function(action, subject) {
    if (action.id == 'org.libvirt.unix.manage') {
        if (subject.isInGroup(libvirtAdminGroup)
            || subject.isInGroup(libvirtVMDevGroup)
            || subject.isInGroup(libvirtUserGroup)) {
            return polkit.Result.YES;
        } else {
            return polkit.Result.NO;
        }
    } else if (action.id.indexOf('org.libvirt.api.') != 0) {
        return polkit.Result.NOT_HANDLED;
    }

    var api = action.id.replace('org.libvirt.api.', '');
    var result = polkit.Result.NO;

    if (subject.isInGroup(libvirtAdminGroup)) {
        result = polkit.Result.YES;
    } else if (subject.isInGroup(libvirtVMDevGroup)
               && libvirtVMDevActions.includes(api)) {
        result = polkit.Result.YES;
    } else if (subject.isInGroup(libvirtUserGroup)
               && libvirtUserActions.includes(api)) {
        result = polkit.Result.YES;
    }

    // вызвать функцию логирования изменения состояния виртуальной машины в
    // случае предоставления доступа к API
    if (result == polkit.Result.YES) {
        logVMActions(action, subject, api);
    }

    return result;
});

Теперь при каждом изменении состояния или конфигурации виртуальной машины в журнал службы polkit (journalctl -u polkit) будет попадать соответствующее сообщение:

дек 04 15:47:40 msvsphere-94-arm.msvsphere.test polkitd[961]: \
<no filename>:189: virtual machine "msvsphere-9-server" \
(UUID="9faaa743-e098-4925-8c4e-8854b88e7a25") start initiated by "virtadmin" user

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

14.13.7. Ограничение прав доступа к виртуальным машинам

Гипервизор libvirt, используемый в МСВСфера ОС, не предоставляет готового решения для проблемы ограничения прав доступа к виртуальным машинам, поскольку каждая организация использует свои правила и политики доступа.

Однако, поскольку для реализаций политик polkit используется язык программирования JavaScript, не составляет труда реализовать практически любую логику внутри политики.

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

Для каждого отдела создадим соответствующую группу и добавим туда всех сотрудников:

# создаём группу для разработчиков devs и добавляем туда пользователей devuser1
# и devuserN
$ sudo groupadd -U devuser1,devuserN devs

# создаём группу для тестировщиков qa и добавляем туда пользователей qauser1 и
# qauserN
$ sudo groupadd -U qauser1,qauserN qa

Не забудьте добавить пользователей в группу libvirt-user чтобы предоставить им права на подключения к виртуальным машинам.

Теперь реализуем функцию для проверки доступа к виртуальным машинам:

function checkUserVMAccess(action, subject) {
    // сохраняем название виртуальной машины в переменную
    var vmName = action.lookup('domain_name');
    // если имя не определено, то разрешаем доступ — будут применяться
    // ограничения роли
    if (!vmName) {
        return true;
    }

    // если название виртуальной машины начинается с "dev-" и пользователь
    // входит в группу devs, то разрешаем доступ
    if (vmName.startsWith('dev-') && subject.isInGroup('devs')) {
        return true;
    }
    // если название виртуальной машины начинается с "qa-" и пользователь
    // входит в группу qa, то разрешаем доступ
    else if (vmName.startsWith('qa-') && subject.isInGroup('qa')) {
        return true;
    }
    // запрещаем доступ в остальных случаях
    return false;
}

И включим эту функцию в общую логику проверки для роли пользователя виртуальных машин:

polkit.addRule(function(action, subject) {
    if (action.id == 'org.libvirt.unix.manage') {
        if (subject.isInGroup(libvirtAdminGroup)
            || subject.isInGroup(libvirtVMDevGroup)
            || subject.isInGroup(libvirtUserGroup)) {
            return polkit.Result.YES;
        } else {
            return polkit.Result.NO;
        }
    } else if (action.id.indexOf('org.libvirt.api.') != 0) {
        return polkit.Result.NOT_HANDLED;
    }

    var api = action.id.replace('org.libvirt.api.', '');
    var result = polkit.Result.NO;

    if (subject.isInGroup(libvirtAdminGroup)) {
        result = polkit.Result.YES;
    } else if (subject.isInGroup(libvirtVMDevGroup)
               && libvirtVMDevActions.includes(api)) {
        result = polkit.Result.YES;
    }
    // разрешить пользователю доступ к виртуальной машине если проверка ролевых
    // полномочий завершилась успешно и функция checkUserVMAccess вернула true
    else if (subject.isInGroup(libvirtUserGroup)
               && libvirtUserActions.includes(api)
               && checkUserVMAccess(action, subject)) {
        result = polkit.Result.YES;
    }

    if (result == polkit.Result.YES) {
        logVMActions(action, subject, api);
    }

    return result;
});

В результате применения новой политики обычные пользователи средства виртуализации получат доступ к виртуальной машине только в следующих случаях:

  • пользователь входит в группы libvirt-user и devs, название виртуальной машины начинается с префикса dev-;

  • пользователь входит в группы libvirt-user и qa, название виртуальной машины начинается с префикса qa-.

Для пользователей, входящих в группы libvirt-admin (администратор средства виртуализации) и libvirt-vm-dev (разработчик виртуальных машин), дополнительные ограничения применяться не будут.

14.13.8. Итоговая реализация в МСВСфера ОС

Описанная выше реализация ролевой модели на базе политик polkit поставляется в RPM-пакете libvirt-fstec. Перед его установкой вам необходимо включить драйвер контроля доступа polkit в настройках гипервизора — данная процедура описана в разделе «14.12.2.1. Включение драйвера контроля доступа polkit».

После включения поддержки polkit установите пакет libvirt-fstec с помощью следующей команды:

$ sudo dnf install -y libvirt-fstec

Пакет создаст в вашей системе группы libvirt-vm-dev, libvirt-admin, libvirt-user, libvirt-sec-admin и файл /etc/sudoers.d/libvirt-fstec, содержащий описанные в этой главе правила sudo для реализации ролевой модели.

Так же в каталоге /etc/polkit-1/rules.d будут созданы следующие файлы:

  • 97-libvirt-fstec-vars.rules — в этом файле объявляются названия групп, соответствущие ролям, а также список действий, разрешённых для ролей разработчика и пользователя виртуальной машины.

  • 99-libvirt-fstec.rules — реализация правил для контроля доступа к API libvirt.

Вам, как системному администратору, потребуется самостоятельно создать файл /etc/polkit-1/rules.d/98-libvirt-user-rules и определить в нём собственную реализацию описанных ранее в этой главе функций logVMActions и checkUserVMAccess, соответствующую политике безопасности вашего предприятия и решаемой задаче. Реализация ролевой модели управления доступом сознательно разделена на несколько файлов, чтобы вы могли вносить изменения, не нарушая при этом целостность файлов RPM-пакета libvirt-fstec.

В качестве стартовой точки вы можете создать файл /etc/polkit-1/rules.d/98-libvirt-user-rules следующего содержания:

function logVMActions(action, subject, api) {
    var vmName = action.lookup('domain_name');
    var vmUUID = action.lookup('domain_uuid');

    var actionText;

    switch(api) {
    case 'domain.delete':
        actionText = 'deletion';
        break;
    case 'domain.init-control':
        actionText = 'reboot or shutdown';
        break;
    case 'domain.start':
    case 'domain.stop':
        actionText = api.split('.')[1];
        break;
    case 'domain.save':
    case 'domain.write':
        actionText = 'modification';
        break;
    }

    if (actionText) {
        polkit.log(`virtual machine "${vmName}" (UUID="${vmUUID}") ${actionText} initiated by "${subject.user}" user`);
    }
}

function checkUserVMAccess(action, subject) {
    // в этой функции вам необходимо реализовать собственные правила проверки
    // доступа к виртуальной машине для пользователей средства виртуализации
    return true;
}

И доработать функцию checkUserVMAccess по своему усмотрению.

В случае необходимости, используя файл 98-libvirt-user-rules вы также можете предоставить пользователям дополнительные права доступа к API-вызовам гипервизора, например:

libvirtUserActions.push(...[
    'domain.start',
    'network.getattr',
    'network.read',
    'network-port.create',
    'network-port.delete',
    'network-port.read',
]);