POSIX ACL

A Unix/Linux szerverek üzemeltetése wikiből

Írta: Tevesz Ágnes és Korn András, 2009. december. Utolsó jelentős módosítás: 2012. november.

A POSIX ACL (hozzáférés-vezérlési listák) a hagyományos unixos fájlrendszer-jogosultságrendszer kiterjesztése. Olyan jogosultság-konstrukciók is leírhatók a segítségével, amelyek a hagyományos jogosultságrendszerrel nem: pl. hogy egy adott fájlt egy csoport tagjai írhassák és olvashassák, egy másik csoport tagjai csak olvashassák, a többiek pedig se ne írhassák, se ne olvashassák.

Tartalomjegyzék

1 Bevezetés

Azok a rendszerek, amelyek támogatják/megvalósítják a POSIX-szabványokat (Portable Operating System Interface), általában egy egyszerű, de hatékony fájl-jogosultsági modellt használnak. Minden fájlhoz három jogosultsághalmaz tartozik: külön megadható, milyen jogai legyenek a fájl tulajdonosának, a fájlt birtokló csoportnak és rajtuk kívül mindenk másnak. A három jogosultsághalmaz három-három bitből áll: ezek az olvasás (r), az írás (w) és végrehajtás (x) jogát reprezentálják. Az így kapott jogokat, minden fájl illetve könyvtár esetében, kilenc biten tudjuk tárolni. Megadható ezen felül a felhasználói azonosító beállítása (set user id), a csoportazonosító beállítása (set group id) és a ragadós (sticky) bit is.

Az összetett feladatok ellátására ez a modell nem biztosított elég lehetőséget, ezért indították a POSIX 1003.1e fejlesztését és így született meg az ACL (Access Control List) szabványtervezet, amely azonban sosem vált szabvánnyá és már valószínűleg nem is fog. A jövő útja valószínűleg az NFSv4 ACL-eké, de ezeket jelen sorok írásakor (2009. végén) a szabad unix-féleségek közül csak a FreeBSD és az IllumOS támogatja, míg az 1003.1e szerinti ACL-eket a Linux is; ráadásul a megvalósítás kiforrottnak tekinthető, és a felhasználói programok oldaláról is megvan a szükséges támogatás, úgyhogy még érdemes ezzel az ACL-rendszerrel foglalkozni. (Egy némileg elavult NFSv4 ACL patch létezik Linuxhoz is; ennek a fejlesztése "Richacl" néven folytatódik.)

2 Az ACL-ek felépítése és működése

Egy ACL bejegyzések sokaságából épül fel (ACE, access control entry). Egy ACL-bejegyzés három információt tartalmaz:

  • A bejegyzés típusa. A következő típusok léteznek (zárójelben a típusok kódja -- a későbbiekben a rövidség érdekében gyakran a kóddal fogok hivatkozni a bejegyzéstípusokra):
    1. tulajdonos (ACL_USER_OBJ);
    2. egyéb konkrét felhasználó (ACL_USER);
    3. tulajdonoscsoport (ACL_GROUP_OBJ);
    4. egyéb konkrét csoport (ACL_GROUP);
    5. maszk (ACL_MASK) - ezzel a USER, a GROUP_OBJ és a GROUP típusú ACL-ek által megadott jogokból kimaszkolhatunk bizonyos biteket, l. később;
    6. mindenki más (ACL_OTHER).
  • Annak a csoportnak vagy felhasználónak a neve, amelyikre az adott bejegyzés vonatkozik; ez lehet
    • üres, vagy
    • egy konkrét felhasználó vagy csoport.
  • Az adott felhasználóhoz vagy csoporthoz rendelt jogok:
    • a szokásos r/w/x bit formájában.

A szintaxis a következő:

<acl típusa>:<név>:<jogok>

Az alábbi táblázat az egyes bejegyzéstípusok szintaxisát részletesebben is bemutatja:

Típus Kód Szöveges forma
ACL-bejegyzéstípusok
tulajdonos ACL_USER_OBJ user::rwx
konkrét felhasználó ACL_USER user:név:rwx
tulajdonoscsoport ACL_GROUP_OBJ group::rwx
konkrét csoport ACL_GROUP group:név:rwx
maszk ACL_MASK mask::rwx
egyéb ACL_OTHER other::rwx

