PostgreSQL / EDB

Jak zapanować nad uprawnieniami w PostgreSQL 17? Default Privileges i Role Grupowe w praktyce

2026-06-30
Podziel się

Każdy administrator PostgreSQL prędzej czy później zderza się z takim problemem: jeden użytkownik tworzy tabelę, a drugi – mimo rzekomo „pełnych” uprawnień – nie ma do niej dostępu. Komunikat permission denied to zmora zespołów, w których wiele osób współdzieli ten sam schemat. W PostgreSQL 17 system uprawnień rygorystycznie chroni własność obiektów, co bywa wyzwaniem. Z pomocą przychodzą jednak Default Privileges oraz koncepcja Ról Grupowych. W tym artykule na praktycznym przykładzie pokażemy, jak krok po kroku zainicjować uprawnienia, rozwiązać problem izolacji i raz na zawsze uniknąć chaosu w bazie danych.

Zanim jednak wskoczymy do terminala i zaczniemy wykonywać kolejne komendy, warto uświadomić sobie jedną kluczową cechę architektury PostgreSQL. W tym silniku bazodanowym rządzi twarda zasada własności: użytkownik, który tworzy obiekt (np. tabelę), staje się jego absolutnym i jedynym właścicielem. Nawet jeżeli nadasz koledze z zespołu szerokie uprawnienia do całego schematu, system domyślnie zablokuje mu dostęp do nowo powstających w nim tabel. Poniższy poradnik to kompletne case study. Zaczniemy od celowego wygenerowania typowego problemu z uprawnieniami, aby na własne oczy zobaczyć, gdzie leży pułapka, a następnie wdrożymy docelowe, zautomatyzowane rozwiązanie, które odciąży każdego administratora.

Spis treści:

1. Inicjalizacja uprawnień przez superuser

W pierwszej kolejności superuser nadaje uprawnienia niezbędne do zarządzania obiektami w schemacie public. Bez jawnego nadania prawa CREATE, użytkownik nie może tworzyć tabel w tym schemacie.

postgres=# \c test postgres;
postgres=# \du  -- listowanie użytkowników dla informacji
                                           Lista ról
        Nazwa roli          |                                               Atrybuty                                              
---------------------------+-------------------------------------------------------------------
 new_app_user              |
 new_app_user_full_access  |
 new_app_user_full_access2 |
 postgres                  | Superużytkownik, Utwórz rolę, Utwórz DB, Replikacja, Obejście RLS

test=# grant create on schema public to new_app_user_full_access ; -- nadałem uprawnienia CREATE dla użytkownika
GRANT
test=# grant all ON schema public to new_app_user_full_access ; -- nadałem uprawnienia ALL dla użytkownika
GRANT

2. Tworzenie obiektów i konfiguracja uprawnień domyślnych

Użytkownik new_app_user_full_access tworzy tabelę t1, a następnie definiuje Default Privileges. Jest to kluczowy mechanizm, który automatycznie nadaje uprawnienia do tabel, które zostaną stworzone przez tego konkretnego użytkownika w przyszłości.

postgres=# \c test new_app_user_full_access ; -- połączenie się do bazy test użytkownikiem new_app_user_full_access
Jesteś obecnie połączony do bazy danych "test" jako użytkownik "new_app_user_full_access".
test=> create table t1 (id int); -- tworzenie tabeli przez użytkownika new_app_user_full_access
CREATE TABLE
test=> alter default privileges in schema public GRANT SELECT, INSERT ON TABLES to new_app_user; -- nadanie defaultowych uprawnień dla użytkownika new_app_user przez właściciela new_app_user_full_access
ALTER DEFAULT PRIVILEGES
test=> \conninfo
You are connected to database "test" as user "new_app_user_full_access" on host "localhost" (address "::1") at port "5555".
test=> create table t2 (id int); -- utworzenie tabeli t2 przez użytkownika new_app_user_full_access
CREATE TABLE
test=> insert into t2 values(generate_series(1,100)); -- insert rekordów przez właściciela tabeli new_app_user_full_access
INSERT 0 100

