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 인증을 봅니다.