본문으로 건너뛰기

9.4 컬럼·뷰 권한

테이블 단위 권한(9.1)만으로는 모자란 경우가 있다 — 어떤 사용자는 한 테이블의 일부 컬럼만 봐야 하거나, 가공·필터링한 결과만 보여야 합니다. 컬럼 단위 권한뷰(view) 권한이 답입니다. 둘과 SECURITY DEFINER/INVOKER의 의미를 정리합니다.

컬럼 단위 권한

GRANT SELECT (col1, col2) ON table TO role 형식으로 특정 컬럼만 권한 부여.

-- users.email, users.phone 은 민감 — 일부 역할은 못 보게
GRANT SELECT (id, name) ON users TO app_readers;
-- email, phone에는 SELECT 권한 없음

확인:

SELECT column_name, privilege_type
  FROM information_schema.column_privileges
 WHERE table_name = 'users' AND grantee = 'app_readers';

INSERT·UPDATE도 컬럼 단위 가능:

GRANT INSERT (name, status) ON users TO app_writers;
-- email, phone에는 INSERT 권한 없음 (기본값 또는 NULL만)

함정

-- app_readers가 모든 컬럼을 SELECT 시도하면 ERROR
SELECT * FROM users;
-- ERROR: permission denied for column email

-- 명시적으로 허용된 컬럼만 SELECT 가능
SELECT id, name FROM users;
-- 정상

운영 코드의 SELECT *을 모두 명시 컬럼으로 바꿔야 하는 부담입니다. 그래서 컬럼 권한보다 가 더 흔히 쓰입니다.

뷰로 컬럼 제한

CREATE VIEW users_public AS
  SELECT id, name, created_at
  FROM users;

GRANT SELECT ON users_public TO app_readers;
REVOKE ALL ON users FROM app_readers;

뷰는 재작성 단계에서 underlying 쿼리로 풀린다(5.1). 어떤 권한으로 underlying 테이블에 접근할지가 핵심 — 두 가지 모드가 있습니다.

security_barrier

기본 뷰는 옵티마이저가 뷰 안과 바깥의 WHERE를 자유롭게 섞을 수 있습니다. 보안에 쓰는 뷰는 이게 위험합니다.

-- 정의: salary가 1억 미만인 사람만 보이는 뷰
CREATE VIEW employees_public AS
  SELECT id, name, salary
  FROM employees
  WHERE salary < 100000000;

-- 사용자가:
SELECT * FROM employees_public
 WHERE name = pg_terminate_backend(1234);   -- 부작용 있는 함수

옵티마이저가 사용자 WHERE를 underlying 쿼리에 먼저 평가하면 salary 조건 통과 전에 pg_terminate_backend(1234)가 호출돼 row가 leak되거나 부작용이 일어납니다.

해결: security_barrier:

CREATE VIEW employees_public WITH (security_barrier = true) AS
  SELECT id, name, salary
  FROM employees
  WHERE salary < 100000000;

옵티마이저가 뷰의 WHERE를 먼저 평가하도록 강제. 보안용 뷰는 항상 security_barrier = true.

updatable view

단순 뷰는 INSERT/UPDATE/DELETE 가능 (auto-updatable). 조건:

  • 단일 테이블 FROM
  • 집계·GROUP BY·DISTINCT·LIMIT 없음
  • 모든 컬럼이 단순 컬럼 참조
CREATE VIEW active_users AS
  SELECT id, name, email FROM users WHERE deleted_at IS NULL;

-- 자동 updatable
INSERT INTO active_users (name, email) VALUES ('a','a@b.com');
-- users 에 deleted_at = NULL로 자동 삽입

복잡한 뷰는 INSTEAD OF 트리거로 직접 변환 로직 정의합니다.

WITH CHECK OPTION

뷰에 INSERT/UPDATE 시 뷰의 조건을 위반하는 row가 들어가지 않게 강제.

CREATE VIEW active_users AS
  SELECT id, name, email FROM users WHERE deleted_at IS NULL
  WITH CHECK OPTION;

