AizuDemy

Tutorial Hardening PostgreSQL: Implementasi Row Level Security & pgaudit

Tutorial Hardening PostgreSQL: Implementasi Row Level Security & pgaudit
๐ŸŽง
Dengarkan Artikel Ini
Suara AI Otomatis โ€ข 9 mnt baca baca
โšก TL;DR

Poin Kunci Artikel Ini:

  • Dalam pola ini, data milik seluruh tenant disimpan dalam tabel yang sama dan dibedakan melalui kolom penanda seperti .
  • Setiap sintaks query SQL yang dibangun oleh aplikasi wajib menyertakan klausul penyaringan seperti .
  • Kelemahan fatal metode ini terletak pada faktor kelalaian manusia (human error).
๐Ÿ“‹ Daftar Isi Materi Tutup โ–ด

Ancaman Kebocoran Data Multi-Tenant dan Ketiadaan Audit Log

Arsitektur aplikasi modern berjenis Software-as-a-Service (SaaS) umumnya mengadopsi model shared database, shared schema demi efisiensi infrastruktur dan kemudahan pemeliharaan. Dalam pola ini, data milik seluruh tenant disimpan dalam tabel yang sama dan dibedakan melalui kolom penanda seperti tenant_id. Pendekatan ini hemat sumber daya, namun menghadirkan risiko keamanan tingkat tinggi yang dikenal sebagai cross-tenant data leakage atau kebocoran data antar tenant.

Secara historis, sebagian besar pengembang menggantungkan isolasi tenant sepenuhnya pada logika aplikasi backend. Setiap sintaks query SQL yang dibangun oleh aplikasi wajib menyertakan klausul penyaringan seperti WHERE tenant_id = 'tenant_a'. Kelemahan fatal metode ini terletak pada faktor kelalaian manusia (human error). Bug pada penulisan ORM, kegagalan parameter parsing, atau celah SQL Injection (SQLi) dapat mengeksekusi query tanpa klausul WHERE, sehingga mengekspos seluruh data milik tenant lain kepada entitas yang tidak berhak.

Masalah mendasar kedua pada instalasi standar PostgreSQL adalah keterbatasan fungsi pencatatan log bawaan (default logging). Secara default, engine PostgreSQL hanya mencatat error koneksi, query yang mengalami batas waktu (timeout), atau statement yang memicu kegagalan sistem. Engine tidak mencatat aktivitas eksekusi query harian yang berhasil seperti SELECT, UPDATE, INSERT, atau DELETE pada objek-objek sensitif.

Ketiadaan jejak audit granular ini menimbulkan titik buta (blind spot) dalam analisis keamanan jaringan. Ketika terjadi insiden pencurian data atau eskalasi hak akses internal (insider threat), tim SecOps dan Database Administrator (DBA) tidak memiliki sarana forensik untuk mengidentifikasi akun mana yang mengambil data, kapan transaksi diubah, atau query mana yang digunakan untuk mengekstraksi informasi. Hal ini berimbas langsung pada kegagalan pemenuhan standar kepatuhan regulasi industri seperti ISO/IEC 27001, SOC 2 Type II, HIPAA, serta Regulasi Perlindungan Data Pribadi (UU PDP / GDPR).

Implementasi Row Level Security (RLS) PostgreSQL

Row Level Security (RLS) adalah fitur bawaan engine PostgreSQL yang diperkenalkan sejak versi 9.5. RLS mengintersepsi seluruh query di tingkat parser dan rewriter engine database. Engine mengevaluasi aturan akses (policy) secara otomatis pada tiap baris (row) tabel sebelum mengembalikan hasil ke aplikasi. Dengan RLS, meskipun aplikasi secara tidak sengaja mengirimkan query SELECT * FROM transactions; tanpa filter WHERE, database hanya mengembalikan baris data yang berhak diakses oleh konteks sesi pengguna yang aktif.

Langkah 1: Membuat Database, Role Aplikasi, dan Tabel Schema

Langkah awal dimulai dengan membuat konteks database terpisah, role non-superuser untuk koneksi aplikasi backend, dan tabel sampel transaksi. Hindari penggunaan akun postgres atau superuser lain untuk operasional harian aplikasi.

