PL/SQLのプロシージャやパッケージは「誰の権限で実行されるか」によって安全性と使い勝手が大きく変わります。設計の要はAUTHID(Definer/Invoker)と、実行権限・オブジェクト権限の付与方針をどう組み合わせるかです。ここでは典型パターンと落とし穴、実務で使えるテンプレートをまとめます。
AUTHIDの基本と選択基準
PL/SQLは明示しなければAUTHID DEFINER(定義者権限)で動きます。これは作成者スキーマの権限でSQLが評価される挙動です。対してAUTHID CURRENT_USER(Invoker Rights)は呼び出し者の権限で実行されます。アプリ固有データをカプセル化してAPIだけ公開したいならDefiner、共通ユーティリティとして各スキーマの権限をそのまま反映させたいならInvokerを選ぶのが原則です。
GRANTの基本:EXECUTEは直接付与、ロールは無効
Definer/Invokerのいずれでも、他スキーマからプロシージャを呼ばせるにはEXECUTE権限が必要です。注意点は「ロール経由の権限はPL/SQL内では無効」になることです。実行者に必要なオブジェクト権限(表のSELECTなど)はロールではなく直接GRANTしておきます。
-- APIを公開する側
CREATE OR REPLACE PACKAGE app_api AUTHID DEFINER AS
PROCEDURE add_order(p_cust NUMBER, p_amt NUMBER);
END app_api;
/
GRANT EXECUTE ON app_api TO app_user; -- ロール経由ではなく直接
-- Invoker Rightsを使うユーティリティ側(呼び出し者が自分の権限で実行)
CREATE OR REPLACE PACKAGE util_rep AUTHID CURRENT_USER AS
FUNCTION total_sales(p_from DATE, p_to DATE) RETURN NUMBER;
END util_rep;
/
-- 呼び出し者に必要な表の権限は直接付与(ロール不可)
GRANT SELECT ON sales TO app_user;
Definer Rightsでの安全なカプセル化(SECURE DEFINERパターン)
Definerは強力ですが、過剰権限の漏洩やSQLインジェクションの危険が伴います。セキュアに保つには入口で呼び出し元を検証し、動的SQLのパラメータは必ずバインドし、必要最小のオブジェクト権限だけを所有スキーマに持たせます。
CREATE OR REPLACE PACKAGE order_api AUTHID DEFINER AS
PROCEDURE upsert_order(p_id NUMBER, p_amt NUMBER);
END;
/
CREATE OR REPLACE PACKAGE BODY order_api AS
PROCEDURE assert_caller IS
BEGIN
IF SYS_CONTEXT('USERENV','SESSION_USER') NOT IN ('APP_USER','BATCH_USER') THEN
RAISE_APPLICATION_ERROR(-20000, 'not allowed');
END IF;
END;
PROCEDURE upsert_order(p_id NUMBER, p_amt NUMBER) IS
BEGIN
assert_caller;
MERGE INTO orders d
USING (SELECT p_id id, p_amt amt FROM dual) s
ON (d.id = s.id)
WHEN MATCHED THEN UPDATE SET amount = s.amt
WHEN NOT MATCHED THEN INSERT (id, amount) VALUES (s.id, s.amt);
END;
END;
/
GRANT EXECUTE ON order_api TO app_user, batch_user;
この構成なら呼び出しSQLに余計な権限を与えずに済み、インジェクション余地も最小化できます。
Invoker Rightsでの再利用と多テナント対応
ユーティリティ系やレポート系処理は、呼び出し者のオブジェクトに対して自然に動くInvokerが適します。スキーマを跨ぐときは完全修飾名か同名シノニムの設計を揃え、コンテキスト変数でテナントやロールを判定してクエリを切り替えます。
CREATE OR REPLACE PACKAGE rep_pkg AUTHID CURRENT_USER AS
FUNCTION sum_by_dept(p_dept NUMBER) RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY rep_pkg AS
FUNCTION sum_by_dept(p_dept NUMBER) RETURN NUMBER IS v NUMBER;
BEGIN
SELECT SUM(amount) INTO v FROM sales WHERE dept_id = p_dept; -- 呼び出し者のsales
RETURN NVL(v,0);
END;
END;
/
-- 呼び出し者側に SELECT ON sales を直接GRANTしておく
AUTHIDの混在呼び出しと権限解決の勘所
InvokerからDefinerを呼ぶ、DefinerからInvokerを呼ぶ、といった混在は珍しくありません。権限の評価は「現在実行中のユニットのAUTHID」に依存して切り替わるため、どの層でどの権限が必要かを事前に棚卸ししておきます。ユーティリティ層はInvoker、ドメインロジック層はDefinerといった層分けにすると読みやすくなります。
動的SQLと権限の組み合わせ:必ずバインドし、ホワイトリストで絞る
動的SQLが必要な場合でも、文字列連結でスキーマ名や列名を外部入力から通さないのが鉄則です。必要に応じてホワイトリスト検証を行い、値部分のみバインドします。
PROCEDURE purge_older_than(p_days NUMBER) AUTHID DEFINER IS
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM audit_log WHERE log_time < SYSDATE - :1'
USING p_days; -- 値をバインド
END;
権限の最小化と依存の見える化
APIスキーマはアプリ表の直接権限だけを持たせ、システム権限は最小限にします。不要になった権限は定期的に削除し、DBA_DEPENDENCIESやUSER_TAB_PRIVSを使って依存関係を棚卸しします。ユニットテストでは意図した権限が無いと動かないことを逆に確認する「負のテスト」も入れておくと運用事故が減ります。
-- 依存の可視化例
SELECT owner, name, referenced_owner, referenced_name, referenced_type
FROM user_dependencies
WHERE type IN ('PROCEDURE','PACKAGE','PACKAGE BODY')
ORDER BY 1,2;
スキーマ分離と公開面の限定
実体スキーマ(表を持つ)とAPIスキーマ(PL/SQLを持つ)を分離し、外部にはAPIスキーマのパッケージだけを公開する構成が定石です。実体スキーマの表はAPIスキーマにだけ直接GRANTし、呼び出し側ユーザーにはAPIのEXECUTEのみ付与します。こうするとアクセス経路が一本化され、監査とリスク管理が容易になります。
本番運用のベストプラクティスまとめ
本番ではDefinerを原則にして権限を厳密にカプセル化し、ユーティリティやレポーティングはInvokerで再利用性を確保するのが安定解です。EXECUTEとオブジェクト権限は必ず直接GRANTし、ロール依存を避けます。動的SQLはバインドとホワイトリストで固定し、呼び出し者検証や接続元識別(DBMS_SESSION.SET_IDENTIFIER)を組み合わせると追跡性が高まります。エディションベース・リディフィニションやパッケージ仕様の後方互換も意識し、権限設計とデプロイ戦略をセットで設計するとリリースの安全性が上がります。
チェックリスト(運用に入れる前に確認したい要点)
AUTHIDは要件に合っているか、EXECUTEは直接付与されているか、PL/SQL内で必要な表・シノニムの権限はロールではなく直接GRANTか、動的SQLに文字列連結はないか、Definer入口で呼び出し者検証をしているか、依存と権限の棚卸しは最新か、という観点を一度に見直しておくと、セキュリティ事故と本番障害の多くを未然に防げます。