ADB-centered row-level access control across heterogeneous DB sources
(AWS RDS Postgres + MySQL) using Oracle VPD + Data Redaction +
Secure Application Context, packaged as a one-click demo.
Mechanism:
- LOGON trigger calls ctx_pkg.init once per session to load the user's
allowed regions from the permission mapping tables into a Secure App
Context (VPD_CTX, USING ctx_pkg).
- VPD policy function vpd_region_filter reads SYS_CONTEXT and returns
an IN-list predicate (or '1=0' for fail-closed, NULL for '*'),
which Oracle injects into every SELECT on the protected views.
- Data Redaction reuses the same context to mask PII (email, full_name)
when the allowed-regions value is not '*'.
- 5 documented bypass attempts (direct DB link SELECT, SET_CONTEXT
spoof, DBMS_RLS drop, mapping table SELECT) all blocked by GRANT
scoping + DEFINER rights on ctx_pkg.
One-click entrypoint:
- ./run.sh {prereq|source|adb|tests|audit|all|teardown}
- Source DDL (Postgres + MySQL customers + 12-row seed each) is
applied via local psql/mysql; ADB-side setup via sqlplus with .env
values injected as SQL*Plus DEFINE substitutions.
Verified E2E on ADB 26ai + AWS RDS PG + RDS MySQL (mysql_community
gateway) on 2026-05-26: VPDUSER_A sees only APAC rows (PG 2 / MySQL 6,
PII masked), VPDUSER_B sees all (PG 12 / MySQL 17, PII unmasked).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
3.9 KiB
SQL
120 lines
3.9 KiB
SQL
-- ============================================================
|
|
-- 01_dblinks.sql
|
|
-- Run as ADMIN.
|
|
--
|
|
-- ADB 에서 외부 Postgres / MySQL 로 가는 DB Link 를 만든다.
|
|
-- DBMS_CLOUD_ADMIN.CREATE_DATABASE_LINK 를 쓰면 ADB 가 내장 게이트웨이로
|
|
-- heterogeneous (PG/MySQL) 연결을 처리해 준다.
|
|
--
|
|
-- 멱등성: 같은 이름의 link/credential 이 있으면 drop 후 재생성.
|
|
--
|
|
-- DEFINE: &DBLINK_PG_NAME, &DBLINK_MY_NAME,
|
|
-- &PG_HOST, &PG_PORT, &PG_DB, &PG_USER, &PG_PASSWORD,
|
|
-- &MY_HOST, &MY_PORT, &MY_DB, &MY_USER, &MY_PASSWORD
|
|
-- ============================================================
|
|
-- ECHO OFF: heredoc 의 DEFINE 으로 주입되는 패스워드가 stdout 으로 흐르지 않게.
|
|
-- 디버그 필요할 땐 sql 파일 단독으로만 실행하고, run.sh 경유 시는 절대 ON 하지 말 것.
|
|
SET ECHO OFF
|
|
SET VERIFY OFF
|
|
SET FEEDBACK ON
|
|
SET DEFINE ON
|
|
SET TERMOUT ON
|
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
|
|
|
PROMPT === 1) 기존 DB link / credential 정리 (있으면 drop) ===
|
|
DECLARE
|
|
PROCEDURE drop_link_if_exists(p_name IN VARCHAR2) IS
|
|
l_cnt NUMBER;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO l_cnt FROM user_db_links
|
|
WHERE db_link = UPPER(p_name);
|
|
IF l_cnt > 0 THEN
|
|
DBMS_CLOUD_ADMIN.DROP_DATABASE_LINK(db_link_name => UPPER(p_name));
|
|
DBMS_OUTPUT.PUT_LINE('dropped db_link ' || UPPER(p_name));
|
|
END IF;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
DBMS_OUTPUT.PUT_LINE('drop_link skip ' || p_name || ': ' || SQLERRM);
|
|
END;
|
|
|
|
PROCEDURE drop_cred_if_exists(p_name IN VARCHAR2) IS
|
|
l_cnt NUMBER;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO l_cnt FROM user_credentials
|
|
WHERE credential_name = UPPER(p_name);
|
|
IF l_cnt > 0 THEN
|
|
DBMS_CLOUD.DROP_CREDENTIAL(credential_name => UPPER(p_name));
|
|
DBMS_OUTPUT.PUT_LINE('dropped credential ' || UPPER(p_name));
|
|
END IF;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
DBMS_OUTPUT.PUT_LINE('drop_cred skip ' || p_name || ': ' || SQLERRM);
|
|
END;
|
|
BEGIN
|
|
drop_link_if_exists('&DBLINK_PG_NAME');
|
|
drop_link_if_exists('&DBLINK_MY_NAME');
|
|
drop_cred_if_exists('&DBLINK_PG_NAME._CRED');
|
|
drop_cred_if_exists('&DBLINK_MY_NAME._CRED');
|
|
END;
|
|
/
|
|
|
|
PROMPT === 2) credential 등록 (원격 DB 로그인 정보) ===
|
|
BEGIN
|
|
DBMS_CLOUD.CREATE_CREDENTIAL(
|
|
credential_name => '&DBLINK_PG_NAME._CRED',
|
|
username => '&PG_USER',
|
|
password => '&PG_PASSWORD'
|
|
);
|
|
DBMS_CLOUD.CREATE_CREDENTIAL(
|
|
credential_name => '&DBLINK_MY_NAME._CRED',
|
|
username => '&MY_USER',
|
|
password => '&MY_PASSWORD'
|
|
);
|
|
END;
|
|
/
|
|
|
|
PROMPT === 3) DB Link 생성 — Postgres ===
|
|
BEGIN
|
|
DBMS_CLOUD_ADMIN.CREATE_DATABASE_LINK(
|
|
db_link_name => '&DBLINK_PG_NAME',
|
|
hostname => '&PG_HOST',
|
|
port => &PG_PORT,
|
|
service_name => '&PG_DB',
|
|
credential_name => '&DBLINK_PG_NAME._CRED',
|
|
gateway_params => JSON_OBJECT('db_type' VALUE 'postgres'),
|
|
ssl_server_cert_dn => NULL,
|
|
directory_name => NULL
|
|
);
|
|
END;
|
|
/
|
|
|
|
PROMPT === 4) DB Link 생성 — MySQL ===
|
|
BEGIN
|
|
DBMS_CLOUD_ADMIN.CREATE_DATABASE_LINK(
|
|
db_link_name => '&DBLINK_MY_NAME',
|
|
hostname => '&MY_HOST',
|
|
port => &MY_PORT,
|
|
service_name => '&MY_DB',
|
|
credential_name => '&DBLINK_MY_NAME._CRED',
|
|
-- mysql_community: AWS RDS for MySQL, self-managed MySQL CE 등에 필요.
|
|
-- 디폴트 'mysql' 은 MySQL Enterprise/Commercial 전용.
|
|
gateway_params => JSON_OBJECT('db_type' VALUE 'mysql_community'),
|
|
ssl_server_cert_dn => NULL,
|
|
directory_name => NULL
|
|
);
|
|
END;
|
|
/
|
|
|
|
PROMPT === 5) 결과 확인 — DB link 목록 ===
|
|
COL db_link FORMAT a25
|
|
COL host FORMAT a60
|
|
SELECT db_link, host FROM user_db_links ORDER BY db_link;
|
|
|
|
PROMPT === 6) 연결 검증 — 각 link 로 가벼운 SELECT ===
|
|
WHENEVER SQLERROR CONTINUE
|
|
PROMPT -- Postgres
|
|
SELECT COUNT(*) AS pg_customers FROM "public"."customers"@&DBLINK_PG_NAME;
|
|
PROMPT -- MySQL
|
|
SELECT COUNT(*) AS my_customers FROM "&MY_DB"."customers"@&DBLINK_MY_NAME;
|
|
|
|
PROMPT === 01_dblinks done ===
|
|
EXIT
|