데이터베이스 용어 사전
보안RLS · Row Level Security · 감사

행 수준 보안

같은 테이블에서 사용자마다 보이는 행을 다르게 제한하는 기능. 멀티테넌시에서 애플리케이션 버그를 막는다.

테이블 단위가 아니라 행 단위로 접근을 통제하는 기능(PostgreSQL의 RLS).

무엇을 푸나 — 멀티테넌시

한 테이블에 여러 고객사(tenant) 의 데이터가 섞여 있다

애플리케이션이 매 쿼리에 WHERE tenant_id = ? 를 붙인다

  • 한 군데라도 빠뜨리면 남의 데이터가 보인다
  • 코드 리뷰로 100% 막는 것은 불가능에 가깝다

RLS는 그 조건을 DB가 강제로 붙여 준다.

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_setting('app.tenant_id')::BIGINT);

-- 애플리케이션은 커넥션마다 자기 테넌트를 설정한다
SET app.tenant_id = '42';
SELECT * FROM orders;        -- tenant_id = 42 인 행만 보인다. WHERE 를 안 써도 된다

이제 WHERE를 빠뜨려도 남의 데이터가 나오지 않는다.

읽기와 쓰기를 나눠 건다

CREATE POLICY tenant_select ON orders FOR SELECT
  USING (tenant_id = current_setting('app.tenant_id')::BIGINT);

CREATE POLICY tenant_insert ON orders FOR INSERT
  WITH CHECK (tenant_id = current_setting('app.tenant_id')::BIGINT);
  • USING — 읽을 수 있는 행을 정한다 (SELECT · UPDATE 대상)
  • WITH CHECK — 쓸 수 있는 값을 정한다 (INSERT · UPDATE 결과)

WITH CHECK를 빠뜨리면 남의 테넌트 id로 데이터를 넣을 수 있다.

주의할 점

  • 테이블 소유자와 슈퍼유저는 기본적으로 RLS 를 우회한다
    • FORCE ROW LEVEL SECURITY 로 소유자에게도 적용할 수 있다
  • 커넥션 풀에서 SET 값이 남는다
    • 반납 시 초기화하거나 SET LOCAL (트랜잭션 범위) 을 쓴다
  • 정책 조건이 인덱스를 타는지 확인한다 (tenant_id 인덱스 필수)
SET LOCAL app.tenant_id = '42';   -- 트랜잭션이 끝나면 사라진다 (풀 환경에서 안전)

MySQL에는 RLS가 없어 뷰 + 권한으로 흉내 낸다.

감사 — 누가 무엇을 했나

-- PostgreSQL: pgaudit 확장
SHOW pgaudit.log;      -- write, ddl, role ...

-- 애플리케이션 레벨 이력은 트리거로 남긴다
CREATE TRIGGER trg_audit AFTER INSERT OR UPDATE OR DELETE ON account
  FOR EACH ROW EXECUTE FUNCTION write_audit_log();

면접 함정

  • "애플리케이션에서 걸러면 충분" → 경로가 여럿이고 한 번만 빠뜨려도 유출이다.
  • "RLS를 켜면 자동으로 안전" → 소유자 우회·WITH CHECK 누락·커넥션 풀 잔존값이 실제 구멍이다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 보안·권한 — 인증·인가·암호화·SQL 인젝션