Fix DDS variant E2E + expand DDS capability docs

Real run against ADB 23.26.2.2.0 surfaced two issues:

- END USERs can't be direct grantees of a regular ROLE (ORA-01917).
  Connection privilege must flow through a DATA ROLE — added
  connect_only_role for ddsuser_none so it can authenticate
  without holding any data grant (mirrors VPDUSER_NONE UX).
- DDS Data Grants on top of the shared v_customers_* views silently
  returned 0 rows because the VPD policy on those views evaluates
  1=0 for sessions whose LOGON trigger didn't load the VPD context
  (i.e. all ddsuser_*). Created dedicated DDS-only views
  (v_dds_customers_pg / v_dds_customers_my) so DDS Data Grants are
  the sole authority.

E2E matrix now passes (ddsuser_my MY=17, ddsuser_pg PG=12,
ddsuser_both 12/17, ddsuser_none ORA-00942 on both). Notably DDS
returns ORA-00942 where VPD returned 0 rows — stronger object
hiding.

Expanded docs/05-dds-variant.md with:
- §1.1 capability matrix (End User, Data Role, Data Grant, MAC,
  ORA_END_USER_CONTEXT, OAuth2 federation, End User Context Object,
  ORA_IS_COLUMN_AUTHORIZED, dictionary views)
- §1.2 VPD/RAS-vs-DDS comparison
- §1.3 best-fit scenarios (multi-tenant SaaS, agentic AI, HR/PHI,
  federated identity, compliance)
- §1.4 limitations
- §5 operation-level grant example (manager-only UPDATE salary)
- §8 actual E2E results table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
devmrko
2026-05-26 15:44:05 +09:00
parent 9702349dbe
commit b7ee325b67
2 changed files with 160 additions and 51 deletions

View File