-- Login ke psql sebagai superuser postgres
CREATE DATABASE tenant_db;
\c tenant_db

-- Buat role aplikasi non-superuser tanpa hak akses membuat database/role
CREATE ROLE app_user WITH LOGIN PASSWORD 'PasswordSuperAman123!';

-- Buat tabel transaksi multi-tenant
CREATE TABLE transactions (
    id SERIAL PRIMARY KEY,
    tenant_id VARCHAR(50) NOT NULL,
    amount NUMERIC(12, 2) NOT NULL,
    description TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- Berikan hak akses manipulasi data pada tabel dan sequence ke role app_user
GRANT ALL ON TABLE transactions TO app_user;
GRANT USAGE, SELECT ON SEQUENCE transactions_id_seq TO app_user;

Langkah 2: Mengisi Data Sampel Multi-Tenant

Eksekusi sintaks berikut untuk memasukkan sampel data dari dua tenant berbeda, yaitu tenant_a dan tenant_b:

INSERT INTO transactions (tenant_id, amount, description) VALUES
('tenant_a', 150000.00, 'Pembayaran Tagihan Layanan Tenant A'),
('tenant_a', 300000.00, 'Pembelian Lisensi Tahunan Tenant A'),
('tenant_b', 500000.00, 'Pembayaran Invoice Konsultasi Tenant B');

Langkah 3: Mengaktifkan RLS dan Menyusun Policy Isolasi

Secara default, RLS pada tabel baru berstatus nonaktif. Fitur harus diaktifkan secara eksplisit menggunakan instruksi ALTER TABLE. Selanjutnya, buat security policy yang memanfaatkan custom session variable (app.current_tenant_id).

-- Aktifkan mekanisme RLS pada tabel transactions
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;

-- Buat policy isolasi tenant untuk operasi SELECT, INSERT, UPDATE, dan DELETE
CREATE POLICY tenant_isolation_policy ON transactions
    FOR ALL
    TO app_user
    USING (tenant_id = current_setting('app.current_tenant_id', true))
    WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true));

Penjelasan teknis klausa policy:

  • FOR ALL: Menerapkan policy pada seluruh operasi manipulasi data (SELECT, INSERT, UPDATE, DELETE).
  • TO app_user: Membatasi penerapan policy hanya untuk role app_user.
  • USING: Menentukan baris data yang dapat dibaca (read) atau diubah (update/delete). Fungsi current_setting('app.current_tenant_id', true) membaca variabel sesi yang ditetapkan oleh koneksi aplikasi.
  • WITH CHECK: Memvalidasi data baru yang dimasukkan (INSERT) atau diperbarui (UPDATE). Jika tenant_id pada data baru tidak cocok dengan variabel sesi aktif, operasi dibatalkan dengan error penolakan.

Langkah 4: Pengujian dan Verifikasi Isolasi Data

Uji isolasi data dengan melakukan simulasi koneksi sebagai pengguna aplikasi app_user, lalu atur variabel sesi sebelum menjalankan query.

-- Pindah koneksi sebagai app_user
\c tenant_db app_user

-- Konfigurasi identitas tenant pada sesi aktif
SET LOCAL app.current_tenant_id = 'tenant_a';

-- Query seluruh data tanpa klausa WHERE manual
SELECT * FROM transactions;
-- Hasil: Hanya mencantumkan 2 baris milik tenant_a

-- Uji coba pelanggaran policy (mencoba memasukkan data tenant_b saat sesi tenant_a)
INSERT INTO transactions (tenant_id, amount, description) 
VALUES ('tenant_b', 900000.00, 'Percobaan Bypass Data Tenant B');
-- Hasil: ERROR: new row violates row-level security policy for table "transactions"

Konfigurasi Extension pgaudit di Linux

Meskipun RLS berhasil mengisolasi data, pemantauan dan akuntabilitas aktivitas database tetap memerlukan audit logging yang komprehensif. Extension pgaudit (PostgreSQL Audit Extension) menyediakan pencatatan audit log terperinci yang memenuhi persyaratan standar keamanan publik dan privat, dengan mencatat statement SQL terkonfigurasi ke log standar OS.