Minden ACL kötelezően tartalmaz egy USER_OBJ, egy GROUP_OBJ és egy OTHER típusú bejegyzést. Azokat az ACL-eket, amelyek ezen a három bithármason kívül további elemeket is tartalmaznak, kiterjesztett vagy bővített ACL-eknek hívjuk. Ezek tartalmaznak egy mask bejegyzést illetve megnevezett felhasználókat és csoportokat is. Más szavakkal: ha egy ACL-ben szerepel GROUP vagy USER típusú (tehát konkrét felhasználóra vagy konkrét csoportra vonatkozó) bejegyzés, akkor kötelezően tartalmaz egy MASK típusú bejegyzést is; a MASK bejegyzés minden egyéb esetben opcionális.

A maszk a jogosultságok elbírálásakor jut szerephez. Ha egy processz megpróbál megnyitni egy olyan fájlt, amelyhez bővített ACL tartozik, egy jogosultságvizsgáló algoritmus fut le, amelynek a működése a következőképpen foglalható össze:

  1. Ha a processz effektív UID-ja megegyezik a fájl tulajdonosáéval, akkor a USER_OBJ bejegyzésben megadott jogosultságbitek az érvényesek.
  2. Ha nem egyezik, és van a processz effektív UID-jára vonatkozó USER típusú ACL-bejegyzés, az ebben szereplő jogosultságbitek és a maszk ÉS kapcsolata az irányadó, kivéve, ha a maszk üres ("---"), mert akkor az OTHER bejegyzés az irányadó.
    • Ez azt jelenti, hogy az OTHER-ben beállított engedélyek felülírhatják a nevesített user ACL-ekben levő tiltásokat!
  3. Ha nincs ilyen bejegyzés, és a processz effektív GID-ja megegyezik a fájl tulajdonoscsoportjával, akkor a GROUP_OBJ bejegyzésben megadott jogosultságbitek az irányadóak. A maszk nem jut szerephez.
  4. Ha nem egyezik meg, akkor, ha a maszk nem üres, a processz effektív GID-jára és kiegészítő csoportjaira vonatkozó ACL-bejegyzésekben megadott jogosultságokat VAGY kapcsolatba hozzuk egymással, az eredményt ÉS kapcsolatba hozzuk a maszkkal, és így kapott jogosultságbiteket tekintjük irányadónak; ha ezek lehetővé teszik a fájl megnyitását, akkor az sikerül, ha nem, akkor nem.
  5. Ha a maszk üres volt, akkor az OTHER-ben levő jogosultságbitek az irányadóak.
    • Vagyis: az OTHER-ben megadott engedély megint erősebb, mint bármiféle tiltás!
  6. Ha nincs ilyen ACL-bejegyzés, akkor az OTHER típusú bejegyzésben szereplő jogosultságbitek jutnak érvényre.

Vegyük észre, hogy ez azt jelenti, hogy az azonos típusú ACL-bejegyzések közül az engedélyezők "erősebbek" a tiltóknál, az OTHER-ben megadott engedélyek pedig üres maszk esetén "mindent visznek", kivéve, ha az effektiv UID ill. GID egyezik a tulajdonossal ill. tulajdonoscsoporttal. Intuitív, ugye? :) (Ez valószínűleg bug a Linuxban; a Solaris 9 üres maszk esetén is "helyesen" viselkedik. Ha lesz időm, jelentem, hátha megjavítják.)

Megjegyzés: az itt leírt algoritmus az, amelyet a Linux ténylegesen megvalósítani látszik; az acl(5) manpage által leírt algoritmus nem pontosan ez. Nem világos, hogy az implementáció vagy a dokumentáció hibás.

Az alábbi táblázat egy egyszerű példán kersztül szemlélteti a maszk működését:

Típus Szöveges forma Jogok
konkrét csoport group:név:r-x r-x
mask mask::rw- rw-
Effektív jogok r--

2.1 ACL leképezése hagyományos jogosultságbitekre

Ha valamilyen hagyományos, tehát ACL-eket nem támogató eszközzel kérdezzük le egy fájl jogosultságbitjeit, valahogyan meg kell bennük jeleníteni az ACL által reprezentált jogokat is; tehát szükségünk van egy leképezésre, ami az ACL tartalmát hagyományos unixos jogosultságbitekre képezi le. Egy ilyen leképezés természetesen nem lehet bijekció, hiszen az ACL-nek nagyobb a kifejezőereje, mint a jogosultságbiteknek; de egyértelmű azért lehet.