@@ -31,16 +31,19 @@
-- ------------------------------------------------------------
-- This script creates *new* end users with the `ddsuser_` prefix
-- so it does NOT collide with the VPD demo users (`vpduser_*`).
-- Both paths can coexist on the same ADB. The shared objects
-- (views v_customers_pg / v_customers_my, db_source table, etc.)
-- are reused — DDS adds a parallel access path, it does not
-- replace anything.
--
-- We deliberately do NOT enable `SET USE DATA GRANTS ONLY` on
-- the views — doing so would block the existing VPD users who
-- rely on the regular GRANT SELECT path. The DDS users get their
-- access exclusively through DATA GRANTs (no regular SELECT
-- grant is issued), so MAC is not required for this demo.
-- It also creates **dedicated DDS-only views** (`v_dds_customers_pg`,
-- `v_dds_customers_my`) — separate from the VPD demo's
-- `v_customers_pg`/`v_customers_my`. Reason: the VPD views have a
-- live `DBMS_RLS` policy that returns `1=0` for any session whose
-- LOGON trigger didn't load the VPD application context — i.e. for
-- our DDS end users. Putting DDS Data Grants on top of the VPD
-- views would silently return 0 rows (DDS allows but VPD blocks).
-- Dedicated views with no VPD policy let DDS be the sole gatekeeper.
--
-- We deliberately do NOT enable `SET USE DATA GRANTS ONLY` on the
-- DDS views in this demo — but doing so is the recommended
-- production posture (single declarative policy plane).
--
-- DEFINE: &DDSUSER_MY_PASSWORD, &DDSUSER_PG_PASSWORD,
-- &DDSUSER_BOTH_PASSWORD, &DDSUSER_NONE_PASSWORD
@@ -49,6 +52,25 @@ SET ECHO OFF
SET FEEDBACK ON
SET DEFINE ON
PROMPT === 0. Creating DDS-only views (no VPD policy attached) ===
-- Functionally identical to v_customers_pg / v_customers_my but
-- kept separate so that DDS Data Grants are the sole authority.
CREATE OR REPLACE VIEW v_dds_customers_pg AS
SELECT "customer_id" AS customer_id,
"full_name" AS full_name,
"email" AS email,
"signup_date" AS signup_date,
"region" AS region
FROM "public"."customers"@RDS_POSTGRES_LINK;
CREATE OR REPLACE VIEW v_dds_customers_my AS
SELECT "customer_id" AS customer_id,
"full_name" AS full_name,
"email" AS email,
"signup_date" AS signup_date,
"region" AS region
FROM "ecommerce_poc"."customers"@RDS_LINK;
PROMPT === 1. Creating local END USERs (schemaless, no objects) ===
-- End users in DDS do not own a schema and cannot create objects.
-- They authenticate, then receive access purely via DATA GRANTs.
@@ -67,21 +89,23 @@ GRANT CREATE SESSION TO dds_db_role;
CREATE DATA ROLE my_only_role;
CREATE DATA ROLE pg_only_role;
CREATE DATA ROLE both_sources_role;
-- (No role for the "none" user — absence of a data role = default deny.)
-- A "connect only" data role for ddsuser_none — they need to be able
-- to log in (so we can prove "authenticated but no data visible"),
-- but they must hold NO data grants. END USERs can't be grantees of
-- a regular ROLE directly (ORA-01917) — connection privilege must
-- flow through a DATA ROLE.
CREATE DATA ROLE connect_only_role;
GRANT dds_db_role TO my_only_role;
GRANT dds_db_role TO pg_only_role;
GRANT dds_db_role TO both_sources_role;
GRANT dds_db_role TO connect_only_role;
PROMPT === 3. Mapping end users to data roles ===
GRANT DATA ROLE my_only_role TO "ddsuser_my";
GRANT DATA ROLE pg_only_role TO "ddsuser_pg";
GRANT DATA ROLE both_sources_role TO "ddsuser_both";
-- ddsuser_none gets nothing — they can authenticate (no, actually
-- they cannot, because they have no data role carrying dds_db_role).
-- Grant CREATE SESSION directly so they can prove "logged in but
-- denied at the data layer" — same UX as VPDUSER_NONE in the VPD demo.
GRANT dds_db_role TO "ddsuser_none";
GRANT DATA ROLE connect_only_role TO "ddsuser_none";
PROMPT === 4. Creating DATA GRANTs (the declarative equivalent of the VPD policy) ===
-- These five lines replace the entire vpd_region_filter PL/SQL
@@ -91,24 +115,24 @@ PROMPT === 4. Creating DATA GRANTs (the declarative equivalent of the VPD policy
-- DDSUSER_MY -> ALL rows from v_customers_my, no PG access.
CREATE DATA GRANT admin.dds_my_only_grant_mysql
AS SELECT
ON admin.v_customers_my
ON admin.v_dds_customers_my
TO my_only_role;
-- DDSUSER_PG -> ALL rows from v_customers_pg, no MY access.
CREATE DATA GRANT admin.dds_pg_only_grant_pg
AS SELECT
ON admin.v_customers_pg
ON admin.v_dds_customers_pg
TO pg_only_role;
-- DDSUSER_BOTH -> both views.
CREATE DATA GRANT admin.dds_both_grant_pg
AS SELECT
ON admin.v_customers_pg
ON admin.v_dds_customers_pg
TO both_sources_role;
CREATE DATA GRANT admin.dds_both_grant_mysql
AS SELECT
ON admin.v_customers_my
ON admin.v_dds_customers_my
TO both_sources_role;
-- DDSUSER_NONE: NO data grant -> default deny.
@@ -121,7 +145,7 @@ PROMPT === 5. (Optional) Region row-level filter — DDS-style ===
--
-- CREATE OR REPLACE DATA GRANT admin.dds_both_grant_pg
-- AS SELECT
-- ON admin.v_customers_pg
-- ON admin.v_dds_customers_pg
-- WHERE region = 'APAC'
-- TO both_sources_role;
--
@@ -130,7 +154,7 @@ PROMPT === 5. (Optional) Region row-level filter — DDS-style ===
--
-- CREATE OR REPLACE DATA GRANT admin.dds_both_grant_mysql
-- AS SELECT
-- ON admin.v_customers_my
-- ON admin.v_dds_customers_my
-- WHERE region IN ('APAC','EMEA')
-- TO both_sources_role;
@@ -142,7 +166,7 @@ PROMPT === 6. (Optional) Column masking — DDS-style ===
--
-- CREATE OR REPLACE DATA GRANT admin.dds_both_grant_pg
-- AS SELECT (ALL COLUMNS EXCEPT email)
-- ON admin.v_customers_pg
-- ON admin.v_dds_customers_pg
-- TO both_sources_role;
--
-- Excluded columns return NULL (same UX as redaction), but it's
@@ -154,8 +178,10 @@ PROMPT === DDS variant ready ===
-- Verify with:
-- sqlplus '"ddsuser_pg"'/<pw>@<service>
-- SQL> SELECT ORA_END_USER_CONTEXT.username FROM dual;
-- SQL> SELECT COUNT(*) FROM admin.v_customers_pg; -- expect 12
-- SQL> SELECT COUNT(*) FROM admin.v_customers_my; -- expect 0 (no grant)
-- SQL> SELECT COUNT(*) FROM admin.v_dds_customers_pg; -- expect 12
-- SQL> SELECT COUNT(*) FROM admin.v_dds_customers_my; -- expect ORA-00942
-- (DDS hides the object entirely when no grant exists — note this is
-- stronger than VPD which would return "0 rows" for the same case.)
--
-- Audit the grants:
-- SELECT * FROM dba_data_grants;