3. Weryfikacja uprawnień użytkownika new_app_user

W tej sekwencji sprawdzamy działanie uprawnień domyślnych oraz ograniczeń nałożonych na użytkownika.

  • Tabela t1: new_app_user nie ma do niej dostępu, ponieważ tabela istniała przed ustawieniem ALTER DEFAULT PRIVILEGES.
  • Tabela t2: dostęp jest możliwy (SELECT/INSERT), ponieważ powstała po ustawieniu uprawnień domyślnych.
  • Operacje UPDATE/DELETE: są blokowane, gdyż nie zostały uwzględnione w definicji uprawnień.
test=> \c test new_app_user; -- podłączenie do bazy test przez użytkownika new_app_user
Jesteś obecnie połączony do bazy danych "test" jako użytkownik "new_app_user".
test=> select * from t1; -- brak uprawnień ponieważ tabela t1 istniała wcześniej niż powstał użytkownik new_app_user
ERROR:  permission denied for table t1
test=> select count(*) from t2; -- tabela t2 powstała później niż użytkownik new_app_user w związku z tym zadziałał mechanizm przydzielania DEFAULT PRIVILEGES przez właściciela tabeli
 count
-------
   100
test=> create table t3 (id int); -- użytkownik new_app_user nie ma uprawnień do tworzenia tabel w schemacie
ERROR:  permission denied for schema public
test=> delete from t2; -- przydzielone defaultowe uprawnienia nie dają możliwości usuwania z tabeli
ERROR:  permission denied for table t2
test=> insert into t2 values (generate_series(100,110)); -- przydzielone defaultowe uprawnienia pozwalały na INSERT do tabeli t2
INSERT 0 11
test=> select count(*) from t2; -- tabela t2 powstała później niż użytkownik new_app_user w związku z tym zadziałał mechanizm przydzielania DEFAULT PRIVILEGES przez właściciela tabeli
 count
-------
   111
test=> update t2 set id=1; -- przydzielone defaultowe uprawnienia dają możliwości update rekordów z tabeli
ERROR:  permission denied for table t2

test=> \dp
Uprawnienia dostępu
Schemat | Nazwa |  Typ   |                        Uprawnienia dostępu                         | Uprawnienia kolumn | Polityki
---------+-------+--------+------------------------------------------------------------+--------------------+----------
public  | t1    | tabela |                                                            |                    |
public  | t2    | tabela | new_app_user=ar/new_app_user_full_access                  +|                    |
|       |        | new_app_user_full_access=arwdDxtm/new_app_user_full_access |                    |
(2 wiersze)

4. Wprowadzenie drugiego właściciela i problem izolacji uprawnień

Pojawia się nowy użytkownik z pełnymi uprawnieniami. Kluczowym wnioskiem z tego etapu jest fakt, że Default Privileges są przypisane do roli, która je nadaje. Uprawnienia domyślne ustawione przez pierwszego użytkownika nie dotyczą tabel tworzonych przez drugiego użytkownika.

test=> \c test postgres
Jesteś obecnie połączony do bazy danych "test" jako użytkownik "postgres".
test=# create user new_app_user_full_access2; -- utworzenie nowego użytkownika przez użytkownika postgres
CREATE ROLE
test=# grant connect ON database test to new_app_user_full_access2; -- nadanie uprawnienia connect
GRANT
test=# grant usage on schema public TO new_app_user_full_access2; -- nadanie uprawnienia USAGE
GRANT
test=# grant create on schema public to new_app_user_full_access2 ; -- nadanie uprawnienia CREATE
GRANT
test=# \c test new_app_user_full_access2 ; -- połączenie do test użytkownikiem new_app_user_full_access2
Jesteś obecnie połączony do bazy danych "test" jako użytkownik "new_app_user_full_access2".
test=> create table t3 (id int); -- utworzenie tabeli t3
CREATE TABLE
test=> \dp -- sprawdzenie uprawnień widać, że do tabeli t3 nie przydzielono uprawnień ponieważ właścicielem jest inny użytkownik niż new_app_user_full_access a default privileges nie są przydzielane między różnymi właścicielami
                                                   Uprawnienia dostępu
 Schemat | Nazwa |  Typ   |                        Uprawnienia dostępu                         | Uprawnienia kolumn | Polityki