Ha fontos lenne, hogy a programok a lekérdezett jogosultságbitek alapján maguk is eldönthessék, van-e joguk írni vagy olvasni egy fájlt, akkor a leképezésnek "személyre szabottnak" kellene lennie, tehát a futó processz EUID-ja, EGID-ja és csoporttagságai ismeretében olyan jogosultságbiteket kellene prezentálni, amelyek az effektív hozzáférési jogosultságokat tükrözik. Tehát például, ha egy fájl ACL-je u::rwx,g::r--,g:csop1:rw,o::---,m::rwx (vagyis csak a tulajdonos és a csop1 csoport számára írható), akkor azon processzeknek, amelyek EGID-ja nem egyezik meg a tulajdonoscsoportéval, de tagjai a csop1 csoportnak, azt kellene mutatnunk, hogy az "other" bitcsoport értéke rw. Szerencsére azonban erre szinte soha nincs szükség, mivel a programoknak alapvetően nem feladata a kernelben amúgy is meglevő jogosultságkezelés reimplementálása (ez mindenképpen csak tökéletlenül sikerülhetne, hiszen lehetetlen transzcendentális módon megállapítani, milyen műveleteket engedélyezne a kernel; vagyis, amíg meg nem próbálunk meg egy műveletet végrehajtani, nem tudjuk megbízhatóan eldönteni, van-e hozzá jogunk).

A POSIX ACL jogosultságbitekre való leképezésére a következő megoldást találták ki:

  1. A tulajdonos jogosultságbitjei megegyeznek a USER_OBJ típusú ACL-bejegyzés tartalmával (ez elég természetes).
  2. Az "egyéb" jogosultságbitjei megegyeznek az OTHER típusú ACL-bejegyzés tartalmával (szintén elég természetes).
  3. A tulajdonoscsoport látszólagos jogosultságbitjei a következőképpen állnak elő:
    1. Ha van MASK típusú bejegyzés az ACL-ben, akkor a tulajdonoscsoport látszólagos jogai a maszk értékével egyeznek meg. Ez egyáltalán nem intuitív, és pl. a find(1) -perm-et is át tudja verni.
    2. Ha nincs MASK típusú bejegyzés az ACL-ben, akkor a tulajdonoscsoport jogai a GROUP_OBJ típusú bejegyzés értékével egyeznek meg.

Ugyanez a leképezés fordítva is működik: ha a chmod paranccsal vagy az azonos nevű rendszerhívással állítjuk a jogosultságbiteket, akkor a csoportra vonatkozó bitek a maszk típusú bejegyzésbe íródnak bele, ha van ilyen a fájlon. Erre jó odafigyelni, mert esetleg nem ezt szeretnénk. Ha ugyanis töröljük valamelyik bitet a maszkból, akkor természetesen hiába szerepel az a bit valamelyik nevesített felhasználóra vagy csoportra vonatkozó bejegyzésben, nem fog érvényre jutni.

Alapértelmezés szerint, amikor módosítjuk az ACL-t egy fájlon vagy könyvtáron, a maszk újraszámolódik és a nevesített felhasználókra és csoportokra vonatkozó jogosultságok összességének VAGY-kapcsolatával fog megegyezni; így, ha pl. egy ls -l kimenetét nézzük, képet kapunk arról, legfeljebb milyen jogokat osztottunk ki ACL-ek formájában.

Azt is jó tudni, hogy a fent leírt leképezés igazából nem a jogok kiolvasásakor, hanem a fájlrendszerben történő rögzítésükkor történik meg (hiszen az inode-ban adottak a jogosultságbitekhez tartozó mezők, ahová valamilyen értéket mindenképpen be kell írni). Ez azért fontos, mert előfordulhatnak (és elő is fordulnak) olyan hibák, amikor a tulajdonoscsoport jogait tartalmazó mezőbe valami miatt nem a maszk értéke kerül (vagy fordítva), és amíg nem nézzük meg közelről a teljes ACL-t, csak tanácstalanul vakarjuk a fejünket és nem értjük, miért nem működnek az ACL-lel beállított jogok. Jelen sorok írásakor (2009. végén) pl. az xfsrestore állít be nullás maszkot úgy, hogy azt az értéket, amelynek a maszkban szerepelnie kellene, a tulajdonoscsoport jogosultságaihoz azért beírja. Így ránézésre látszólag minden rendben van, a gyakorlatban viszont nem jut érvényre az ACL, mert minden bitet kimaszkolunk belőle.

