Files
vpd-permission-poc/docs/06-agent-ords-vpd-dds-security-brief.md

80 KiB

06 · ORDS 기반 Agent 사용자 식별·권한 매핑 및 VPD/DDS 데이터 접근 통제 설계

목적: ORDS 기반 Agent 요청의 사용자 식별, 권한 매핑, Oracle VPD/DDS 기반 데이터 접근 통제, 사번/부서코드 기반 행 단위 필터링 구조 정의 범위: 권한 요청/승인 API 구현은 제외. DBA 수작업 등록을 전제로 권한 매핑 테이블 설계, 보안 정책 적용 방식, ADB 실행 검증 예제 포함 용어: "Deep Security"는 Oracle Deep Data Security(DDS) 로 표기


목차

문서 흐름은 VPD와 DDS를 먼저 독립적으로 설명한 뒤, 비교 섹션에서 적용 기준을 정리하는 방식으로 구성한다. 고객 설명 시에는 1~3장에서 공통 구조를 먼저 설명하고, 4장은 VPD 경로, 5장은 DDS 경로, 6장은 적용 선택 기준으로 연결한다.


1. 설계 개요 및 적용 범위

본 구조의 기본 원칙은 Agent 계층에서 데이터 권한을 판정하지 않는 것이다. ORDS는 요청자를 식별하고, DB는 보호 객체(VIEW/TABLE)에 연결된 정책으로 허용된 행(row)과 컬럼(column)만 반환한다.

요청 처리 흐름은 다음과 같다. Agent 요청은 ORDS를 통과하고, ORDS는 Authorization 헤더 또는 DB 접속 사용자로 요청자를 식별한다. 이후 실제 데이터 제한은 DB 내부 정책에서 수행한다. 기본 적용 경로는 Bearer Key 기반 VPD이며, DDS는 DDS END USER 또는 지원 드라이버의 Security Context 전파가 가능한 경우의 확장 경로로 구분한다.

아키텍처 개요 및 적용 경로

요청 처리 절차:

  1. Agent가 ORDS API 호출
  2. ORDS가 요청 수신
  3. ORDS 또는 DB가 요청자를 식별
  4. DB Session Context 또는 DDS 보안 Identity에 권한 기준 생성
  5. Agent 요청은 보호 객체(VIEW/TABLE) 조회
  6. VPD가 행(row) 정책 적용. Redaction은 민감 컬럼 값을 NULL/마스킹하고, DDS는 컬럼(column) 허용/제외 적용
  7. 허용된 결과만 반환

구성 요소:

구분 선택지 역할
사용자 식별 DB User Oracle 로그인 계정으로 요청자 식별
사용자 식별 Bearer Key ORDS Authorization 헤더의 키로 내부 사용자 식별
사용자 식별 DDS END USER DDS 보안 사용자로 식별
정책 적용 VPD SYS_CONTEXT, p_object, 권한 테이블로 행(row) 제한
정책 적용 DDS DATA ROLE, DATA GRANT로 행(row)/컬럼(column)/작업(action) 제한
컬럼 처리 Redaction 또는 DDS 컬럼 Grant Redaction은 값을 NULL/마스킹, DDS는 컬럼 허용/제외

컬럼 처리 정리:

방식 역할 이 문서의 설명 기준
VPD 기본 정책 행(row) 조건 반환 컬럼 통제 수단으로 설명하지 않음
Oracle Data Redaction 컬럼 값을 NULL, 부분 마스킹, 정규식 마스킹 값으로 변환 컬럼 Grant가 아니라 값 마스킹/NULL 처리
DDS DATA GRANT AS SELECT (ALL COLUMNS EXCEPT ...)로 컬럼 허용/제외 컬럼 통제로 설명
Column-level VPD 옵션 sec_relevant_cols, sec_relevant_cols_opt 옵션이 있으나 운영 권한 모델로는 혼동 가능 본 구조에서는 사용하지 않고 설명 범위에서도 제외

따라서 이 문서에서의 결론은 아래와 같다.

VPD = 행(row) 통제
Redaction = 컬럼 값 NULL/마스킹
DDS = 행(row) + 컬럼(column) + 작업(action) 통제

주요 구분:

DB에 접속한 사용자와 실제 권한을 적용할 사용자는 다를 수 있다. 특히 ORDS 공통 계정으로 접속하는 구조에서는 DB user != key user다.

설명 순서:

  1. 사용자 식별 방식과 권한 매핑 구조 정의
  2. Bearer Key 기반 VPD 경로 설명
  3. VPD에서 행(row) 통제와 Redaction 컬럼 값 마스킹 구분
  4. DDS에서 END USER, DATA ROLE, DATA GRANT 기반 통제 설명
  5. VPD와 DDS의 적용 조건, 운영 차이, 검증 결과 비교

보호 객체 범위:

본 예제는 API 노출 지점과 우회 차단 구조를 설명하기 위해 VIEW를 주로 사용한다. 단, 지원 범위가 VIEW로 한정되는 것은 아니다. VPD와 DDS는 보호 정책을 VIEW 또는 TABLE 같은 DB 객체에 연결할 수 있다. VIEW는 원본 TABLE을 숨기고 API용 노출 객체를 분리하기 위한 래퍼 패턴이다.

1.1 전체 처리 흐름 요약

다음 세 개의 다이어그램은 전체 구조를 요약한다. 이후 상세 섹션에서 동일한 흐름을 SQL, 정책, 검증 결과 기준으로 설명한다.

두 가지 사용자 식별 시나리오 화면

확인 항목 의미
시나리오 1 - DB User 기반 Oracle 접속 계정으로 요청자를 식별
시나리오 2 - Bearer Key 기반 ORDS Authorization 헤더의 키로 내부 사용자를 식별
공통 하단 흐름 식별 이후에는 DB 보안 정책이 보호 객체(VIEW/TABLE)에 적용
기본 방향 ORDS Bearer Key 기반은 VPD + Redaction을 기본 적용

권한 등록 매핑 화면

확인 항목 의미
APP_USER 실제 권한을 적용할 내부 사용자
AGENT_BEARER_KEY Bearer Key Hash로 내부 사용자를 찾는 매핑
USER_ROLE 사용자가 어떤 role을 갖는지
PERMISSION 어떤 보호 객체를 조회할 수 있는지
PERMISSION_RULE 보호 객체 내 허용 행(row) 조건
p_object Oracle이 VPD 함수에 넘겨주는 현재 조회 객체 이름

권한 미부여 및 차단 결과 화면

확인 항목 의미
권한 매핑 없음 보호 객체 DB 권한은 있어도 VPD 조건이 통과하지 못해 0건
DB 권한 미부여 VPD 판단 전 객체가 보이지 않아 ORA-00942 또는 ORA-01031
Bearer 헤더 없음 ORDS Handler 필수값 차단. 예: ORA-20101
잘못된 Key Key Hash 매핑 실패. 예: ORA-20002

1.2 운영 권한 등록 항목

권한 요청/승인 API 구현은 본 범위에서 제외한다. 단, DBA가 수작업으로 등록하더라도 아래 항목은 사전에 정의되어야 한다.

표의 객체명은 설계 설명용 논리명이다. ADB 실행 예제는 기존 객체와의 충돌을 방지하기 위해 CB_APP_USER, CB_AGENT_BEARER_KEY처럼 CB_* 접두어를 사용한다. 또한 db_username은 DB User 기반 시나리오를 위한 설계 컬럼이며, Bearer Key 중심 실행 예제의 CB_APP_USER에는 포함하지 않았다.

등록 영역 필요한 값 저장 또는 적용 위치 의미
내부 사용자 user_id, 사용자명, 사번, 부서코드, 활성 여부 APP_USER 실제 권한 적용 대상
DB User 매핑 Oracle username APP_USER.db_username 또는 별도 매핑 컬럼 DB User 기반 시나리오에서 사용자 식별
Bearer Key 매핑 Key Hash, Key Prefix, 만료일, 회수일, 활성 여부 AGENT_BEARER_KEY ORDS Authorization 헤더의 키를 내부 사용자로 변환
역할 role id, role name APP_ROLE 권한 그룹
사용자-역할 user id, role id USER_ROLE 사용자가 어떤 역할을 갖는지
보호 객체 권한 target name, action PERMISSION 예: CB_V_SEARCH_DOCUMENTS, SELECT
행 규칙 rule type, rule value PERMISSION_RULE 예: MY_DEPT=HR, SELF, ALL
민감 컬럼 처리 컬럼 값 표시 허용 여부 APP_USER.can_read_contents, Redaction 정책 예: contents 원문 또는 NULL
보호 객체 목록 schema, object name, enabled 보호 객체 목록 테이블 또는 배포 설정 DBMS_RLS.ADD_POLICY 반복 등록 기준
DDS 사용자 end user name CREATE END USER DDS 직접 사용자 식별
DDS 역할/권한 DATA ROLE, DATA GRANT, Predicate, Column List CREATE DATA ROLE, CREATE DATA GRANT DDS 선언형 행/컬럼 권한

운영 등록 항목은 두 축으로 구분한다.

요청 주체 식별
  -> DB User 또는 Bearer Key로 내부 사용자 식별

데이터 접근 범위
  -> role / permission / permission_rule 또는 DDS DATA GRANT로 결정

1.3 권한 판정 절차 및 차단 유형

VPD의 동적 권한 판정은 p_objectpermission.target_name 매핑으로 수행된다.

Agent 요청
  -> ORDS가 사용자 식별
  -> DB Session Context에 USER_ID / EMP_NO / DEPT_CODE 저장
  -> 보호 객체 조회
  -> Oracle이 VPD 함수에 p_object 전달
  -> permission.target_name = p_object 인 권한 확인
  -> permission_rule이 행(row)의 컬럼 값과 일치하는지 확인
  -> 조건을 충족하는 행(row)만 반환
확인 지점 통과 조건 실패 결과
ORDS 헤더 Authorization: Bearer <key> 형식 ORA-20101 또는 401/403
Key 매핑 Key Hash가 활성 사용자와 매핑 ORA-20002, Context 초기화
DB 객체 권한 ORDS runtime schema가 보호 객체 조회 가능 ORA-00942 또는 ORA-01031
VPD 객체 권한 permission.target_name = p_object 존재 0건
VPD 행 규칙 MY_DEPT, SELF, ALL 규칙 통과 해당 행(row) 제외
민감 컬럼 처리 컬럼 표시 Flag 또는 DDS 컬럼 Grant 통과 Redaction NULL 또는 컬럼 제외
DDS Identity END USER 또는 EndUserSecurityContext 전파 DATA GRANT 미적용, ORA-00942 가능

따라서 차단 또는 미조회 결과는 발생 지점에 따라 의미가 다르다.

권한 매핑 없음 = SQL은 실행되지만 결과 0건
DB 객체 권한 미부여 = 객체가 보이지 않거나 권한 오류
Bearer Key 문제 = ORDS/DB Package 단계에서 차단
DDS Context 없음 = DDS DATA GRANT가 적용될 사용자 Identity 없음

1.4 설계 결론

구분 결론
기본 권장안 ORDS가 Authorization: Bearer <key>를 받고 DB 테이블에서 사용자를 매핑하는 구조는 VPD + Redaction을 기본 적용안으로 권장한다.
DDS 적용 조건 DDS는 실제 사용자가 END USER로 접속하거나, 지원 드라이버 또는 호출 애플리케이션이 EndUserSecurityContext를 DB 호출 전에 전파할 수 있을 때 적합하다.
유의 사항 ORDS PL/SQL Handler가 키를 조회해 변수에 저장하는 것만으로는 DDS END USER가 되지 않는다. 이 경로는 ADB에서 차단 검증했다.
운영 관리 VPD/DDS 모두 객체별 적용이 필요하다. 대신 Dictionary View와 Inventory SQL로 적용 현황을 중앙 확인한다.

