Розробка SELinux-модуля для програми

Давним-давно, у далекій-далекій країні
… державна служба NSA розробила систему безпеки для ядра і оточення Linux, і назвала її SELinux. І з тих пір люди розділилися на дві категорії: disabled/permissive і enforcing. Сьогодні я покажу вам шлях Сили і переведу на іншу сторону всіх бажаючих.

Припущення
У тексті буде міститися багато технічної інформації, тому автор припускає, що читач:

  • Має якесь додаток (демон), який має працювати з SELinux
  • Переглянув різницю між DAC, MAC і RBAC
  • Знайомий з адмініструванням Linux
  • щось читав про SELinux і може розшифрувати user_u:user_r:user_home_t:s0
  • Має під рукою CentOS 7
  • На якому встановлені пакети setools-console, policycoreutils-devel, selinux-policy-devel
  • І включений SELinux в режимі permissive з політикою targeted або minimum
Це все про вас? Тоді поїхали!

Базові типи
В якості піддослідного я взяв jnode — досить типове додаток, яке спілкується по мережі, ходить в базу, читає конфіги, пише свої дані і tmp-файли та моніторить свій стан (cpu, mem, disk).

Створимо файл jnode.te (te = Type Enforcement)

З чого потрібно почати писати модуль? З опису базових типів:

policy_module(jnode, 1.0.0)
# тип для процесу
type jnode_t;
# тип для виконуваного файлу
type jnode_exec_t;
# тип для конфіг-файлів
type jnode_conf_t;
# тип для кеша ( аналог /var/cache/ )
type jnode_cache_t;
# тип для лог-файлу
type jnode_log_t;
# тип для тимчасових файлів
type jnode_tmp_t;
# тип порту, який слухає jnode ( протокол binkp )
type binkp_port_t;

Чому так багато? Тому, що це різні категорії доступу для системи, наприклад:

  • jnode_exec_t буде виконуваним для всіх і до нього будуть застосовані правила переходу типів.
  • jnode_t буде типом процесу і саме на нього будуть вішатися всі дозволи.
  • jnode_conf_t r/o для програми і r/w для адміністратора.
  • jnode_cache_t буде append_only для приоложения і r/w для адміністратора.
  • jnode_log_t буде append_only для програми і r/w для syslog/logrotate/journald
  • jnode_tmp_t r/w для застосування і denied для всіх інших.
  • binkp_port_t потрібен для керування портами, які може слухати додаток.
Ліричний відступ
Теоретично, майже на цьому місці ( додавши лише правило переходу ) можна припинити писати модуль, скомпілювати його, встановити в систему і промаркувати файлову систему за допомогою chcon. Після цього зібрати логи за допомогою утиліти audit2allow і отримати кілька сотень абсолютно незрозумілих рядків, які будуть щось вирішувати. З них в подальшому вийде модуль, який навіть буде працювати. Але розуміння це вам не додасть, чи не так?

Тому я пропоную інший шлях: читати відмінності файли і там вибирати те, що вам потрібно. У /usr/share/selinux/devel/include/ можна знайти кілька сотень .if-файлів, в яких містяться стандартні макроси базової політики SELinux. На жаль, вам доведеться використовувати grep і cat самостійно, я лише покажу кілька основних макросів і те, як вони полегшують життя.

Макроси атрибутів
Для полегшення життя в SELinux існує поняття attribute — нікого контейнера типів, (к) якого теж можна призначати права доступу. Таким образів, додавши свій новий тип в той чи інший аттрибут ми автоматично видаємо йому стандартні права для цього атрибута. Щоб не запам'ятовувати всі ці атрибути є вже готові макроси, які розмічають типи за атрибутами (часто з кількох). Дивіться:

# це конфіг-файли
files_config_file(jnode_conf_t)
# це якісь файли
files_type(jnode_cache_t)
# це лог-файли
logging_log_file(jnode_log_t)
# це тимчасові файли
files_tmp_file(jnode_tmp_t)
# а це порт
corenet_port(binkp_port_t)

Макроси стандартних дозволів
Коли визначені типи, можна призначити стандартне поведінки для застосування. Для цього теж скористаємося макросами, вони досить легко знаходяться за ключовими словами і роблять код человекочитаемым:

