본문 바로가기
서버구축 (WEB,DB)

PostgreSQL 테이블 접근 권한 제어 Row-Level Security (RLS) 기능

by 날으는물고기 2025. 2. 13.

PostgreSQL 테이블 접근 권한 제어 Row-Level Security (RLS) 기능

Using Postgres Row-Level Security in Python and Django

Row-Level Security (RLS)는 PostgreSQL에서 사용자의 권한에 따라 특정 행(row)의 접근을 제한하는 기능입니다. 일반적인 테이블 수준의 접근 제어(GRANT, REVOKE)와 달리, 특정 조건을 만족하는 행만 조회/수정/삭제 가능하도록 설정할 수 있습니다.

RLS 개념 및 동작 방식

RLS의 주요 특징

  • 행 단위 보안: 특정 테이블 내에서도 개별 행(row)마다 접근 권한을 다르게 설정 가능.
  • 정책 기반 접근 제어: SQL 정책(pg_policy)을 생성하여 행별 접근 제어 규칙을 설정.
  • 슈퍼유저도 기본적으로 제한 가능: FORCE ROW LEVEL SECURITY를 사용하면, 슈퍼유저도 RLS를 우회할 수 없음.
  • 사용자/역할(Role) 기반 정책 적용 가능.

RLS 적용 시나리오

  • 멀티 테넌트(Multi-Tenant) 환경에서 개별 고객 데이터 분리.
  • 특정 부서 또는 팀만 특정 데이터를 조회/수정 가능하도록 제한.
  • 개별 사용자가 자신의 데이터만 접근할 수 있도록 설정.

RLS 설정 및 관리

기본적인 RLS 활성화 및 비활성화

RLS는 기본적으로 비활성화되어 있으며, 테이블 단위로 설정할 수 있습니다.

-- RLS 활성화
ALTER TABLE roles ENABLE ROW LEVEL SECURITY;

-- RLS 비활성화
ALTER TABLE roles DISABLE ROW LEVEL SECURITY;

🔹 확인 방법

SELECT relname, relrowsecurity, relforcerowsecurity
FROM pg_class
WHERE relname = 'roles';
  • relrowsecurity = true: RLS 활성화됨.
  • relforcerowsecurity = true: 슈퍼유저도 우회 불가능.

RLS 정책 생성 및 활용

기본적인 RLS 정책 추가

RLS는 특정 테이블에 대해 정책(Policy)을 추가하여 제어합니다.

CREATE POLICY policy_name
ON table_name
FOR { SELECT | INSERT | UPDATE | DELETE }
TO { PUBLIC | 특정 사용자 | 특정 ROLE }
USING (조건);

기본적인 예제

사용자 본인 데이터만 조회 가능하도록 설정

CREATE POLICY user_data_policy
ON users
FOR SELECT
USING (id = current_user::text);

🔹 정책 해석

  • FOR SELECT: 조회(SELECT)만 제한.
  • USING (id = current_user::text): id 값이 현재 로그인한 사용자(current_user)와 같은 경우만 허용.

RLS 정책 종류 및 예제

SELECT 제한

CREATE POLICY restrict_select
ON orders
FOR SELECT
TO public
USING (customer_id = current_user::text);

🔹 해석

  • orders 테이블에서 customer_id가 현재 로그인한 사용자인 경우만 조회 가능.

INSERT 제한

CREATE POLICY allow_insert
ON transactions
FOR INSERT
TO app_users
WITH CHECK (amount > 0);

🔹 해석

  • app_users 그룹의 사용자는 amount > 0인 데이터만 삽입 가능.

UPDATE 제한

CREATE POLICY allow_update
ON orders
FOR UPDATE
TO managers
USING (status != 'completed');

🔹 해석

  • managers 역할을 가진 사용자는 statuscompleted가 아닌 경우에만 수정 가능.

DELETE 제한

CREATE POLICY allow_delete
ON logs
FOR DELETE
TO admins
USING (created_at < now() - interval '30 days');

🔹 해석

  • admins 역할을 가진 사용자만 30일이 지난 logs 데이터를 삭제 가능.

RLS 정책 확인 및 삭제

현재 테이블의 RLS 정책 확인

SELECT polname, polpermissive, polcmd, polqual, polwithcheck
FROM pg_policy
WHERE polrelid = 'orders'::regclass;

RLS 정책 삭제

DROP POLICY restrict_select ON orders;