Langkah 1: Instalasi Package pgaudit di Operating System

Sesuaikan proses instalasi paket pgaudit dengan versi PostgreSQL dan distribusi Linux yang digunakan.

Untuk Ubuntu / Debian (PostgreSQL 15):

sudo apt-get update
sudo apt-get install -y postgresql-15-pgaudit

Untuk RHEL / Rocky Linux / AlmaLinux (PostgreSQL 15):

sudo dnf install -y pgaudit15_15

Langkah 2: Konfigurasi Parameter postgresql.conf

Modifikasi file konfigurasi utama database agar modul pgaudit dimuat ke memori saat engine booting.

sudo nano /etc/postgresql/15/main/postgresql.conf

Tambahkan atau perbarui blok konfigurasi berikut pada file postgresql.conf:

# Muat shared library pgaudit saat startup
shared_preload_libraries = 'pgaudit'

# Set kelas aktivitas yang dicatat dalam log
pgaudit.log = 'read, write, ddl, role'

# Hemat ruang disk dengan tidak mencatat skema katalog internal
pgaudit.log_catalog = off

# Catat parameter query (misal nilai variabel pada prepared statement)
pgaudit.log_parameter = on

# Catat nama relasi/tabel yang terlibat dalam statement
pgaudit.log_relation = on

# Catat statement berulang secara lengkap
pgaudit.log_statement_once = off

# Konfigurasi format file log PostgreSQL
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_line_prefix = '%m [%p] %q%u@%d '

Langkah 3: Restart Service PostgreSQL

Parameter shared_preload_libraries membutuhkan restart penuh pada service database agar engine dapat mengunggah pustaka biner pgaudit.

sudo systemctl restart postgresql

Langkah 4: Aktivasi Extension pada Database Target

Setelah service berjalan kembali, aktifkan extension pgaudit di dalam database target menggunakan hak akses superuser.

-- Login sebagai superuser
psql -d tenant_db -U postgres

-- Buat extension pgaudit
CREATE EXTENSION pgaudit;

Langkah 5: Verifikasi Output Audit Log

Jalankan query baca atau tulis pada database untuk menguji pencatatan log audit:

-- Jalankan query audit test
SELECT * FROM transactions WHERE tenant_id = 'tenant_a';

Pantau output berkas log di sistem operasi Linux melalui perintah tail:

sudo tail -f /var/log/postgresql/postgresql-15-main.log | grep AUDIT

Format log audit yang dihasilkan akan menampilkan detail terperinci seperti berikut:

2026-03-30 10:15:22.123 WIB [12345] app_user@tenant_db AUDIT: SESSION,1,1,READ,SELECT,TABLE,public.transactions,"SELECT * FROM transactions WHERE tenant_id = 'tenant_a';",<not logged>

Mencegah Bypass Policy dan Rotasi Audit Log

Implementasi standar RLS dan pgaudit memerlukan penyesuaian tingkat lanjut (hardening) untuk menutupi celah keamanan sekunder dan menjaga kestabilitas ruang penyimpanan disk.

Mencegah Superuser dan Table Owner Bypass Policy RLS

Secara default pada PostgreSQL, pengguna bertipe superuser dan pemilik tabel (table owner) memiliki hak bawaan untuk melewati (bypass) seluruh batasan RLS. Jika kredensial table owner digunakan oleh aplikasi backend, aturan RLS tidak akan berefek.

Untuk memaksakan agar pemilik tabel juga wajib tunduk pada aturan RLS, manfaatkan instruksi FORCE ROW LEVEL SECURITY:

-- Paksa pemilik tabel untuk mematuhi aturan RLS
ALTER TABLE transactions FORCE ROW LEVEL SECURITY;

Mitigasi Kebocoran Variabel Sesi pada Connection Pooling

