-- ADR-090-B: awoooi_migrator 限權角色 + 憑證分離 -- 建立時間: 2026-04-18 台北時區 -- 建立者: ogt + Claude Opus 4.7 (1M) -- -- 上游: ADR-090 主檔 + feedback_secrets_leak_incidents_2026-04-18 -- -- 目的: -- 1. 把 migration 操作從「應用 superuser」(awoooi) 拆出,避免 CI / AI 腳本需要生產密碼 -- 2. awoooi_migrator 只能 CREATE / ALTER / DROP / INDEX / COMMENT,不能 SELECT / DML -- 3. 若 migrator 帳號外洩,攻擊者也無法讀取資料,只能結構性破壞 (可 rollback) -- -- 執行者: 統帥 (需 superuser 權限 postgres 執行) — Claude 只起草,不執行 -- -- 執行步驟 (請統帥在 188 主機上 psql as postgres 超級使用者): -- 1. 以 postgres 連上 awoooi_prod -- 2. 把下方 替換為您親自產生的密碼 -- 3. 執行本檔 -- 4. 更新 K8s secret awoooi-secrets 新增 MIGRATION_DATABASE_URL -- 5. 測試: PGPASSWORD='' psql -h 188 -U awoooi_migrator -d awoooi_prod -- → 應可 CREATE TABLE x(); 但不能 SELECT * FROM incidents; -- -- 回滾: DROP OWNED BY awoooi_migrator; DROP ROLE awoooi_migrator; -- ============================================================================ -- Step 1: 建立 migrator 角色 (預設無密碼,立即設定) -- ============================================================================ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'awoooi_migrator') THEN CREATE ROLE awoooi_migrator WITH LOGIN; END IF; END $$; -- ★ 替換為您親自產生的 32+ 字元隨機密碼 (建議 openssl rand -base64 32) ★ ALTER ROLE awoooi_migrator WITH PASSWORD ''; -- 註: ALTER ROLE 不會寫入 pg_stat_statements log (若有 log_statement=all 請先關掉) -- ============================================================================ -- Step 2: 授予 DDL 權限 (CREATE / ALTER / DROP / INDEX / COMMENT) -- ============================================================================ -- 允許連線 awoooi_prod GRANT CONNECT ON DATABASE awoooi_prod TO awoooi_migrator; -- 允許在 public schema 建表 / 建 index GRANT USAGE, CREATE ON SCHEMA public TO awoooi_migrator; -- 允許管理所有現有表 (ALTER / DROP / INDEX / COMMENT) -- 注意: 這不包含 SELECT / INSERT / UPDATE / DELETE GRANT REFERENCES, TRIGGER ON ALL TABLES IN SCHEMA public TO awoooi_migrator; -- 允許執行所有 funcs (ALTER FUNCTION / DROP FUNCTION 需要) GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO awoooi_migrator; -- 未來新建物件自動繼承上述權限 (對 awoooi 這個 owner 建的物件) ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT REFERENCES, TRIGGER ON TABLES TO awoooi_migrator; -- 允許使用 pgcrypto / vector 等 extension GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO awoooi_migrator; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO awoooi_migrator; -- ============================================================================ -- Step 3: 明確撤銷 DML 權限 (雙重保險,即使以後有誤 grant 也攔得住) -- ============================================================================ REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM awoooi_migrator; ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM awoooi_migrator; -- ============================================================================ -- Step 4: 驗收查詢 (執行後手動檢查) -- ============================================================================ -- 4.1 角色存在? -- SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolcanlogin -- FROM pg_roles WHERE rolname = 'awoooi_migrator'; -- -- 預期: rolname=awoooi_migrator, rolcanlogin=t, rolsuper=f -- 4.2 schema 權限? -- SELECT has_schema_privilege('awoooi_migrator','public','CREATE'); -- -- 預期: t -- 4.3 DML 權限應該沒有? -- SET ROLE awoooi_migrator; -- SELECT * FROM incidents LIMIT 1; -- 預期: ERROR permission denied -- RESET ROLE; -- 4.4 DDL 權限應該有? -- SET ROLE awoooi_migrator; -- CREATE TABLE test_migrator_check (id INT); -- DROP TABLE test_migrator_check; -- RESET ROLE; -- -- 預期: 兩條都成功 -- ============================================================================ -- END OF MIGRATION adr090b_awoooi_migrator_role.sql -- 安裝後 CI / AI 腳本憑證路徑: -- 未來所有 migration 使用 MIGRATION_DATABASE_URL (awoooi_migrator) -- 應用 pod 繼續用 DATABASE_URL (awoooi, 限 DML) -- 兩條 URL 分別存 K8s secret 的不同 key -- ============================================================================