데이타베이스/PostgreSQL

PostgreSQL RLS: 데이터 보안의 새로운 지평을 열다!

shimdh 2025. 10. 30. 20:23
728x90

안녕하세요, 데이터베이스 애호가 여러분! 오늘은 PostgreSQL의 강력한 보안 기능인 행 수준 보안(Row-Level Security, RLS) 에 대해 깊이 파헤쳐 보겠습니다. 데이터 유출 사고가 빈번한 요즘, 민감한 정보를 보호하는 것은 단순한 선택이 아닌 필수입니다. RLS는 사용자 역할에 따라 테이블의 각 행에 대한 접근을 세밀하게 제어함으로써, 데이터베이스 보안을 한 차원 업그레이드합니다. 이를 통해 규정 준수(예: GDPR, HIPAA)를 충족하고, 불필요한 데이터 노출을 최소화할 수 있죠.

이 글에서는 RLS의 기본 개념부터 실제 적용 사례까지 단계적으로 탐구하겠습니다. 초보자도 쉽게 따라할 수 있도록 실전 예시를 포함했으니, 함께 따라가 보세요. PostgreSQL을 사용 중이시거나 데이터 보안을 강화하고 싶다면, 이 기능은 반드시 도입해 보세요!

728x90

RLS란 무엇인가?

RLS는 쿼리를 실행하는 사용자의 특성(역할, 권한 등) 에 따라 테이블의 특정 행(row) 에 대한 접근을 동적으로 제어하는 PostgreSQL의 내장 기능입니다. 간단히 말해, 같은 테이블을 조회하더라도 사용자마다 '보이는 데이터'가 다르게 필터링됩니다.

예를 들어:

  • 인사팀(HR) 직원은 자신의 부서 데이터만 볼 수 있습니다.
  • IT팀 직원은 IT 관련 데이터만 접근 가능합니다.
  • 영업팀(Sales) 직원은 고객 관련 데이터만 확인할 수 있습니다.

이 기능은 마치 호텔의 스마트 룸 키처럼 작동합니다. 각 키(사용자 역할)가 허용된 문(행)만 열 수 있도록 설계된 거죠. 결과적으로, 데이터베이스 내에서 '필요 이상의 정보'가 노출되는 위험을 크게 줄일 수 있습니다. 특히 다중 테넌트(multi-tenant) 애플리케이션(예: SaaS 서비스)에서 유용하며, 보안 감사 로그와 결합하면 추적성도 강화됩니다.

RLS의 핵심 개념

RLS를 효과적으로 활용하려면 몇 가지 핵심 요소를 이해해야 합니다. 아래에서 각 개념을 자세히 설명하겠습니다.

1. 정책(Policy): 접근 규칙의 심장

정책은 RLS의 핵심으로, 어떤 행이 어떤 사용자에게 보일지(또는 숨길지) 를 정의하는 규칙 세트입니다. 정책은 SQL 명령으로 생성되며, 다음 요소를 기반으로 설계할 수 있습니다:

  • 사용자 역할(role): 특정 그룹 멤버십 확인.
  • 데이터 내용: 행의 컬럼 값(예: 부서 = 'HR').
  • 세션 변수: 애플리케이션에서 전달된 동적 값(예: 현재 사용자 ID).

이점: 정책 하나로 수백 개의 행을 자동 필터링하므로, 애플리케이션 코드에서 보안 로직을 구현할 필요가 줄어듭니다. 예를 들어, "현재 로그인한 사용자가 소유한 레코드만 보여주기" 같은 규칙을 간단히 적용할 수 있죠.

2. 활성화/비활성화: 유연한 적용

RLS는 테이블 단위로 활성화(ENABLE ROW LEVEL SECURITY) 또는 비활성화(DISABLE ROW LEVEL SECURITY)할 수 있습니다. 모든 테이블에 일괄 적용하지 않고, 민감한 테이블(예: 사용자 개인정보 테이블)만 선택적으로 켜는 게 좋습니다.

: 개발 중에는 비활성화해 테스트를 쉽게 하고, 프로덕션 환경에서만 활성화하세요. 이는 성능 오버헤드를 최소화하면서 보안을 최적화합니다.

3. 사용자 역할(User Role): 권한의 기반

PostgreSQL의 역할(role)은 RLS 정책과 직접 연동됩니다. 미리 정의된 역할(예: hr_user, it_user)에 따라 접근 범위를 제한하죠. 역할은 GRANT 명령으로 사용자에게 부여되며, 다중 역할 지원으로 복잡한 조직 구조(예: 부서 + 직급)를 표현할 수 있습니다.

예시: manager 역할은 모든 부서 데이터를 볼 수 있지만, staff 역할은 자기 부서만 접근 가능.

4. 보안 장벽(Security Barrier): 무자비한 보호벽

RLS가 활성화된 테이블에서 실행되는 모든 쿼리(INSERT, UPDATE, DELETE 포함)는 정책을 강제 준수합니다. 심지어 슈퍼유저(superuser) 나 테이블 소유자도 정책을 우회할 수 없습니다. 이는 '의도치 않은 접근'을 막는 강력한 장벽으로, 악의적 공격이나 실수로 인한 유출을 방지합니다.

주의: 보안 장벽을 테스트할 때는 FORCE ROW LEVEL SECURITY 옵션을 사용해 모든 사용자에게 정책을 강제 적용하세요.