---------+-------+--------+------------------------------------------------------------+--------------------+----------
 public  | t1    | tabela |                                                            |                    |
 public  | t2    | tabela | new_app_user=ar/new_app_user_full_access                  +|                    |
         |       |        | new_app_user_full_access=arwdDxtm/new_app_user_full_access |                    |
 public  | t3    | tabela |                                                            |                    |
(3 wiersze)

5. Samodzielna konfiguracja Default Privileges i dostęp do tabel

Aby new_app_user mógł korzystać z obiektów drugiego administratora, ten również musi zdefiniować swoje uprawnienia domyślne lub nadać je ręcznie do obiektów już istniejących.

test=> alter default privileges in schema public GRANT SELECT, INSERT ON TABLES to new_app_user; -- nadanie default privileges dla new_app_user
ALTER DEFAULT PRIVILEGES
test=> create table t4 (id int); -- utworzenie nowej tabeli t4 przez new_app_user_full_access2
CREATE TABLE
test=> insert into t3 values (generate_series(1,100)); -- insert rekordów do t3
INSERT 0 100
test=> insert into t4 values (generate_series(1,100)); -- insert rekordów t4
INSERT 0 100
test=> \c test new_app_user; -- podłączenie do bazy test użytkownika new_app_user
Jesteś obecnie połączony do bazy danych "test" jako użytkownik "new_app_user".
test=> select count(*) from t4; -- widzi rekordy w tabeli t4 ponieważ powstała po utworzeniu użytkownika new_app_user
 count
-------
   100
(1 wiersz)

test=> select count(*) from t3; -- new_app_user nie ma uprawnień ponieważ tabela istniała zanim użytkownik powstał trzeba nadać ręcznie uprawnienia przez właściciela
ERROR:  permission denied for table t3
test=> \c test new_app_user_full_access2 ;
Jesteś obecnie połączony do bazy danych "test" jako użytkownik "new_app_user_full_access2".
test=> grant select on table t3 to new_app_user; -- nadanie uprawnień przez właściciela t3
GRANT
test=> \c test new_app_user;
test=> select count(*) from t3; -- użytkownik możne wykonać select na tabeli
count
-----

100

6. Ważna uwaga dotycząca błędów przy nadawaniu uprawnień zbiorczych

W trakcie pracy z PostgreSQL należy pamiętać o zasadzie atomowości operacji nadawania uprawnień. Jeżeli wykonasz komendę GRANT ALL ON ALL TABLES IN SCHEMA public TO ... jako użytkownik, który nie jest superużytkownikiem, Postgres spróbuje zmienić uprawnienia dla każdej tabeli po kolei. W momencie napotkania tabeli, której nie jesteś właścicielem (np. t3 stworzonej przez innego administratora), system zwróci błąd permission denied.

Zgodnie z zasadą „wszystko albo nic”, cała ta operacja zostanie natychmiast przerwana i wycofana. Oznacza to, że uprawnienia nie zostaną nadane nawet dla tych tabel, których jesteś właścicielem i które znajdowały się na liście. Aby skutecznie nadać uprawnienia do wszystkich tabel różnych właścicieli, operację tę musi wykonać użytkownik postgres.

7. Zarządzanie uprawnieniami poprzez Role Grupowe

