P
PUGUH
multi-tenancy postgresql security

Multi-Tenancy dengan Row-Level Security di PostgreSQL

ARSAKA Team ·

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

AspekApp-Level FilterRLS
KeamananDeveloper bisa lupa WHERE clauseDatabase enforce otomatis
Bug riskTinggi — 1 query tanpa filter = data leakRendah — policy always active
PerformanceIndex bisa tidak terpakaiPostgreSQL optimize query plan
AuditSulit membuktikan isolasiPolicy 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

  1. Superuser bypass RLS — Pastikan application role bukan superuser
  2. Migration complexity — Setiap tabel baru harus punya RLS policy
  3. Cross-tenant queries — Untuk admin/analytics, butuh role khusus yang bypass RLS
  4. 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.