Správa uživatelů a databázových objektů v PostgreSQL

Z PostgreSQL
Skočit na navigaci Skočit na vyhledávání

14.11.2008 Autor: Pavel Stěhule

Určení vlastníků db objektů se v řadě projektů vůbec neřeší nebo řeší příliš pozdě (ve chvíli, kdy chaos ve vlastnictví začne komplikovat administraci). Tento článek se věnuje popisu problematice vlastnictví db objektů, plus obsahuje pár základních doporučení.

Vlastník objektu

V PostgreSQL se seznam uživatelů sdílí napříč všemi databázemi v clusteru (instanci). V tomto se PostgreSQL naprosto odlišuje od MySQL, kde se uživatelé registrují ke každé databázi. Většina projektů, které běží na webu si vystačí s jedním db účtem, v lepším případě se dvěma (obyčejný uživatel, a administrátor). I v takovém případě je dobré, když vlastníkem objektu je uživatel s vyššími právy (hlavně jiný než aplikační uživatel). Potom, v případě prolomení běžného aplikačního účtu Vám alespoň útočník nemůže smazat tabulky nebo pozměnit strukturu databáze. Totiž změnu db objektu (včetně jeho odstranění) může provést pouze vlastník objektu (v tomto článku vlastníkem objektu míním většinou vlastníka tabulky, nicméně vlastnit se dají i další db objekty - sekvence, funkce). Stejně jako v případě systémových účtů se snažíme omezovat použití účtu root, snažíme se omezovat použití účtu superuživatele - účet postgres.

postgres=# create role pst_sro nologin;
CREATE ROLE
postgres=# set role to pst_sro;
SET
postgres=> create table foo(a integer);
CREATE TABLE
postgres=# set role to pavels;
SET
postgres=> alter table foo add column b integer;
ERROR:  must be owner of relation foo

Vlastníkem tabulky je role pst_sro. V tomto případě zastupuje imaginární firmu PST s.r.o. Vždy doporučuji jako vlastníka použít virtuální roli (zkráceně roli) - s atributem nologin. Důvod je jednoduchý - reální uživatelé se občas mění (obyčejně častěji než název firmy). Pokud budeme všechny vytvářecí a pozměňující skripty spouštět pod tímto uživatelem, tak nikdy nebudeme mít chaos ve vlastnictví. Navíc automaticky jsou db objekty nepřístupné všem aplikačním účtům - což nás nutí zamyslet se nad bezpečnostní politikou a explicitně nastavit přístup. Osobně bych nikdy programátorům právo přepnutí se do role, která se používá jako vlastník nikdy nedal. Vede to totiž k tomu, že programátoři provedou změny pomocí GUI, a zapomenou na vytvoření pozměňujících (alter) skriptů. Ty se používají pro udržení minimálně dvou izolovaných prostředí (vývojového a produkce). Toto pozapomenutí je snadný způsob jak si znepříjemnit start nové verze aplikace. Pouze vybraní administrátoři nebo vývojáři by měli mít tuto možnost. V příkladě ti, co mohou používat roli power_developer. Všimněte si, že se snažím, co nejdéle používat role. Poznámka: v ANSI SQL se nijak zvlášť nerozlišuje mezi rolí a uživatelským účtem. V textu používám termín role pro ANSI SQL roli s atributem nologin a termín účet pro ANSI SQL roli s atributem login. Role s atributem nologin nelze použít pro přihlášení do databáze.

postgres=# grant pst_sro to power_developer;
GRANT ROLE
postgres=> set role to power_developer;
SET
postgres=> set role to power_developer;
SET
postgres=> alter table foo add column b integer;
ERROR:  must be owner of relation foo

Vypadá to jako zrada, power_developer má práva role pst_sro, a přesto nedokáže modifikovat tabulku. O zradu se nejedná. Za tímto chováním se skrývá atribut noinherit, který si vynucuje explicitní přepnutí do dané role.

postgres=> set role to pst_sro;
SET
postgres=> alter table foo add column b integer;
ALTER TABLE