W środowiskach, w których wielu użytkowników posiada uprawnienia do tworzenia obiektów (np. CREATE), często dochodzi do konfliktów własności, co widać w poprzednich krokach. PostgreSQL rygorystycznie przestrzega zasady, że tylko właściciel obiektu może nim w pełni zarządzać.

Aby uniknąć sytuacji, w której jeden administrator nie może modyfikować tabel stworzonych przez drugiego, zaleca się wprowadzenie Roli Właścicielskiej.

Koncepcja Roli Grupowej

Zamiast nadawać uprawnienia CREATE bezpośrednio poszczególnym użytkownikom, tworzymy jedną rolę techniczną (np. app_owner), która staje się formalnym właścicielem wszystkich obiektów. Użytkownicy stają się członkami tej roli, dziedzicząc jej uprawnienia.

Implementacja krok po kroku

Jako superużytkownik wykonujemy następujące kroki:

A. Tworzenie roli grupowej (bez możliwości logowania):

CREATE ROLE app_owner NOLOGIN;

B. Przypisanie użytkowników do grupy:

GRANT app_owner TO new_app_user_full_access;
GRANT app_owner TO new_app_user_full_access2;

C. Nadanie uprawnień schematu dla całej grupy:

GRANT USAGE, CREATE ON SCHEMA public TO app_owner;

Zmiana sposobu pracy (Wymuszenie własności)

Aby nowo tworzone tabele zawsze należały do grupy app_owner (a nie do konkretnego usera), użytkownicy przed rozpoczęciem pracy powinni przełączyć swój kontekst:

SET ROLE app_owner;
CREATE TABLE t5 (id int); -- Tabela t5 należy teraz do grupy, a nie do osoby

Można to również zautomatyzować na poziomie bazy danych, aby każdy obiekt stworzony przez danego użytkownika był automatycznie przypisywany do roli grupowej:

ALTER USER new_app_user_full_access SET ROLE app_owner;

Korzyści z tego rozwiązania:

  • spójność: każdy członek grupy app_owner ma takie same prawa do wszystkich tabel w schemacie;
  • brak konfliktów: operacje typu GRANT ALL ON ALL TABLES wykonywane przez któregokolwiek członka grupy zakończą się sukcesem, ponieważ grupa jest właścicielem wszystkich obiektów;
  • łatwa rotacja pracowników: przy odejściu pracownika lub dodaniu nowego, wystarczy odebrać lub nadać członkostwo w roli app_owner, bez konieczności czasochłonnej zmiany właściciela ( ALTER OWNER) setek tabel.

Krok 1. Tworzenie roli technicznej i przypisanie członków

Tworzymy rolę app_owner, która nie służy do logowania, lecz pełni funkcję właściciela wszystkich obiektów aplikacyjnych. Następnie przypisujemy do niej dotychczasowych administratorów.

-- Utworzenie roli grupowej
CREATE ROLE app_owner NOLOGIN;

-- Przypisanie użytkowników do roli grupowej
GRANT app_owner TO new_app_user_full_access;
GRANT app_owner TO new_app_user_full_access2;

-- Nadanie roli app_owner uprawnień do schematu public
GRANT USAGE, CREATE ON SCHEMA public TO app_owner;

Krok 2. Migracja własności istniejących tabel

Aby uniknąć problemów z uprawnieniami do tabel t1, t2, t3 oraz t4, musimy zmienić ich właściciela na nowo utworzoną grupę. Dzięki temu każdy członek grupy app_owner będzie miał do nich pełny dostęp.

-- Zmiana właściciela dla tabel stworzonych przez new_app_user_full_access
ALTER TABLE public.t1 OWNER TO app_owner;
ALTER TABLE public.t2 OWNER TO app_owner;

-- Zmiana właściciela dla tabel stworzonych przez new_app_user_full_access2
ALTER TABLE public.t3 OWNER TO app_owner;
ALTER TABLE public.t4 OWNER TO app_owner;

