Consolidate data access control backoffice updates
This commit is contained in:
262
src/main/resources/templates/masking-rules.html
Normal file
262
src/main/resources/templates/masking-rules.html
Normal file
@@ -0,0 +1,262 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('컬럼 마스킹')}"></head>
|
||||
<body>
|
||||
<nav th:replace="~{fragments/layout :: nav}"></nav>
|
||||
<main class="container py-4">
|
||||
<div class="page-title">
|
||||
<h1>컬럼 마스킹</h1>
|
||||
<details class="explanation-details">
|
||||
<summary>도움말</summary>
|
||||
<p>마스킹 방법을 사전 정의 템플릿으로 등록하고, 민감 컬럼마다 하나의 기본 규칙을 연결합니다. 컬럼 연결 목록은 <strong>마스킹 대상 블랙리스트</strong>이며, 연결하지 않은 컬럼은 ASO 마스킹 대상이 아닙니다. 행 접근은 행 접근 정책이 계속 담당하며 이 화면은 반환값 표시 방식만 다룹니다.</p>
|
||||
<p>연결된 컬럼은 행 접근 정책이 조회를 허용한 사용자에게도 기본적으로 마스킹됩니다. <a href="/user-masking-rules">컬럼 원문 표시 허용 사용자</a>에 등록한 사용자만 원문을 봅니다. 이 설정은 행 접근 권한을 추가하지 않습니다. 컬럼 연결·해제 또는 연결된 규칙의 활성 상태 변경 시 DBMS_REDACT 정책도 자동 동기화됩니다.</p>
|
||||
<section class="content-band mt-3 mb-0">
|
||||
<h2>ASO 마스킹 적용 흐름</h2>
|
||||
<p><strong>ASO/Data Redaction은 권한 테이블을 직접 조회하지 않습니다.</strong> 토큰 처리 중 <code>set_masking_rule_context(user_id)</code>가 사용자별 마스킹 예외를 세션 context로 만들고, ASO 정책은 그 context만 읽습니다.</p>
|
||||
<div class="diagram-canvas">
|
||||
<svg class="product-flow-diagram" viewBox="0 0 1120 410" role="img" aria-labelledby="aso-flow-title aso-flow-desc">
|
||||
<title id="aso-flow-title">Bearer 토큰 기반 ASO 마스킹 적용 흐름</title>
|
||||
<desc id="aso-flow-desc">Bearer 토큰으로 사용자 컨텍스트와 컬럼별 MR context를 만들고, Oracle Data Redaction이 MR context 값에 따라 원문 또는 마스킹값을 반환한다.</desc>
|
||||
<defs>
|
||||
<marker id="aso-flow-arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth"><path d="M0,0 L0,6 L9,3 z" fill="currentColor"/></marker>
|
||||
</defs>
|
||||
<path class="diagram-link" marker-end="url(#aso-flow-arrow)" d="M185 92 H225"/>
|
||||
<path class="diagram-link" marker-end="url(#aso-flow-arrow)" d="M385 92 H425"/>
|
||||
<path class="diagram-link" marker-end="url(#aso-flow-arrow)" d="M585 92 H625"/>
|
||||
<path class="diagram-link" marker-end="url(#aso-flow-arrow)" d="M785 92 H825"/>
|
||||
<path class="diagram-link diagram-link-secondary" marker-end="url(#aso-flow-arrow)" d="M705 212 V144"/>
|
||||
<path class="diagram-link" marker-end="url(#aso-flow-arrow)" d="M910 136 V205"/>
|
||||
<path class="diagram-link" marker-end="url(#aso-flow-arrow)" d="M810 248 H850"/>
|
||||
<path class="diagram-link" marker-end="url(#aso-flow-arrow)" d="M930 291 V315"/>
|
||||
<path class="diagram-link" marker-end="url(#aso-flow-arrow)" d="M850 248 H720 V315"/>
|
||||
<g class="diagram-node"><rect x="25" y="48" width="160" height="88" rx="12"/><text x="105" y="79" class="diagram-node-title">Bearer Token</text><text x="105" y="106" class="diagram-node-detail">요청 주체 식별</text></g>
|
||||
<g class="diagram-node"><rect x="225" y="48" width="160" height="88" rx="12"/><text x="305" y="76" class="diagram-node-title">토큰 검증</text><text x="305" y="103" class="diagram-node-detail">set_user_by_bearer</text><text x="305" y="120" class="diagram-node-note">실패 시 context 정리</text></g>
|
||||
<g class="diagram-node"><rect x="425" y="48" width="160" height="88" rx="12"/><text x="505" y="76" class="diagram-node-title">사용자 context</text><text x="505" y="103" class="diagram-node-detail">CB_AGENT_CTX.USER_ID</text><text x="505" y="120" class="diagram-node-note">EMP_NO · DEPT_CODE</text></g>
|
||||
<g class="diagram-node diagram-node-accent"><rect x="625" y="48" width="160" height="88" rx="12"/><text x="705" y="76" class="diagram-node-title">마스킹 context</text><text x="705" y="103" class="diagram-node-detail">set_masking_rule_</text><text x="705" y="120" class="diagram-node-detail">context(user_id)</text></g>
|
||||
<g class="diagram-node diagram-node-accent"><rect x="825" y="48" width="245" height="88" rx="12"/><text x="947" y="76" class="diagram-node-title">컬럼별 세션 변수</text><text x="947" y="103" class="diagram-node-detail">MR_<column_id> = Y / N</text><text x="947" y="120" class="diagram-node-note">현재 DB 세션에만 설정</text></g>
|
||||
<g class="diagram-node diagram-node-data"><rect x="525" y="212" width="280" height="72" rx="12"/><text x="665" y="240" class="diagram-node-title">마스킹 예외·규칙 메타데이터</text><text x="665" y="263" class="diagram-node-detail">cb_user_masking_rule · cb_column_masking_rule</text></g>
|
||||
<g class="diagram-node diagram-node-accent"><rect x="850" y="205" width="160" height="86" rx="12"/><text x="930" y="235" class="diagram-node-title">DBMS_REDACT</text><text x="930" y="260" class="diagram-node-detail">SYS_CONTEXT로 MR 읽음</text></g>
|
||||
<g class="diagram-node diagram-node-data"><rect x="850" y="315" width="160" height="64" rx="12"/><text x="930" y="341" class="diagram-node-title">MR = Y</text><text x="930" y="363" class="diagram-node-detail">원문 표시</text></g>
|
||||
<g class="diagram-node"><rect x="640" y="315" width="160" height="64" rx="12"/><text x="720" y="341" class="diagram-node-title">MR = N / NULL</text><text x="720" y="363" class="diagram-node-detail">마스킹 적용</text></g>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="form-hint mb-0">연결 고리: 토큰 검증 후 <code>set_masking_rule_context(v_user_id)</code>가 <code>MR_<column_id></code>를 설정하고, Data Redaction 조건식은 <code>SYS_CONTEXT('CB_AGENT_CTX', 'MR_<column_id>')</code>만 평가합니다. 자세한 SQL은 <a href="/security-sql-scripts?script=aso-masking-runtime">ASO 마스킹 런타임 스크립트</a>에서 확인하세요.</p>
|
||||
</section>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
|
||||
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}"></div>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="architecture-kicker">행 접근과 분리된 컬럼 제어</span>
|
||||
<h2>ASO 마스킹 설정 순서</h2>
|
||||
<p class="section-subtitle">행 접근은 행 접근 규칙과 Oracle VPD 정책이 결정합니다. 이 화면은 허용된 행 안에서 컬럼 값을 원문으로 줄지, 마스킹해서 줄지만 관리합니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="effective-preview">
|
||||
<dl>
|
||||
<div>
|
||||
<dt>대상 컬럼 등록</dt>
|
||||
<dd>마스킹 후보 블랙리스트에 DB 컬럼을 올립니다. 이 단계만으로는 값이 마스킹되지 않습니다.</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>기본 규칙 연결</dt>
|
||||
<dd>컬럼에 활성 컬럼 마스킹 규칙을 연결하면 DBMS_REDACT 정책이 동기화됩니다.</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>원문 표시 허용</dt>
|
||||
<dd>예외 사용자는 <a href="/user-masking-rules">컬럼 원문 표시 허용 사용자</a>에서 지정합니다.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>DB ASO 정책 동기화</h2>
|
||||
<p class="section-subtitle">이제 컬럼 기본 규칙을 연결·해제하거나 연결된 규칙을 활성·비활성화하면 DB 정책도 자동으로 반영됩니다. 아래 버튼은 자동 동기화 도입 전의 기존 설정을 한 번에 보정하거나, 운영 점검 시 현재 설정을 다시 반영할 때만 사용하세요.</p>
|
||||
<form method="post" action="/masking-rules/synchronize" class="inline-form">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<button class="btn btn-outline-primary" type="submit">현재 DB 정책 동기화</button>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead><tr><th>보호 객체</th><th>DB 정책</th><th>백오피스 활성 컬럼</th><th>DB Redaction 컬럼</th><th>레거시 VPD 컬럼 제어</th><th>DB 정책 활성</th><th>상태 판단</th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="status : ${policyStatuses}">
|
||||
<td><code th:text="${status.targetLabel()}">POC_2.KB_CUSTOMERS</code></td>
|
||||
<td><code th:text="${status.policyName()}">KB_CUSTOMER_PII_REDACT</code></td>
|
||||
<td th:text="${status.configuredColumnCount()}">0</td>
|
||||
<td th:text="${status.appliedColumnCount()}">0</td>
|
||||
<td><span class="badge" th:classappend="${status.legacyVpdColumnPolicyCount() == 0} ? ' text-bg-secondary' : ' text-bg-danger'" th:text="${status.legacyVpdColumnPolicyCount() == 0} ? '없음' : ${status.legacyVpdColumnPolicyCount() + '건 활성'}">없음</span></td>
|
||||
<td><span class="badge" th:classappend="${status.policyEnabled()} ? ' text-bg-success' : ' text-bg-secondary'" th:text="${status.policyEnabled()} ? 'DB 적용 ON' : 'DB 적용 OFF'">DB 적용 OFF</span></td>
|
||||
<td><span class="badge" th:classappend="${' ' + status.badgeClass()}" th:text="${status.statusLabel()}">미적용</span><br><small class="text-muted" th:text="${status.detail()}">설명</small></td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(policyStatuses)}"><td colspan="7" class="text-muted">조회할 ASO 정책이 없습니다.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>사전 정의 마스킹 방식</h2>
|
||||
<p class="section-subtitle">임의 SQL이나 정규식을 직접 입력하지 않고 검증된 템플릿만 선택합니다.</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead><tr><th>템플릿</th><th>방식</th><th>동작</th><th>ASO 구현</th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="template : ${templates}">
|
||||
<td><code th:text="${template.code()}">NULLIFY</code></td>
|
||||
<td th:text="${template.label()}">값 숨김(NULL)</td>
|
||||
<td th:text="${template.description()}">설명</td>
|
||||
<td><code th:text="${template.asoFunction()}">DBMS_REDACT.NULLIFY</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>컬럼 마스킹 규칙 등록</h2>
|
||||
<p class="section-subtitle">업무용 이름을 붙여 템플릿을 재사용합니다. 예: <code>KB_RRN_STANDARD</code>.</p>
|
||||
<form method="post" action="/masking-rules" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
규칙 코드
|
||||
<input class="form-control" name="ruleCode" maxlength="64" pattern="[A-Za-z][A-Za-z0-9_]{2,63}" required placeholder="KB_RRN_STANDARD">
|
||||
<span class="form-hint">영문·숫자·밑줄만 사용합니다.</span>
|
||||
</label>
|
||||
<label>
|
||||
규칙명
|
||||
<input class="form-control" name="ruleName" maxlength="100" required placeholder="주민번호 기본 마스킹">
|
||||
</label>
|
||||
<label>
|
||||
마스킹 템플릿
|
||||
<select class="form-select" name="templateCode" required>
|
||||
<option th:each="template : ${templates}" th:value="${template.code()}"
|
||||
th:text="${template.label()}">값 숨김(NULL)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
설명
|
||||
<input class="form-control" name="description" maxlength="400" placeholder="적용 목적 또는 업무 기준">
|
||||
</label>
|
||||
<button class="btn btn-primary" type="submit">규칙 등록</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>등록된 컬럼 마스킹 규칙</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead><tr><th>코드</th><th>규칙명</th><th>템플릿</th><th>설명</th><th>상태</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="rule : ${rules}">
|
||||
<td><code th:text="${rule.ruleCode()}">KB_RRN_STANDARD</code></td>
|
||||
<td th:text="${rule.ruleName()}">주민번호 기본 마스킹</td>
|
||||
<td th:text="${rule.templateLabel()}">값 숨김(NULL)</td>
|
||||
<td th:text="${rule.description() ?: '-'}">설명</td>
|
||||
<td><span class="badge" th:classappend="${rule.enabled()} ? ' text-bg-success' : ' text-bg-secondary'" th:text="${rule.enabledYn()}">Y</span></td>
|
||||
<td>
|
||||
<form method="post" action="/masking-rules/active" class="inline-form">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<input type="hidden" name="ruleId" th:value="${rule.ruleId()}">
|
||||
<input type="hidden" name="active" th:value="${!rule.enabled()}">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="submit" th:text="${rule.enabled()} ? '비활성화' : '활성화'">변경</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(rules)}"><td colspan="6" class="text-muted">등록된 컬럼 마스킹 규칙이 없습니다.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>마스킹 대상 컬럼 추가</h2>
|
||||
<p class="section-subtitle">DB 실제 컬럼을 ASO 마스킹 후보 블랙리스트에 등록합니다. 이 단계는 컬럼을 선택 목록에 올리는 작업이며, 실제 마스킹 적용은 아래에서 기본 규칙을 연결할 때 수행됩니다.</p>
|
||||
<form method="post" action="/masking-rules/target-columns" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
대상 객체 / 컬럼
|
||||
<select class="form-select" name="target" required>
|
||||
<option value="">선택하세요</option>
|
||||
<th:block th:each="object : ${maskingTargetObjects}">
|
||||
<optgroup th:label="${object.displayName()}" th:if="${!#lists.isEmpty(availableMaskingColumnsByObject[object.objectId()])}">
|
||||
<option th:each="columnName : ${availableMaskingColumnsByObject[object.objectId()]}"
|
||||
th:value="|${object.objectId()}:${columnName}|"
|
||||
th:text="${object.displayName() + '.' + columnName}">POC_2.KB_CONTRACTS.PREMIUM</option>
|
||||
</optgroup>
|
||||
</th:block>
|
||||
</select>
|
||||
<span class="form-hint">현재 관리 대상 ASO 정책이 있는 객체만 표시됩니다. 예: <code>POC_2.KB_CONTRACTS.PREMIUM</code>.</span>
|
||||
</label>
|
||||
<button class="btn btn-outline-primary" type="submit">대상 컬럼 추가</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<h2>민감 컬럼에 기본 규칙 연결</h2>
|
||||
<p class="section-subtitle">이 목록이 마스킹 대상 블랙리스트입니다. 컬럼 하나에는 활성 규칙 하나만 연결할 수 있으며, 컬럼 원문 표시 허용 사용자는 별도 화면에서 지정합니다. 오른쪽 DB 상태는 컬럼이 속한 객체의 실제 Redaction 정책 상태입니다.</p>
|
||||
<form method="post" action="/masking-rules/columns" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
민감 컬럼
|
||||
<select class="form-select" name="columnId" required>
|
||||
<option value="">선택하세요</option>
|
||||
<th:block th:each="object : ${objects}">
|
||||
<optgroup th:label="${object.displayName()}" th:if="${!#lists.isEmpty(sensitiveColumnsByObject[object.objectId()])}">
|
||||
<option th:each="column : ${sensitiveColumnsByObject[object.objectId()]}"
|
||||
th:value="${column.columnId()}"
|
||||
th:text="${column.columnName() + ' [' + column.displayPolicyLabel() + ']'}">RRN_MASKED</option>
|
||||
</optgroup>
|
||||
</th:block>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
적용할 컬럼 마스킹 규칙
|
||||
<select class="form-select" name="ruleId" required>
|
||||
<option value="">선택하세요</option>
|
||||
<option th:each="rule : ${rules}" th:if="${rule.enabled()}" th:value="${rule.ruleId()}"
|
||||
th:text="${rule.ruleName() + ' · ' + rule.templateLabel()}">주민번호 기본 마스킹</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="btn btn-primary" type="submit">컬럼에 연결</button>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead><tr><th>대상 컬럼</th><th>규칙</th><th>템플릿</th><th>백오피스 설정</th><th>DB ASO 적용 상태</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="columnRule : ${columnRules}">
|
||||
<td><code th:text="${columnRule.targetLabel()}">POC_2.KB_CUSTOMERS.RRN_MASKED</code></td>
|
||||
<td th:text="${columnRule.ruleName()}">주민번호 기본 마스킹</td>
|
||||
<td th:text="${columnRule.template().label()}">주민등록번호 부분 마스킹</td>
|
||||
<td><span class="badge" th:classappend="${columnRule.ruleEnabled()} ? ' text-bg-success' : ' text-bg-warning'" th:text="${columnRule.ruleEnabled()} ? '기본 규칙 연결됨' : '규칙 비활성'">기본 규칙 연결됨</span></td>
|
||||
<td th:with="policyStatus=${policyStatusByObjectName[columnRule.objectName()]}">
|
||||
<span class="badge"
|
||||
th:if="${policyStatus}"
|
||||
th:classappend="${' ' + policyStatus.badgeClass()}"
|
||||
th:text="${policyStatus.statusLabel()}">적용됨</span>
|
||||
<span class="badge text-bg-secondary" th:unless="${policyStatus}">관리 정책 없음</span>
|
||||
<div class="text-muted small" th:if="${policyStatus}" th:text="${policyStatus.detail()}">상세</div>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" action="/masking-rules/columns/delete" class="inline-form">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<input type="hidden" name="columnId" th:value="${columnRule.columnId()}">
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit">연결 해제</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(columnRules)}"><td colspan="6" class="text-muted">민감 컬럼에 연결된 컬럼 마스킹 규칙이 없습니다.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user