Proč si atributem noinherit komplikovat život? Prostě proto, abych zabránil vytváření db objektů rolí power_developer. Tím si omezím vlastnictví pouze na roli pst_sro.

Zkusím jako obyčejný uživatel vytvořit tabulku.

postgres=> set role to pavels;
SET
postgres=> create table tab(a integer);
CREATE TABLE

Žádný problém - jenomže to je problém. Obyčejní uživatelé by neměli mít práva vytvářet tabulky. Kde bychom skončili. Zde je jádro pudla v nastavení práv schématu public (ne náhodou řada DBA jako první krok po instalaci zruší toto schéma).

Jako superuser musím povolit roli pst_sro vytvářet nová schémata (protože uživatel postgres je vlastníkem databáze postgres - v praxi by i vlastníkem databáze měl být vyčleněná role - v tomto případě pst_sro).

postgres=# grant create on database postgres to pst_sro;
GRANT
postgres=# set role to pst_sro;
SET
postgres=> create schema personalistika;
CREATE SCHEMA
postgres=> set role to pavels;
SET
postgres=> create table personalistika.foo(a integer);
ERROR:  permission denied for schema personalistika

Nyní chybějící oprávnění zabránilo uživateli pavels vytvořit tabulku. Ještě jednou zkusím vytvořit tabulku pod uživatelem power_developer.

postgres=> set role to power_developer;
SET
postgres=> CREATE table personalistika.foo(a integer);
ERROR:  permission denied for schema personalistika

Takže se musím explicitně přepnout do role pst_sro.

postgres=> set role to pst_sro;
SET
postgres=> CREATE table personalistika.foo(a integer);
CREATE TABLE

Atribut noinherit si vynucuje explicitní přepnutí do role. Důvod byl naznačen - chci mít pouze jednoho vlastníka objektů. Jsou však případy, kdy vynucení explicitního přepínání rolí nechceme. Proto tu je atribut inherit. Při použití atributu inherit role získá práva všech rolí jejichž je členem - bez nutnosti explicitního přepínání.

Uživatel

Docela často se vedou spory o přístupu k db uživatelům. Řeší se otázka, zda řešit zabezpečení na aplikační nebo databázové úrovni. Pokud je pro Vás databáze obyčejným storage systémem, pak to asi řešit nebudete, prostě vše (včetně práv) budete řešit na aplikační úrovni. Pokud chcete ale lépe zabezpečit svojí aplikaci, pak se vyplatí uvažovat o zabezpečení i na úrovni databáze. Jednak máte jistotu, že toto zabezpečení Vám pravděpodobně nikdo neobejde (včetně Vás samotných - nejnepříjemnějším útočníkem je vlastní hloupost), jednak zabezpečení na úrovni db je hodně efektivně implementováno a konečně pokud používáte víc db účtu, pak se můžete z logů dozvědět dost zajímavé informace. Např. který uživatel spouští chybný nebo pomalý SQL příkaz. Pro malé systémy (do 100 uživatelů) si dovolím doporučit zabezpečení aplikace na úrovni databáze - dost si můžete zjednodušit kód. Pro větší systémy pak kombinovanou správu. Interní uživatelé používají skutečné db účty - ostatní uživatelé sdílí vybrané db účty a mají vlastní aplikační účet. Samozřejmě, pokud se duplikují aplikační a db účty, je těžko myslitelná efektivní správa účtu bez nějakého systému zajišťujícího synchronizaci účtů. Ohledně výkonu nemusíte mít strach - před pár lety jsem testoval databázi s padesáti tisíci uživateli.

Doporučení

To je v krátkosti základ toho, co lze s vlastnictvím a právy provádět. Z praxe mohu jen doporučit:

  • vlastníkem všech objektů by měla být jedna role (vyjímkou jsou tabulky obsahující extrémně citlivé údaje - např. přihlašovací).
  • přístup k db objektům opět nastavovat pouze rolím, nikoliv uživatelům - pak v dalším kroku nastavíte uživatelům potenciální role (jako uživatele beru i role, které slouží aplikacím).