1.5 요구사항 대응

요구사항 대응 방안
Agent를 Oracle User처럼 권한 제어 가능하다. DB User 기반은 SESSION_USER로 매핑하고, ORDS 공통 계정 기반은 Bearer Key를 내부 사용자로 매핑한다.
사번/부서코드로 검색 데이터 필터링 VPD는 SYS_CONTEXT에 사번/부서코드를 저장하고 정책 함수가 행(row) 조건을 반환한다. DDS는 DATA GRANT ... WHERE dept_code = ...로 선언한다.
ORDS API 개발 위치 ORDS Handler는 Authorization 헤더 필수값 확인과 키 전달을 담당한다. 실제 권한 판단은 DB Package, VPD 함수, DDS DATA GRANT에서 수행한다.
Bearer Key 기반 시나리오 VPD 권장. Key User가 많고 권한이 자주 변경되면 테이블 기반 권한 관리가 운영상 유리하다.
DDS 사용 시나리오 DDS END USER 또는 Driver-level EndUserSecurityContext 전파가 가능할 때 권장한다. 단순 ORDS Handler Key Lookup만으로는 부족하다.
권한 현황 확인 VPD는 DBA_POLICIES, Redaction View, 업무 권한 테이블을 확인한다. DDS는 DBA_DATA_GRANTS, DBA_DATA_ROLE_GRANTS, DBA_DATA_ROLES, DBA_END_USERS를 확인한다.

1.6 검증 결과 요약

검증은 VPD, DDS, ORDS Handler 검증, 공통 인벤토리로 구분해 확인했다.

VPD + Redaction 검증:

항목 상태 근거
Bearer Key -> Context 설정 실행 검증 완료 CB_ORDScb_hr_key, cb_fin_key, cb_all_keyCB_AGENT_CTX에 저장
행(row) 제한 실행 검증 완료 HR 3건, FIN 본인 1건, ALL 6건
Redaction 값 마스킹/NULL 실행 검증 완료 contents 컬럼 값은 VPD가 아니라 Redaction이 처리. HR/FIN은 NULL, ALL은 원문 표시
Key 오류 처리 실행 검증 완료 Invalid Key는 ORA-20002, Context 초기화 후 0건
원본/권한 테이블 우회 차단 실행 검증 완료 CB_SEARCH_DOCUMENTS, CB_APP_USER, CB_AGENT_BEARER_KEY 직접 조회는 ORA-00942

DDS 검증:

항목 상태 근거
Direct END USER 접속 실행 검증 완료 cb_dds_hr, cb_dds_fin, cb_dds_all, cb_dds_none로 접속
DATA GRANT 행(row) 제한 실행 검증 완료 HR 3건, FIN 2건, ALL 6건
DATA GRANT 컬럼(column) 허용/제외 실행 검증 완료 HR/FIN은 ALL COLUMNS EXCEPT contents, ALL은 전체 컬럼
권한 없는 END USER 실행 검증 완료 cb_dds_none은 보호 객체 조회 시 ORA-00942
원본/VPD 객체 우회 차단 실행 검증 완료 DDS End User가 원본 TABLE 또는 VPD 보호 객체 직접 조회 시 ORA-00942

ORDS Handler 검증:

항목 상태 근거
Mandatory Bearer 헤더 실행 검증 완료 헤더가 없으면 ORA-20101
VPD Bearer Handler 실행 검증 완료 Handler Package가 Key를 Context로 저장하고 HR 행(row) 3건 반환
DDS Bearer 단독 매핑 Handler 차단 검증 완료 Key를 cb_dds_hr로 매핑해도 dds_context_username=null, ORA-00942, EXPECTED_BLOCKED
실제 ORDS HTTP Endpoint 호출 별도 환경 필요 ADB에는 ORDS Package와 Handler 정의를 구성했다. 외부 URL 호출은 ORDS URL/인증 설정 확인 후 수행

공통 확인:

항목 상태 근거
VPD/Redaction Inventory 실행 검증 완료 DBA_POLICIES, REDACTION_POLICIES에서 정책 확인
DDS Inventory 실행 검증 완료 DBA_DATA_GRANTS, DBA_DATA_ROLE_GRANTS에서 DATA GRANT와 END USER 매핑 확인
DDS Driver-level Security Context 별도 구성 필요 IAM/DB Access Token 및 지원 드라이버 설정 필요. 문서에는 공식 API 기준 경로를 설명

1.7 권장 구현 구조

현재 요구사항의 주 경로는 아래 흐름이다.

Agent
  -> ORDS Endpoint
  -> Authorization 헤더 필수 확인
  -> Bearer Key Hash 조회
  -> 내부 user_id / 사번 / 부서코드 / 컬럼 권한 식별
  -> DB Session Context reset + set
  -> 보호 객체(VIEW/TABLE) 조회
  -> VPD 행(row) 제한 + Redaction 값 NULL/마스킹
  -> 허용된 결과만 반환

역할을 나누면 아래와 같다.

계층 맡는 일 맡지 않는 일
Agent ORDS API 호출, Authorization: Bearer <key> 전달 행/컬럼 권한 판단
ORDS Handler 헤더 필수값 확인, 키를 DB Package로 전달, 오류를 HTTP 응답으로 변환 업무 권한 규칙 직접 구현
DB Package Key Hash 검증, 내부 사용자 조회, SYS_CONTEXT 값 reset/set 보호 객체별 행 조건을 직접 SQL에 붙임
VPD Function 보호 객체 이름과 사용자 Context로 행 제한 조건 생성 Bearer Key 원문 저장
Redaction 컬럼 권한에 따라 민감 컬럼 NULL 처리 행 제한
Inventory SQL VPD, Redaction, DDS 적용 현황 확인 업무 승인 사유 관리

DDS는 아래 조건이 충족될 때 같은 구조의 확장 경로로 설명한다.

DDS END USER 직접 접속
  또는
지원 드라이버 또는 호출 애플리케이션이 EndUserSecurityContext를 DB 호출 전에 Attach
  -> DATA ROLE 확인
  -> DATA GRANT 적용
  -> 허용된 행(row)/컬럼(column)/작업(action)만 반환

1.8 실행 검증 완료 기준

아래 결과가 나오면 현재 요구사항의 실행 가능한 예제로 볼 수 있다.

VPD 완료 기준:

검증 기대 결과
Context 없음 0건
잘못된 Bearer Key ORA-20002, Context 초기화 후 0건
cb_hr_key HR 행(row) 3건, contents NULL
cb_fin_key 본인 행(row) 1건, contents NULL
cb_all_key 전체 행(row) 6건, contents 원문 표시
원본 TABLE 직접 조회 ORA-00942 또는 직접 권한 미부여

DDS 완료 기준:

검증 기대 결과
DDS cb_dds_hr HR 행(row) 3건, contents NULL
DDS cb_dds_fin FIN 행(row) 2건, contents NULL
DDS cb_dds_all 전체 행(row) 6건, contents 원문 표시
DDS cb_dds_none ORA-00942

ORDS Handler 검증 완료 기준:

검증 기대 결과
Bearer 헤더 없음 ORA-20101
VPD Handler cb_hr_key HR 행(row) 3건, contents NULL
ORDS Handler의 DDS Bearer 단독 매핑 검증 dds_context_username=null, ORA-00942, EXPECTED_BLOCKED

공통 완료 기준:

검증 기대 결과
중앙 권한 확인 DBA_POLICIES, REDACTION_POLICIES, DBA_DATA_GRANTS, DBA_DATA_ROLE_GRANTS에서 정책 확인

1.9 VPD 적용 단위 기준

VPD는 보호할 TABLE 또는 VIEW에 객체별로 연결한다. DBMS_RLS.ADD_POLICYobject_name은 실제 객체 이름을 받는 값이다.

검토 항목 기준
schema 전체에 한 번에 VPD를 걸 수 있는가 아니다. ADD_POLICY 1회는 보호 객체 1개에 적용된다.
object_name => '*' 또는 % 표현이 가능한가 아니다. wildcard로 전체 TABLE/VIEW를 지정하는 방식이 아니다.
statement_types에 여러 SQL 작업을 지정하면 전체 객체 적용인가 아니다. SELECT, INSERT, UPDATE, DELETE처럼 적용할 SQL 작업 종류를 지정하는 값이다. 보호 객체 wildcard가 아니다.
객체가 많으면 어떻게 하는가 보호 객체 목록을 관리하고, 목록을 루프 돌면서 ADD_POLICY를 객체별로 생성한다.
같은 정책 함수를 재사용할 수 있는가 가능하다. 정책 함수는 하나로 두고, p_object 값으로 현재 조회 객체를 구분한다.
매핑 테이블에 권한이 없으면 어떻게 되는가 VPD 조건이 통과하지 못하므로 해당 보호 객체 조회 결과는 0건이다.

정리:

전체 적용 = Wildcard 1회 등록
아님

전체 적용 = 보호 객체 목록 전체를 돌며 객체별 ADD_POLICY 등록
운영 검토 항목 - 적용 단위, 권한 조회, 누락 방지
검토 항목 기준
Bearer Key만으로 DDS 사용자 권한까지 적용되는가 ORDS Handler에서 Key를 읽는 것만으로는 적용되지 않는다. VPD는 SYS_CONTEXT 방식으로 가능하고, DDS는 EndUserSecurityContext 전파가 필요하다.
권한 현황을 한눈에 볼 수 있는가 가능하다. VPD는 DBA_POLICIES와 업무 권한 테이블, DDS는 DBA_DATA_GRANTS, DBA_DATA_ROLE_GRANTS, DBA_DATA_ROLES, DBA_END_USERS로 확인한다.
VPD 정책을 모든 VIEW/TABLE에 한 번에 걸 수 있는가 아니다. DBMS_RLS.ADD_POLICY는 보호 객체 하나를 지정한다. object_name => '*' 같은 Wildcard 등록은 하지 않는다. 여러 객체는 같은 정책 함수를 재사용하되 객체별로 등록하거나 스크립트로 일괄 생성한다.
DDS도 객체별로 권한을 선언해야 하는가 DATA GRANT는 보호 객체, 역할, 행/컬럼 조건 단위로 선언한다. 적용은 선언 단위지만 Dictionary View로 중앙 조회가 가능하다.
객체가 많아지면 누락 위험은 어떻게 줄이는가 보호 객체 목록 테이블 또는 설정 파일을 기준으로 VPD/DDS 등록 스크립트를 생성하고, Dictionary 조회 결과를 검증 단계에 포함한다.
오류 코드 상세 - ORA 코드 해석과 정상 차단 여부
오류 코드 이 문서에서의 의미 정상 차단 여부 확인 방법
ORA-00942 table 또는 view가 없거나, 현재 사용자에게 객체가 보이지 않음 DDS에서 DATA GRANT가 없거나 원본 TABLE 권한을 숨긴 경우 정상 차단 DBA_DATA_GRANTS, DBA_DATA_ROLE_GRANTS, DBA_POLICIES, 직접 Grant 여부 확인
ORA-20002 잘못되었거나 만료된 Bearer Key 정상 차단. RAISE_APPLICATION_ERROR로 만든 사용자 정의 오류 agent_bearer_key의 Hash, Active, Revoked, Expires 상태 확인
ORA-20101 Authorization: Bearer <key> 헤더가 없거나 형식이 틀림 정상 차단. ORDS Handler Package에서 만든 사용자 정의 오류 ORDS 요청 헤더와 Handler Bind Variable 확인
ORA-01031 권한 부족 우회 시도 테스트에서는 정상 차단 사용자가 DBMS_SESSION, 정책 변경, 원본 객체 접근 권한을 갖고 있는지 확인
ORA-02019 원격 DB 연결 식별자 또는 DB Link 접근 불가 원격 원본 직접 조회 차단 테스트에서는 정상 차단 DB Link 존재 여부, 직접 Grant 여부, 네트워크/원격 접속 설정 확인

