1853 lines
80 KiB
Markdown
1853 lines
80 KiB
Markdown
# 06 · ORDS 기반 Agent 사용자 식별·권한 매핑 및 VPD/DDS 데이터 접근 통제 설계
|
|
|
|
> 목적: ORDS 기반 Agent 요청의 사용자 식별, 권한 매핑, Oracle VPD/DDS 기반 데이터 접근 통제, 사번/부서코드 기반 행 단위 필터링 구조 정의
|
|
> 범위: 권한 요청/승인 API 구현은 제외. DBA 수작업 등록을 전제로 권한 매핑 테이블 설계, 보안 정책 적용 방식, ADB 실행 검증 예제 포함
|
|
> 용어: "Deep Security"는 Oracle **Deep Data Security(DDS)** 로 표기
|
|
|
|
---
|
|
|
|
## 목차
|
|
|
|
- [1. 설계 개요 및 적용 범위](#1-설계-개요-및-적용-범위)
|
|
- [2. 사용자 식별 및 권한 매핑 구조](#2-사용자-식별-및-권한-매핑-구조)
|
|
- [3. 권한 매핑 테이블 설계](#3-권한-매핑-테이블-설계)
|
|
- [4. VPD 기반 접근 통제 설계](#4-vpd-기반-접근-통제-설계)
|
|
- [5. DDS 기반 접근 통제 설계](#5-dds-기반-접근-통제-설계)
|
|
- [6. VPD/DDS 비교 및 적용 기준](#6-vpddds-비교-및-적용-기준)
|
|
- [7. ADB 실행 검증](#7-adb-실행-검증)
|
|
- [8. 결과 화면 및 운영 확인 항목](#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 전파가 가능한 경우의 확장 경로로 구분한다.
|
|
|
|

|
|
|
|
요청 처리 절차:
|
|
|
|
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` 옵션이 있으나 운영 권한 모델로는 혼동 가능 | 본 구조에서는 사용하지 않고 설명 범위에서도 제외 |
|
|
|
|
따라서 이 문서에서의 결론은 아래와 같다.
|
|
|
|
```text
|
|
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 선언형 행/컬럼 권한 |
|
|
|
|
운영 등록 항목은 두 축으로 구분한다.
|
|
|
|
```text
|
|
요청 주체 식별
|
|
-> DB User 또는 Bearer Key로 내부 사용자 식별
|
|
|
|
데이터 접근 범위
|
|
-> role / permission / permission_rule 또는 DDS DATA GRANT로 결정
|
|
```
|
|
|
|
### 1.3 권한 판정 절차 및 차단 유형
|
|
|
|
VPD의 동적 권한 판정은 `p_object`와 `permission.target_name` 매핑으로 수행된다.
|
|
|
|
```text
|
|
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` 가능 |
|
|
|
|
따라서 차단 또는 미조회 결과는 발생 지점에 따라 의미가 다르다.
|
|
|
|
```text
|
|
권한 매핑 없음 = 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 권장 구현 구조
|
|
|
|
현재 요구사항의 주 경로는 아래 흐름이다.
|
|
|
|
```text
|
|
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는 아래 조건이 충족될 때 같은 구조의 확장 경로로 설명한다.
|
|
|
|
```text
|
|
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건이다. |
|
|
|
|
정리:
|
|
|
|
```text
|
|
전체 적용 = Wildcard 1회 등록
|
|
아님
|
|
|
|
전체 적용 = 보호 객체 목록 전체를 돌며 객체별 ADD_POLICY 등록
|
|
```
|
|
|
|
<details>
|
|
<summary><strong>운영 검토 항목</strong> - 적용 단위, 권한 조회, 누락 방지</summary>
|
|
|
|
| 검토 항목 | 기준 |
|
|
|---|---|
|
|
| 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 조회 결과를 검증 단계에 포함한다. |
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>오류 코드 상세</strong> - ORA 코드 해석과 정상 차단 여부</summary>
|
|
|
|
| 오류 코드 | 이 문서에서의 의미 | 정상 차단 여부 | 확인 방법 |
|
|
|---|---|---|---|
|
|
| `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`이 여기에 해당한다. |
|
|
| 정상 차단 오류 | 보안 검증에서는 실패가 아니라 “우회가 막혔다”는 증거다. 단, 운영 장애 분석에서는 같은 코드라도 발생 위치와 사용자, 대상 객체를 함께 봐야 한다. |
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>운영 책임 상세</strong> - 누가 무엇을 관리하는지</summary>
|
|
|
|
| 영역 | 담당 역할 | 관리 항목 |
|
|
|---|---|---|
|
|
| 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` 같은 컬럼이 그 역할이다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>성능·감사·운영 체크리스트</strong> - 적용 후 점검 항목</summary>
|
|
|
|
| 구분 | 체크 항목 |
|
|
|---|---|
|
|
| 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` 결과를 배포 전후 비교 |
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
## 2. 사용자 식별 및 권한 매핑 구조
|
|
|
|
이 장에서는 Agent 요청이 DB 권한 적용 대상으로 변환되는 방식을 정리한다. DB User 기반, Bearer Key 기반, DDS END USER 기반을 구분하고, 본 요구사항의 기본 경로는 `DB user != Key User` 구조임을 명확히 한다.
|
|
|
|
<details>
|
|
<summary><strong>상세 설명</strong> - DB User, Bearer Key, DDS END USER 식별 경로</summary>
|
|
|
|
### 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 테이블 예:
|
|
|
|
```sql
|
|
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가 가리키는 내부 사용자별 권한을 대체하는 개념은 아니다.
|
|
|
|
```sql
|
|
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에 적용하려면 아래 변환이 필요하다.
|
|
|
|
```text
|
|
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는 아니다.
|
|
|
|
지원 드라이버 또는 호출 애플리케이션 계층 기준 의사 코드:
|
|
|
|
```python
|
|
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](https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/end-user-security-context.html) | Driver가 End-user Security Context Payload를 Connection Metadata로 전달하고, DB가 이를 검증해 Context를 생성/Attach |
|
|
| [Configure the Database for Local End-User Authentication](https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/configure-database-local-end-user-authentication.html) | Local End User는 호출 애플리케이션이 제공한 User Name과 Security Context Lookup Key로 식별 |
|
|
| [Configure Python Applications](https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/configure-python-applications.html) | `create_end_user_security_context`, `set_end_user_security_context`, `clear_end_user_security_context` API 지원 |
|
|
| [Read End-User Context Attributes](https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/read-end-user-context-attributes.html) | `ORA_END_USER_CONTEXT`로 현재 Security Context의 Attribute 참조 가능 |
|
|
| [ORDS parameter binding](https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/25.3/orddg/ORDS-reference.html) | `ORDS.DEFINE_PARAMETER`로 HTTP 헤더를 Handler Bind Variable에 매핑 가능 |
|
|
|
|
</details>
|
|
|
|
## 3. 권한 매핑 테이블 설계
|
|
|
|
이 장에서는 VPD 경로에서 사용할 업무 권한 테이블을 정의한다. 핵심 구조는 `사용자 -> 역할 -> 보호 객체 권한 -> 행 규칙`이며, Bearer Key는 내부 사용자로 매핑되는 식별 수단으로 둔다.
|
|
|
|
<details>
|
|
<summary><strong>상세 설명</strong> - Key User, Role, Permission, 행 규칙</summary>
|
|
|
|
VPD에서 권한은 사용자, 역할, 보호 객체, 행(row) 조건으로 구분한다.
|
|
|
|
```mermaid
|
|
%%{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` 값으로 이 컬럼을 조회한다.
|
|
|
|
</details>
|
|
|
|
## 4. VPD 기반 접근 통제 설계
|
|
|
|
이 장에서는 ORDS가 Bearer Key를 내부 사용자로 매핑한 뒤, DB Session Context와 VPD 정책 함수로 행(row)을 제한하는 방식을 설명한다. 컬럼 값 NULL/마스킹은 VPD가 아니라 Oracle Data Redaction으로 분리해 설명한다.
|
|
|
|
<details>
|
|
<summary><strong>상세 설명</strong> - SYS_CONTEXT, p_object, EXISTS, Redaction</summary>
|
|
|
|
VPD는 보호 객체(VIEW/TABLE)에 정책 함수를 연결한다. Agent나 ORDS가 권한 조건을 직접 붙이지 않아도, DB가 조회 시점에 조건을 자동으로 붙인다.
|
|
|
|

|
|
|
|
VPD를 이해할 때 필요한 세 가지:
|
|
|
|
| 요소 | 역할 |
|
|
|---|---|
|
|
| `SYS_CONTEXT` | 현재 요청자 값 확인. 예: `USER_ID`, 사번, 부서코드 |
|
|
| `p_object` | 현재 조회 중인 보호 객체 이름 |
|
|
| `EXISTS` | 권한 테이블에 접근 근거가 있는지 확인 |
|
|
|
|
ORDS Handler가 실행하는 SQL은 검색 조건만 갖는다.
|
|
|
|
```sql
|
|
SELECT doc_id, title, owner_emp_no, dept_code
|
|
FROM app.v_search_documents
|
|
WHERE contains_text = :q;
|
|
```
|
|
|
|
권한 판단은 VPD 함수가 수행한다.
|
|
|
|
```sql
|
|
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 조회 권한이 있더라도 업무 권한 매핑이 없으면 결과는 나오지 않는다.
|
|
|
|
```text
|
|
DB SELECT Grant 있음
|
|
+ VPD policy 연결됨
|
|
+ user_role / permission / permission_rule 매핑 없음
|
|
= 조회 가능하지만 결과 0건
|
|
```
|
|
|
|
반대로 보호 객체 자체에 대한 DB 권한이 없거나 원본 TABLE을 직접 조회하려고 하면 VPD 판단 전 단계에서 객체가 보이지 않거나 권한 오류가 발생한다.
|
|
|
|
```text
|
|
보호 VIEW/TABLE DB 권한 미부여
|
|
= ORA-00942 또는 ORA-01031
|
|
```
|
|
|
|
### 4.1 SYS_CONTEXT 동작
|
|
|
|

|
|
|
|
ORDS Handler는 요청자를 식별한 뒤 같은 DB 세션에 값을 저장한다.
|
|
|
|
```sql
|
|
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 정책 함수는 조회 시점에 같은 세션에서 값을 읽는다.
|
|
|
|
```sql
|
|
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마다 따로 적용된다.
|
|
|
|
```sql
|
|
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;
|
|
```
|
|
|
|
의미를 풀면 아래와 같다.
|
|
|
|
```sql
|
|
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 분리 | 민감 컬럼이 있는 객체와 없는 객체를 분리 | 컬럼 권한이 단순하고 운영 분리가 쉬울 때 |
|
|
|
|
예:
|
|
|
|
```sql
|
|
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 함수를 실행할지 연결하는 코드다.
|
|
|
|
```sql
|
|
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_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 함수를 붙이는 등록 스크립트 예:
|
|
|
|
```sql
|
|
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`과 비교한다. 그래서 정책 함수는 하나로 유지하고, 정책 연결만 보호 객체별로 반복할 수 있다.
|
|
|
|
보호 객체 목록 테이블을 쓰는 예:
|
|
|
|
```sql
|
|
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`를 생성”하는 방식이다.
|
|
|
|
정책 연결 확인:
|
|
|
|
```sql
|
|
SELECT object_owner,
|
|
object_name,
|
|
policy_name,
|
|
function,
|
|
sel,
|
|
enable
|
|
FROM dba_policies
|
|
WHERE object_owner = 'APP'
|
|
ORDER BY object_name, policy_name;
|
|
```
|
|
|
|
</details>
|
|
|
|
## 5. DDS 기반 접근 통제 설계
|
|
|
|
이 장에서는 DDS `END USER`, `DATA ROLE`, `DATA GRANT`를 이용해 보호 객체의 행(row), 컬럼(column), 작업(action)을 선언형으로 통제하는 방식을 설명한다. Bearer Key를 DDS에 적용하려면 Key 조회 결과가 `EndUserSecurityContext`로 DB 호출 전에 전파되어야 한다.
|
|
|
|
<details>
|
|
<summary><strong>상세 설명</strong> - END USER, DATA ROLE, DATA GRANT</summary>
|
|
|
|
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`로 확인했다.
|
|
|
|
기본 예:
|
|
|
|
```sql
|
|
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) 허용/제외를 한 번에 선언하는 예:
|
|
|
|
```sql
|
|
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`가 필요하다.
|
|
|
|
```sql
|
|
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 권한 요약 조회:
|
|
|
|
```sql
|
|
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별 권한 매트릭스 조회:
|
|
|
|
```sql
|
|
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;
|
|
```
|
|
|
|
</details>
|
|
|
|
## 6. VPD/DDS 비교 및 적용 기준
|
|
|
|
이 장에서는 앞에서 분리 설명한 VPD와 DDS를 운영 기준으로 비교한다. 비교 기준은 사용자 식별 방식, 권한 저장 위치, 행/컬럼 통제 범위, Bearer Key 처리 방식, 적용 단위, 권한 미부여 시 결과로 둔다.
|
|
|
|
<details>
|
|
<summary><strong>상세 설명</strong> - 운영 선택 기준</summary>
|
|
|
|
| 항목 | 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에 동시에 걸지 말고 별도 보호 객체로 분리 |
|
|
|
|
</details>
|
|
|
|
## 7. ADB 실행 검증
|
|
|
|
이 장에서는 ADB에 생성한 로컬 테이블 기준으로 VPD + Redaction, DDS, ORDS Handler Bearer 검증을 실행한 스크립트와 결과를 제시한다.
|
|
|
|
<details>
|
|
<summary><strong>상세 설명</strong> - 실행 가능한 VPD/DDS 스크립트와 결과</summary>
|
|
|
|
이 예제는 RDS, DB Link, Postgres, MySQL을 사용하지 않는다. ADB 안에 로컬 테이블을 만들고, 같은 흐름에서 VPD 경로와 DDS 경로를 각각 확인한다.
|
|
|
|
실행:
|
|
|
|
```bash
|
|
./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/마스킹 처리
|
|
|
|
사용한 핵심 스크립트:
|
|
|
|
```sql
|
|
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 매핑:
|
|
|
|
```sql
|
|
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:
|
|
|
|
```sql
|
|
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;
|
|
/
|
|
```
|
|
|
|
실행 결과:
|
|
|
|
```text
|
|
=== 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. 권한이 없으면 객체가 보이지 않음
|
|
|
|
사용한 핵심 스크립트:
|
|
|
|
```sql
|
|
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;
|
|
```
|
|
|
|
실행 결과:
|
|
|
|
```text
|
|
=== 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:
|
|
|
|
```sql
|
|
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로 매핑:
|
|
|
|
```sql
|
|
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 핵심:
|
|
|
|
```sql
|
|
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 검증 핵심:
|
|
|
|
```sql
|
|
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;
|
|
```
|
|
|
|
실행 결과:
|
|
|
|
```text
|
|
=== 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 정책 연결 확인:
|
|
|
|
```sql
|
|
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 정책 확인:
|
|
|
|
```sql
|
|
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 요약:
|
|
|
|
```sql
|
|
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별 권한 매트릭스:
|
|
|
|
```sql
|
|
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;
|
|
```
|
|
|
|
실행 결과 요약:
|
|
|
|
```text
|
|
=== 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 시스템 필요 |
|
|
|
|
</details>
|
|
|
|
## 8. 결과 화면 및 운영 확인 항목
|
|
|
|
이 장에서는 고객 설명에 사용할 화면 형태의 예시를 정리한다. 사용자 식별 시나리오, 권한 매핑, Authorization 헤더 처리, VPD 결과, DDS 결과, 중앙 권한 인벤토리를 순서대로 확인한다.
|
|
|
|
<details>
|
|
<summary><strong>상세 설명</strong> - 시나리오, 권한 등록, 실행 결과 확인</summary>
|
|
|
|
### 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 조회 결과를 비교 |
|
|
|
|
</details>
|