postgres=# create role personalista inherit nologin;
CREATE ROLE
postgres=# grant usage on schema personalistika to personalista;
GRANT
postgres=# grant select on personalistika.foo to personalista;
GRANT
postgres=# grant personalista to pavels;
GRANT ROLE
postgres=# set role to pavels;
SET
postgres=> select * from personalistika.foo ;
 a 
---
(0 rows)
  • Aplikačním účtům odeberte právo vytvářet funkce v PL/pgSQL

Přístup k informacím o rolích, vlastnictví

Vlastník objektu je zobrazený ve výpisu - v tomto případě výpisu tabulek:

postgres=# \dt personalistika.foo 
            List of relations
     Schema     | Name | Type  |  Owner  
----------------+------+-------+---------
 personalistika | foo  | table | pst_sro
(1 row)

Členství uživatele v rolích zjistíme jednoduše - metapříkazem \du:

postgres=# \du pavels
                   List of roles
 Role name | Attributes |        Member of         
-----------+------------+--------------------------
 pavels    |            | {developer,personalista}

Pro zobrazení přístupových práv k objektům slouží příkaz '\dp:

postgres=# \dp personalistika.foo 
                    Access privileges
     Schema     | Name | Type  |    Access privileges    
----------------+------+-------+-------------------------
 personalistika | foo  | table | pst_sro=arwdDxt/pst_sro
                               : developer=r/pst_sro
                               : personalista=r/pst_sro
(1 row)

V konzole nemáme žádnou možnost, jak zjistit efektivní práva. K dispozici máme systémové funkce, které poskytují informaci o tom, zda konkrétní uživatel má nebo nemá určitá práva k tabulkám:

postgres=# select has_table_privilege('pavels','personalistika.foo','select');
 has_table_privilege 
---------------------
 t
(1 row)

postgres=# select has_table_privilege('pavels','personalistika.foo','insert');
 has_table_privilege 
---------------------
 f
(1 row)

pg_hba.conf

Kromě SQL příkazu GRANT ještě v případě PostgreSQL nesmíme zapomenout na konfigurační soubor pg_hba.conf ve kterém povolujeme (případně zakazujeme) konkrétním uživatelům přístup z určitých ip adres. Struktura souboru je poměrně jednoduchá - soustředím se pouze na omezení přístupu z ip.

host	all	postgres	192.10.1.20/32		md5
host	all	postgres	0.0.0.0/0		reject
host	template1   all		0.0.0.0/0		reject
host	all	all		0.0.0.0/0		md5

Soubor obsahuje sloupce určující typ přístupu, určení databáze, určení uživatele, určení ip adresy a způsob autorizace, případně odmítnutí přístupu. Při rozhodování, zda uživatele přihlásit nebo nepřihlásit k databázi systém postupuje po řádcích, a aplikuje první metodu autorizace pro kterou najde shodu v určení způsobu způsobu přístupu, uživatele a databáze. Výše uvedené řádky si lze vyložit následujícím způsobem - uživatel postgres může přistupovat do všech databází, ale pouze z adresy 192.10.1.20 a to ještě se musí přihlášení potvrdit heslem. Ze všech ostatních ip adres se uživatel postgres nemůže připojit k žádné databázi. Ostatní uživatelé se mohou připojit (po zadání hesla) ke všem databázím vyjma databáze template1.

Při nastavování práv bychom se měli řídit jednoduchým pravidlem - žádný uživatel by neměl vidět více, než nezbytně potřebuje ke své práci. To se lehko říká, a poměrně snadno implementuje, pokud na bezpečnost myslíme již při návrhu aplikace - bezpečnostní hlediska jsou docela dobrým vodítkem při návrhu db modelu. Dodatečně to pak již moc nejde. V tomto článku se nesnažím duplikovat dokumentaci, šlo mi o to v krátkosti ukázat možnosti správy databáze. Pro detailní informace doporučuji otevřít dokumentaci - http://www.postgresql.org/docs/8.3/interactive/user-manag.html.