슈퍼유저 및 RLS 강제 적용

  • PostgreSQL에서 기본적으로 슈퍼유저는 RLS를 우회할 수 있습니다.
  • 이를 차단하려면 강제 RLS 모드(FORCE ROW LEVEL SECURITY)를 설정합니다.
ALTER TABLE orders FORCE ROW LEVEL SECURITY;

이제 슈퍼유저도 RLS 정책을 따라야 합니다.

RLS 활성화 후 발생할 수 있는 오류 및 해결책

"new row violates row-level security policy" 오류

원인: INSERT를 위한 WITH CHECK 정책이 없음.
해결 방법: WITH CHECK 정책을 추가해야 합니다.

CREATE POLICY allow_insert_orders
ON orders
FOR INSERT
WITH CHECK (customer_id = current_user::text);

"permission denied for relation" 오류

원인: 테이블에 대한 SELECT 권한이 없음.
해결 방법: GRANT SELECT 및 RLS 정책 추가.

GRANT SELECT ON orders TO public;

CREATE POLICY allow_read_orders
ON orders
FOR SELECT
USING (true);

슈퍼유저 우회 불가 문제

원인: FORCE ROW LEVEL SECURITY가 활성화됨.
해결 방법: FORCE ROW LEVEL SECURITY 해제.

ALTER TABLE orders NO FORCE ROW LEVEL SECURITY;

실제 서비스 적용 사례

멀티 테넌트 환경에서 RLS 활용

각 고객(tenant)이 자신의 데이터만 볼 수 있도록 tenant_id를 기준으로 설정.

CREATE POLICY tenant_isolation
ON users
FOR SELECT, UPDATE, DELETE
USING (tenant_id = current_setting('app.current_tenant')::uuid);

current_setting('app.current_tenant')을 사용하면 동적으로 테넌트 ID를 관리할 수 있음.

RLS 주요 기능 요약

기능 설명
ENABLE ROW LEVEL SECURITY RLS 활성화
DISABLE ROW LEVEL SECURITY RLS 비활성화
CREATE POLICY 새로운 RLS 정책 추가
DROP POLICY 기존 RLS 정책 삭제
FORCE ROW LEVEL SECURITY 슈퍼유저도 강제 적용

최적의 RLS 활용 방법

  1. 테이블에 RLS 활성화 후 필요한 정책을 추가 (SELECT, INSERT, UPDATE, DELETE).
  2. 멀티 테넌트 환경에서는 current_setting()을 활용하여 동적 RLS 적용.
  3. 정책 적용 후 반드시 pg_policy를 조회하여 정상적으로 등록되었는지 확인.
  4. 슈퍼유저도 제한이 필요한 경우 FORCE ROW LEVEL SECURITY 설정.

PostgreSQL에서 Row-Level Security (RLS)가 활성화된 모든 테이블을 찾아 비활성화하는 SQL 스크립트는 다음과 같습니다.

전체 RLS 비활성화 쿼리

DO $$ 
DECLARE 
    r record;
BEGIN
    FOR r IN 
        SELECT relname 
        FROM pg_class 
        WHERE relrowsecurity = true
    LOOP
        EXECUTE format('ALTER TABLE %I DISABLE ROW LEVEL SECURITY;', r.relname);
    END LOOP;
END $$;
  1. pg_class에서 relrowsecurity = true인 테이블을 조회 → RLS가 활성화된 테이블 목록을 가져옴.
  2. FOR 루프를 사용하여 각 테이블에 대해 ALTER TABLE <table_name> DISABLE ROW LEVEL SECURITY; 실행.
  3. EXECUTE format(...)을 이용해 동적으로 SQL을 실행하여 각 테이블의 RLS를 비활성화.

적용 후 확인 방법

SELECT relname, relrowsecurity 
FROM pg_class 
WHERE relrowsecurity = true;
  • 실행 후 결과가 없으면 모든 테이블에서 RLS가 비활성화됨을 의미합니다.

⚠ 주의 사항

  • 슈퍼유저 권한이 필요합니다. (ALTER TABLE 권한이 필요)
  • 테이블 이름에 대한 충돌 방지를 위해 format('%I', table_name)을 사용 (SQL 인젝션 방지).
  • 멀티 테넌트 환경에서는 보안 정책이 변경될 수 있으므로 주의해서 실행.

 

위 쿼리를 실행하면 모든 테이블에서 RLS가 비활성화됩니다.

728x90

댓글