Portál AbcLinuxu, 8. května 2025 14:05
Po krátké exkurzi útrobami SELinuxu se podíváme na praktickou ukázku psaní vlastního modulu politiky. Tato znalost je nezbytná pro údržbu SELinuxového systému. Je to také počítačové vyjádření naší představy o tom, jak má vypadat zabezpečení našeho systému; tuto představu bychom měli mít zcela jasně před sebou pokaždé, když sedneme ke své klávesnici.
V klasickém UNIXu byla bezpečnostní politika více méně tvořena
devíticí bitů ve stylu rwxr-x---
u souborů plus nějaké
čachry se setuid a několika statickými pravidly typu „raw
socket smí otevřít pouze root“. Tento systém nedával příliš mnoho
možností, jak politiku měnit: Změna práv souborů příkazy
chmod
a chown
, případně nastavení
seteuid/setegid bitů pomocí stejných příkazů. Typickými příklady UNIXové
bezpečnostní politiky jsou:
utmp
a wtmp
udržují informace
o právě přihlášených uživatelích, respektive o historii jejich
přihlášení a odhlášení. Přístup k těmto souborům je omezen
tak, že práva k těmto souborům jsou rw-rw-r--
pro
uživatele root
a skupinu wtmp
. Pouze
několik programů (přihlašovací a emulátory terminálu) mají
nastaveno spouštění jako setegid wtmp
a tyto programy
manipulují se soubory utmp
/wtmp
předem daným
způsobemnamed
a skupinou named
, přístup k jeho datům (DNS
zónám) je nastaven tak, že zapisovat může pouze administrátor DNS
a DNS server pouze číst
(rw-r----- dnsadmin:named
). Podobně, web server běží
pod vlastním uživatelem a skupinou, s podobným nastavením práv
k datům. DNS a web server tak navzájem nemohou číst svoje data
a DNS a web administrátoři si nemohou navzájem svoje data
přepsatPři tvoření modulu politiky pro SELinux budeme používat podobné uvažování, pouze s pokročilejšími nástroji.
Máme na serveru SVN repozitář našeho nejnovějšího projektu. Přístup
uživatelů očekáváme pomocí protokolu svn+ssh, tj. uživatelé jsou vedeni
v systému, přihlašují se pomocí ssh, které pod jejich účtem spustí
program svnserve
pro práci s repozitářem. Chceme
izolovat SVN repozitář a zbytek systému, přičemž spojovacím můstkem
bude program svnserve
. Požadavky tedy jsou:
svnserve
tak, aby mohla provádět pouze
nezbytně nutné operace – práce s repozitářem a nic
víc.svnserve
.Začneme s velmi jednoduchou kostrou:
[root@localhost svn_policy]# touch svn.te svn.if svn.fc [root@localhost svn_policy]# cat >>svn.te policy_module(svn,1.0.0) ^D [root@localhost svn_policy]# ls svn.fc svn.if svn.te [root@localhost svn_policy]# make -f /usr/share/selinux/devel/Makefile Compiling targeted svn module /usr/bin/checkmodule: loading policy configuration from tmp/svn.tmp /usr/bin/checkmodule: policy configuration loaded /usr/bin/checkmodule: writing binary representation (version 10) to tmp/svn.mod Creating targeted svn.pp policy package rm tmp/svn.mod.fc tmp/svn.mod [root@localhost svn_policy]# semodule -i svn.pp [root@localhost svn_policy]# semodule -l | grep svn svn 1.0.0
Tato ukázka je z distribuce Fedora 11, bude ale fungovat na spoustě
dalších. Direktivou policy_module
říkáme kompilátoru
politiky, jak se náš modul jmenuje, případně jakou má verzi.
Jak bylo řečeno v minulém díle, při překladu jsou naše soubory
různě zpracovány programem m4
a výsledek je předhozen
kompilátoru politiky (checkmodule
). Mezivýsledek mezi
m4
a checkmodule
si můžeme prohlédnout ve
zmíněném souboru tmp/svn.tmp
, výsledek po veškeré kompilaci
je pak svn.pp
.
Dobrá zpráva je, že se náš modul podařilo přeložit. Špatná zpráva je, že nesplňuje požadavky, takže na oběd ještě nejdeme.
Podobně, jako bychom v klasickém UNIXu vytvořili dedikovaného
uživatele, vytvoříme dedikovaný typ (doménu). Do souboru
svn.te
napíšeme:
type svn_t; type svn_exec_t; application_domain(svn_t, svn_exec_t)
Tím zadefinujeme typy pro běžící aplikaci svnserve
a její obraz na disku (spustitelný soubor). Makro
application_domain
zajistí hned několik věcí, které si
zhruba popíšeme níže. Konkrétní obsah můžeme dohledat ve zdrojových
souborech referenční politiky nebo nějakým auditovacím nástrojem
(sesearch
, apol
).
Další řádek přijde do souboru svn.fc
, kde zadefinujeme
kontext pro soubor svnserve
:
/usr/bin/svnserve gen_context(system_u:object_r:svn_exec_t,s0)
Po kompilaci a instalaci modulu tento kontext nastavíme:
[root@localhost svn_policy]# restorecon /usr/bin/svnserve [root@localhost svn_policy]# ls -lZ /usr/bin/svnserve -rwxr-xr-x. root root system_u:object_r:svn_exec_t:s0 /usr/bin/svnserve
Je dobré si povšimnout, co všechno za nás makro
application_domain
zařídilo: Můžeme jako administrátor
změnit kontext souboru, jako uživatel nad ním provést ls
,
spouštět, a dokonce i číst. Pokud bychom pouze zadefinovali
typ, nemohli bychom nad ním provést žádnou operaci (pokud nejsme
„unconfined“ uživatel, což budeme předpokládat, že nejsme).
Mezi těmito dvěma stavy existuje samozřejmě několik mezistupňů,
například soubor sshd
, mající typ sshd_exec_t
,
není přístupný uživatelům ani pro prohlížení atributů pomocí
ls
.
Jak bylo řečeno, program svnserve
můžeme nyní spouštět dle
libovůle, ovšem pod typem, ve kterém běží náš shell (např.
sysadm_t
). Pro spuštění pod typem svn_t
je
potřeba použít pravidlo přeměny typu. Pro tento účel si vyrobíme makro
v souboru svn.if
:
## <summary>svn policy</summary> ## <desc> ## <p> ## subversion policy for selinux ## </p> ## </desc> # ######################################## ## <summary> ## Execute a domain transition to run svn. ## </summary> ## <param name="domain"> ## Domain allowed to transition. ## </param> ## <param name="role"> ## Role allowed to transition. ## </param> # interface(`svn_domtrans',` gen_require(` type svn_t, svn_exec_t, $1; role $2; ') domtrans_pattern($1,svn_exec_t,svn_t) role $2 types svn_t; ')
A zavoláme jej ze souboru svn.te
. V našem
příkladu budeme transformovat z běžné uživatelské role
user_r
. Je ale možné pro tento účel vytvořit vlastní roli.
svn_domtrans(user_t, user_r)
Přeložíme, přihlásíme se jako uživatel s rolí user_r
.
Zkusíme spustit svnserve
:
[a@localhost ~]$ svnserve [a@localhost ~]$
Program na rozdíl od předchozího případu nevypsal žádnou nápovědu a pokud se podíváme do logu, najdeme několik hlášek od AVC, například:
type=AVC msg=audit(1254851349.524:227): avc: denied { read write } for pid=18512 comm="svnserve" name="tty1" dev=tmpfs ino=474 scontext=user_u:user_r:svn_t:s0 tcontext=user_u:object_r:user_tty_device_t:s0 tclass=chr_file
Což znamená, že se podařilo úspěšně přeměnit typ procesu na
svn_t
, a tento nový typ nemá dostatečná oprávnění pro
zápis na náš terminál. Vlastně v různých verzích politiky (tj. na
různých distribucích) narazíme na různá omezení. V Debianu Lenny nemá
nově vytvořený typ pro aplikace ani právo na otevření dynamického
linkeru (ld.so
), tudíž se ani nespustí. Takto SELinux
funguje – co není povoleno, je zakázáno, a stěží bychom
hledali argument pro tvrzení, že všechny možné aplikace potřebují
zapisovat na terminál uživatele.
Další fáze tedy spočívá v iterativním povolování různých akcí, nejlépe tak, že najdeme vhodné makro v rejstříku a/nebo zdrojových kódech referenční politiky. Začneme s
libs_use_ld_so(svn_t) libs_use_shared_libs(svn_t) term_use_all_terms(svn_t) domain_use_interactive_fds(svn_t)
Zjistíme, že aplikace má problémy se soubory lokalizace, najdeme tedy makro, které povoluje jejich čtení:
miscfiles_read_localization(svn_t)
Dále se aplikaci nedaří zjistit, kdo jsme (uživatelské jméno).
Standardní mechanizmus je přes NSS, což vyžaduje čtení
/etc/nsswitch.conf
, obvykle následované čtením
/etc/passwd
.
files_read_etc_files(svn_t)
Uvedené makro sice povoluje čtení spousty věcí v /etc
navíc, obsah těchto souborů ale nemá bezpečnostní následky. (Například
/etc/shadow
do působnosti tohoto makra nespadá.) Některé
instalace můžou využívat další databáze uživatelů (např. LDAP), pak
musíme povolit i příslušné interakce s LDAP serverem.
V mém konkrétním případě je před LDAP ještě strčen démon NSCD,
takže povoluji interakci s ním:
nscd_socket_use(svn_t)
Aplikace často potřebují dočasné soubory, obvykle v adresáři
/tmp
. Typické nastavení politiky vytvoří k dané
aplikační doméně (u nás svn_t
) souborový typ pro
dočasné soubory (svn_tmp_t
) a pomocí pravidel přeměny
typů nařídí používání tohoto privátního typu pro dočasné soubory. Tím se
zajistí, že ostatní aplikace nebudou mít k dočasným souborům
přístup, i kdyby v rámci klasického UNIXu tento přístup byl
povolen (např. naše zákeřné getty běžící pod rootem).
type svn_tmp_t; files_tmp_file(svn_tmp_t) manage_dirs_pattern(svn_t, svn_tmp_t, svn_tmp_t) manage_files_pattern(svn_t, svn_tmp_t, svn_tmp_t) files_tmp_filetrans(svn_t, svn_tmp_t, { file dir })
Při přímém přístupu přes ssh, kde se nebude alokovat interaktivní
terminál, narazíme ještě na problém, že náš proces typu svn_t
nebude mít povoleno zapisovat do roury, která vede ze ssh démona na jeho
standardní výstup (obdobně pro čtení ze standardního vstupu).
V době psaní jsem nenarazil na příhodné makro, ale v zásadě se
nic neděje, pokud věci napíšeme bez makra:
require { type sshd_t; class fifo_file { read write }; } allow svn_t sshd_t : fifo_file { read write getattr };
Posledním úskalím, které není až tak podstatné, ale bude generovat
zbytečné hlášky v logu, je poslání signálu SIGCHLD ssh démonovi,
pokud náš proces svnserve
skončí.
ssh_sigchld(svn_t)
Po přeložení a nainstalování této politiky bychom měli být schopni bez
problémů spustit svnserve
z role user_r
a vidět nápovědu; a to jak interaktivně, tak přímo přes ssh.
[a@localhost ~]$ svnserve You must specify exactly one of -d, -i, -t or -X. Type 'svnserve --help' for usage. [a@localhost ~]$ ssh a@0 svnserve a@0's password: You must specify exactly one of -d, -i, -t or -X. Type 'svnserve --help' for usage. [a@localhost ~]$
Tím bychom měli hotové rozchození aplikace v omezené doméně. Tento
postup budeme jistě v praxi aplikovat ještě mnohokrát při omezování
dalších aplikací. Jak bylo řečeno, jde o podobný princip, jako
v klasickém UNIXu založit dedikovaného uživatele pro nějakou
aplikaci. Rozdíl je ale v tom, že v UNIXu budou mít procesy
spuštěné pod tímto uživatelem automaticky mnohem širší práva, než jaká
jsme zadefinovali pro typ svn_t
; namátkou přístup
k síti nebo možnost spouštění jiných procesů. Dále, pokud bychom
přeměnu typu procesu z user_t
na svn_t
při spuštění svnserve
přirovnali k UNIXovému
mechanizmu seteuid/setegid, bude mít v UNIXu spuštěná aplikace
stále přístup ke všem datům uživatele, který ji spustil; typ procesu
svn_t
nemá ale bez povolení přístup ani k našemu
terminálu, natož pak k datům.
Mluvíme-li o datech, zbývá ještě zadefinovat typ pro soubory
repozitáře a přidělit k němu přístup pro
svnserve
. Do souboru svn.te
připíšeme:
type svndata_t; files_type(svndata_t); svn_manage_data(svn_t)
Přičemž makro svn_manage_data
si zavedeme jednoduše
v svn.if
takto:
######################################## ## <summary> ## Manage svn data files (repos). ## </summary> ## <param name="domain"> ## Domain allowed access. ## </param> # interface(`svn_manage_data',` gen_require(` type svndata_t, $1; class file { manage_file_perms }; class dir { manage_dir_perms }; ') allow $1 svndata_t : file { manage_file_perms }; allow $1 svndata_t : dir { manage_dir_perms }; ')
A v souboru svn.fc
zadefinujeme příslušný kontext
pro adresář, kde budou repozitáře:
/data/svn(/.*)? gen_context(system_u:object_r:svndata_t,s0)
Po přeložení a instalaci modulu můžeme jako administrátor pomocí
svnadmin
repozitář vyrobit a nastavit mu kontext.
[root@localhost ~]# mkdir -p /data/svn [root@localhost ~]# svnadmin create /data/svn/gaussgun [root@localhost ~]# restorecon -R /data/svn [root@localhost ~]# ls -lZa /data drwxr-xr-x. root root root:object_r:default_t:s0 . drwxr-xr-x. root root system_u:object_r:root_t:s0 .. drwxr-xr-x. root root system_u:object_r:svndata_t:s0 svn
Typ default_t
, který se přiřadil adresáři
/data
, bude dělat neplechu při pokusu o přístup. Je to
„catch-all“ typ, ke kterému nemá mimo administrátora nikdo
přístup. Pro adresáře kořenové struktury se více hodí
root_t
:
[root@localhost ~]# semanage fcontext -a -t root_t /data [root@localhost ~]# restorecon /data [root@localhost ~]# ls -laZ /data drwxr-xr-x. root root system_u:object_r:root_t:s0 . drwxr-xr-x. root root system_u:object_r:root_t:s0 .. drwxr-xr-x. root root system_u:object_r:svndata_t:s0 svn
Nyní už můžeme jako uživatel zkusit první checkout:
[a@localhost ~]$ svn co svn+ssh://a@localhost/data/svn/gaussgun a@localhost's password: a@localhost's password: Checked out revision 0.
(Dvojitý prompt pro heslo je vlastnost SVN.)
Můžeme si také ověřit, že konvenční cestou nemáme k repozitáři přístup:
[a@localhost ~]$ ls -lZ /data ls: cannot access /data/svn: Permission denied ?--------- ? ? svn [a@localhost ~]$ ls /data/svn/gaussgun ls: cannot access /data/svn/gaussgun: Permission denied
Vše tedy zatím funguje, jak má. Pokud ale zkusíme commit, narazíme na problém.
[a@localhost gaussgun]$ echo 'specifikace gauss gun' > spec.txt [a@localhost gaussgun]$ svn add spec.txt A spec.txt [a@localhost gaussgun]$ svn commit -m 'prvni nastrel specifikace' a@localhost's password: Adding spec.txt Transmitting file data .svn: Commit failed (details follow): svn: Can't open file '/data/svn/gaussgun/db/txn-current-lock': Permission denied
Tentokrát není problém v SELinuxu, ale v UNIXových právech. Soubory v repozitáři patří rootovi, a tudíž je uživatel nemůže přepsat. Vyrobíme unixovou skupinu svn, do ní přidáme potřebné uživatele a nastavíme práva např. takto:
[root@localhost svn]# chown -R .svn /data/svn/gaussgun [root@localhost svn]# chmod -R o-rwx /data/svn/gaussgun [root@localhost svn]# chmod -R g+wX /data/svn/gaussgun/db [root@localhost svn]# ls -laZ /data/svn/gaussgun/ drwxr-x---. root svn system_u:object_r:svndata_t:s0 . drwxr-xr-x. root root system_u:object_r:svndata_t:s0 .. drwxr-x---. root svn system_u:object_r:svndata_t:s0 conf drwxrws---. root svn system_u:object_r:svndata_t:s0 db -r--r-----. root svn system_u:object_r:svndata_t:s0 format drwxr-x---. root svn system_u:object_r:svndata_t:s0 hooks drwxr-x---. root svn system_u:object_r:svndata_t:s0 locks -rw-r-----. root svn system_u:object_r:svndata_t:s0 README.txt
Nyní bude vše fungovat, jak má. SVN umí pracovat s UNIX právy,
takže nové revize budou mít správná práva, a dědičnost typů se
postará o to, aby nové soubory měly typ svndata_t
.
[a@localhost gaussgun]$ svn commit -m 'prvni nastrel specifikace' a@localhost's password: Adding spec.txt Transmitting file data . Committed revision 1.
Podařilo se nám vytvořit netriviální modul politiky, který definuje
pravidla pro aplikaci svnserve
a data v SVN
repozitářích pod /data/svn
. Zdrojové kódy modulu mají asi
100 řádků včetně dokumentace.
Podobným principem lze omezovat libovolnou aplikaci, pouze ve fázi iterativního přidávání práv se budou různé aplikace lišit. Tato fáze se dá obecně použít i při opravě existující (ale nefungující) politiky. Obecný postup je:
strace
zjistit konkrétní příčinu problému.audit2allow
.Náš modul politiky splňuje naše původní požadavky. Tyto požadavky ale vyšly z nějakých předpokladů a potřeb. Je vhodné si čas od času ověřit, zda tyto stále odpovídají realitě. Pro různé jiné situace nemusí být naše politika dostatečná. Možné náměty na vylepšení by mohly být:
svnadmin
, aby
správci SVN repozitářů nemuseli být správci systému.svnserve
a nedovoluje obecný přístup do shellu.Úspěšné vytvoření vlastního modulu politiky v rozsahu, jako je náš modul pro SVN, lze pokládat za ukončení „základního výcviku“ práce se SELinuxem. Měli bychom být schopni nainstalovat, nakonfigurovat a spravovat SELinuxový systém, chápat jeho vnitřní funkce a případně jej tu a tam doladit. Zbývá snad pouze dodat, že polévka Gazpacho se jí studená. V příštím díle se vydáme dále, konkrétně se podíváme na koncepty MCS a MLS.
Článek vznikl za podpory ČVUT FEL, Katedra kybernetiky, kde jsou k dispozici, mimo jiné, studijní programy Otevřená informatika a Kybernetika a robotika.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.