Multi-Tenancy dengan Row-Level Security di PostgreSQL
Multi-tenancy adalah arsitektur di mana satu instance aplikasi melayani banyak organisasi (tenant) dengan data yang terisolasi. Implementasi yang benar krusial karena data leak antar tenant adalah security incident yang serius.
3 Strategi Multi-Tenancy
1. Database per Tenant
Setiap tenant punya database sendiri. Isolasi sempurna, tapi operational cost tinggi.
2. Schema per Tenant
Satu database, tapi setiap tenant punya schema sendiri. Moderate isolation.
3. Row-Level Security (RLS)
Satu database, satu schema, tapi setiap row punya tenant_id dan akses
dikontrol di database level.
PUGUH menggunakan strategi ke-3 (RLS) karena memberikan keseimbangan terbaik antara security, performance, dan operational simplicity.
Implementasi RLS di PostgreSQL
Step 1: Tambah organization_id di setiap tabel
CREATE TABLE decisions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id UUID NOT NULL,
title TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Step 2: Enable RLS
ALTER TABLE decisions ENABLE ROW LEVEL SECURITY;
Step 3: Buat Policy
CREATE POLICY tenant_isolation ON decisions
USING (organization_id = current_setting('app.tenant_id')::UUID);
Step 4: Set tenant context per request
SET app.tenant_id = 'org-uuid-here';
SELECT * FROM decisions; -- Hanya return data tenant ini
Keuntungan RLS vs Application-Level Filtering
| Aspek | App-Level Filter | RLS |
|---|---|---|
| Keamanan | Developer bisa lupa WHERE clause | Database enforce otomatis |
| Bug risk | Tinggi — 1 query tanpa filter = data leak | Rendah — policy always active |
| Performance | Index bisa tidak terpakai | PostgreSQL optimize query plan |
| Audit | Sulit membuktikan isolasi | Policy tercatat di database |
Bagaimana PUGUH handle ini
PUGUH mengabstraksi complexity RLS via SDK. Developer cukup provide JWT token,
PUGUH extract organization_id dari token dan set database context otomatis:
# Di produk Anda, cukup validate token
context = await puguh.auth.validate_token(token)
# context.organization_id sudah ter-set
# Semua query otomatis ter-filter per organisasi
Tidak perlu menulis WHERE organization_id = ? di setiap query.
Tidak perlu worry tentang data leak karena lupa filter.
Database enforce isolation di level paling fundamental.
Gotchas yang perlu diperhatikan
- Superuser bypass RLS — Pastikan application role bukan superuser
- Migration complexity — Setiap tabel baru harus punya RLS policy
- Cross-tenant queries — Untuk admin/analytics, butuh role khusus yang bypass RLS
- Testing — Test harus verify isolasi (insert data tenant A, query sebagai tenant B, expect 0 results)
Multi-tenancy yang benar bukan hanya tentang menambahkan organization_id.
Ini tentang memastikan isolasi di setiap layer — database, application, dan API.