svnserve-sgidtunnel - secure wrapper for svnserve

A probléma: Szeretném biztonságosabbá tenni a subversion tárolók svn+ssh:// elérését. Ha az subversion nem mond semmit, olvass utána egy kicsit. Röviden: egy verziókezelőről beszélünk, ami több elérési módot is támogat:

A szerverhez többen rendelkeznek ssh hozzáféréssel. Mindenki nevesített felhasználói fiókkal rendelkezik, nincsenek "közös" fiókok. Jellemzően nincsen szükség teljes shell hozzáférésre, a felhasználóknak elég, ha a weblapjaikat tudják frissíteni, fájlokat másolgatni. Erre az scp/sftp hozzáférés bőven elég, így ezek a felhasználók rssh shell-t kaptak. A fejlesztők a subversion tárolókhoz célszerűen ssh-n keresztül férnek hozzá, így nincsen szükség újabb jelszavakra, konfigurálásra.

Milyen problémák merülhetnek fel ilyen környezetben?

Az svn+ssh hozzáférés úgy működik, hogy bejelentkezés után az ssh csatorna szerver oldalán elindul egy svnserve folyamat a felhasználó nevében, ami kimenet és bemenet segítségével kommunikál a kliens oldalon futó svn klienssel. A kapcsolat leépítésével ez az svnserve folyamat is megszűnik létezni. Ez egyrészt előnyös, mert nincsen extra nyitott port, és nem kell, hogy olyankor is fusson az svnserve, amikor épp senki sem használja. Másrészt viszont azt is jelenti, hogy az felhasználók nevében indított svnserve folyamatoknak írás/olvasás jogokkal hozzá kell férnie a repository bizonyos részeihez.

Olvasási/írási jogok hiánya

Amennyiben a felhasználó file:// vagy svn+ssh:// protokolon keresztük használja a repository-t, úgy olvasni kell tudnia a repository összes fájlját, valamit a commit során létrejövő fájlokat létre kell tudnia hozni. Ezek részben ideiglenes állományok, lock-ok, másrészt maguk a revízió állományai és azok metaadatai. Célszerű a jogosultságokat úgy beállítani, hogy a repo konfigurációs állományai, hook-jait csak olvasni tudják a felhasználók. Elfogadott gyakorlat, hogy a fejlesztőket egy közös csoportba teszik, és majd a repository állományait ennek a csoportnak "adják" ( chown -R :svn *). Ilyenkor a csoport jogosultságokat kell csupán beállítani. Vagy ez mégsem elég?

Umask problémák, group sticky bit

Ha a felhasználó nevében futó program létrehoz egy fájlt, akkor alapértelmezetten annak a fájlnak a tulajdonosa a felhasználó, csoportja a felhasználó elsődleges csoportja lesz és jogosultságai a felhasználó umask-ja alapján kerülnek beállításra. Ez tipikusan olyan állományokat eredményezhet a repository-ban, amit már felhasználók nem tudnak olvasni, sőt, maga az svn technikai felhasználó sem. (például 640 alice:alice ).

Régebben ezt úgy oldották meg, hogy a valódi svn binárisokat olyan wrapper-eken keresztül érték el, amik a valódi program meghívása előtt először átállították az umask-ot. Az umask probléma a subversion nem túl régi verzióiban már gyakorlatilag nem létezik. Az FSFS 'fájlrendszer' az új revízió állományokat a korábbi revízió-fájlokéval megegyező jogosultsággal hozza létre, így már nem kell aggódni amiatt, hogy a komittáló felhasználó umask-ja miatt más felhasználók nem tudják majd eléreni a repository-t.

A jogosultság-biteken így már nem jelentenek problémát, viszont azt továbbra is meg kell oldani, hogy a db mappában létrehozott új állományok csoportja ne az őket létrehozó felhasználó elsődleges csoportja legyen, hanem az a bizonyos "közös" svn csoport, aminek minden fejlesztő tagja. Ennek az elterjedt módja a db mappa (amit ugye svn:svn birtokol) SGID bitjének beállítása (kivéve *BSD Unixokat, ahol erre nincsen szükség). Ezután az összes itt létrehozott fájl a szülő mappa csoportát örökli.

Rosszindulat

A subversion tároló jogosultságai a nagykönyv ajánlásai alapján egy technikai user és csoport, svn:svn birtokában van, SGID bit beállítva a szükséges könyvtárakra. A fejlesztők, amint megkapják az svn-csoport tagságot, gond nélkül tudnak dolgozni, hiszen írási joguk van a tárolókra, és nem fogják elrontani a jogosultságaikat, mert egyrészt a SGID bit be van állíta, így az újonnan létrehozott revision fájlok a csoport tulajdonában leszenek, és a subversion a nagyon régi verzióktól eltekintve jól állítja be az umask-okat, pontosabban a revision környező fájlok mintájára.

A subversion belső jogosultság kezelése meghatározza, melyik fejlesztőnk a projekt melyik részét írhatja-olvashatja. Viszont, mivel az összes fejlesztőnk tagra az svn csoportnak, közvetlen fájlműveletekkel a revision fájlok bármelyikét tudják írni, olvasni, törölni. Gyakorlatilag olyan, mint amikor a buta adatbázis admin SQL szinten jól levédi az tábláit, viszont a nyers adatbázis fájlokat (táblatereket) bárki haza tudja vinni... De itt még törölni is lehet.