주의할 점:

구분 설명
ORA-00942 실제로 객체가 없을 때도 발생하고, 권한 미부여로 객체가 숨겨질 때도 발생한다. 보안 테스트에서는 Dictionary 조회 결과와 함께 해석해야 한다.
ORA-20000~ORA-20999 Oracle 표준 오류가 아니라 개발자가 RAISE_APPLICATION_ERROR로 정의하는 사용자 오류 범위다. 이 문서의 ORA-20002, ORA-20101이 여기에 해당한다.
정상 차단 오류 보안 검증에서는 실패가 아니라 “우회가 막혔다”는 증거다. 단, 운영 장애 분석에서는 같은 코드라도 발생 위치와 사용자, 대상 객체를 함께 봐야 한다.
운영 책임 상세 - 누가 무엇을 관리하는지
영역 담당 역할 관리 항목
Agent API 호출 주체 Bearer Key 전달, 요청 Parameter 구성
ORDS/API API Gateway 및 Handler Mandatory Authorization 헤더 확인, Handler Package 호출, 오류 응답 변환
DB 보안 Package Key 사용자 식별 Key Hash 조회, 사용자 활성 상태 확인, Session Context reset/set
VPD/Redaction 데이터 제한 행 제한 정책, 민감 컬럼 처리, 보호 객체별 정책 연결
DDS 선언형 데이터 권한 END USER, DATA ROLE, DATA GRANT, 컬럼 Grant
DBA/Security 보안 객체 등록 DB User, Role, Grant, VPD Policy, DDS Object, Inventory 점검
업무 승인 관리 권한 요청/승인 이력 요청자, 승인자, 사유, ticket, 만료일, 회수 이력

권한 요청/승인 API는 본 범위에서 제외하지만, 테이블 설계에는 이력을 남길 자리를 둔다. 예를 들어 permission_request, permission_approval, agent_bearer_keyissued_at, expires_at, revoked_at 같은 컬럼이 그 역할이다.

성능·감사·운영 체크리스트 - 적용 후 점검 항목
구분 체크 항목
Index agent_bearer_key(key_hash), user_role(user_id, role_id), permission(role_id, target_name, action_name), permission_rule(perm_id, rule_type, rule_value)
업무 컬럼 행 제한에 쓰는 dept_code, owner_emp_no는 검색 대상 테이블 또는 VIEW 기반 TABLE에 Index 검토
VPD 함수 함수 안에서 복잡한 업무 로직을 실행하지 말고, 권한 테이블을 확인하는 SQL 조건을 반환하는 구조 유지
권한 누락 방지 보호 객체 목록을 관리하고, DBMS_RLS.ADD_POLICY 또는 DATA GRANT 등록 결과를 Inventory SQL로 비교
우회 차단 ORDS runtime schema와 Agent DB user에는 원본 TABLE, 권한 테이블 직접 조회 권한을 주지 않음
key 관리 key 원문 저장 금지, hash 저장, prefix만 표시, 만료/회수/교체 절차 운영
감사 로그 request id, key id 또는 user id, ORDS endpoint, result count, 오류 코드, 실행 시각 기록
정기 점검 DBA_POLICIES, REDACTION_POLICIES, DBA_DATA_GRANTS, DBA_DATA_ROLE_GRANTS 결과를 배포 전후 비교

2. 사용자 식별 및 권한 매핑 구조

이 장에서는 Agent 요청이 DB 권한 적용 대상으로 변환되는 방식을 정리한다. DB User 기반, Bearer Key 기반, DDS END USER 기반을 구분하고, 본 요구사항의 기본 경로는 DB user != Key User 구조임을 명확히 한다.

상세 설명 - DB User, Bearer Key, DDS END USER 식별 경로

2.1 DB User 기반

Agent 또는 ORDS가 Oracle 계정으로 DB에 접속하고, DB는 SESSION_USER를 기준으로 사용자를 식별한다.

Oracle 계정 사번 부서 역할
AGENT_HR_001 E10234 HR HR 검색
AGENT_FIN_002 E20411 FIN FIN 검색

흐름:

  1. Agent 또는 ORDS가 AGENT_HR_001로 DB 접속
  2. DB가 SYS_CONTEXT('USERENV', 'SESSION_USER') 확인
  3. app_user에서 DB 계정에 해당하는 내부 사용자 조회
  4. 사용자-역할-권한 테이블에서 접근 범위 확인
  5. 보호 객체(VIEW/TABLE) 조회 시 선택한 정책 적용
  6. VPD 경로는 VPD가 행(row)을 제한하고, 민감 컬럼 값은 Redaction으로 NULL/마스킹 처리
  7. 컬럼 자체를 제외하려면 VIEW/TABLE 설계 또는 DDS DATA GRANT 사용
  8. DDS 경로는 DATA GRANT가 행(row)/컬럼(column)/작업(action)을 제한

DB User 기반은 설명과 감사가 단순하다. 다만 ORDS가 항상 같은 DB 계정으로 접속하면 DB 계정만으로 실제 사용자를 구분할 수 없다.

2.2 Bearer Key 기반

ORDS가 공통 DB 계정으로 접속하는 경우에는 Authorization 헤더의 Bearer Key로 실제 사용자를 식별한다.

흐름:

  1. Agent가 ORDS API 호출
  2. ORDS 헤더에 Authorization: Bearer <key> 포함
  3. ORDS Handler가 Bearer Key 필수 여부 확인
  4. Key가 없거나 일치하지 않으면 401/403 또는 사용자 정의 오류 반환
  5. Key Hash 계산
  6. agent_bearer_key 테이블에서 내부 사용자 조회
  7. 내부 사용자의 USER_ID, 사번, 부서코드, 컬럼 허용 Flag를 DB 세션에 저장
  8. 보호 객체(VIEW/TABLE) 조회
  9. VPD가 허용 행(row)만 반환
  10. Redaction이 컬럼 허용 Flag를 보고 민감 컬럼을 NULL 처리하거나 원문 표시

Bearer Key 테이블 예:

CREATE TABLE agent_bearer_key (
  key_id       NUMBER PRIMARY KEY,
  user_id      NUMBER NOT NULL REFERENCES app_user(user_id),
  key_hash     VARCHAR2(128) NOT NULL UNIQUE,
  key_prefix   VARCHAR2(16),
  issued_at    DATE DEFAULT SYSDATE NOT NULL,
  expires_at   DATE,
  revoked_at   DATE,
  active       CHAR(1) DEFAULT 'Y' CHECK (active IN ('Y','N'))
);

이 구조에서는 DB 계정이 하나여도 Key 사용자는 여러 명일 수 있다.

레벨 의미
DB 접속 사용자 CB_ORDS ORDS가 DB에 접속할 때 쓰는 계정
Key 사용자 agent_hr, agent_fin_self, agent_all Bearer Key가 가리키는 내부 사용자
권한 규칙 HR 행(row), 본인 행(row), contents 컬럼 허용 여부 실제 데이터 제한 기준

따라서 Bearer Key 기반의 핵심은 아래와 같이 정리한다.

DB user != key user. DB User는 접속 계정이고, Key User는 실제 권한 적용 대상이다.

2.3 DDS END USER 기반

DDS는 END USER, DATA ROLE, DATA GRANT로 권한을 선언한다.

DDS 객체 역할
END USER 스키마를 소유하지 않는 보안 사용자
DATA ROLE 데이터 권한 묶음
DATA GRANT 어떤 VIEW/TABLE의 어떤 행(row)/컬럼(column)/작업(action)을 허용하는지 선언

Bearer Key가 많고 Key별 권한 변경이 잦은 구조라면 VPD의 권한 테이블 방식이 운영상 적합하다. 반대로 실제 사용자가 DDS END USER로 명확히 전파되고 정책을 DDL로 관리하려면 DDS가 적합하다.

DDS에는 확장 객체로 APPLICATION IDENTITY도 있다. 이 객체는 DDS 객체다. 다만 본 문서의 두 가지 사용자 식별 시나리오에서는 제외한다.

DDS 확장 객체 의미 이 문서에서 제외한 이유
APPLICATION IDENTITY 특정 애플리케이션을 DB 안의 보안 Identity로 등록 현재 요청은 Agent/ORDS 자체 권한보다 사용자별 데이터 권한 설명이 핵심

APPLICATION IDENTITY는 애플리케이션 공통 권한이나 OAuth/OIDC 기반 애플리케이션 식별이 필요할 때 검토한다. Bearer Key가 가리키는 내부 사용자별 권한을 대체하는 개념은 아니다.

CREATE APPLICATION IDENTITY hcm_app
  MAPPED TO 'AZURE_CLIENT_ID=<client-id>';

GRANT DATA ROLE hcm_role
  TO hcm_app;

DDS에서 Bearer Key를 다룰 때는 두 단계를 구분해야 한다. ORDS Handler가 Authorization 헤더를 읽고 Key를 내부 사용자로 매핑하는 것은 가능하다. 그러나 그 매핑값이 자동으로 DDS END USER가 되지는 않는다.

DDS Bearer Key 처리 검증

ADB에서 ORDS Handler Package를 만들어 검증한 결과:

검증 항목 결과
ORDS Handler에서 Authorization 헤더 필수 처리 가능
Handler가 Key를 내부 사용자 또는 DDS End User 이름으로 매핑 가능
같은 Handler에서 VPD Context 설정 후 조회 가능
같은 Handler에서 DDS EndUserSecurityContext 생성/Attach 검증 결과 생성/Attach되지 않음. DB Session 안의 PL/SQL Handler 단계는 Driver-level Attach 지점이 아님
CB_ORDS Session에서 Key만 매핑하고 DDS 보호 객체 조회 ORA-00942, dds_context_username=null

따라서 단순 Bearer Key 테이블 Lookup 방식은 VPD 적용이 권장된다. DDS로 Bearer 기반 사용자 권한을 적용하려면 Key 검증 후 DDS EndUserSecurityContext Payload를 DB 호출 전에 전달하는 별도 드라이버 또는 호출 애플리케이션 경로가 필요하다.

Bearer 유형 DDS 적용 가능 여부 설명
IAM/OIDC Bearer Token 가능 Token이 End-user Identity와 Role Claim을 담고 있고, 지원 드라이버 또는 호출 애플리케이션이 DDS Security Context Payload로 DB에 전달해야 함
애플리케이션 자체 API Key 조건부 가능 Key를 검증한 뒤 Local END USER와 Security Context Lookup Key로 변환하고, DB 호출 전에 Security Context를 Attach해야 함
ORDS PL/SQL Handler의 단순 DB 테이블 Lookup Key VPD 권장 Handler 내부에서 Key를 찾는 것만으로 DDS End User Context가 생성되지 않음

DDS Security Context 전달 방식:

항목 확인 내용
전달 단위 Oracle Client Driver가 EndUserSecurityContext Payload를 DB Connection Metadata로 전달
Driver 지원 JDBC, Python, ODP.NET
Python API oracledb.create_end_user_security_context(), connection.set_end_user_security_context(), connection.clear_end_user_security_context()
IAM 사용자 end_user_token + database_access_token 전달
Local 사용자 (end_user_name, security_context_lookup_key) + database_access_token 전달
DB Pool User 권한 CREATE SESSION, CREATE END USER SECURITY CONTEXT 필요
DB에서 읽는 값 ORA_END_USER_CONTEXT.username, Custom Context는 ORA_END_USER_CONTEXT.<schema>.<context>.<attr>

즉, Bearer Key를 DDS에 적용하려면 아래 변환이 필요하다.

Authorization: Bearer <api-key>
  -> 호출 애플리케이션 계층에서 Key 검증
  -> 내부 사용자 확인
  -> EndUserSecurityContext Payload 생성
  -> SQL 실행 전에 DB Connection에 Security Context Attach
  -> DATA ROLE / DATA GRANT 적용

반대로 이 변환을 하지 않고 ORDS가 공통 DB 계정으로만 접속하면, DDS는 Bearer Key별 사용자를 구분하지 못한다. 이 경우 DB user=CB_ORDS, key user=cb_dds_hr 같은 매핑값은 호출 애플리케이션 내부 변수일 뿐이고 DDS 보안 Identity는 아니다.

지원 드라이버 또는 호출 애플리케이션 계층 기준 의사 코드:

import oracledb

# 1. 호출 애플리케이션에서 Bearer API Key 검증 후 내부 사용자 식별
end_user_name = "cb_dds_hr"
lookup_key = "<security-context-lookup-key-for-this-user>"
database_access_token = "<db-access-token-issued-by-iam>"

# 2. DDS End-user Security Context Payload 생성
user_context = oracledb.create_end_user_security_context(
    # Local End-user 방식: (end user name, security context lookup key)
    end_user_identity=(end_user_name, lookup_key),
    database_access_token=database_access_token,
    attributes={
        "hr.hcm_context": {
            "emp_no": "E10234",
            "dept_code": "HR"
        }
    }
)

# 3. DB 연결에 Attach 후 조회
connection.set_end_user_security_context(user_context)
try:
    cursor = connection.cursor()
    cursor.execute("""
        SELECT doc_id, title
        FROM   admin.cb_dds_v_search_documents
    """)
finally:
    connection.clear_end_user_security_context()

주의:

항목 의미
Local End User의 DATA ROLE 호출 애플리케이션이 임의로 넣는 것이 아니라 DB에 Grant된 DATA ROLE이 적용됨
database_access_token 호출 애플리케이션이 DB에 Security Context를 Attach할 권한이 있음을 증명하는 Token
현재 ADB 로컬 실행 예제 DDS Direct END USER Logon은 실행 검증됨. ORDS Handler의 DDS Bearer 단독 매핑 경로는 차단됨. Driver 기반 Security Context Attach는 IAM/DB Access Token 구성이 필요하므로 별도 구성 필요

검증 근거:

문서 확인한 내용
End-User Security Context Driver가 End-user Security Context Payload를 Connection Metadata로 전달하고, DB가 이를 검증해 Context를 생성/Attach
Configure the Database for Local End-User Authentication Local End User는 호출 애플리케이션이 제공한 User Name과 Security Context Lookup Key로 식별
Configure Python Applications create_end_user_security_context, set_end_user_security_context, clear_end_user_security_context API 지원
Read End-User Context Attributes ORA_END_USER_CONTEXT로 현재 Security Context의 Attribute 참조 가능
ORDS parameter binding ORDS.DEFINE_PARAMETER로 HTTP 헤더를 Handler Bind Variable에 매핑 가능

3. 권한 매핑 테이블 설계

이 장에서는 VPD 경로에서 사용할 업무 권한 테이블을 정의한다. 핵심 구조는 사용자 -> 역할 -> 보호 객체 권한 -> 행 규칙이며, Bearer Key는 내부 사용자로 매핑되는 식별 수단으로 둔다.

상세 설명 - Key User, Role, Permission, 행 규칙

VPD에서 권한은 사용자, 역할, 보호 객체, 행(row) 조건으로 구분한다.

