80 KiB
06 · ORDS 기반 Agent 사용자 식별·권한 매핑 및 VPD/DDS 데이터 접근 통제 설계
목적: ORDS 기반 Agent 요청의 사용자 식별, 권한 매핑, Oracle VPD/DDS 기반 데이터 접근 통제, 사번/부서코드 기반 행 단위 필터링 구조 정의 범위: 권한 요청/승인 API 구현은 제외. DBA 수작업 등록을 전제로 권한 매핑 테이블 설계, 보안 정책 적용 방식, ADB 실행 검증 예제 포함 용어: "Deep Security"는 Oracle Deep Data Security(DDS) 로 표기
목차
- 1. 설계 개요 및 적용 범위
- 2. 사용자 식별 및 권한 매핑 구조
- 3. 권한 매핑 테이블 설계
- 4. VPD 기반 접근 통제 설계
- 5. DDS 기반 접근 통제 설계
- 6. VPD/DDS 비교 및 적용 기준
- 7. ADB 실행 검증
- 8. 결과 화면 및 운영 확인 항목
문서 흐름은 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 전파가 가능한 경우의 확장 경로로 구분한다.
요청 처리 절차:
- Agent가 ORDS API 호출
- ORDS가 요청 수신
- ORDS 또는 DB가 요청자를 식별
- DB Session Context 또는 DDS 보안 Identity에 권한 기준 생성
- Agent 요청은 보호 객체(VIEW/TABLE) 조회
- VPD가 행(row) 정책 적용. Redaction은 민감 컬럼 값을 NULL/마스킹하고, DDS는 컬럼(column) 허용/제외 적용
- 허용된 결과만 반환
구성 요소:
| 구분 | 선택지 | 역할 |
|---|---|---|
| 사용자 식별 | 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다.
설명 순서:
- 사용자 식별 방식과 권한 매핑 구조 정의
- Bearer Key 기반 VPD 경로 설명
- VPD에서 행(row) 통제와 Redaction 컬럼 값 마스킹 구분
- DDS에서 END USER, DATA ROLE, DATA GRANT 기반 통제 설명
- 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_object와 permission.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_ORDS가 cb_hr_key, cb_fin_key, cb_all_key를 CB_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_POLICY의 object_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_key의 issued_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 검색 |
흐름:
- Agent 또는 ORDS가
AGENT_HR_001로 DB 접속 - DB가
SYS_CONTEXT('USERENV', 'SESSION_USER')확인 app_user에서 DB 계정에 해당하는 내부 사용자 조회- 사용자-역할-권한 테이블에서 접근 범위 확인
- 보호 객체(VIEW/TABLE) 조회 시 선택한 정책 적용
- VPD 경로는 VPD가 행(row)을 제한하고, 민감 컬럼 값은 Redaction으로 NULL/마스킹 처리
- 컬럼 자체를 제외하려면 VIEW/TABLE 설계 또는 DDS DATA GRANT 사용
- DDS 경로는 DATA GRANT가 행(row)/컬럼(column)/작업(action)을 제한
DB User 기반은 설명과 감사가 단순하다. 다만 ORDS가 항상 같은 DB 계정으로 접속하면 DB 계정만으로 실제 사용자를 구분할 수 없다.
2.2 Bearer Key 기반
ORDS가 공통 DB 계정으로 접속하는 경우에는 Authorization 헤더의 Bearer Key로 실제 사용자를 식별한다.
흐름:
- Agent가 ORDS API 호출
- ORDS 헤더에
Authorization: Bearer <key>포함 - ORDS Handler가 Bearer Key 필수 여부 확인
- Key가 없거나 일치하지 않으면 401/403 또는 사용자 정의 오류 반환
- Key Hash 계산
agent_bearer_key테이블에서 내부 사용자 조회- 내부 사용자의
USER_ID, 사번, 부서코드, 컬럼 허용 Flag를 DB 세션에 저장 - 보호 객체(VIEW/TABLE) 조회
- VPD가 허용 행(row)만 반환
- 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가 되지는 않는다.
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를 이해할 때 필요한 세 가지:
| 요소 | 역할 |
|---|---|
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 동작
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 |
정책 활성화 여부 | 등록 즉시 적용 |
실행 시 흐름:
APP.V_SEARCH_DOCUMENTS에 연결된P_AGENT_DOC_FILTER정책 확인APP.AGENT_DOC_VPD_FILTER함수 호출p_schema = 'APP',p_object = 'V_SEARCH_DOCUMENTS'전달- 함수가
SYS_CONTEXT와 권한 테이블 확인 - 함수가 반환한 조건을 원래 SQL에 추가
- 조건을 통과한 행(row)만 반환
적용 단위:
| 검토 항목 | 기준 |
|---|---|
ADD_POLICY에서 모든 객체를 한 번에 지정할 수 있는가 |
아니다. object_schema, object_name으로 보호 객체 하나를 지정한다. |
object_name => '*' 또는 % 같은 Wildcard를 쓸 수 있는가 |
아니다. VPD ADD_POLICY의 object_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 -> 보호 객체 구조다.
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_GRANTS와 DBA_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 예제의 흐름:
- ORDS runtime schema
CB_ORDS가 DB에 접속 - ORDS Handler가 Bearer Key를 내부 사용자로 매핑
- 내부 사용자 값을
CB_AGENT_CTX에 저장 CB_V_SEARCH_DOCUMENTS조회- VPD가 행(row) 제한
- 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 예제의 흐름:
- DDS
END USER로 접속 DATA ROLE로 권한 묶음 확인DATA GRANT가 보호 객체와 행/컬럼 조건 적용- 권한이 없으면 객체가 보이지 않음
사용한 핵심 스크립트:
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 헤더 기반 사용자 매핑
확인 항목:
| 항목 | 의미 |
|---|---|
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 결과
확인 항목:
| 항목 | 의미 |
|---|---|
| DB user | Oracle 세션 사용자 |
| Key User | Bearer Key가 매핑한 실제 권한 사용자 |
| 행(row) 제한 | 권한 테이블과 VPD 함수로 적용 |
| 민감 컬럼 값 처리 | Redaction으로 NULL/마스킹 |
8.6 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 조회 결과를 비교 |