Példa:

% setfacl -m g::rwx,m::--- test
% ls -la test
-rw----r--+  1 root root    0 Nov 20 18:26 test
% getfacl test
# file: test
# owner: root
# group: root
user::rw-
group::rwx                      #effective:---
mask::---
other::r--

% chmod 720 test
% ls -la test
-rwx-w----+ 1 root root 0 Nov 20 18:26 test*
%getfacl test
# file: test
# owner: root
# group: root
user::rwx
group::rwx                      #effective:-w-
mask::-w-
other::---
  • A tulajdonoscsoportnak minden joga megvan, de ezeket a jogokat a maszk kimaszkolja.
  • Az ls kimenetében a maszkot látjuk, nem a tulajdonoscsoport jogait.
  • A chmod a maszkot állította át, nem a tulajdonoscsoport jogait.

3 Hozzáférési ACL

Az eddigiekben bemutatott ACL-ek segítségével viszonylag finoman szabályozhatjuk, hogy létező fájlrendszer-objektumokhoz való hozzáféréskor mely felhasználóknak és mely csoportoknak milyen jogai legyenek. Az ilyen típusú ACL-eket hozzáférési ACL-nek hívják.

Nézzünk egy példát. Állítsuk az umaskunkat 003-ra; ezzel az újonnan létrehozott fájlok és könyvtárak jogosultságbitjeiből kimaszkoltuk az "egyéb" végrehajtási és írási jogát (1-es ill. 2-es bit; a 4-es az olvasás).

% umask 003
% mkdir konyvtar
% ls -dl konyvtar
drwxrwxr-- [...] júzer csoport [...] konyvtar

A pontok helyén levő információk most nem fontosak. Most írassuk ki a getfacl paranccsal az ACL-t. A getfacl paranccsal megjeleníthetjük egy fájl nevét, tulajdonosát, csoportját és a hozzá tartozó ACL-t.

% getfacl konyvtar
# file: konyvtar
# owner: júzer
# group: csoport
user::rwx
group::rwx
other::r--

Az utolsó három sor a szokásos unixos jogosultságbiteket reprezentáló ACL-bejegyzéseket mutatja. Nézzük meg, hogy hogyan tudnánk egy megnevezett felhasználónak, mondjuk Bobnak jogokat biztosítani. Mivel Bobot szeretjük, olvasási, írási és végrehajtási jogot is adunk neki. A -m paraméter segítségével tudjuk módosítani a jogokat.

% setfacl -m u:bob:rwx konyvtar
% getfacl konyvtar
# file: konyvtar
# owner: júzer
# group: csoport
user::rwx
user:bob:rwx
group::rwx
mask::rwx
other::r--

(Vegyük észre, hogy azt, hogy user:bob:rwx rövidíthettük úgy, hogy u:bob:rwx.)

Itt megjelenik ugyebár a Bob nevű felhasználó bejegyzése és a mask típusú bejegyzés. A mask bejegyzés automatikusan létrejön, ha szükséges, és nem adtuk meg külön. Ahogy már említettem, a maszk alapértelmezés szerint a nevesített felhasználókra és csoportokra, valamint a tulajdonoscsoportra vonatkozó jogosultságok összességének VAGY-kapcsolatával fog megegyezni, így biztosan nem maszkol egyetlen jogot sem. Ha megnézzük az ls -dl konyvtar parancs kimenetét, a következőt láthatjuk:

drwxrwx---+ [...] júzer csoport [...] konyvtar

Itt a + jel azt mutatja, hogy konyvtarhoz tartozik bővített ACL. Ugyan nem látszik, de most már a mask bejegyzés tartalma jelenik meg a tulajdonoscsoport jogai helyén; ez elvileg azért hasznos, mert a mask az ACL-ek által biztosított jogok felső korlátja.

