Kodeloka
  1. Kodeloka
  2. Kelas PostgreSQL
  3. Materi

7 · Keamanan · Modul 9 dari 12

Peran, Hak Akses & Row Level Security

Menerapkan hak akses terkecil di tingkat basis data, dan menegakkan isolasi data multi-penyewa lewat kebijakan baris.

Peran dan Hak Akses

CREATE ROLE aplikasi LOGIN PASSWORD 'rahasia-kuat';

GRANT CONNECT ON DATABASE toko TO aplikasi;
GRANT USAGE ON SCHEMA publik TO aplikasi;
GRANT SELECT, INSERT, UPDATE, DELETE
    ON ALL TABLES IN SCHEMA publik TO aplikasi;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA publik TO aplikasi;

-- WAJIB: berlaku juga untuk tabel yang dibuat NANTI
ALTER DEFAULT PRIVILEGES IN SCHEMA publik
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO aplikasi;

-- Peran khusus baca untuk dasbor & analitik
CREATE ROLE pembaca;
GRANT CONNECT ON DATABASE toko TO pembaca;
GRANT USAGE ON SCHEMA publik TO pembaca;
GRANT SELECT ON ALL TABLES IN SCHEMA publik TO pembaca;

CREATE ROLE analis LOGIN PASSWORD 'lain' IN ROLE pembaca;
⚠️ ALTER DEFAULT PRIVILEGES sering terlupakan

GRANT ... ON ALL TABLES hanya berlaku untuk tabel yang sudah ada saat itu. Tabel yang dibuat migrasi berikutnya tidak akan terjangkau, dan aplikasi tiba-tiba mendapat "permission denied" setelah rilis. ALTER DEFAULT PRIVILEGES menutup celah ini — tetapi perhatikan bahwa ia hanya berlaku untuk objek yang dibuat oleh peran yang menjalankan perintah tersebut.

Row Level Security

RLS menegakkan penyaringan baris di dalam basis data, sehingga tidak bergantung pada aplikasi selalu menuliskan klausa WHERE yang benar.

CREATE TABLE dokumen (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    penyewa_id bigint NOT NULL,
    judul text,
    isi text
);

ALTER TABLE dokumen ENABLE ROW LEVEL SECURITY;

CREATE POLICY isolasi_penyewa ON dokumen
    USING (penyewa_id = current_setting('app.penyewa_id')::bigint);

CREATE POLICY sisip_penyewa ON dokumen
    FOR INSERT
    WITH CHECK (penyewa_id = current_setting('app.penyewa_id')::bigint);
-- Aplikasi menetapkan konteks di awal tiap transaksi
BEGIN;
SET LOCAL app.penyewa_id = '42';
SELECT * FROM dokumen;      -- otomatis hanya milik penyewa 42
COMMIT;
📌 Selalu pakai SET LOCAL, bukan SET

Dengan connection pooler, koneksi dipakai bergantian oleh permintaan yang berbeda. SET biasa bertahan setelah transaksi selesai, sehingga permintaan berikutnya bisa mewarisi konteks penyewa yang salah — kebocoran data lintas penyewa yang sangat serius. SET LOCAL otomatis hilang saat transaksi berakhir.

🚫 RLS tidak berlaku untuk pemilik tabel

Secara bawaan, pemilik tabel dan peran SUPERUSER melewati semua kebijakan RLS. Bila aplikasi Anda terhubung sebagai pemilik tabel, RLS tidak melindungi apa pun. Aplikasi harus memakai peran terpisah yang bukan pemilik. Untuk memaksa RLS berlaku bahkan pada pemilik:

ALTER TABLE dokumen FORCE ROW LEVEL SECURITY;

Kolom Sensitif

-- Hak akses per kolom
GRANT SELECT (id, nama, email) ON pengguna TO analis;
-- kolom lain seperti nik dan tanggal_lahir tidak terjangkau

-- View sebagai lapisan penyamaran
CREATE VIEW pengguna_publik AS
SELECT id, nama,
       left(email, 2) || '***@' || split_part(email, '@', 2) AS email
FROM pengguna;

GRANT SELECT ON pengguna_publik TO analis;

Enkripsi & Autentikasi

-- pg_hba.conf
hostssl  toko  aplikasi  10.0.0.0/8  scram-sha-256
host     all   all       0.0.0.0/0   reject

-- postgresql.conf
ssl = on
password_encryption = scram-sha-256
⚠️ md5 sudah tidak layak dipakai

Metode autentikasi md5 sudah lama dianggap lemah. Gunakan scram-sha-256 untuk semua entri pg_hba.conf. Perlu diingat bahwa mengubah password_encryption saja tidak cukup — password yang sudah ada tetap tersimpan dalam format lama sampai disetel ulang.

Audit

ALTER SYSTEM SET log_connections = on;
ALTER SYSTEM SET log_disconnections = on;
ALTER SYSTEM SET log_statement = 'ddl';    -- semua perubahan skema

-- Memeriksa siapa punya hak apa
SELECT grantee, table_name, privilege_type
FROM information_schema.table_privileges
WHERE table_schema = 'publik'
ORDER BY grantee, table_name;