Megoldás

Vegyük ki a felhasználókat az svn csoportból. Nem az a helyes kérdés, hogy milyen jogosultságokkal ruházzuk fel őket ahoz, hogy elérjék a repository-t. Clark-Wilson szemlélettel a helyes kérdés az, hogy miként tudjuk kikényszeríteni, hogy csak bizonyos subversion progikon keresztül érhessék el a repository-t, másként ne. Tovább szűkítve a problémát, svn+ssh:// hozzáféréshez elég, ha csupán a svnserve folyamaton keresztül tudják elérni a repo-t. Az svnserve futtatható binárison beállított SUID/SGID bit megoldja, hogy a progit meghívó felhasználó jogosultságainál erősebb hozzáféréssel rendelkezzen a folyamat. Az svnserve meg kikényszeríti az svn belső hozzáférés verzérlését.

Mivel az svnserve megengedi, hogy bizonyos parancssori kapcsolókkal alternatív konfigurációs fájlt adjunk meg, a belső jogosultság kezelést a szorgalmas rosszakaró könnyen kikerülheti. Egy másik parancssori kapcsoló segítségével más felhasználó nevében ténykedhetünk. Az ilyen lehetőségek miatt nem tartom célszerűnek az svnserve SUID/SGID beállítását. Helyette inkább egy olyan kis wrappert csinálok, ami ellenőrzi, hogy tunnel módban, trükkös paraméterek nélkül hívták-e meg a parancsot, és ha nem, úgy eldobja a többlet-jogosultságokat és úgy adja át a vezérlést az svnservenek. Majd ezt a wrappert ruházom fel SGID bittel, és állítom át a csoportját az svn technikai csoportra. A parancssori kapcsolók és az svn+ssh:// működésének tanulmányozása azt a felismerést hozta, hogy tunnel módban buherálás nélkül csak és kizárólag a -t (tunnel mode) paramétert adja át az svn kliens. A többi paraméter vagy a démon módra vonatkozik és ilyenkor nincsen szükség többlet jogosultságra, vagy pedig biztonsági okból nem szeretném engedni a többlet jogosultságok használatát a paraméter jelenlétében.

A wrapper működése tehát egyszerű: megvizsgálja, hogy pontosan egy paraméterrel hívták-e meg, és az pontosan -t . Ha igen, úgy átadja a vezérlést az svnservenek, ilyenkor ez a process is az svn csoport jogait örökli. Ha más paraméterezéssel hívják meg, akkor először eldobja a jogosultságokat, és csak ez után hívja meg az eredeti binárist. Két apró bónusz is belekerült a wrapper-be: egyrészt a buzgálkodás (pl.: a PATH változó manipulálás) elkerülése érdekében saját, minimális környezeti változókkal hívja meg az svnserve folyamatot, másrészt lehetőség adatik beállítani egy virtuális repository root-t, ami segítségével a repository pontos helyét is elrejthetjük a kíváncsi de viszonylag tudatlan szemek elől.

Mivel a SGID bit szkriptek esetén nem működik, C-ben írtam meg, ez biztonsági szempontból is jobb megoldás. Míg szkripteknek szükségszerűen r-x jogosultságot kell adni (hiszen az interpreternek előbb fel kell olvasni őket), addig a gépi kódnak elég a --x. Mivel most a biztonság fontosabb szempont most, a további trükközések elkerülése végett a konfiguráció #defineokkal történik és a binárisba szépen bele lesz sütve.


#include <stdio.h>
#include <unistd.h>
#include <string.h>

#define REAL_PATH "/usr/bin/svnserve.real"
#define ROOT "/var/svn"

char *secargv[] = { "", "--tunnel", "--root=" ROOT, NULL }, *newenv[] = { "PATH=/bin:/usr/bin", "SHELL=/bin/sh", NULL };
char **newargv = secargv;

int main(int argc, char **argv) {
	if (argc != 2 || strcmp(argv[1], "-t")) {
		if (setgid(getgid()) == -1) { //permanently drop privs
			perror("setgid()");
			return 1;
		}
		newargv = argv;
	}
	execve(REAL_PATH, newargv, newenv);
	perror("could not execute " REAL_PATH);
	return -1;
}

Forgatás és telepítés

A szerveren nincsen gcc (ugye?!) ezért a build/deploy is 2 részre bomlik.


#!/bin/sh

F=svnserve-sgidtunnel

gcc -Wall -O2 -o $F ${F}.c
strip $F

# # run on target machine:
# chmod 2111 $F
# sudo chown .svn $F
# sudo mv $F /usr/local/bin
# sudo dpkg-divert --add --rename --divert /usr/bin/svnserve.real /usr/bin/svnserve
# sudo ln -s /usr/local/bin/$F /usr/bin/svnserve
© 2003-2020 lithium.io7.org
Erre a weblapra a Creative Commons Nevezd meg! - Így add tovább! 3.0 Unported Licensz vonatkozik.