Ha megpróbáljuk módosítani a tulajdonoscsoport jogait a chmod segítségével r-x-re, akkor a következő történik:

% chmod g-w konyvtar
% ls -dl konyvtar
drwxr-x---+ [...] júzer csoport [...] konyvtar
% getfacl --omit-header konyvtar
user::rwx
user:bob:rwx            #effective:r-x
group::rwx              #effective:r-x
mask::r-x
other::---

Ezen a példán látható a mask hatása: maszkolja a Bob felhasználó és a tulajdonoscsoport egyes jogait.

Most vegyük el a tulajdonoscsoport írási és végrehajtási jogát a maszk újraszámolása nélkül, majd chmoddal állítsunk be 2770-es jogokat:

% setfacl -n -m g::r konyvtar
% chmod 2770 konyvtar
% ls -dl konyvtar
drwxrws---+ [...] júzer csoport [...] konyvtar
% getfacl konyvtar
# file: konyvtar
# owner: júzer
# group: csoport
# flags: -s-
user::rwx
user:bob:rwx
group::r--
mask::rwx
other::---

Látszik, hogy

  • hiába adna a 2770-es bitkombináció írási és végrehajtási jogot a tulajdonoscsoportnak, csak a maszk módosult;
  • a getfacl kimenetében megjelent az, hogy flags: -s-, ami azt jelenti, hogy a setgid bit be van állítva.

A setgidnek igazából semmi köze az ACL-hez; csak azért van ott, hogy az ACL-ek könnyen lementhetőek és helyreállíthatóak legyenek anélkül, hogy a setuid/setgid/sticky bitek állapota elveszne. A getfacl segítségével ugyanis rekurzívan is kilistázhatjuk egy egész könyvtárfa összes ACL-jét, a setfacl pedig a getfacl kimenetét olvasva vissza tudja ezeket állítani; ha a getfacl kimenete nem tartalmazná a setuid/setgid/sticky bitet, akkor szinte használhatatlan lenne. Sajnos évekig ez volt a helyzet.

4 Alapértelmezett ACL

A hozzáférési ACL-eken túlmenően ún. alapértelmezett ACL-eket is használhatunk. Ha egy könyvtárhoz tartozik alapértelmezett ACL, akkor az adott könyvtárban újonnan létrehozott valamennyi fájlrendszer-objektum hozzáférési ACL-ként ezt az alapértelmezett ACL-t örökli (az új alkönyvtárak alapértelmezett ACL-ként is).

Új fájlrendszer-objektum létrehozásakor, ha a szülőkönyvtárnak van alapértelmezett ACL-je, az alábbi történik:

  • Az új objektum hozzáférési ACL-ként megkapja a szülőkönyvtár alapértelmezett ACL-jét.
  • Ezután a maszkot kivéve az összes ACL-bejegyzésből törlődik minden olyan bit, amely az objektumot létrehozó rendszerhívásnak átadott mode paraméterben (ami a létrehozandó objektum jogosultságbitjeit tartalmazza) nincs beállítva.
  • Az umask nem jut szerephez!

Lássunk egy példát! A setfacl paranccsal kétféleképpen is beállíthatunk default ACL-t; az alábbi két parancssor ekvivalens:

 
setfacl -d -m g:csoport2:r-x konyvtar
setfacl -m d:g:csoport2:r-x konyvtar

(Linuxon az r-x helyett írhatunk csak simán rx-et is, de pl. Solarison nem: ott mindig pontosan három karakterrel kell megadnunk a jogosultságbiteket.)

A második forma előnye akkor mutatkozik meg, ha egyszerre akarunk hozzáférési és alapértelmezett ACL-t is létrehozni; a setfacl -m után ugyanis vesszővel elválasztva több ACL-elemet is felsorolhatunk, és a második szintaxis lehetővé teszi az alapértelmezett és a hozzáférési ACL-ek keverését, míg az első nem.

Nézzük meg, mit csináltunk!

getfacl konyvtar
# file: konyvtar
# owner: júzer
# group: csoport
user::rwx
user:bob:rwx
group::r-x
mask::rwx
other::---
default:user::rwx
default:group::r-x
default:group:csoport2:r-x
default:mask::r-x
default:other::---

