Hlavní navigace

Web server Nginx bez práv roota s pomocí systemd

 Autor: Nginx, Inc.
Je zvykem, že webový server se spouští pod uživatelem root. Hlavním důvodem je nutnost otevírat nízké TCP porty jako 80 a 443. Systemd ale nabízí elegantní způsob, jak změnit poměry a nechat běžet server pod běžným uživatelem.
Petr Krčmář 17. 6. 2020
Doba čtení: 4 minuty

Sdílet

Nginx s rootem

Nginx má po startu ve výchozím stavu v paměti několik procesů. Hlavní proces (master) se spouští s právy uživatele root a spouští si větší množství pracovních procesů (worker) pod zvoleným běžným uživatelem. Ty se starají o samotné zpracování požadavků podle konfigurace a nepotřebují žádná speciální oprávnění.

Hlavní proces ale potřebuje vysoká oprávnění, protože kromě zpracovávání konfigurace a spouštění dalších procesů potřebuje především otevírat TCP porty nižší než 1024. Obvykle to jsou ty webové, tedy 80 a 443. To ovšem v Linuxu může dělat jen root.

Zároveň ale platí, že s vysokými oprávněními by mělo v systému běžet co nejméně procesů. Je to doporučení velmi rozumné, protože v případě úspěšné kompromitace pak nemusí dojít k tak velkým škodám, jako v případě privilegované aplikace.

Nginx bez roota

Linux už ovšem velmi dlouhou dobu nabízí funkci capabilities, což bychom mohli česky přeložit jako schopnosti. Ty umožňují silná oprávnění roota dělit do malých skupin a přidělit tedy procesu třeba jen jedno privilegium. Takový proces pak nemá veškerá božská práva roota, ale jen jednu přesně vybranou schopnost.

Tohle se nám hodí například u našeho web serveru, kterému můžeme přidělit schopnost CAP_NET_BIND_SERVICE, u které manuálová stránka uvádí: Bind a socket to Internet domain privileged ports (port numbers less than 1024). Tohle přesně potřebujeme.

Stačí tedy jen správně nakonfigurovat init systém, aby zaváděl webový server pod běžným uživatelem a přidal mu tohle potřebné oprávnění navíc. Podobný postup bude pochopitelně možné využít i u jiných služeb, které se chovají podobně a roota vyžadují jen kvůli otevření portů.

Konfigurujeme službu

Moderní distribuce používají systemd, který je na podobné kousky velmi dobře vybaven. Nejprve si tedy zkopírujeme standardně dodávaný unit soubor na místo určené pro naše lokální úpravy.

# cp /lib/systemd/system/nginx.service /etc/systemd/system/

Poté už jej budeme vždy editovat v novém umístění v /etc/, kde nehrozí přepsání balíčkovacím systémem při aktualizaci. V sekci [Service] doplníme informaci o tom, pod jakým uživatelem a skupinou má hlavní proces Nginx běžet:

User=www-data
Group=www-data

Tahle jednoduchá změna zajistí běh pod neprivilegovaným uživatelem. Dále mu musíme přidělit zmíněnou schopnost, která dovolí otevření nízkých portů.

AmbientCapabilities=CAP_NET_BIND_SERVICE

Tohle ovšem nestačí, protože Nginx si ještě při startu vytváří soubor /run/nginx.pid, což by teď přestalo fungovat, protože nadřazený adresář vlastní root. Proto musíme vytvořit nový adresář vlastněný správným uživatelem.

Se systemd je to snadné, stačí opět do konfiguračního souboru služby připsat jeden řádek. Ten způsobí vytvoření správného podadresáře s vhodnými právy:

RuntimeDirectory=nginx

Poté tam ještě musíme nechat umístit onen soubor s PID, takže je potřeba upravit správný řádek, aby obsahoval novou cestu:

PIDFile=/run/nginx/nginx.pid

Konfigurujeme Nginx

Tahle část bude snadná, protože z hlediska běhu web serveru se mění jen jediná věc: umístění zmíněného PID souboru. Proto musíme v hlavním konfiguračním souboru /etc/nginx/nginx.conf změnit patřičný řádek.

pid /run/nginx/nginx.pid;

To je vlastně vše. Teď stačí už jen službu restartovat a vyzkoušet, že se všechno povedlo.

Restart a ověření

Použijeme dva příkazy, nejprve necháme systémového démona znovu načíst konfiguraci a vytvořit si strom závislostí mezi službami. Poté otočíme samotný web server.

# systemctl daemon-reload
# systemctl restart nginx

Protože se vše povedlo, nedostali jsme žádnou chybu. Pokud přeci jen ano, budeme muset zkoumat žurnál pomocí příkazu journalctl -xe. Nezbývá, než ověřit, že vše funguje, jak jsme si představovali.

Nejprve si vypíšeme seznam procesů v systému a jejich vlastníků:

# ps axu | grep nginx

www-data  5119  0.0  0.1  69640  1696 ?        Ss   20:39   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data  5120  0.0  0.2  69956  2576 ?        S    20:39   0:00 nginx: worker process

Vidíte, že oba procesy běží pod uživatelem www-data a ani hlavní proces tedy už neběží pod rootem. Nyní ještě výpis otevřených portů pomocí příkazu ss:

# ss -tlpn

State     Recv-Q    Send-Q       Local Address:Port        Peer Address:Port
LISTEN    0         128                0.0.0.0:80                  0.0.0.0:*        users:(("nginx",pid=5120,fd=6),("nginx",pid=5119,fd=6))
LISTEN    0         128                   [::]:80                     [::]:*        users:(("nginx",pid=5120,fd=7),("nginx",pid=5119,fd=7))

Také tady se vše podařilo a webový server si otevřel výchozí TCP port 80. Vše tedy běží jako obvykle, jen zbytečně nepřidělujeme privilegia, která nejsou potřeba.

Pozor na práva

Změna přináší ještě jedno úskalí, které vyplývá ze změny uživatele hlavního procesu. Musíme zajistit, aby všechny konfigurační soubory a další zdroje byly dostupné novému uživateli.

Já jsem testoval na aktuálním Debianu Buster a na něm nejsou potřeba žádné úpravy práv, protože adresář /etc/nginx/ je čitelný pro všechny. Nemusí to tak ale být na každé distribuci a je potřeba si to pohlídat.

MIF obecny

Týká se to také například souborů s certifikáty a privátními klíči, které bývají často uloženy na disku jinde a zatímco dříve k nim měl Nginx automaticky přístup (protože jako root mohl kamkoliv), teď už to nemusí automaticky platit. Naštěstí nejde o nijak komplikované úpravy a běžný linuxový správce je snadno zvládne.

Pozor také na použití stejného uživatele pro běh hlavního procesu a webové aplikace napsané například v PHP. Pokud by vše běželo pod jedním uživatelem, mohla by děravá aplikace získat přístup ke všem tajemstvím na serveru, včetně privátních klíčů. Je tedy rozumné použít jiného uživatele pro Nginx a jiného pro provoz PHP-FPM. Tím se jednoduše citlivá data oddělí.

Zdroje