Krok 3. Konfiguracja uprawnień domyślnych dla grupy

Ustawiamy uprawnienia domyślne w taki sposób, aby każda tabela stworzona w przyszłości przez rolę app_owner była dostępna dla użytkownika new_app_user z ograniczonym zakresem praw (tylko SELECT i INSERT).

-- Ustawienie uprawnień domyślnych dla roli app_owner
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public
GRANT SELECT, INSERT ON TABLES TO new_app_user;

Krok 4. Automatyzacja kontekstu pracy administratorów

To kluczowy element konfiguracji. Aby administratorzy nie musieli pamiętać o każdorazowym wpisywaniu SET ROLE app_owner, wymuszamy, by obiekty tworzone przez nich były automatycznie przypisywane do grupy.

-- Wymuszenie, aby nowo tworzone obiekty przez tych użytkowników należały do grupy
ALTER ROLE new_app_user_full_access SET role = 'app_owner';
ALTER ROLE new_app_user_full_access2 SET role = 'app_owner';

Jak to będzie działać w praktyce?

Po zastosowaniu powyższego skryptu:

  1. Gdy new_app_user_full_access utworzy tabelę t5, jej właścicielem automatycznie zostanie app_owner.
  2. Użytkownik new_app_user_full_access2 będzie mógł natychmiast modyfikować tabelę t5 (usuwać rekordy, zmieniać strukturę), ponieważ obaj należą do tej samej grupy właścicielskiej.
  3. Użytkownik new_app_user automatycznie otrzyma dostęp do t5 (SELECT/INSERT) dzięki zdefiniowanym uprawnieniom domyślnym dla roli app_owner.
  4. Błąd permission denied przy komendach zbiorczych (np. GRANT ALL ON ALL TABLES) przestanie występować, o ile operacja będzie dotyczyć tabel należących do grupy.

8. Weryfikacja

Poniżej zestaw komend weryfikacyjnych, które pozwolą potwierdzić, czy migracja do modelu roli grupowej przebiegła pomyślnie. Dzięki temu jest pewność, że uprawnienia są spójne.

Sprawdzenie właściciela tabel w schemacie public

To najważniejszy krok. Wszystkie tabele aplikacyjne powinny teraz należeć do app_owner.

SELECT schemaname, tablename, tableowner
FROM pg_catalog.pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;

Czego szukamy w wynikach?

W kolumnie tableowner dla tabel t1, t2, t3 oraz t4 powinna widnieć wartość app_owner. Jeżeli gdzieś nadal widnieje konkretny użytkownik (np. new_app_user_full_access), oznacza to, że dany ALTER TABLE ... OWNER TO nie został wykonany.

Weryfikacja przynależności do grupy

Sprawdzamy, czy administratorzy zostali poprawnie przypisani do roli grupowej i czy mają ustawioną automatyczną zmianę roli.

SELECT r.rolname AS uzytkownik,
       g.rolname AS grupa,
       CASE WHEN r.rolconfig IS NOT NULL THEN 'TAK' ELSE 'NIE' END AS autokonfiguracja_roli
FROM pg_roles r
JOIN pg_auth_members m ON r.oid = m.member
JOIN pg_roles g ON m.roleid = g.oid
WHERE g.rolname = 'app_owner';

Czego szukamy w wynikach?

Tabela powinna pokazać obu administratorów w grupie app_owner. Kolumna autokonfiguracja_roli potwierdza, czy zadziałało polecenie ALTER ROLE ... SET role.

Test uprawnień domyślnych (Default Privileges)

Możemy podejrzeć, jakie uprawnienia zostaną nadane nowym tabelom, zanim je w ogóle stworzymy.

SELECT nspname AS schemat,
       defaclrole::regrole AS wlasciciel_przyszlych_tabel,
       defaclobjtype AS typ_obiektu,
       defaclacl AS nadane_uprawnienia