%%{init: {'themeVariables': {'fontSize': '17px'}}}%%
erDiagram
  APP_USER ||--o{ AGENT_BEARER_KEY : "key maps to user"
  APP_USER ||--o{ USER_ROLE : "user has role"
  APP_ROLE ||--o{ USER_ROLE : "assigned to user"
  APP_ROLE ||--o{ PERMISSION : "role grants permission"
  PERMISSION ||--o{ PERMISSION_RULE : "permission has row rule"

  APP_USER {
    NUMBER user_id PK
    VARCHAR2 db_username
    VARCHAR2 employee_no
    VARCHAR2 dept_code
    CHAR can_read_contents
    CHAR active
  }

  AGENT_BEARER_KEY {
    NUMBER key_id PK
    NUMBER user_id FK
    VARCHAR2 key_hash
    DATE expires_at
    CHAR active
  }

  APP_ROLE {
    NUMBER role_id PK
    VARCHAR2 role_name
  }

  USER_ROLE {
    NUMBER user_id FK
    NUMBER role_id FK
  }

  PERMISSION {
    NUMBER perm_id PK
    NUMBER role_id FK
    VARCHAR2 target_name
    VARCHAR2 action_name
  }

  PERMISSION_RULE {
    NUMBER rule_id PK
    NUMBER perm_id FK
    VARCHAR2 rule_type
    VARCHAR2 rule_value
  }

테이블 의미:

테이블 의미
APP_USER 내부 사용자. DB 계정 또는 Bearer Key가 최종적으로 가리키는 대상
AGENT_BEARER_KEY Bearer Key로 내부 사용자를 찾기 위한 테이블
APP_ROLE 권한 묶음
USER_ROLE 사용자와 역할 매핑
PERMISSION 어떤 보호 객체(VIEW/TABLE)를 어떤 작업으로 볼 수 있는지
PERMISSION_RULE 해당 보호 객체 안에서 어떤 행(row)을 볼 수 있는지

PERMISSION.target_name에는 V_SEARCH_DOCUMENTS, T_SEARCH_DOCUMENTS, V_EMPLOYEE_DOCS처럼 보호 객체 이름을 등록한다. VPD 함수는 하드코딩된 객체 이름이 아니라 Oracle이 넘겨준 p_object 값으로 이 컬럼을 조회한다.

4. VPD 기반 접근 통제 설계

이 장에서는 ORDS가 Bearer Key를 내부 사용자로 매핑한 뒤, DB Session Context와 VPD 정책 함수로 행(row)을 제한하는 방식을 설명한다. 컬럼 값 NULL/마스킹은 VPD가 아니라 Oracle Data Redaction으로 분리해 설명한다.

상세 설명 - SYS_CONTEXT, p_object, EXISTS, Redaction

VPD는 보호 객체(VIEW/TABLE)에 정책 함수를 연결한다. Agent나 ORDS가 권한 조건을 직접 붙이지 않아도, DB가 조회 시점에 조건을 자동으로 붙인다.

VPD 조회 조건 적용 흐름

VPD를 이해할 때 필요한 세 가지:

요소 역할
SYS_CONTEXT 현재 요청자 값 확인. 예: USER_ID, 사번, 부서코드
p_object 현재 조회 중인 보호 객체 이름
EXISTS 권한 테이블에 접근 근거가 있는지 확인

ORDS Handler가 실행하는 SQL은 검색 조건만 갖는다.

SELECT doc_id, title, owner_emp_no, dept_code
FROM   app.v_search_documents
WHERE  contains_text = :q;

권한 판단은 VPD 함수가 수행한다.

EXISTS (
  SELECT 1
  FROM   app.user_role ur
  JOIN   app.permission p
  ON     p.role_id = ur.role_id
  JOIN   app.permission_rule r
  ON     r.perm_id = p.perm_id
  WHERE  ur.user_id = TO_NUMBER(SYS_CONTEXT('AGENT_CTX', 'USER_ID'))
  AND    p.target_name = '<p_object 값>'
  AND    p.action_name = 'SELECT'
  AND   (
          r.rule_type = 'ALL'
          OR (r.rule_type = 'MY_DEPT'
              AND dept_code = SYS_CONTEXT('AGENT_CTX', 'DEPT_CODE'))
          OR (r.rule_type = 'SELF'
              AND owner_emp_no = SYS_CONTEXT('AGENT_CTX', 'EMP_NO'))
        )
)

의미:

판단 확인 방식 결과
이 보호 객체를 조회할 수 있는가 permission.target_name = p_object 없으면 0건
이 행(row)을 볼 수 있는가 permission_rule과 행 컬럼 비교 조건에 맞는 행(row)만 반환

즉, ORDS runtime schema에 보호 VIEW/TABLE 조회 권한이 있더라도 업무 권한 매핑이 없으면 결과는 나오지 않는다.

DB SELECT Grant 있음
+ VPD policy 연결됨
+ user_role / permission / permission_rule 매핑 없음
= 조회 가능하지만 결과 0건

반대로 보호 객체 자체에 대한 DB 권한이 없거나 원본 TABLE을 직접 조회하려고 하면 VPD 판단 전 단계에서 객체가 보이지 않거나 권한 오류가 발생한다.

보호 VIEW/TABLE DB 권한 미부여
= ORA-00942 또는 ORA-01031

4.1 SYS_CONTEXT 동작

SYS_CONTEXT 동작 방식

ORDS Handler는 요청자를 식별한 뒤 같은 DB 세션에 값을 저장한다.

DBMS_SESSION.SET_CONTEXT('AGENT_CTX', 'USER_ID', '42');
DBMS_SESSION.SET_CONTEXT('AGENT_CTX', 'EMP_NO', 'E10234');
DBMS_SESSION.SET_CONTEXT('AGENT_CTX', 'DEPT_CODE', 'HR');

VPD 정책 함수는 조회 시점에 같은 세션에서 값을 읽는다.

SYS_CONTEXT('AGENT_CTX', 'USER_ID')    -- 42
SYS_CONTEXT('AGENT_CTX', 'EMP_NO')     -- E10234
SYS_CONTEXT('AGENT_CTX', 'DEPT_CODE')  -- HR

조회 대상 이름은 SYS_CONTEXT에 넣지 않는다. Oracle이 VPD 정책 함수를 호출할 때 p_object 인자로 현재 조회 중인 보호 객체 이름을 넘겨준다.

4.2 여러 VIEW/TABLE 조회

여러 보호 객체가 한 SQL에 들어가도 원리는 같다. VPD는 정책이 연결된 각 VIEW/TABLE마다 따로 적용된다.

SELECT d.doc_id,
       d.title,
       e.employee_name
FROM   app.v_search_documents d
JOIN   app.v_employee_docs e
ON     e.owner_emp_no = d.owner_emp_no
WHERE  d.contains_text = :q;

의미를 풀면 아래와 같다.

SELECT d.doc_id,
       d.title,
       e.employee_name
FROM   app.v_search_documents d
JOIN   app.v_employee_docs e
ON     e.owner_emp_no = d.owner_emp_no
WHERE  d.contains_text = :q
AND    EXISTS (V_SEARCH_DOCUMENTS 권한 확인)
AND    EXISTS (V_EMPLOYEE_DOCS 권한 확인);

원본 TABLE을 직접 조회할 수 있게 열어두면 보호 객체를 우회할 수 있다. 따라서 API가 조회할 보호 객체만 권한을 주고, 원본 TABLE과 권한 테이블은 직접 조회 권한을 주지 않는다.

4.3 컬럼 처리

VPD 기본 정책은 행(row) 제한을 담당한다. 컬럼을 허용하거나 제외하는 권한 모델로는 사용하지 않는다. 이 문서의 실행 예제에서 contents 컬럼을 NULL로 만드는 것은 VPD가 아니라 Oracle Data Redaction이다.

정확히 나누면 아래와 같다.

구분 역할 이 문서의 적용
VPD 기본 정책 WHERE에 붙을 행(row) 조건 반환 사용
Redaction 특정 컬럼 값을 NULL 또는 마스킹 값으로 변환 사용. 컬럼 Grant가 아니라 값 마스킹
VIEW/TABLE 설계 애초에 노출 객체에서 컬럼을 제외 선택 가능
DDS DATA GRANT AS SELECT (ALL COLUMNS EXCEPT ...)로 컬럼 허용/제외 DDS 경로에서 사용
Column-level VPD 옵션 sec_relevant_cols, sec_relevant_cols_opt 옵션이 있으나 이 구조의 권한 모델로는 사용하지 않음 미사용

따라서 아래 예제에서 컬럼 값 NULL 처리는 DBMS_REDACT.ADD_POLICY가 수행한다. VPD는 어떤 행(row)이 반환될지를 결정하고, Redaction은 반환되는 행(row) 안의 contents 값을 보일지 NULL로 바꿀지를 결정한다.

컬럼 허용/제외를 권한으로 선언해야 한다면 DDS의 DATA GRANT 또는 별도 VIEW/TABLE 설계를 사용한다.

방식 의미 적합한 경우
보호 VIEW/TABLE 설계 API에서 노출할 객체에 필요한 컬럼만 포함 특정 API에서 컬럼을 아예 제공하지 않을 때
Oracle Data Redaction 컬럼 값을 NULL, 부분 마스킹, 정규식 마스킹 값으로 변환 같은 객체를 쓰되 사용자 권한에 따라 컬럼 값을 숨길 때
DDS DATA GRANT 컬럼 허용/제외를 선언 DDS END USER 또는 Security Context 기반 통제가 가능할 때
별도 VIEW/TABLE 분리 민감 컬럼이 있는 객체와 없는 객체를 분리 컬럼 권한이 단순하고 운영 분리가 쉬울 때

예:

BEGIN
  DBMS_REDACT.ADD_POLICY(
    object_schema => USER,
    object_name   => 'CB_V_SEARCH_DOCUMENTS',
    column_name   => 'CONTENTS',
    policy_name   => 'CB_CONTENTS_REDACT',
    function_type => DBMS_REDACT.NULLIFY,
    expression    =>
      'SYS_CONTEXT(''CB_AGENT_CTX'', ''CAN_READ_CONTENTS'') IS NULL
       OR SYS_CONTEXT(''CB_AGENT_CTX'', ''CAN_READ_CONTENTS'') != ''Y'''
  );
END;
/
상태 행(row) 제한 contents 컬럼
HR Key HR 행(row)만 조회 NULL
FIN Self Key 본인 행(row)만 조회 NULL
ALL Key 전체 행(row) 조회 원문 표시

4.4 VPD 정책 연결

DBMS_RLS.ADD_POLICY는 권한을 직접 부여하는 코드가 아니다. 보호 객체를 조회할 때 어떤 VPD 함수를 실행할지 연결하는 코드다.

BEGIN
  DBMS_RLS.ADD_POLICY(
    object_schema   => 'APP',
    object_name     => 'V_SEARCH_DOCUMENTS',
    policy_name     => 'P_AGENT_DOC_FILTER',
    function_schema => 'APP',
    policy_function => 'AGENT_DOC_VPD_FILTER',
    statement_types => 'SELECT',
    enable          => TRUE
  );
END;
/

파라미터 매핑:

ADD_POLICY 항목 예제 값 역할 연결되는 값
object_schema APP VPD를 적용할 보호 객체의 소유 schema VPD 함수의 p_schema
object_name V_SEARCH_DOCUMENTS VPD를 적용할 보호 객체 이름 VPD 함수의 p_object, permission.target_name
policy_name P_AGENT_DOC_FILTER VPD 정책 관리 이름 정책 조회, 비활성화, 삭제
function_schema APP VPD 정책 함수가 있는 schema APP.AGENT_DOC_VPD_FILTER 호출
policy_function AGENT_DOC_VPD_FILTER 행(row) 필터 조건을 반환하는 함수 EXISTS (...) 반환
statement_types SELECT 적용할 SQL 작업 조회 SQL에만 적용
enable TRUE 정책 활성화 여부 등록 즉시 적용

실행 시 흐름:

  1. APP.V_SEARCH_DOCUMENTS에 연결된 P_AGENT_DOC_FILTER 정책 확인
  2. APP.AGENT_DOC_VPD_FILTER 함수 호출
  3. p_schema = 'APP', p_object = 'V_SEARCH_DOCUMENTS' 전달
  4. 함수가 SYS_CONTEXT와 권한 테이블 확인
  5. 함수가 반환한 조건을 원래 SQL에 추가
  6. 조건을 통과한 행(row)만 반환

적용 단위:

검토 항목 기준
ADD_POLICY에서 모든 객체를 한 번에 지정할 수 있는가 아니다. object_schema, object_name으로 보호 객체 하나를 지정한다.
object_name => '*' 또는 % 같은 Wildcard를 쓸 수 있는가 아니다. VPD ADD_POLICYobject_name은 실제 TABLE, VIEW, SYNONYM 이름이다.
statement_types에 여러 SQL 작업을 지정하면 전체 객체 적용인가 아니다. SELECT, INSERT, UPDATE, DELETE처럼 적용할 SQL 작업 종류를 지정하는 값이다. 보호 객체 Wildcard가 아니다.
같은 정책 함수를 여러 객체에 재사용할 수 있는가 가능하다. 같은 함수가 p_object를 받아 현재 조회 대상별 권한을 판단하게 만들 수 있다.
객체가 많으면 어떻게 등록하는가 보호 객체 목록을 기준으로 DBMS_RLS.ADD_POLICY를 반복 실행하는 등록 스크립트를 만든다.
정책이 붙지 않은 객체는 어떻게 되는가 그 객체에는 VPD가 적용되지 않는다. 그래서 원본 TABLE 직접 권한을 주지 않고, 노출 VIEW/TABLE 목록을 관리해야 한다.

여러 보호 객체에 같은 VPD 함수를 붙이는 등록 스크립트 예:

BEGIN
  FOR r IN (
    SELECT 'V_SEARCH_DOCUMENTS' AS object_name FROM dual
    UNION ALL
    SELECT 'V_EMPLOYEE_DOCS' AS object_name FROM dual
  ) LOOP
    DBMS_RLS.ADD_POLICY(
      object_schema   => 'APP',
      object_name     => r.object_name,
      policy_name     => 'P_AGENT_' || r.object_name,
      function_schema => 'APP',
      policy_function => 'AGENT_DOC_VPD_FILTER',
      statement_types => 'SELECT',
      enable          => TRUE
    );
  END LOOP;
END;
/

이 방식에서 권한 함수는 p_object로 현재 보호 객체 이름을 받고, permission.target_name과 비교한다. 그래서 정책 함수는 하나로 유지하고, 정책 연결만 보호 객체별로 반복할 수 있다.

보호 객체 목록 테이블을 쓰는 예:

CREATE TABLE app_protected_object (
  object_name VARCHAR2(128) PRIMARY KEY,
  enabled     CHAR(1) DEFAULT 'Y' CHECK (enabled IN ('Y','N')) NOT NULL
);

INSERT INTO app_protected_object(object_name) VALUES ('V_SEARCH_DOCUMENTS');
INSERT INTO app_protected_object(object_name) VALUES ('V_EMPLOYEE_DOCS');

BEGIN
  FOR r IN (
    SELECT object_name
    FROM   app_protected_object
    WHERE  enabled = 'Y'
  ) LOOP
    DBMS_RLS.ADD_POLICY(
      object_schema   => 'APP',
      object_name     => r.object_name,
      policy_name     => 'P_AGENT_' || r.object_name,
      function_schema => 'APP',
      policy_function => 'AGENT_DOC_VPD_FILTER',
      statement_types => 'SELECT',
      enable          => TRUE
    );
  END LOOP;
END;
/

정리하면 “전체 적용”은 한 번의 Wildcard 지정이 아니라 “보호 객체 목록 전체를 돌면서 객체별 ADD_POLICY를 생성”하는 방식이다.

정책 연결 확인:

SELECT object_owner,
       object_name,
       policy_name,
       function,
       sel,
       enable
FROM   dba_policies
WHERE  object_owner = 'APP'
ORDER  BY object_name, policy_name;

5. DDS 기반 접근 통제 설계

이 장에서는 DDS END USER, DATA ROLE, DATA GRANT를 이용해 보호 객체의 행(row), 컬럼(column), 작업(action)을 선언형으로 통제하는 방식을 설명한다. Bearer Key를 DDS에 적용하려면 Key 조회 결과가 EndUserSecurityContext로 DB 호출 전에 전파되어야 한다.

상세 설명 - END USER, DATA ROLE, DATA GRANT

DDS는 Oracle 보안 오브젝트로 권한을 선언한다. VPD처럼 권한 테이블을 읽는 함수가 아니라, END USER -> DATA ROLE -> DATA GRANT -> 보호 객체 구조다.

DDS 보안 오브젝트 모델

DDS 설정 흐름

DATA GRANT는 어떤 보호 객체(VIEW/TABLE)에 대해 어떤 행(row), 컬럼(column), 작업(operation)을 허용하는지 선언한다.

항목 DDS 표현
사용자 CREATE END USER
역할 CREATE DATA ROLE
대상 DATA GRANT ... ON <VIEW 또는 TABLE>
행(row) 제한 WHERE dept_code = 'HR'
컬럼(column) 허용/제외 AS SELECT (ALL COLUMNS EXCEPT contents)
권한 미부여 ORA-00942, 객체 자체가 보이지 않음

참고로 DDS에는 CREATE APPLICATION IDENTITY도 있다. 이 객체는 애플리케이션 자체를 DB 보안 Identity로 등록하고 DATA ROLE을 부여할 때 사용한다. 본 문서의 실행 예제는 사용자별 권한 차이를 보여주기 위해 END USER만 사용한다.

DDS에서 Bearer Key를 사용하려면 Key 자체를 DATA GRANT가 읽는 것이 아니라, Key 검증 결과가 DDS EndUserSecurityContext로 DB 호출 전에 전달되어야 한다. ORDS PL/SQL Handler 안에서 Key를 테이블 조회로 매핑하는 것만으로는 DDS 보안 사용자가 바뀌지 않는다. 이 패턴은 아래 ADB 검증에서 EXPECTED_BLOCKED로 확인했다.

기본 예:

CREATE END USER "agent_hr_001"
  IDENTIFIED BY "&AGENT_HR_001_PASSWORD";

CREATE ROLE dds_connect_role;
GRANT CREATE SESSION TO dds_connect_role;

CREATE DATA ROLE hr_search_role;
GRANT dds_connect_role TO hr_search_role;
GRANT DATA ROLE hr_search_role TO "agent_hr_001";

CREATE OR REPLACE DATA GRANT admin.dg_hr_search_documents
  AS SELECT
  ON admin.v_search_documents
  WHERE dept_code = 'HR'
  TO hr_search_role;

행(row) 제한과 컬럼(column) 허용/제외를 한 번에 선언하는 예:

CREATE OR REPLACE DATA GRANT admin.dg_hr_search_documents
  AS SELECT (ALL COLUMNS EXCEPT contents)
  ON admin.v_search_documents
  WHERE dept_code = 'HR'
  TO hr_search_role;

여러 보호 객체를 함께 조회하면 각 대상에 대한 DATA GRANT가 필요하다.

CREATE OR REPLACE DATA GRANT admin.dg_hr_search_documents
  AS SELECT
  ON admin.v_search_documents
  WHERE dept_code = 'HR'
  TO hr_search_role;

CREATE OR REPLACE DATA GRANT admin.dg_hr_employee_docs
  AS SELECT
  ON admin.v_employee_docs
  WHERE dept_code = 'HR'
  TO hr_search_role;

v_search_documents 권한만 있고 v_employee_docs 권한이 없으면, 두 보호 객체를 함께 조회하는 SQL은 v_employee_docs 접근 단계에서 실패하거나 허용되지 않는다.

DDS 적용과 확인 단위:

검토 항목 기준
DDS도 객체별로 스크립트가 필요한가 DATA GRANT는 보호 객체, DATA ROLE, 조건 단위로 선언한다. 객체/역할/조건이 다르면 별도 DATA GRANT가 필요하다.
권한을 한눈에 확인할 수 있는가 가능하다. DBA_DATA_GRANTSDBA_DATA_ROLE_GRANTS를 조합하면 DATA ROLE, 보호 객체, 행 조건, 제외 컬럼을 볼 수 있다.
한눈에 볼 수 없는 것은 무엇인가 승인 요청 사유, 업무 결재 상태, Ticket 번호 같은 업무 메타데이터는 DDS Dictionary에 없다. 별도 승인 테이블이나 Ticket 시스템에 남겨야 한다.
운영에서 권장하는 방식 DATA GRANT 이름 규칙과 보호 객체 목록을 표준화하고, Dictionary 조회 결과를 정기 점검 또는 배포 검증에 포함한다.

DDS 권한 요약 조회:

SELECT grant_name,
       object_name,
       grantee,
       privilege,
       COALESCE(
         LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_name),
         'ALL COLUMNS'
       ) AS allowed_columns,
       COALESCE(MAX(granted_with_all_columns_except), '-') AS excluded_columns,
       predicate
FROM   dba_data_grants
WHERE  owner = 'ADMIN'
GROUP  BY grant_name, object_name, grantee, privilege, predicate
ORDER  BY object_name, grant_name;

DDS end user별 권한 매트릭스 조회:

SELECT rg.grantee AS end_user_name,
       rg.data_role,
       dg.grant_name,
       dg.object_name,
       dg.predicate,
       COALESCE(MAX(dg.granted_with_all_columns_except), '-') AS excluded_columns
FROM   dba_data_role_grants rg
LEFT   JOIN dba_data_grants dg
ON     dg.grantee = rg.data_role
GROUP  BY rg.grantee,
          rg.data_role,
          dg.grant_name,
          dg.object_name,
          dg.predicate
ORDER  BY rg.grantee, dg.object_name, dg.grant_name;

6. VPD/DDS 비교 및 적용 기준

이 장에서는 앞에서 분리 설명한 VPD와 DDS를 운영 기준으로 비교한다. 비교 기준은 사용자 식별 방식, 권한 저장 위치, 행/컬럼 통제 범위, Bearer Key 처리 방식, 적용 단위, 권한 미부여 시 결과로 둔다.

상세 설명 - 운영 선택 기준
항목 VPD DDS
권한 표현 방식 PL/SQL 정책 함수가 SQL 조건 반환 CREATE DATA GRANT로 권한 선언
정책 적용 단위 DBMS_RLS.ADD_POLICY 1회당 보호 객체 1개 DATA GRANT 1개당 보호 객체, 역할, 조건 1묶음
권한 저장 위치 업무 권한 테이블 Oracle Dictionary의 DDS 보안 오브젝트
중앙 확인 방법 DBA_POLICIES, REDACTION_POLICIES, 업무 권한 테이블 DBA_DATA_GRANTS, DBA_DATA_ROLE_GRANTS, DBA_DATA_ROLES, DBA_END_USERS
사용자 식별 DB 계정, Bearer Key, SYS_CONTEXT 조합 END USER, DATA ROLE
DB user와 실제 사용자 공통 DB 계정 아래 다수 key 사용자를 테이블로 구분 가능 DDS 사용자가 실제 사용자에 가깝게 매핑되어야 명확
Bearer Key 처리 Key Hash를 테이블에서 찾아 Context 저장 Key User를 DDS EndUserSecurityContext로 전파해야 가능. ORDS Handler의 DDS Bearer 단독 매핑은 차단됨
보호 객체 VIEW 또는 TABLE VIEW 또는 TABLE
행(row) 제한 EXISTS, dept_code, owner_emp_no 조건 반환 DATA GRANT ... WHERE ...
컬럼(column) 허용/제외 VIEW/TABLE 설계. Redaction은 값 마스킹/NULL AS SELECT (ALL COLUMNS EXCEPT ...)
권한 미부여 시 0건 반환 가능 객체 자체가 보이지 않음. 예: ORA-00942
권한 변경 권한 테이블 INSERT/UPDATE/DELETE CREATE OR REPLACE DATA GRANT, DROP DATA GRANT, GRANT DATA ROLE
운영상 주의 정책을 붙이지 않은 객체는 보호되지 않음 승인 사유 같은 업무 메타데이터는 Dictionary에 없음
적합한 경우 key 사용자 많음, 권한 변경 잦음, 테이블 기반 운영 역할이 안정적이고 정책을 DDL로 명확히 관리

선택 기준:

선택지 추천 상황 설명
VPD Bearer Key 사용자가 많고 세분화된 업무 권한 테이블이 필요한 경우 DB user != key user 구조에 적합
DDS DDS END USER Identity를 명확히 전파할 수 있는 경우 선언형 보안 오브젝트 관리에 적합
둘 다 비교 전환기 또는 기능 검증 같은 VIEW/TABLE에 동시에 걸지 말고 별도 보호 객체로 분리

7. ADB 실행 검증

이 장에서는 ADB에 생성한 로컬 테이블 기준으로 VPD + Redaction, DDS, ORDS Handler Bearer 검증을 실행한 스크립트와 결과를 제시한다.

상세 설명 - 실행 가능한 VPD/DDS 스크립트와 결과

이 예제는 RDS, DB Link, Postgres, MySQL을 사용하지 않는다. ADB 안에 로컬 테이블을 만들고, 같은 흐름에서 VPD 경로와 DDS 경로를 각각 확인한다.

실행:

./scripts/run_agent_ords_security_adb_local.sh

실행 파일:

파일 역할
scripts/run_agent_ords_security_adb_local.sh 전체 실행
sql/adb/16_agent_ords_security_local_cleanup.sql CB_* 예제 객체 정리
sql/adb/17_agent_ords_security_local_vpd_setup.sql VPD용 로컬 테이블, 권한 테이블, context, redaction, 정책 생성
sql/adb/18_agent_ords_security_local_vpd_test.sql CB_ORDS로 Bearer Key 기반 VPD 테스트
sql/adb/19_agent_ords_security_local_dds_setup.sql DDS용 로컬 테이블, END USER, DATA ROLE, DATA GRANT 생성
sql/adb/20_agent_ords_security_local_dds_test.sql DDS end user별 조회 테스트
sql/adb/21_agent_ords_security_ords_enable_schema.sql CB_ORDS schema를 ORDS에 enable
sql/adb/22_agent_ords_security_ords_handler_setup.sql ORDS Module/Handler와 Handler Package 생성
sql/adb/23_agent_ords_security_ords_handler_test.sql Handler Package 직접 실행으로 VPD/DDS Bearer 경로 검증
sql/adb/24_agent_ords_security_inventory.sql VPD/DDS 정책과 권한을 중앙 조회

7.1 VPD + Redaction 실행 검증

VPD 예제의 흐름:

  1. ORDS runtime schema CB_ORDS가 DB에 접속
  2. ORDS Handler가 Bearer Key를 내부 사용자로 매핑
  3. 내부 사용자 값을 CB_AGENT_CTX에 저장
  4. CB_V_SEARCH_DOCUMENTS 조회
  5. VPD가 행(row) 제한
  6. Redaction이 contents 값을 NULL/마스킹 처리

사용한 핵심 스크립트:

CREATE TABLE cb_search_documents (
  doc_id        NUMBER PRIMARY KEY,
  title         VARCHAR2(100) NOT NULL,
  owner_emp_no  VARCHAR2(20)  NOT NULL,
  dept_code     VARCHAR2(20)  NOT NULL,
  contents      VARCHAR2(4000),
  created_at    DATE DEFAULT SYSDATE NOT NULL
);

CREATE OR REPLACE VIEW cb_v_search_documents AS
SELECT doc_id, title, owner_emp_no, dept_code, contents, created_at
FROM   cb_search_documents;

CREATE TABLE cb_app_user (
  user_id            NUMBER PRIMARY KEY,
  user_name          VARCHAR2(50) NOT NULL,
  employee_no        VARCHAR2(20) NOT NULL,
  dept_code          VARCHAR2(20) NOT NULL,
  can_read_contents  CHAR(1) DEFAULT 'N' CHECK (can_read_contents IN ('Y','N')) NOT NULL,
  active             CHAR(1) DEFAULT 'Y' CHECK (active IN ('Y','N')) NOT NULL
);

INSERT INTO cb_app_user VALUES (101, 'agent_hr',       'E10234', 'HR',  'N', 'Y');
INSERT INTO cb_app_user VALUES (102, 'agent_fin_self', 'E2001',  'FIN', 'N', 'Y');
INSERT INTO cb_app_user VALUES (103, 'agent_all',      'E99999', 'HQ',  'Y', 'Y');

INSERT INTO cb_agent_bearer_key(key_id, user_id, key_hash, key_prefix)
VALUES (1, 101, STANDARD_HASH('cb_hr_key', 'SHA256'), 'cb_hr');

Bearer Key 매핑:

CREATE OR REPLACE CONTEXT cb_agent_ctx USING cb_agent_ctx_pkg;

CREATE OR REPLACE PACKAGE BODY cb_agent_ctx_pkg AS
  PROCEDURE set_user_by_bearer(p_bearer_key IN VARCHAR2) AS
    v_user_id           cb_app_user.user_id%TYPE;
    v_emp_no            cb_app_user.employee_no%TYPE;
    v_dept_code         cb_app_user.dept_code%TYPE;
    v_can_read_contents cb_app_user.can_read_contents%TYPE;
  BEGIN
    SELECT u.user_id, u.employee_no, u.dept_code, u.can_read_contents
    INTO   v_user_id, v_emp_no, v_dept_code, v_can_read_contents
    FROM   cb_agent_bearer_key k
    JOIN   cb_app_user u
    ON     u.user_id = k.user_id
    WHERE  k.key_hash = STANDARD_HASH(p_bearer_key, 'SHA256')
    AND    k.active = 'Y'
    AND    k.revoked_at IS NULL
    AND    (k.expires_at IS NULL OR k.expires_at > SYSDATE)
    AND    u.active = 'Y';

    set_user_values(v_user_id, v_emp_no, v_dept_code, v_can_read_contents);
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      clear_user;
      RAISE_APPLICATION_ERROR(-20002, 'Invalid or expired Bearer key');
  END;
END;
/

VPD와 Redaction:

CREATE OR REPLACE FUNCTION cb_agent_doc_vpd_filter(
  p_schema IN VARCHAR2,
  p_object IN VARCHAR2
) RETURN VARCHAR2
AUTHID DEFINER
AS
  v_user_id     VARCHAR2(30);
  v_target_name VARCHAR2(128);
BEGIN
  v_user_id := SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID');

  IF v_user_id IS NULL THEN
    RETURN '1 = 0';
  END IF;

  v_target_name := REPLACE(UPPER(p_object), '''', '''''');

  RETURN
    'EXISTS (
       SELECT 1
       FROM   admin.cb_user_role ur
       JOIN   admin.cb_permission p ON p.role_id = ur.role_id
       JOIN   admin.cb_permission_rule r ON r.perm_id = p.perm_id
       WHERE  ur.user_id = TO_NUMBER(SYS_CONTEXT(''CB_AGENT_CTX'', ''USER_ID''))
       AND    p.target_name = ''' || v_target_name || '''
       AND    p.action_name = ''SELECT''
       AND   (
               r.rule_type = ''ALL''
               OR (r.rule_type = ''MY_DEPT''
                   AND dept_code = SYS_CONTEXT(''CB_AGENT_CTX'', ''DEPT_CODE''))
               OR (r.rule_type = ''SELF''
                   AND owner_emp_no = SYS_CONTEXT(''CB_AGENT_CTX'', ''EMP_NO''))
             )
     )';
END;
/

BEGIN
  DBMS_RLS.ADD_POLICY(
    object_schema   => USER,
    object_name     => 'CB_V_SEARCH_DOCUMENTS',
    policy_name     => 'CB_AGENT_DOC_POLICY',
    function_schema => USER,
    policy_function => 'CB_AGENT_DOC_VPD_FILTER',
    statement_types => 'SELECT',
    enable          => TRUE
  );
END;
/

BEGIN
  DBMS_REDACT.ADD_POLICY(
    object_schema => USER,
    object_name   => 'CB_V_SEARCH_DOCUMENTS',
    column_name   => 'CONTENTS',
    policy_name   => 'CB_CONTENTS_REDACT',
    function_type => DBMS_REDACT.NULLIFY,
    expression    =>
      'SYS_CONTEXT(''CB_AGENT_CTX'', ''CAN_READ_CONTENTS'') IS NULL
       OR SYS_CONTEXT(''CB_AGENT_CTX'', ''CAN_READ_CONTENTS'') != ''Y'''
  );
END;
/

실행 결과:

=== VPD 1. No Bearer key / context: fail closed ===
ROWS_VISIBLE
------------
0

=== VPD 2. Authorization: Bearer cb_hr_key -> HR department rows ===
CTX_USER_ID  CTX_EMP_NO   CTX_DEPT_COD CTX_CAN_READ_CONTENTS
------------ ------------ ------------ ----------------------
101          E10234       HR           N

DOC_ID TITLE                    OWNER_EMP_NO DEPT_CODE CONTENTS
------ ------------------------ ------------ --------- --------
1      HR payroll guide         E1001        HR
2      HR recruiting plan       E1002        HR
6      HR benefits notice       E1003        HR

ROWS_VISIBLE
------------
3

=== VPD 3. Authorization: Bearer cb_fin_key -> self row only ===
CTX_USER_ID  CTX_EMP_NO   CTX_DEPT_COD CTX_CAN_READ_CONTENTS
------------ ------------ ------------ ----------------------
102          E2001        FIN          N

DOC_ID TITLE                    OWNER_EMP_NO DEPT_CODE CONTENTS
------ ------------------------ ------------ --------- --------
3      Finance close checklist  E2001        FIN

ROWS_VISIBLE
------------
1

=== VPD 4. Authorization: Bearer cb_all_key -> all rows ===
ROWS_VISIBLE
------------
6

DOC_ID TITLE                    OWNER_EMP_NO DEPT_CODE CONTENTS
------ ------------------------ ------------ --------- ---------------------------
1      HR payroll guide         E1001        HR        Payroll policy and HR guide
2      HR recruiting plan       E1002        HR        Recruiting plan for HR team
3      Finance close checklist  E2001        FIN       Monthly close checklist

=== VPD 5. Invalid Bearer key -> ORA-20002 and context cleared ===
ORA-20002: Invalid or expired Bearer key

ROWS_VISIBLE_AFTER_INVALID_KEY
------------------------------
0

=== VPD 6. Bypass attempts ===
SELECT COUNT(*) FROM admin.cb_search_documents
ORA-00942: table or view "ADMIN"."CB_SEARCH_DOCUMENTS" does not exist

VPD 결과 해석:

요청 상태 적용된 사용자 결과
Bearer Key 없음 없음 0건
cb_hr_key USER_ID=101, DEPT_CODE=HR, CAN_READ_CONTENTS=N HR 행(row) 3건, contents NULL
cb_fin_key USER_ID=102, EMP_NO=E2001, CAN_READ_CONTENTS=N 본인 행(row) 1건, contents NULL
cb_all_key USER_ID=103, CAN_READ_CONTENTS=Y 전체 6건, contents 원문 표시
잘못된 Key Context 초기화 오류 후 0건

7.2 DDS 실행 검증

DDS 예제의 흐름:

  1. DDS END USER로 접속
  2. DATA ROLE로 권한 묶음 확인
  3. DATA GRANT가 보호 객체와 행/컬럼 조건 적용
  4. 권한이 없으면 객체가 보이지 않음

사용한 핵심 스크립트:

CREATE TABLE cb_dds_documents (
  doc_id        NUMBER PRIMARY KEY,
  title         VARCHAR2(100) NOT NULL,
  owner_emp_no  VARCHAR2(20)  NOT NULL,
  dept_code     VARCHAR2(20)  NOT NULL,
  contents      VARCHAR2(4000),
  created_at    DATE DEFAULT SYSDATE NOT NULL
);

CREATE OR REPLACE VIEW cb_dds_v_search_documents AS
SELECT doc_id, title, owner_emp_no, dept_code, contents, created_at
FROM   cb_dds_documents;

CREATE END USER "cb_dds_hr"   IDENTIFIED BY "CbDds#Hr2026Local1";
CREATE END USER "cb_dds_fin"  IDENTIFIED BY "CbDds#Fin2026Local1";
CREATE END USER "cb_dds_all"  IDENTIFIED BY "CbDds#All2026Local1";
CREATE END USER "cb_dds_none" IDENTIFIED BY "CbDds#None2026Local1";

CREATE ROLE cb_dds_connect_role;
GRANT CREATE SESSION TO cb_dds_connect_role;

CREATE DATA ROLE cb_dds_hr_role;
CREATE DATA ROLE cb_dds_fin_role;
CREATE DATA ROLE cb_dds_all_role;
CREATE DATA ROLE cb_dds_connect_only_role;

GRANT cb_dds_connect_role TO cb_dds_hr_role;
GRANT cb_dds_connect_role TO cb_dds_fin_role;
GRANT cb_dds_connect_role TO cb_dds_all_role;
GRANT cb_dds_connect_role TO cb_dds_connect_only_role;

GRANT DATA ROLE cb_dds_hr_role           TO "cb_dds_hr";
GRANT DATA ROLE cb_dds_fin_role          TO "cb_dds_fin";
GRANT DATA ROLE cb_dds_all_role          TO "cb_dds_all";
GRANT DATA ROLE cb_dds_connect_only_role TO "cb_dds_none";

CREATE DATA GRANT admin.cb_dg_hr_docs
  AS SELECT (ALL COLUMNS EXCEPT contents)
  ON admin.cb_dds_v_search_documents
  WHERE dept_code = 'HR'
  TO cb_dds_hr_role;

CREATE DATA GRANT admin.cb_dg_fin_docs
  AS SELECT (ALL COLUMNS EXCEPT contents)
  ON admin.cb_dds_v_search_documents
  WHERE dept_code = 'FIN'
  TO cb_dds_fin_role;

CREATE DATA GRANT admin.cb_dg_all_docs
  AS SELECT
  ON admin.cb_dds_v_search_documents
  TO cb_dds_all_role;

실행 결과:

=== cb_dds_hr ===
END_USER_NAME
----------------
"cb_dds_hr"

ROWS_VISIBLE
------------
3

DOC_ID TITLE                OWNER_EMP_NO DEPT_CODE CONTENTS
------ -------------------- ------------ --------- --------
1      HR payroll guide     E1001        HR
2      HR recruiting plan   E1002        HR
6      HR benefits notice   E1003        HR

=== cb_dds_fin ===
ROWS_VISIBLE
------------
2

DOC_ID TITLE                    OWNER_EMP_NO DEPT_CODE CONTENTS
------ ------------------------ ------------ --------- --------
3      Finance close checklist  E2001        FIN
4      Finance audit memo       E2002        FIN

=== cb_dds_all ===
ROWS_VISIBLE
------------
6

DOC_ID TITLE                    OWNER_EMP_NO DEPT_CODE CONTENTS
------ ------------------------ ------------ --------- ---------------------------
1      HR payroll guide         E1001        HR        Payroll policy and HR guide
2      HR recruiting plan       E1002        HR        Recruiting plan for HR team
3      Finance close checklist  E2001        FIN       Monthly close checklist
4      Finance audit memo       E2002        FIN       Audit memo for finance team
5      Sales forecast           E3001        SALES     Quarterly sales forecast
6      HR benefits notice       E1003        HR        Benefits notice for employees

=== cb_dds_none ===
FROM admin.cb_dds_v_search_documents
ORA-00942: table or view "ADMIN"."CB_DDS_V_SEARCH_DOCUMENTS" does not exist

DDS 결과 해석:

DDS 사용자 DATA ROLE DATA GRANT 결과
cb_dds_hr cb_dds_hr_role WHERE dept_code = 'HR', EXCEPT contents 3건, contents NULL
cb_dds_fin cb_dds_fin_role WHERE dept_code = 'FIN', EXCEPT contents 2건, contents NULL
cb_dds_all cb_dds_all_role 행 제한 없음, 컬럼 제외 없음 6건, contents 원문 표시
cb_dds_none cb_dds_connect_only_role 없음 ORA-00942

7.3 ORDS Handler Bearer 검증

이 검증은 ORDS Handler가 Authorization 헤더를 받아 처리하는 구조를 ADB 안에서 재현한다. 외부 ORDS URL 호출은 하지 않고, Handler가 호출하는 같은 Package를 CB_ORDS로 직접 실행했다.

ORDS schema enable:

BEGIN
  ORDS.ENABLE_SCHEMA(
    p_enabled             => TRUE,
    p_schema              => 'CB_ORDS',
    p_url_mapping_type    => 'BASE_PATH',
    p_url_mapping_pattern => 'cb-ords',
    p_auto_rest_auth      => FALSE
  );
  COMMIT;
END;
/

Header를 Handler Bind Variable로 매핑:

ORDS.DEFINE_PARAMETER(
  p_module_name        => 'cb.agent.security',
  p_pattern            => 'vpd/documents',
  p_method             => 'POST',
  p_name               => 'Authorization',
  p_bind_variable_name => 'auth_header',
  p_source_type        => 'HEADER',
  p_param_type         => 'STRING',
  p_access_method      => 'IN'
);

VPD Handler 핵심:

FUNCTION vpd_search_json(p_authorization IN VARCHAR2) RETURN CLOB AS
  v_bearer_key VARCHAR2(4000);
BEGIN
  admin.cb_agent_ctx_pkg.clear_user;
  v_bearer_key := extract_bearer_key(p_authorization);
  admin.cb_agent_ctx_pkg.set_user_by_bearer(v_bearer_key);

  -- 이후 admin.cb_v_search_documents 조회.
  -- VPD는 CB_AGENT_CTX 값을 읽어 허용 행(row)만 반환한다.
END;

DDS Bearer 검증 핵심:

FUNCTION dds_bearer_probe_json(p_authorization IN VARCHAR2) RETURN CLOB AS
  v_bearer_key      VARCHAR2(4000);
  v_mapped_end_user VARCHAR2(128);
BEGIN
  v_bearer_key := extract_bearer_key(p_authorization);
  v_mapped_end_user := mapped_dds_end_user(v_bearer_key);

  SELECT ORA_END_USER_CONTEXT.username
  INTO   v_dds_context_username
  FROM   dual;

  EXECUTE IMMEDIATE
    'SELECT COUNT(*) FROM admin.cb_dds_v_search_documents'
  INTO v_rows_visible;
END;

실행 결과:

=== ORDS handler probe 1. VPD path with mandatory Bearer header ===
{
  "scenario": "VPD_BEARER_HEADER",
  "db_user": "CB_ORDS",
  "context_user_id": "101",
  "context_emp_no": "E10234",
  "context_dept_code": "HR",
  "context_read_contents": "N",
  "rows": [
    {"doc_id":1,"title":"HR payroll guide","dept_code":"HR","contents":null},
    {"doc_id":2,"title":"HR recruiting plan","dept_code":"HR","contents":null},
    {"doc_id":6,"title":"HR benefits notice","dept_code":"HR","contents":null}
  ]
}

=== ORDS handler probe 2. VPD path without Bearer header ===
ORA-20101: Authorization header must be Bearer <key>

=== ORDS handler probe 3. DDS path with Bearer header only ===
{
  "scenario": "DDS_BEARER_HANDLER_PROBE",
  "db_user": "CB_ORDS",
  "bearer_mapped_end_user": "cb_dds_hr",
  "dds_context_username": null,
  "query_error": "ORA-00942: table or view \"ADMIN\".\"CB_DDS_V_SEARCH_DOCUMENTS\" does not exist",
  "result": "EXPECTED_BLOCKED"
}

=== ORDS handler probe 4. DDS path with all-access Bearer header only ===
{
  "scenario": "DDS_BEARER_HANDLER_PROBE",
  "db_user": "CB_ORDS",
  "bearer_mapped_end_user": "cb_dds_all",
  "dds_context_username": null,
  "query_error": "ORA-00942: table or view \"ADMIN\".\"CB_DDS_V_SEARCH_DOCUMENTS\" does not exist",
  "result": "EXPECTED_BLOCKED"
}

해석:

항목 의미
VPD + Bearer 헤더 ORDS Handler가 Key를 검증하고 SYS_CONTEXT를 채우면 정상 동작
Bearer 헤더 없음 Mandatory Header로 처리되어 차단
DDS + Bearer 단독 매핑 Key를 cb_dds_hr로 매핑해도 DDS Security Context가 없으므로 차단
DDS 적용 조건 지원 드라이버 또는 호출 애플리케이션이 EndUserSecurityContext를 DB 호출 전에 Attach해야 함

7.4 중앙 권한 인벤토리

VPD와 DDS 모두 적용 결과를 Dictionary View로 확인할 수 있다. 이 예제에서는 sql/adb/24_agent_ords_security_inventory.sql을 실행해 정책 연결과 DDS Grant Matrix를 확인한다.

VPD 정책 연결 확인:

SELECT object_owner,
       object_name,
       policy_name,
       pf_owner,
       function,
       sel,
       enable
FROM   dba_policies
WHERE  object_owner = 'ADMIN'
AND    object_name LIKE 'CB%'
ORDER  BY object_name, policy_name;

Redaction 정책 확인:

SELECT object_owner,
       object_name,
       policy_name,
       enable,
       expression
FROM   redaction_policies
WHERE  object_owner = 'ADMIN'
AND    object_name LIKE 'CB%'
ORDER  BY object_name, policy_name;

DDS Grant 요약:

SELECT grant_name,
       object_name,
       grantee,
       privilege,
       COALESCE(
         LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_name),
         'ALL COLUMNS'
       ) AS allowed_columns,
       COALESCE(MAX(granted_with_all_columns_except), '-') AS excluded_columns,
       predicate
FROM   dba_data_grants
WHERE  owner = 'ADMIN'
AND    object_name LIKE 'CB%'
GROUP  BY grant_name, object_name, grantee, privilege, predicate
ORDER  BY object_name, grant_name;

DDS End User별 권한 매트릭스:

SELECT rg.grantee AS end_user_name,
       rg.data_role,
       dg.grant_name,
       dg.object_name,
       dg.predicate,
       COALESCE(MAX(dg.granted_with_all_columns_except), '-') AS excluded_columns
FROM   dba_data_role_grants rg
LEFT   JOIN dba_data_grants dg
ON     dg.grantee = rg.data_role
WHERE  rg.grantee LIKE 'cb\_dds\_%' ESCAPE '\'
GROUP  BY rg.grantee,
          rg.data_role,
          dg.grant_name,
          dg.object_name,
          dg.predicate
ORDER  BY rg.grantee, dg.object_name, dg.grant_name;

실행 결과 요약:

=== Inventory 1. VPD policies attached to protected objects ===
OBJECT_OWNER OBJECT_NAME            POLICY_NAME          FUNCTION
------------ ---------------------- -------------------- ------------------------
ADMIN        CB_V_SEARCH_DOCUMENTS  CB_AGENT_DOC_POLICY  CB_AGENT_DOC_VPD_FILTER

=== Inventory 2. Redaction policies attached to protected objects ===
OBJECT_OWNER OBJECT_NAME            POLICY_NAME          ENABLE
------------ ---------------------- -------------------- ------
ADMIN        CB_V_SEARCH_DOCUMENTS  CB_CONTENTS_REDACT   YES

=== Inventory 3. DDS grant summary, one row per DATA GRANT ===
GRANT_NAME      OBJECT_NAME                GRANTEE          EXCLUDED_COLUMNS PREDICATE
--------------- -------------------------- ---------------- ---------------- ---------------
CB_DG_ALL_DOCS  CB_DDS_V_SEARCH_DOCUMENTS  CB_DDS_ALL_ROLE  -                1 = 1
CB_DG_FIN_DOCS  CB_DDS_V_SEARCH_DOCUMENTS  CB_DDS_FIN_ROLE  CONTENTS         dept_code='FIN'
CB_DG_HR_DOCS   CB_DDS_V_SEARCH_DOCUMENTS  CB_DDS_HR_ROLE   CONTENTS         dept_code='HR'

=== Inventory 4. DDS end user -> data role -> data grant matrix ===
END_USER_NAME  DATA_ROLE                 GRANT_NAME      PREDICATE
-------------- ------------------------- --------------- ---------------
cb_dds_all     CB_DDS_ALL_ROLE           CB_DG_ALL_DOCS  1 = 1
cb_dds_fin     CB_DDS_FIN_ROLE           CB_DG_FIN_DOCS  dept_code='FIN'
cb_dds_hr      CB_DDS_HR_ROLE            CB_DG_HR_DOCS   dept_code='HR'
cb_dds_none    CB_DDS_CONNECT_ONLY_ROLE  -               -

확인 가능한 것과 별도 관리가 필요한 것:

구분 확인 방법
어떤 객체에 VPD가 붙었는지 DBA_POLICIES
어떤 컬럼이 Redaction 대상인지 REDACTION_POLICIES, REDACTION_COLUMNS
어떤 DDS DATA GRANT가 있는지 DBA_DATA_GRANTS
어떤 DDS END USER가 어떤 DATA ROLE을 받았는지 DBA_DATA_ROLE_GRANTS
승인 요청자, 승인 일시, 사유, Ticket 번호 DDS/VPD Dictionary만으로는 부족. 별도 승인 이력 테이블 또는 Ticket 시스템 필요

8. 결과 화면 및 운영 확인 항목

이 장에서는 고객 설명에 사용할 화면 형태의 예시를 정리한다. 사용자 식별 시나리오, 권한 매핑, Authorization 헤더 처리, VPD 결과, DDS 결과, 중앙 권한 인벤토리를 순서대로 확인한다.

상세 설명 - 시나리오, 권한 등록, 실행 결과 확인

8.1 두 사용자 식별 시나리오

두 가지 사용자 식별 시나리오 화면

확인 항목:

항목 의미
DB User 기반 Oracle 접속 계정으로 요청자를 식별
Bearer Key 기반 ORDS Authorization 헤더의 키로 내부 사용자를 식별
공통 처리 식별 후 DB 보안 정책이 행/컬럼을 제한
기본 권장 흐름 ORDS Bearer Key 기반은 VPD + Redaction을 기본으로 적용

8.2 권한 등록/매핑

권한 등록 매핑 화면

확인 항목:

항목 의미
APP_USER key 또는 DB user가 최종적으로 가리키는 내부 사용자
AGENT_BEARER_KEY Bearer Key Hash와 내부 사용자 매핑
USER_ROLE 사용자와 역할 연결
PERMISSION 어떤 보호 객체를 어떤 action으로 볼 수 있는지
PERMISSION_RULE 해당 보호 객체 안에서 어떤 행(row)을 볼 수 있는지
p_object 매칭 VPD가 현재 조회 객체 이름을 permission.target_name과 비교

8.3 Authorization 헤더 기반 사용자 매핑

Authorization 헤더 기반 사용자 매핑 화면

확인 항목:

항목 의미
ORDS Header Bearer Key 필수값. 없으면 차단
ORDS Handler Key를 받아 내부 사용자로 매핑
key_hash DB에 저장된 비교값. 원문 Key 저장 금지
사번/부서코드 key가 가리키는 내부 사용자 정보
DB 조회 결과 VPD/DDS 정책 적용 후 허용된 데이터만 반환

8.4 권한 미부여 및 차단 결과

권한 미부여 및 차단 결과 화면

확인 항목:

항목 의미
매핑 없음 보호 객체 DB 권한은 있어도 VPD 조건이 통과하지 못해 0건
DB 권한 미부여 VPD 판단 전 객체가 보이지 않아 ORA-00942 또는 ORA-01031
Bearer 헤더 없음 ORDS Handler 필수값 차단. 예: ORA-20101
잘못된 key key hash 매핑 실패. 예: ORA-20002

8.5 VPD 결과

VPD 결과 화면

확인 항목:

항목 의미
DB user Oracle 세션 사용자
Key User Bearer Key가 매핑한 실제 권한 사용자
행(row) 제한 권한 테이블과 VPD 함수로 적용
민감 컬럼 값 처리 Redaction으로 NULL/마스킹

8.6 DDS 결과

DDS 결과 화면

확인 항목:

항목 의미
DDS user END USER
DATA ROLE 권한 묶음
DATA GRANT 보호 객체, 행(row), 컬럼(column) 허용/제외
권한 미부여 ORA-00942, 객체 자체가 보이지 않음

8.7 중앙 권한 인벤토리

중앙 권한 인벤토리 화면

확인 항목:

항목 의미
VPD policy 어느 보호 객체에 어떤 정책 함수가 붙었는지
Redaction policy 어떤 컬럼이 NULL 또는 마스킹 대상인지
DDS DATA GRANT End User/DATA ROLE별 행/컬럼 권한
누락 확인 보호 객체 목록과 Dictionary 조회 결과를 비교