Files
vpd-permission-poc/sql/adb/01_dblinks.sql
devmrko 68d53dc5a9 Initial commit — VPD Permission POC (clone-and-go)
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>
2026-05-26 14:03:32 +09:00

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