실제 예시: 'employees' 테이블에 RLS 적용하기

이론은 그만! 실제로 'employees' 테이블에 RLS를 적용해 보겠습니다. 회사 데이터베이스를 가정하고, 각 부서 직원이 자신의 부서 데이터만 볼 수 있도록 설정하죠. (PostgreSQL 9.5 이상에서 지원되며, psql 클라이언트로 실행 가능합니다.)

1. 테이블 생성 및 데이터 삽입

먼저, 직원 정보를 저장할 테이블을 만듭니다. 급여처럼 민감한 컬럼을 포함해 보안의 필요성을 강조하죠.

CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    department TEXT NOT NULL,
    salary NUMERIC
);

INSERT INTO employees (name, department, salary)
VALUES
    ('Alice', 'HR', 60000),
    ('Bob', 'IT', 70000),
    ('Charlie', 'Sales', 50000),
    ('Diana', 'HR', 65000);  -- 추가 데이터로 테스트 보강

2. 행 수준 보안 활성화

테이블에 RLS를 켭니다. 이제 모든 쿼리가 정책을 따르게 됩니다.

ALTER TABLE employees ENABLE ROW LEVEL SECURITY;

3. 정책 정의

부서별로 SELECT 정책을 생성합니다. USING 절로 필터링 조건을 지정하죠. (영업팀 정책도 추가해 완전성을 더했습니다.)

  • HR 정책:
  • CREATE POLICY hr_policy ON employees FOR SELECT USING (department = 'HR');
  • IT 정책:
  • CREATE POLICY it_policy ON employees FOR SELECT USING (department = 'IT');
  • Sales 정책 (추가):
  • CREATE POLICY sales_policy ON employees FOR SELECT USING (department = 'Sales');

이 정책들은 각 부서 역할이 해당 행만 볼 수 있게 합니다.

4. 사용자 역할 생성 및 할당

역할을 만들고 사용자에게 부여합니다. (실제 환경에서는 애플리케이션 연결 시 역할 설정.)

CREATE ROLE hr_user;
CREATE ROLE it_user;
CREATE ROLE sales_user;

GRANT hr_user TO alice;    -- Alice: HR 사용자
GRANT it_user TO bob;      -- Bob: IT 사용자
GRANT sales_user TO charlie;  -- Charlie: Sales 사용자

5. 접근 제어 테스트

각 사용자로 로그인해 쿼리해 보세요. (SET ROLE로 시뮬레이션.)

  • Alice (HR 사용자):
  • SET ROLE hr_user; SELECT * FROM employees;
  • 출력*:
    ```
    id | name | department | salary
  • ---+-------+------------+--------
    1 | Alice | HR | 60000
    4 | Diana | HR | 65000
    (2 rows)
  • HR 데이터만 보입니다!
  • Bob (IT 사용자):
  • SET ROLE it_user; SELECT * FROM employees;
  • 출력*:
    ```
    id | name | department | salary
  • ---+------+------------+--------
    2 | Bob | IT | 70000
    (1 row)
  • Charlie (Sales 사용자):
  • SET ROLE sales_user; SELECT * FROM employees;
  • 출력*:
    ```
    id | name | department | salary
  • ---+---------+------------+--------
    3 | Charlie | Sales | 50000
    (1 row)

6. 고급 조건 추가: 관리자 권한과 동적 필터링

더 복잡한 시나리오를 위해, 관리자는 모든 데이터를 보고, 일반 사용자는 세션 변수로 부서 필터링을 하도록 확장해 보죠.

-- 관리자 정책 (모든 데이터 접근)
CREATE POLICY manager_policy ON employees
FOR ALL  -- SELECT, INSERT, UPDATE, DELETE 모두 적용
USING (current_user = 'manager' OR role() = 'manager')
WITH CHECK (current_user = 'manager' OR role() = 'manager');

-- 동적 부서 필터링 정책 (애플리케이션에서 설정)
CREATE POLICY dynamic_dept_policy ON employees
FOR SELECT
USING (department = current_setting('app.current_department', true));

사용 예:

-- Alice가 HR 부서로 쿼리 (세션 변수 설정)
SELECT set_config('app.current_department', 'HR', false);
SET ROLE hr_user;
SELECT * FROM employees;  -- HR 데이터만 출력

이처럼 RLS는 INSERT/UPDATE 시에도 정책을 적용해 데이터 무결성을 유지합니다.

결론: RLS로 데이터 보안 강화하기

PostgreSQL RLS는 단순한 '데이터 숨기기'가 아닌, 사용자 중심의 세밀한 접근 제어를 제공합니다. 이를 통해 조직은 민감 정보를 안전하게 관리하면서도 생산성을 유지할 수 있죠. 특히 클라우드 환경이나 대규모 데이터베이스에서 빛을 발하며, 성능 영향도 최소화(인덱스 활용 시)됩니다.

RLS를 도입할 때는:

  • 정책을 철저히 테스트하세요.
  • 감사 로그(예: pgaudit 확장)와 결합해 모니터링하세요.
  • 성능 튜닝을 위해 EXPLAIN ANALYZE로 쿼리 최적화하세요.

데이터 보안을 고민 중이시라면, 지금 당장 PostgreSQL RLS를 실험해 보세요.

728x90