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>
47 lines
1.6 KiB
SQL
47 lines
1.6 KiB
SQL
-- ============================================================
|
|
-- 08_tests_user_b.sql
|
|
-- Run as VPDUSER_B (group GLOBAL_ADMINS, allowed_regions=*).
|
|
-- Expected: ALL regions visible (no row filter).
|
|
-- ============================================================
|
|
SET FEEDBACK ON
|
|
SET LINESIZE 200
|
|
SET PAGESIZE 100
|
|
|
|
PROMPT
|
|
PROMPT === Who am I, and what context did the LOGON trigger load? ===
|
|
SELECT USER AS session_user,
|
|
SYS_CONTEXT('VPD_CTX','USER_ID') AS app_user_id,
|
|
SYS_CONTEXT('VPD_CTX','V_CUSTOMERS_PG') AS regions_pg,
|
|
SYS_CONTEXT('VPD_CTX','V_CUSTOMERS_MY') AS regions_my
|
|
FROM dual;
|
|
|
|
PROMPT
|
|
PROMPT === Distinct regions visible from Postgres view (expect: all regions) ===
|
|
SELECT DISTINCT region FROM admin.v_customers_pg ORDER BY 1;
|
|
|
|
PROMPT
|
|
PROMPT === Distinct regions visible from MySQL view (expect: all regions) ===
|
|
SELECT DISTINCT region FROM admin.v_customers_my ORDER BY 1;
|
|
|
|
PROMPT
|
|
PROMPT === Row counts (expect higher than VPDUSER_A) ===
|
|
SELECT 'V_CUSTOMERS_PG' AS view_name, COUNT(*) AS rows_visible FROM admin.v_customers_pg
|
|
UNION ALL
|
|
SELECT 'V_CUSTOMERS_MY', COUNT(*) FROM admin.v_customers_my;
|
|
|
|
PROMPT
|
|
PROMPT === PII REDACTION (expect REAL email/full_name — GLOBAL_ADMINS has '*' so no masking) ===
|
|
COLUMN customer_id FORMAT 9999
|
|
COLUMN full_name FORMAT A20
|
|
COLUMN email FORMAT A30
|
|
COLUMN region FORMAT A8
|
|
SELECT customer_id, full_name, email, region
|
|
FROM admin.v_customers_pg
|
|
ORDER BY customer_id;
|
|
|
|
SELECT customer_id, full_name, email, region
|
|
FROM admin.v_customers_my
|
|
ORDER BY customer_id;
|
|
|
|
EXIT;
|