A default ACL megjelenítésével és működésével kapcsolatban vannak különbségek az egyes implementációk között; pl. elképzelhető, hogy a default ACL-t csak akkor látjuk, ha -d kapcsolóval hívjuk meg a getfacl. Azt is vegyük észre, hogy a Linux automatikusan létrehozott a hozzáférési ACL alapján néhány alapértelmezett ACL-bejegyzést; ez sem feltétlenül történik ugyanígy minden más oprendszeren.

Hozzunk most létre egy alkönyvtárat az mkdir paranccsal! Az mkdir(1) program 0777-es mode paraméterrel hívja meg az mkdir() rendszerhívást. Lássuk, milyen ACL-t kap az uj alkönyvtár:

mkdir konyvtar/alkonyvtar
getfacl konyvtar/alkonyvtar
# file: konyvtar/alkonyvtar
# owner: júzer
# group: csoport
user::rwx
group::r-x
group:csoport2:r-x
mask::r-x
other::---
default:user::rwx
default:group::r-x
default:group:csoport2:r-x
default:mask::r-x
default:other::---

Látható, hogy az új könyvtár az ACL-t mint hozzáférési és alapértelmezett ACL-t is örökölte.

5 Alkalmazási példák

Tegyük fel, hogy van egy SSL-kulcsunk. Nem szeretnénk, hogy bárki elolvashassa, kivéve a webszervert. A webszerver a www-data nevű felhasználó jogaival fut. Ha a www-data felhasználó tulajdonába helyezzük a fájlt, akkor ő el tudja rajta állítani a jogokat is, amit nem szeretnénk; ACL-lel viszont megoldhatjuk, hogy a root tulajdonában maradjon, de a www-data is el tudja olvasni a tartalmát anélkül, hogy a jogokat elállíthatná. ACL nélküli megoldás is van: legyen saját csoportja a www-data usernek, és birtokolja ez a csoport az SSL-kulcsot. Ha csak a csoportnak van rá olvasási joga, akkor nem tudja elállítani a jogokat.

Ha víruskeresőt futtatunk a bejövő leveleken, általában különböző felhasználóként fut a levelezőszerver és a víruskereső. Azt szeretnénk, ha a levelezőszerver által létrehozott fájlok a víruskereső számára is olvashatóak lennének, de senki más számára nem lennének olvashatóak. Megtehetjük, hogy egy olyan könyvtárba tesszük ezeket a fájlokat, amelyen olyan alapértelmezett ACL van, amely a víruskeresős felhasználónak olvasási jogot biztosít. ACL nélkül megoldás: helyezzük közös csoportba ezt a két felhasználót, és az adott könyvtár legyen ennek a csoportnak a tulajdona, valamint setgid-es. Az is szükséges, hogy a levelezőszerver umaskja megfelelő legyen, tehát ne maszkolja ki a csoport olvasási jogát.

6 Potenciális ZH-kérdések

  • Milyen elemekből áll(hat) egy POSIX ACL? (A szimbolikus azonosítók nem kellenek betű szerint, csak a szerepük/funkciójuk.)
  • Mik a 100-as UID-val, 200-as GID-dal futó, 300-as és 400-as kiegészítő csoporttagsággal rendelkező processz effektív jogai azon a fájlon, amelynek a POSIX ACL-je az alábbi bejegyzéseket tartalmazza? (És itt jönne egy getfacl-kimenet.)
  • Mondjon példát olyan jogosultság-specifikációra, amely hagyományos unixos jogosultságokkal nem írható le ésszerűen, de POSIX ACL-ekkel igen!
  • Hogyan (és milyen esetekben) lehet a POSIX ACL-ek látszólag szükséges alkalmazását mégis elkerülni és hagyományos unixos jogosultságokkal ugyanazt a hatást elérni? Mutassa ezt be egy példán! (Segítség: lehet, hogy csak ésszerűtlen megoldás van.)
  • Használható-e POSIX ACL arra, hogy egy csoport összes tagja számára mindenképpen biztosítsunk hozzáférést egy könyvtár tartalmához, akkor is, ha a csoport tagjai nem működnek ebben együtt? Miért?
  • Mit látunk az ls -l kimenetében a jogosultságbitek helyén olyan könyvtárbejegyzések esetében, amelyekhez bővített POSIX ACL tartozik?
Személyes eszközök