DBAtlas · 데이터베이스 운영 아틀라스

row-level security로 tenant 격리 구성

PostgreSQL보안

명령어

ALTER TABLE app.orders ENABLE ROW LEVEL SECURITY;
-- Make the table owner subject to the policy too
ALTER TABLE app.orders FORCE ROW LEVEL SECURITY;

CREATE POLICY orders_tenant_isolation ON app.orders
  FOR ALL
  TO app_readwrite
  USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::int)
  WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::int);

-- The application sets the tenant at the start of every transaction (pooler-friendly)
BEGIN;
SET LOCAL app.tenant_id = '42';
SELECT * FROM app.orders;
COMMIT;

-- Configured policies
SELECT schemaname, tablename, policyname, roles, cmd, qual, with_check
FROM pg_policies
WHERE tablename = 'orders';

하는 일

테이블에 RLS를 활성화하고, 세션 변수의 tenant id로 행을 필터링하며(USING) 다른 tenant에 대한 쓰기를 차단하는(WITH CHECK) 정책을 정의한다.

사용 시점

멀티테넌트 애플리케이션에서 애플리케이션 코드에 의존하지 않고 데이터베이스에서 데이터 격리를 강제하기 위해.

주의

superuser와 BYPASSRLS 속성을 가진 롤은 항상 정책을 우회한다. 테이블 소유자는 FORCE ROW LEVEL SECURITY가 있을 때만 정책을 적용받는다. RLS가 활성화되어 있고 일치하는 정책이 없으면 결과가 비어 있다(오류 없음). current_setting(..., true)은 변수가 정의되지 않으면 NULL을 반환한다. NULLIF는 RESET 후 남은 빈 문자열이 ''::int로 실패하는 것을 막는다. 사용자는 SET으로 app.tenant_id를 바꿀 수 있으므로 신뢰할 수 있는 애플리케이션 계층만 설정해야 하며 최종 사용자에게 SQL 접근을 주어서는 안 된다. transaction 모드 pooler를 사용하면 SET 대신 SET LOCAL을 사용한다. 정책에 사용되는 tenant_id 컬럼에 인덱스를 만든다. RLS가 적용되는 사용자로 실행하면 pg_dump가 실패하므로 BYPASSRLS 롤로 백업한다.

DBAtlas에서 열기 →

관련 항목