-- 정상
INSERT INTO active_users (name, email) VALUES ('a','a@b.com');

-- ERROR — 조건을 우회하려는 시도
UPDATE active_users SET deleted_at = now() WHERE id = 1;
-- ERROR:  new row violates check option for view "active_users"

WITH CASCADED CHECK OPTION은 뷰 위에 또 뷰가 있을 때 모든 조건 체크.

SECURITY DEFINER vs INVOKER

함수 권한 컨텍스트의 두 모드.

옵션의미
SECURITY INVOKER (기본)호출자(현재 사용자)의 권한으로 함수 본문 실행
SECURITY DEFINER함수 생성자의 권한으로 본문 실행

DEFINER는 사용자가 직접 만질 수 없는 데이터를 정해진 함수로만 접근하게 하는 패턴입니다.

-- 일반 사용자는 audit_log 직접 INSERT 불가
REVOKE ALL ON audit_log FROM app_writers;

-- 함수로만 추가 가능
CREATE FUNCTION log_event(event text) RETURNS void AS $$
  INSERT INTO audit_log(event, created_at, user_name)
    VALUES (event, now(), session_user);
$$ LANGUAGE sql
SECURITY DEFINER
SET search_path = pg_catalog, public;   -- 보안 베스트 프랙티스

GRANT EXECUTE ON FUNCTION log_event(text) TO app_writers;

SECURITY DEFINER의 함정

함정대응
search_path가 사용자 제어 가능SET search_path = pg_catalog, ...로 고정
함수 본문이 사용자 입력을 SQL로 연결SQL injection 위험 — 파라미터화
잊혀진 SECURITY DEFINER 함수가 슈퍼유저 권한 노출정기 감사
-- 위험한 예
CREATE FUNCTION bad_lookup(t text) RETURNS setof record AS $$
  EXECUTE 'SELECT * FROM ' || t;   -- t를 그대로 SQL에 — SQL injection
$$ LANGUAGE plpgsql SECURITY DEFINER;

search_path와 보안

PostgreSQL은 객체 이름을 search_path 순서로 해석. 사용자가 search_path를 조작해 다른 스키마의 동명 객체로 함수가 흘러가게 만들 수 있습니다.

운영 베스트:

CREATE FUNCTION ...
  SET search_path = pg_catalog, public, app
  AS $$ ... $$;

함수 안에서 search_path를 명시적으로 고정. SECURITY DEFINER에서는 필수입니다.

컬럼·뷰 vs RLS 선택

요구사항권장
일부 컬럼 숨김뷰 (security_barrier=true) 또는 컬럼 권한
일부 row 숨김RLS
일부 row + 일부 컬럼뷰 (둘 다 포함)
사용자별 다른 데이터RLS
복잡한 가공·변환뷰 + WITH CHECK OPTION
정해진 함수로만 접근SECURITY DEFINER 함수

운영 안티패턴

안티패턴문제
보안용 뷰에 security_barrier 빠뜨림우회 가능
SECURITY DEFINER 함수가 search_path 미고정권한 escalation 가능
컬럼 권한과 뷰를 섞어 씀권한 모델 불분명
INSTEAD OF 트리거에 비즈니스 로직 폭주디버그·추적 어려움

정리

  • 컬럼 권한: GRANT SELECT (col1, col2) — 일부 컬럼만 부여
  • 뷰: 가장 일반적인 패턴입니다. 보안용은 security_barrier = true 필수
  • WITH CHECK OPTION으로 INSERT/UPDATE의 뷰 조건 강제
  • SECURITY DEFINER 함수는 정해진 인터페이스로만 보호 데이터 접근
  • DEFINER 함수는 항상 search_path 명시 — 권한 escalation 방어
  • RLS와 결합해 깊은 권한 모델 구성 가능

다음 절(9.5)에서는 네트워크 보안 — SSL/TLS와 SCRAM-SHA-256 인증을 봅니다.