# Макрос додатки: додає тип jnode_t в список додатків
# і дозволяє йому стартувати з типу jnode_exec_t
application_domain(jnode_t, jnode_exec_t)
# Макрос демона: додає тип jnode_t в список демонів,
# дозволяє його запускати через systemd
# і призначає перехід: якщо systemd запустить файл з типом jnode_exec_t,
# то процес отримає тип jnode_t
init_daemon_domain(jnode_t, jnode_exec_t)
# дозволяє типу jnode_t виконувати стандартні бінарники ( /bin, /usr/bin )
corecmd_exec_bin(jnode_t)
# дозволяє типу jnode_t підключати бібліотеки
libs_use_ld_so(jnode_t)
# дозволяє типу jnode_t читати стан системи ( cpu, memory )
kernel_read_system_state(jnode_t)
# дозволяє типу jnode_t писати в /tmp
files_rw_generic_tmp_dir(jnode_t)
# дозволяє типу jnode_t читати конфіг мережі ( /etc/resolv.conf ітд )
sysnet_read_config(jnode_t)
# дозволяє типу jnode_t отримувати випадкові числа з /dev/(u)random
dev_read_rand(jnode_t)
# дозволяє типу jnode_t отримувати аттрібути файлової системи ( вільне місце )
fs_getattr_xattr_fs(jnode_t)
# дозволяє типу jnode_t робити dns resolve
sysnet_dns_name_resolve(jnode_t)
# дозволяє типу jnode_t ходити в /var/log ( r/o )
logging_search_logs(jnode_t)
# призначає правило: логи, що створює процес jnode_t, 
# матимуть тип jnode_log_t
logging_log_filetrans(jnode_t, jnode_log_t, file)
# призначає правило: tmp-файли, які створює процес jnode_t, 
# матимуть тип jnode_tmp_t
files_poly_member_tmp(jnode_t, jnode_tmp_t)
# дозволяє jnode_t робити bind() на будь-яку адресу
corenet_tcp_bind_generic_node(jnode_t)
# дозволяє jnode_t спілкуватися з postgresql unix-сокету
postgresql_stream_connect(jnode_t)
# дозволяє jnode_t спілкуватися з postgresql по мережі
corenet_tcp_connect_postgresql_port(jnode_t)

Файл контекстів
Тепер прийшла пора прив'язати створені типи до файлової системи. Створимо файл jnode.fc ( fc = File Context ).

# виконуваний файл
/opt/jnode/jnode.run -- gen_context(system_u:object_r:jnode_exec_t)
# все що r/o для сервісу назвемо "конфіг"
/opt/jnode(/.*)? gen_context(system_u:object_r:jnode_conf_t)
/opt/jnode/jar(/.*) gen_context(system_u:object_r:jnode_conf_t)
# і окремо самі конфіги
/opt/jnode/point/.*\.cfg gen_context(system_u:object_r:jnode_conf_t)
# сюди сервіс зможе додавати файли ( але не видаляти )
/opt/jnode/fileechoes(/.*)? gen_context(system_u:object_r:jnode_cache_t)
/opt/jnode/point(/.*)? gen_context(system_u:object_r:jnode_cache_t)
# тут будуть з'являтися і зникати тимчасові файли і папки
/opt/jnode/(inbound|temp)(/.*)? gen_context(system_u:object_r:jnode_tmp_t)
# а сюди будуть писатися логи
/var/log/jnode(/.*)? gen_context(system_u:object_r:jnode_log_t)

Збірка і установка
Створимо яку-небудь папку і покладемо туди файли jnode.te та jnode.fc.
Перейдемо туди і виконаємо складання:

[root@jnode jnode]# make -f /usr/share/selinux/devel/Makefile 
Compiling targeted jnode module
/usr/bin/checkmodule: loading policy configuration from tmp/jnode.tmp
/usr/bin/checkmodule: policy configuration loaded
/usr/bin/checkmodule: writing binary representation (version 17) to tmp/jnode.mod
Creating targeted jnode.pp policy package
rm tmp/jnode.mod.fc tmp/jnode.mod

Встановимо модуль командою semodule -i jnode.pp і включимо його командою semodule -e jnode.

Призначимо номер порту для типу binkp_port_t: semanage port -a -t binkp_port_t -p tcp 24554.

Тепер необхідно перепризначити контексти у відповідності з файлом контекстів:
restoreconn -Rv /opt/jnode. Запускаємо сервіс через systemctl і починаємо чекати.

Фінальний audit2allow
Через деякий час (годину, добу — залежить від активності сервісу) можна виконати команду audit2allow -b -r -t jnode_t і подивитися, що ще додаток просить крім того, що йому вже було дано. Дозволів вийде трохи — може 10-15 рядків, причому не всі з них йому реально потрібні. Тут вже вирішувати вам — що залишити, а що прибрати. У «непотрібної» частини замініть allow на dontaudit — це позбавить від повторюваного сміття в логах. До речі, оновити версію модуля — це дозволить ядру зрозуміти, що його потрібно оновити.

setenforce 1
Коли audit2allow покаже «порожньо» — це означає, що все працює за планом і можна включати enforcing. Вітаю, ви знайшли Силу. Розпоряджайтеся нею з розумом.

Корисні посилання
selinuxproject.org/page/AVCRules — опис allow-правил
selinuxproject.org/page/TypeRules — опис type_ правил
selinuxproject.org/page/ObjectClassesPerms — список класів та дозволів доступів
danwalsh.livejournal.com — блог «батька» SELinux в RedHat та самої refpolicy
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.