Dalam arsitektur modern yang menggunakan middleware connection pooler seperti PgBouncer dengan transaction pooling mode, penggunaan SET LOCAL app.current_tenant_id sangat krusial. Perintah SET LOCAL memastikan variabel sesi hanya berlaku di dalam batas transaksi yang sedang berjalan (BEGIN ... COMMIT) dan otomatis dibersihkan saat transaksi selesai. Jangan gunakan SET biasa tanpa LOCAL, karena nilai variabel dapat tertinggal di koneksi fisik pendukung dan mencemari permintaan tenant lain yang meminjam koneksi sama.

Mengatur Rotasi Log Audit dengan Utilities Logrotate

Aktivasi pgaudit.log = 'read, write' pada database berintensitas tinggi akan menghasilkan volume file log yang sangat besar. Tanpa pengelolaan yang tepat, partisi disk OS dapat penuh dan memicu penghentian mendadak pada engine PostgreSQL.

Buat berkas konfigurasi logrotate khusus untuk log PostgreSQL di Linux:

sudo nano /etc/logrotate.d/postgresql-audit

Masukkan spesifikasi rotasi berkas log sebagai berikut:

/var/log/postgresql/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0640 postgres postgres
    postrotate
        systemctl reload postgresql > /dev/null 2>&1 || true
    endscript
}

Penyelesaian Masalah (Troubleshooting) Permasalahan RLS

Terdapat dua kendala umum saat pengimplementasian RLS di lingkungan produksi:

  • Masalah 1: Query Mengembalikan 0 Row (Hasil Kosong). Penyebab utama adalah variabel sesi app.current_tenant_id belum diinisialisasi sebelum query dieksekusi. Jika fungsi current_setting('app.current_tenant_id') dipanggil tanpa argumen kedua true, PostgreSQL akan melemparkan runtime error. Menggunakan current_setting('app.current_tenant_id', true) mengembalikan nilai NULL jika belum diisi, sehingga perbandingan tenant_id = NULL mengevaluasi ke FALSE dan aman dari kebocoran data.
  • Masalah 2: Error Permission Denied pada Sequence. Saat melakukan INSERT pada tabel berkolom SERIAL atau BIGSERIAL, pengguna butuh akses ke sequence generator. Jika hak akses ini terlewat, eksekusi query INSERT ditolak. Solusi: jalankan perintah GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;.

Checklist Security Hardening PostgreSQL

Gunakan daftar periksa berikut untuk memastikan seluruh parameter keamanan database telah dikonfigurasi dengan benar sebelum diterapkan ke lingkungan produksi:

Fokus KeamananTindakan HardeningSintaks / Parameter Verifikasi
Isolasi Multi-tenantAktifkan RLS dan paksa penguncian ownerALTER TABLE ... FORCE ROW LEVEL SECURITY;
Hak Akses PenggunaTerapkan Principle of Least PrivilegeRole aplikasi non-superuser & tanpa CREATEDB
Audit Logging QueryAktifkan pgaudit pada shared librariesshared_preload_libraries = 'pgaudit'
Manajemen Disk StorageKonfigurasi Logrotate & matikan log catalogpgaudit.log_catalog = off & retensi 30 hari
Isolasi Connection PoolerGunakan pembatasan transaksi lokalSET LOCAL app.current_tenant_id = '...';

Kesimpulan

Strategi hardening pada database PostgreSQL memerlukan kombinasi antara mekanisme pencegahan aktif dan pemantauan pasif. Penggunaan Row Level Security (RLS) memberikan proteksi defensif di layer engine yang menjamin isolasi data multi-tenant tidak bergantung pada kerapian kueri aplikasi. Sementara itu, integrasi extension pgaudit memberikan visibilitas penuh terhadap setiap aktivitas transaksi data, memungkinkan identifikasi potensi anomali secara real-time sekaligus memenuhi standar audit kepatuhan industri.

Kombinasi kedua teknologi ini membentuk fondasi defense-in-depth yang kokoh pada tingkatan arsitektur data. Selalu lakukan uji validasi policy RLS dan simulasi failover logrotate di lingkungan staging sebelum mendistribusikan konfigurasi ini ke kluster produksi.

๐Ÿ“– Artikel Terkait