TROISINH
Triển khai thực tếSecurity & Multi-tenant

Multi-tenant Architecture: Row-Level Security, data isolation — Từ hy vọng sang thực thi

Row-Level Security (RLS) biến tenant isolation từ 'hy vọng' trong code thành thực thi trong engine, ngăn chặn data leak chỉ bằng một dòng config quên lọc.

Khi agent của bạn phục vụ 100 doanh nghiệp khác nhau trên cùng một database, một lỗi WHERE tenant_id = ? trong JOIN phức tạp là đủ để khách hàng A nhìn thấy dữ liệu của khách hàng B. Không phải vì lỗi logic nghiêm trọng, mà chỉ vì developer quên thêm điều kiện lọc ở một dòng code. Row-Level Security (RLS) chính là lớp phòng thủ cuối cùng biến "hy vọng không quên" thành "bất khả thi để lộ".

Vấn đề

Mô hình multi-tenant truyền thống dựa trên "hy vọng-based security": bạn hy vọng developer nhớ thêm WHERE tenant_id = $1 vào mọi câu query, mọi stored procedure, mọi JOIN và subquery. Trong thực tế:

  • Context switching: Developer chuyển từ tính năng A sang B, copy-paste query và quên sửa điều kiện lọc
  • AI-generated SQL: Agent sử dụng tool để tự động generate truy vấn, không có "muscle memory" về tenant isolation
  • Report queries: Câu query báo cáo phức tạp với 5 bảng JOIN dễ bỏ sót predicate ở một bảng con
  • Data leak cascade: Một dòng quên lọc trong query log analytics có thể expose toàn bộ CRM data của tất cả tenants

Khi leak xảy ra, không phải lỗi của một người mà là lỗi của kiến trúc: chúng ta để quyền quyết định ai được xem dữ liệu nằm ở tầng application, nơi con người mắc lỗi.

Ý tưởng cốt lõi

RLS (Row-Level Security) chuyển nhiệm vụ isolation từ application code xuống database engine. Đây là sự thay đổi từ "perimeter defense" (canh gác cổng) sang "ambient reality" (môi trường tự động phân cách).

Ba mô hình multi-tenant

Trước khi đi sâu RLS, cần phân biệt 3 kiến trúc:

Mô hìnhMô tảRLS phù hợp?
SiloMỗi tenant một database riêng biệtKhông cần (đã cách ly vật lý)
BridgeShared database, schema riêng cho mỗi tenantKhông cần RLS row-level (dùng schema isolation)
PoolShared database, shared schema, tenant_id columnRLS bắt buộc - đây là mô hình scalable nhất

RLS tỏa sáng ở mô hình Pool - nơi bạn muốn economies of scale nhưng không trade-off security.

Cơ chế "vũ trụ song song"

RLS hoạt động bằng cách biến tenant_id từ một label (metadata) thành một dimension (chiều không gian dữ liệu):

  1. Tenant Discriminator Column: Mỗi bảng có cột TenantId (UUID hoặc integer) xác định chủ sở hữu row
  2. Security Policies: Database policies attach predicates vào tables:
    • FILTER predicate (SELECT): Chỉ trả về rows có TenantId khớp session context
    • BLOCK predicate (INSERT/UPDATE): Chặn write vào row không thuộc về tenant hiện tại
  3. Session Context: Application set SESSION_CONTEXT (Azure SQL) hoặc SET app.current_tenant (PostgreSQL) ngay sau authentication
  4. Engine-Level Enforcement: Query planner tự động append predicate vào execution plan, trước cả khi data được đọc từ disk

Ví dụ PostgreSQL:

-- Bước 1: Enable RLS trên bảng chứa PII
ALTER TABLE customer_orders ENABLE ROW LEVEL SECURITY;

-- Bước 2: Tạo policy "mỗi tenant chỉ thấy của mình"
CREATE POLICY tenant_isolation ON customer_orders
    USING (tenant_id = current_setting('app.current_tenant')::UUID);

-- Bước 3: Application set context ngay sau khi xác thực JWT
SET app.current_tenant = 'tenant-abc-123';

Ví dụ Azure SQL:

-- Tạo security predicate function
CREATE FUNCTION fn_securitypredicate(@TenantId INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_accessResult
WHERE @TenantId = CAST(SESSION_CONTEXT(N'TenantId') AS INT);

-- Tạo security policy
CREATE SECURITY POLICY TenantFilterPolicy
    ADD FILTER PREDICATE dbo.fn_securitypredicate(TenantId)
        ON dbo.CustomerData,
    ADD BLOCK PREDICATE dbo.fn_securitypredicate(TenantId)
        ON dbo.CustomerData AFTER INSERT
WITH (STATE = ON, SCHEMABINDING = ON);

"Aha" moment: Không phải filter, mà là đa vũ trụ

Điểm then chốt: RLS không phải là "lọc data sau khi đọc". Query planner tái cấu trúc không gian truy vấn - khi Tenant A kết nối, các rows của Tenant B literally không tồn tại trong không gian truy cập của họ. Database engine tối ưu indexes, JOIN orders, và partition pruning dựa trên assumption rằng chỉ có data của tenant hiện tại là visible.

Điều này tạo ra fail-closed architecture: nếu application quên set SESSION_CONTEXT, policy mặc định trả về empty set (hoặc error tùy config), thay vì expose toàn bộ table.

Tại sao nó hoạt động

Logic then chốt nằm ở separation of concerns giữa authorization và business logic:

  • Application layer: Quyết định ai là tenant hiện tại (authentication, JWT parsing)
  • Database layer: Quyết định dữ liệu nào có thể tồn tại trong query result (authorization)

Điều này tạo ra defense in depth thực sự: ngay cả khi attacker bypass application và connect trực tiếp vào DB (insider threat, compromised connection string), họ vẫn chỉ thấy data của tenant được set trong session context của họ.

Trade-off: Performance vs Safety

RLS thêm overhead vào query planning. Nếu thiếu index trên TenantId, engine buộc phải full table scan vì predicate áp dụng trước other filters. Tuy nhiên, đây là trade-off correctness: chậm nhưng đúng vẫn hơn nhanh nhưng leak data.

Debug complexity: Khi user báo "không thấy data", developer phải kiểm tra cả application logic (có set context đúng không) và RLS policy (có chặn nhầm không). Đây là chi phí operational để đổi lấy security guarantee.

Ý nghĩa thực tế

So sánh: Hy vọng vs Thực thi

Tiêu chíApplication-Level FilterRow-Level Security
Failure modeFail-open (quên lọc = expose all)Fail-closed (thiếu context = empty set)
AI Agent safetyAgent generate SQL có thể bỏ WHERE clauseAgent generate SQL bị engine giới hạn scope
Audit complianceKhó prove rằng tất cả queries đều filterPolicy là single source of truth, auditable
Developer cognitive loadPhải nhớ context mọi lúcSet context 1 lần sau auth, quên cũng an toàn
Cross-tenant analyticsCần explicit UNION hoặc ETLDùng superuser hoặc bypass policy cho reporting role

AI Agent context: Khi agent có "quyền" query

Trong kiến trúc GoClaw/OpenClaw, agent có thể dùng tool để query database. Nếu tool đó connect với superuser privileges, prompt injection có thể khiến agent chạy SELECT * FROM users và expose toàn bộ tenant. Với RLS:

  1. Tool connection dùng scoped connection (set tenant context từ JWT của user đang chat với agent)
  2. Agent dùng tool → Tool generate SQL → Database engine apply policy → Agent chỉ thấy data của user đó
  3. Prompt injection attack: Agent bị ép chạy SELECT * FROM sensitive_table → Policy vẫn filter, chỉ trả về rows thuộc về conversation owner

Đây là architectural defense chống lại cả lỗi lập trình và prompt injection.

Hạn chế cần biết

  • Context confusion: Nếu application bug set wrong TenantId trong session context, RLS sẽ "bảo vệ" bạn bằng cách trả về data của tenant khác (nhưng vẫn là wrong data, không phải leak)
  • Schema rigidity: Khó làm tenant-specific schema customization (column thêm cho tenant A nhưng không cho B) trong shared table model
  • Superuser bypass: Users with BYPASSRLS privilege (PostgreSQL) hoặc CONTROL permission (Azure SQL) thấy toàn bộ data - cần strict RBAC cho DBAs
  • Backup/Restore: Dump file chứa toàn bộ rows mọi tenant - cần mã hóa hoặc logical backup với tenant filter

Đào sâu hơn

5-layer Security

Kiến trúc defense-in-depth từ rate limiting đến encryption, bảo vệ agent runtime toàn diện

Agent Permission Model

Tách biệt "muốn làm" và "được phép làm" - kiến trúc quyền hạn cho AI agent

Compliance & Audit

Logging mọi action, GDPR data retention, và audit trail cho multi-tenant systems

Đọc tiếp

Hooks & Quality Control

Giám sát và kiểm soát chất lượng agent qua lifecycle hooks trước khi triển khai production

Production Deployment

Triển khai multi-tenant agent platform từ development lên staging và production

Tài liệu tham khảo

  • PostgreSQL Row-Level Security: Official Documentation - CREATE POLICY syntax và performance tuning
  • Azure SQL Row-Level Security: Microsoft Docs - SESSION_CONTEXT và security predicates
  • AWS Multi-Tenant SaaS: SaaS Factory Reference Architecture - Patterns cho Silo/Bridge/Pool models
  • Paper: "A Systematic Taxonomy of Security Vulnerabilities in the OpenClaw" (2025) - Phân tích SSRF và injection trong multi-tenant agent environments

On this page