FROM pg_default_acl a
JOIN pg_namespace n ON a.defaclnamespace = n.oid
WHERE nspname = 'public';

Interpretacja wyników:

W kolumnie nadane_uprawnienia widać wpis typu new_app_user=ar/app_owner. Oznacza to, że każda tabela stworzona przez app_owner (lub administratorów działających w jego imieniu) automatycznie otrzyma prawa SELECT (r) i INSERT (a) dla użytkownika new_app_user.

Test praktyczny

Najlepszym sposobem na ostateczny test jest stworzenie nowej tabeli przez jednego z administratorów i próba odczytu przez użytkownika z ograniczonym dostępem.

-- Jako administrator:
\c test new_app_user_full_access
CREATE TABLE t_final_test (id int);
INSERT INTO t_final_test VALUES (1);

-- Jako użytkownik z ograniczonym dostępem:
\c test new_app_user
SELECT * FROM t_final_test; -- Powinno zadziałać bez dodatkowego GRANT

Ważna uwaga. Po wykonaniu ALTER ROLE ... SET role = 'app_owner', administratorzy po każdym nowym zalogowaniu będą widoczni w systemie jako app_owner. Można to sprawdzić komendą SELECT current_user;.

9. Uprawnienia

Format wyświetlany w kolumnie „Uprawnienia dostępu” (ACL – Access Control List) składa się z trzech części według schematu: odbiorca=uprawnienia/nadawca. Pojedyncze litery określają konkretne przywileje, które zostały nadane. Poniżej znajduje się legenda skrótów użytych w systemie:

Skrót Pełna nazwa Opis uprawnienia
r read Uprawnienie SELECT
a append Uprawnienie INSERT
w write Uprawnienie UPDATE
d delete Uprawnienie DELETE
D truncate Uprawnienie TRUNCATE
x references Tworzenie kluczy obcych (REFERENCES) do tej tabeli
t trigger Uprawnienie TRIGGER
m maintain Prawo do operacji MAINTAIN (VACUUM, ANALYZE, CLUSTER, REINDEX)

Analiza wpisów z polecenia \dp:

  1. new_app_user=ar/new_app_user_full_access oznacza to, że rola new_app_user otrzymała od użytkownika new_app_user_full_access uprawnienia do wstawiania danych (INSERT – a) oraz ich odczytu (SELECT – r) z tej tabeli.
  2. new_app_user_full_access=arwdDxtm/new_app_user_full_access oznacza to, że użytkownik new_app_user_full_access (prawdopodobnie właściciel tabeli) ma nadany komplet możliwych uprawnień do tego obiektu: INSERT (a), SELECT (r), UPDATE (w), DELETE (d), TRUNCATE (D), REFERENCES (x), TRIGGER (t) oraz MAINTAIN (m).

Uwaga dodatkowa. Gdyby obok którejś z liter znajdowała się gwiazdka (np. r*), oznaczałoby to, że dane uprawnienie zostało nadane z prawem do przekazywania go dalej – tzw. GRANT OPTION.

Interpretacja uprawnień dla Baz Danych oraz Schematów

Podobnie jak w przypadku tabel, uprawnienia do baz danych oraz schematów są zapisywane w formacie odbiorca=uprawnienia/nadawca. Należy jednak zwrócić uwagę, że te same litery mogą oznaczać inne przywileje w zależności od typu obiektu.

Uprawnienia na poziomie Bazy Danych (Database)

Skróty stosowane w kolumnie uprawnień przy listowaniu baz danych (polecenie \l):

Skrót Pełna nazwa Opis uprawnienia
C CREATE Prawo do tworzenia nowych schematów wewnątrz bazy danych.
c CONNECT Prawo do nawiązania połączenia z bazą danych.
T TEMPORARY Prawo do tworzenia tabel tymczasowych podczas sesji w tej bazie.

Przykład: uzytkownik=CT/postgres

