Files
vpd-permission-poc/sql/adb/07_end_users.sql
devmrko ed91306ee3 Pivot scenario from region-based 2 users to source-based 4 users
Replaces the original APAC-vs-all 2-user demo (vpduser_a/b on
KR_ANALYSTS/GLOBAL_ADMINS groups) with a 2x2 source-access matrix:

  vpduser_my    -> MY_ONLY      group  -> MySQL view only
  vpduser_pg    -> PG_ONLY      group  -> Postgres view only
  vpduser_both  -> BOTH_SOURCES group  -> both views
  vpduser_none  -> (no group)          -> nothing (default deny)

Why: source-level segmentation is the more common production
permission story than region-level filtering. Region filtering
remains available as an opt-in variant via commented UPDATE in
sql/adb/03_seed.sql.

Key changes:
- 03_seed.sql, 07_end_users.sql, 00_cleanup.sql, .env.example,
  run.sh updated for the new 4-user model. All 4 users get
  identical view GRANTs; the only differentiator is the
  permission table (proves the model is "data-driven, not
  GRANT-driven").
- 08-11 split into one file per user: my (+ 5 bypass attempts),
  pg, both, none (default-deny verification).
- 12_tests_admin_audit.sql uses LEFT JOIN so vpduser_none shows
  up as NULL permissions, and filters by object_owner=USER to
  exclude cross-schema policies.
- Removed inline "-- comment" after ";" lines in 03_seed.sql:
  SQL*Plus silently skipped the inserts (documented gotcha).
- README.md + docs/01,02 updated for the 4-user matrix. docs/03
  detailed guide keeps the region-filter example but now has a
  preface explaining it's a variant of the default 4-user model.
- docs/04: db_type='mysql_community' note added (RDS MySQL).

E2E verified: PG=0/MY=17, PG=12/MY=0, PG=12/MY=17, PG=0/MY=0
plus all 5 bypass attempts blocked.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-26 14:42:11 +09:00

77 lines
3.2 KiB
SQL

-- ============================================================
-- 07_end_users.sql
-- Create the FOUR end-user accounts with MINIMAL privileges.
-- Add a LOGON trigger that loads each user's context automatically.
-- Run as ADMIN.
--
-- DEFINE: &VPDUSER_MY_PASSWORD, &VPDUSER_PG_PASSWORD,
-- &VPDUSER_BOTH_PASSWORD, &VPDUSER_NONE_PASSWORD
-- ============================================================
SET ECHO OFF
SET FEEDBACK ON
SET DEFINE ON
PROMPT === Creating end-user accounts ===
-- Passwords come from .env (DEFINE) so they aren't hardcoded in source.
-- Production should use IAM / proxy auth / mTLS instead of static passwords.
CREATE USER vpduser_my IDENTIFIED BY "&VPDUSER_MY_PASSWORD";
CREATE USER vpduser_pg IDENTIFIED BY "&VPDUSER_PG_PASSWORD";
CREATE USER vpduser_both IDENTIFIED BY "&VPDUSER_BOTH_PASSWORD";
CREATE USER vpduser_none IDENTIFIED BY "&VPDUSER_NONE_PASSWORD";
-- Login privilege only. No QUOTA — these users never create objects.
GRANT CREATE SESSION TO vpduser_my;
GRANT CREATE SESSION TO vpduser_pg;
GRANT CREATE SESSION TO vpduser_both;
GRANT CREATE SESSION TO vpduser_none;
PROMPT === Granting SELECT on the policy-protected views ONLY ===
-- We deliberately grant SELECT on BOTH views to all four users.
-- The VPD policy decides what they actually see — including 0 rows
-- when there is no permission row. This is what makes the demo a
-- clean security boundary: revoking access doesn't mean revoking
-- the GRANT; it means removing the row in `permission`.
GRANT SELECT ON v_customers_pg TO vpduser_my;
GRANT SELECT ON v_customers_my TO vpduser_my;
GRANT SELECT ON v_customers_pg TO vpduser_pg;
GRANT SELECT ON v_customers_my TO vpduser_pg;
GRANT SELECT ON v_customers_pg TO vpduser_both;
GRANT SELECT ON v_customers_my TO vpduser_both;
GRANT SELECT ON v_customers_pg TO vpduser_none;
GRANT SELECT ON v_customers_my TO vpduser_none;
-- ctx_pkg is bound to the secure context; calling it is harmless
-- (the package always loads ONLY the caller's own permissions).
GRANT EXECUTE ON ctx_pkg TO vpduser_my;
GRANT EXECUTE ON ctx_pkg TO vpduser_pg;
GRANT EXECUTE ON ctx_pkg TO vpduser_both;
GRANT EXECUTE ON ctx_pkg TO vpduser_none;
-- NOTE on what we are deliberately NOT granting:
-- * NO grant on app_user / permission / etc. -> users can't read who-can-see-what
-- * NO grant on the underlying @dblink tables -> can't bypass the view
-- * NO grant on DBMS_RLS -> can't alter/drop the policy
-- * NO EXEMPT ACCESS POLICY -> can't escape VPD
-- * NO CREATE TABLE / CREATE MATERIALIZED VIEW -> can't snapshot filtered data
-- * NO DBA / PDB_DBA / RESOURCE roles -> least privilege
PROMPT === Creating LOGON trigger that auto-loads context ===
CREATE OR REPLACE TRIGGER vpd_logon_trg
AFTER LOGON ON DATABASE
BEGIN
-- Only fire for our application end-users. ADMIN logons keep normal behavior.
IF SYS_CONTEXT('USERENV','SESSION_USER') IN
('VPDUSER_MY','VPDUSER_PG','VPDUSER_BOTH','VPDUSER_NONE') THEN
admin.ctx_pkg.init;
END IF;
EXCEPTION
WHEN OTHERS THEN
-- Never block login on a context-loading error; fail closed (no perms loaded
-- means policy returns 1=0, i.e. no rows). Log if you have an audit table.
NULL;
END;
/
PROMPT === End-users ready ===
EXIT;