Oznacza, że uzytkownik może tworzyć nowe schematy (C) oraz tabele tymczasowe (T), a uprawnienia te nadał użytkownik postgres. Brak litery c oznaczałby brak możliwości samego połączenia się z bazą.

Uprawnienia na poziomie Schematu (Schema)

Skróty stosowane w kolumnie uprawnień przy listowaniu schematów (polecenie \dn+):

Skrót Pełna nazwa Opis uprawnienia
U USAGE Podstawowe uprawnienie pozwalające na „użycie” schematu (wejście do niego i dostęp do obiektów, o ile nadano uprawnienia do samych obiektów).
C CREATE Prawo do tworzenia nowych obiektów (tabel, widoków, funkcji) wewnątrz tego schematu.

Przykład: new_app_user=U/postgres

Oznacza, że rola new_app_user ma prawo USAGE (dostęp) do schematu, co pozwala jej odpytywać istniejące w nim tabele (jeśli posiada do nich osobne uprawnienia), ale nie może tworzyć w tym schemacie żadnych nowych obiektów.

Ważne. W PostgreSQL uprawnienia są hierarchiczne, ale nie są dziedziczone automatycznie w dół. Posiadanie uprawnienia CREATE na poziomie bazy danych pozwala tworzyć schematy, ale nie nadaje automatycznie uprawnień do tabel wewnątrz tych schematów. Każdy poziom (Baza -> Schemat -> Tabela) wymaga osobnej definicji w systemie ACL.

Podsumowanie. Złote zasady zarządzania uprawnieniami w PostgreSQL

Zarządzanie uprawnieniami w PostgreSQL nie musi być polem minowym. Jeżeli wdrożysz opisane wyżej mechanizmy, na zawsze zapomnisz o frustrującym błędzie permission denied w środowiskach deweloperskich i produkcyjnych.

Oto zbiór najważniejszych praktyk, które warto zapamiętać i wdrożyć w swoich projektach:

  • Zawsze używaj Ról Grupowych (Właścicielskich) w pracy zespołowej. Nigdy nie pozwól, aby tabele aplikacyjne należały do kont konkretnych administratorów (np. jan_kowalski). Właścicielem obiektów w schemacie powinna być zawsze bezosobowa rola techniczna (np. app_owner).
  • Automatyzuj zmianę kontekstu. Zamiast liczyć na to, że Twój zespół będzie pamiętał o każdorazowym wpisywaniu SET ROLE app_owner;, wymuś to na poziomie bazy za pomocą ALTER ROLE ... SET role = 'app_owner';. Eliminujesz w ten sposób błąd ludzki.
  • Default Privileges nie działają wstecz. Zmiana uprawnień domyślnych dotyczy tylko obiektów tworzonych w przyszłości przez rolę, dla której te uprawnienia zdefiniowano. Jeżeli tabele już istnieją, musisz nadać do nich uprawnienia ręcznie (np. przez GRANT).
  • Uprawnienia nie dziedziczą się w dół. Posiadanie praw do bazy danych (CONNECT) nie oznacza dostępu do schematów, a dostęp do schematu (USAGE) nie daje od razu praw do odczytu znajdujących się w nim tabel. Każdy poziom hierarchii wymaga świadomego zdefiniowania dostępu.
  • Zasada „wszystko albo nic” przy masowych uprawnieniach. Polecenia typu GRANT ALL ON ALL TABLES IN SCHEMA zadziałają tylko wtedy, gdy masz prawo do modyfikacji każdej pojedynczej tabeli w schemacie. Jeżeli natrafisz na choć jedną, której nie jesteś właścicielem, cała operacja zakończy się niepowodzeniem.

Złota zasada PostgreSQL. Tylko właściciel obiektu (lub superuser) może zmieniać jego strukturę i uprawnienia. Wdrażając model ról grupowych sprawiasz, że „właścicielem” staje się cały Twój zespół, co gwarantuje ciągłość pracy nawet przy dużej rotacji kadr.