2026年4月13日月曜日

Entra IDで認証したリモートMCPサーバーに仮想プライベート・データベースを適用する

リモートMCPサーバーを実行する環境に、仮想プライベート・データベースによる保護を実装します。

以下の記事で作成した環境を実装の対象とします。
仮想プライベート・データベースはReal Application Serverとは異なり、Autonomous AI Databaseとオンプレミスで同じ手順で構成できます。Autonomous AI Databaseでは管理者ユーザーはADMIN、オンプレミスでは通常SYSで接続し、仮想プライベート・データベースを構成するユーザーVPDADMINを作成します。

本記事では、Autonomous AI Databaseを対象とした構成手順を記述します。

Autonomous AI Lakehouse 19cのデータベースとしてSALESADB、OpenRestyによるリバース・プロキシを実行するコンピュート・インスタンスを作成します。APEXワークスペースとしてapexdev、スキーマとしてWKSP_APEXDEVが作成済みとします。

続けて、上記で作成したサンプルのリモートMCPサーバーsampleserverを、Microsoft Entra IDをIdPとしてOpen ID Connect認証できるようにします。Entra IDのアプリとしてORDS MCPおよびORDS MCP Clientを作成します。
この環境に、Oracle Databaseのサンプル・スキーマhuman_resourcesをインストールし、Entra IDで認証されたユーザーを使って仮想プライベート・データベースによるデータ保護を実装します。

仮想プライベート・データベースは以下の記事と同じ構成にします。以下の2つのポリシーを構成します。
  1. employee_in_same_department: サインインしたユーザーと同じ部門にアクセスを限定する
  2. employee_is_manager: サインインしたユーザーがマネージャーである従業員の列SALARYとCOMMISSION_PCTにアクセスを限定する
サンプル・スキーマのhuman_resourcesは、あらかじめインストールしておきます。

以下よりVPDの構成作業を紹介します。


管理ユーザーVPDADMINの作成と各種権限の付与



仮想プライベート・データベースを構成するためのデータベース・ユーザーとしてVPDADMINを作成します。

データベースSALESADBに管理者ユーザーADMINで接続し、作業を進めます。

create user vpdadmin identified by <パスワード>;
alter user vpdadmin quota 25m on data;
grant create session to vpdadmin;
grant create table to vpdadmin;


オンプレミスではユーザーVPDADMINのデフォルト表領域はDATAではない(主にUSERS)場合が多いため、クオータの割り当て先は変更する必要があります。

SQL> create user vpdadmin identified by *********;


User VPDADMINは作成されました。


SQL> alter user vpdadmin quota 25m on data;


User VPDADMINが変更されました。


SQL> grant create session to vpdadmin;


Grantが正常に実行されました。


SQL> grant create table to vpdadmin;


Grantが正常に実行されました。


SQL> 


VPDADMINに、仮想プラベート・データベースを構成するために必要な権限を与えます。

grant create any context to vpdadmin;
grant execute on dbms_rls to vpdadmin;
grant execute on dbms_session to vpdadmin;
grant create procedure to vpdadmin;


SQL> grant create any context to vpdadmin;


Grantが正常に実行されました。


SQL> grant execute on dbms_rls to vpdadmin;


Grantが正常に実行されました。


SQL> grant execute on dbms_session to vpdadmin;


Grantが正常に実行されました。


SQL> grant create procedure to vpdadmin;


Grantが正常に実行されました。


SQL> 


26aiの場合は、権限ADMINISTER ROW LEVEL SECURITY POLICYも必要です。

grant administer row level security policy to vpdadmin;

リモートMCPサーバーを実装しているユーザーWKSP_APEXDEVに、サンプル・スキーマhuman_resourcesの読み取り権限を追加します。
create role hr_role;
grant select on hr.departments to hr_role;
grant select on hr.employees to hr_role;
grant select on hr.jobs to hr_role;
grant select on hr.job_history to hr_role;
grant select on hr.locations to hr_role;
grant select on hr.regions to hr_role;
grant select on hr.countries to hr_role;
grant select on hr.emp_details_view to hr_role;
grant hr_role to wksp_apexdev;
alter user wksp_apexdev default role hr_role;

SQL> create role hr_role;


Role HR_ROLEは作成されました。


SQL> grant select on hr.departments to hr_role;


Grantが正常に実行されました。


SQL> grant select on hr.employees to hr_role;


Grantが正常に実行されました。


SQL> grant select on hr.jobs to hr_role;


Grantが正常に実行されました。


SQL> grant select on hr.job_history to hr_role;


Grantが正常に実行されました。


SQL> grant select on hr.locations to hr_role;


Grantが正常に実行されました。


SQL> grant select on hr.regions to hr_role;


Grantが正常に実行されました。


SQL> grant select on hr.countries to hr_role;


Grantが正常に実行されました。


SQL> grant select on hr.emp_details_view to hr_role;


Grantが正常に実行されました。


SQL> grant hr_role to wksp_apexdev;


Grantが正常に実行されました。


SQL> alter user wksp_apexdev default role hr_role;


User WKSP_APEXDEVが変更されました。


SQL> 


Entra IDで認証したユーザー(JWTのsubの値)と表HR.EMPLOYEESに保存されている従業員を対応づける表AUTH_USERSを作成します。Real Application Serverによる保護では(こちらの記事)では、リモートMCPサーバーを実装しているスキーマに作成しましたが、仮想プライベート・データベースによる保護では、スキーマVPDADMINに作成することにしました。

@mcp-app/vpd-setup/auth_users.sql

SQL> @mcp-app/vpd-setup/auth_users.sql 

SQL> create table auth_users(

  2      employee_id number not null,

  3      department_id number not null,

  4      email varchar2(25),

  5      authenticated_identity varchar2(128) not null

  6  );


Table AUTH_USERSは作成されました。


SQL> 


アプリケーション・コンテキストemp_dept_ctx、ポリシー・ファンクションpred_employee_in_same_departmentpred_employee_is_managerおよび仮想プライベート・データベースのポリシーemployee_in_same_departmentemployee_is_managerを作成します。

スクリプトcreate-vpd-prot.sqlに引数としてリモートMCPサーバーを実装しているスキーマ名を与えて実行します。

@mcp-app/vpd-setup/create-vpd-prot.sql wksp_apexdev

SQL> @mcp-app/vpd-setup/create-vpd-prot.sql wksp_apexdev

SQL> set serveroutput on

SQL> set echo on

SQL> 

SQL> define SCHEMA = &1

SQL> 

SQL> /*

SQL> * create context emp_dept_ctx

SQL> */

SQL> create or replace context emp_dept_ctx using oj_mcp_vpd_config;


Context EMP_DEPT_CTXは作成されました。


SQL> -- create a package to manage context.

SQL> @@../src/vpd/oj_mcp_vpd_config.pks

SQL> create or replace package oj_mcp_vpd_config

  2  as

  3  

  4  procedure init(

  5      p_current_user in varchar2

  6  );

  7  

  8  end;

  9  /


Package OJ_MCP_VPD_CONFIGがコンパイルされました


SQL> @@../src/vpd/oj_mcp_vpd_config.pkb

SQL> create or replace package body oj_mcp_vpd_config

  2  as

  3  

  4  G_NAMESPACE constant varchar2(80) := 'emp_dept_ctx';

  5  

  6  procedure init(

  7      p_current_user in varchar2

  8  )

  9  as

 10      l_employee_id   auth_users.employee_id%type;

 11      l_department_id auth_users.department_id%type;

 12  begin

 13      select employee_id, department_id into l_employee_id, l_department_id

 14      from auth_users

 15      where authenticated_identity = p_current_user;

 16      /*

 17       * Set employee_id and department_id to the application context.

 18       */

 19      dbms_session.set_context(G_NAMESPACE,'employee_id',  l_employee_id);

 20      dbms_session.set_context(G_NAMESPACE,'department_id',l_department_id);

 21  exception

 22      when no_data_found then

 23          -- context initialized with null

 24          dbms_session.set_context(G_NAMESPACE,'employee_id',  null);

 25          dbms_session.set_context(G_NAMESPACE,'department_id',null);

 26      when others then

 27          -- empty app context

 28          raise;

 29  end init;

 30  

 31  end oj_mcp_vpd_config;

 32  /


Package Body OJ_MCP_VPD_CONFIGがコンパイルされました


SQL> -- 

SQL> grant execute on oj_mcp_vpd_config to &SCHEMA;

旧:grant execute on oj_mcp_vpd_config to &SCHEMA

新:grant execute on oj_mcp_vpd_config to wksp_apexdev


Grantが正常に実行されました。


SQL> 

SQL> /*

SQL> * Create policy functions.

SQL> */

SQL> @@../src/vpd/pred_employee_in_same_department.pls 

SQL> CREATE OR REPLACE FUNCTION pred_employee_in_same_department

  2  (

  3      schema_p IN VARCHAR2,

  4      table_p IN VARCHAR2

  5  )

  6  RETURN VARCHAR2

  7  AS

  8      pred VARCHAR2(80);

  9  BEGIN

 10      pred := q'~department_id = SYS_CONTEXT('emp_dept_ctx','department_id')~'; 

 11  RETURN pred;

 12  END;

 13  /


Function PRED_EMPLOYEE_IN_SAME_DEPARTMENTがコンパイルされました


SQL> @@../src/vpd/pred_employee_is_manager.pls

SQL> CREATE OR REPLACE FUNCTION pred_employee_is_manager

  2  (

  3      schema_p IN VARCHAR2,

  4      table_p IN VARCHAR2

  5  )

  6  RETURN VARCHAR2

  7  AS

  8      pred VARCHAR2(80);

  9  BEGIN

 10      pred := q'~manager_id = SYS_CONTEXT('emp_dept_ctx', 'employee_id')~'; 

 11  RETURN pred;

 12  END;

 13  /


Function PRED_EMPLOYEE_IS_MANAGERがコンパイルされました


SQL> 

SQL> /*

SQL> * Create and apply VPD policy.

SQL> */

SQL> begin

  2      dbms_rls.add_policy(

  3          object_schema => 'hr'

  4        , object_name => 'employees'

  5        , policy_name => 'employee_is_manager'

  6        , function_schema => 'vpdadmin'

  7        , policy_function => 'pred_employee_is_manager'

  8        , statement_types => 'select'

  9        , policy_type => DBMS_RLS.CONTEXT_SENSITIVE

 10        , sec_relevant_cols => 'salary,commission_pct'

 11        , sec_relevant_cols_opt => DBMS_RLS.ALL_ROWS

 12        , namespace => 'emp_dept_ctx'

 13        , attribute => 'employee_id'

 14      );

 15  end;

 16  /


PL/SQLプロシージャが正常に完了しました。


SQL> 

SQL> begin

  2      dbms_rls.add_policy(

  3          object_schema => 'hr'

  4        , object_name => 'employees'

  5        , policy_name => 'employee_in_same_department'

  6        , function_schema => 'vpdadmin'

  7        , policy_function => 'pred_employee_in_same_department'

  8        , statement_types => 'select'

  9        , policy_type => DBMS_RLS.CONTEXT_SENSITIVE

 10        , namespace => 'emp_dept_ctx'

 11        , attribute => 'department_id'

 12      );

 13  end;

 14  /


PL/SQLプロシージャが正常に完了しました。


SQL> 


表AUTH_USERSにユーザーの対応を作成します。

ORDSのRESTサービスをJWTにて保護した場合に認識されるユーザーを確認します。

MCP Inspectorよりツールのget_autheticated_identityを実行すると、USERNAMEとしてユーザーIDが返されます。


このユーザーIDをAUTHENTICATED_IDENTITYの値として、表AUTH_USERSに1行挿入します。

insert into wksp_apexdev.auth_users(employee_id, department_id, email, authenticated_identity) values(103,60,'AJAMES','Entra IDのユーザーのID');
commit;

SQL> insert into auth_users(employee_id, department_id, email, authenticated_identity) values(103,60,'AJAMES','****************************');


1行挿入しました。


SQL> commit;


コミットが完了しました。


SQL> 


以上で仮想プライベート・データベースによる保護が実装できました。



スキーマWKSP_APEXDEVの構成




リモートMCPサーバーのリクエストを受け付けるORDSのPOSTハンドラにて、実際の処理を始める前に仮想プライベート・データベースで参照するアプリケーション・コンテキストを初期化するようにします。

リポジトリmcp-appに含まれるインストール・スクリプトinstall-all.sqlを実行すると、仮想プライベート・データベースに対応したPOSTハンドラとなるプロシージャoj_mcp_vpd_post_handlerが作成されます。以下のように、通常のoj_mcp_post_handlerを呼び出す前にvpdadmin.oj_mcp_vpd_config.initを呼び出すことにより、ポリシー・ファンクションが参照するアプリケーション・コンテキストemp_dept_ctxを初期化しています。
create or replace procedure oj_mcp_vpd_post_handler
(
    p_body         in blob,
    p_current_user in varchar2,
    p_status_code  out number
)
as
    l_scope logger_logs.scope%type := 'oj_mcp_vpd_post_handler';

begin
    logger.log_info('Enter VPD POST Handler', l_scope);
    -- Suppress compile-time errors.
    execute immediate 'begin vpdadmin.oj_mcp_vpd_config.init(:1); end;' using p_current_user;
    -- After initializing the context for VPD, perform the standard POST processing.
    oj_mcp_post_handler(p_body, p_current_user, p_status_code);
    logger.log_info('Leave VPD POST Handler');
end oj_mcp_vpd_post_handler;
/
リモートMCPサーバーのPOSTハンドラのコードを、上記のOJ_MCP_VPD_POST_HANDLERを呼び出すように書き換えます。

ユーザーWKSP_APEXDEVに接続して、以下を実行します。
begin
    ords.define_handler(
        p_module_name    => 'sampleserver',
        p_pattern        => 'mcp',
        p_method         => 'POST',
        p_source_type    => 'plsql/block',
        p_source         => 'begin oj_mcp_vpd_post_handler(:body,:current_user,:status_code); end;'
    );
end;
/

SQL> begin

  2      ords.define_handler(

  3          p_module_name    => 'sampleserver',

  4          p_pattern        => 'mcp',

  5          p_method         => 'POST',

  6          p_source_type    => 'plsql/block',

  7          p_source         => 'begin oj_mcp_vpd_post_handler(:body,:current_user,:status_code); end;'

  8      );

  9  end;

 10* /


PL/SQLプロシージャが正常に完了しました。


SQL> 


以上で、リモートMCPサーバーの呼び出し時に仮想プライベート・データベースのポリシーによる保護が適用されます。


動作確認



MCP Inspectorで保護の状態を確認します。

一旦、仮想プライベート・データベースによる保護を外します。
begin
    ords.define_handler(
        p_module_name    => 'sampleserver',
        p_pattern        => 'mcp',
        p_method         => 'POST',
        p_source_type    => 'plsql/block',
        p_source         => 'begin oj_mcp_post_handler(:body,:current_user,:status_code); end;'
    );
end;
/
仮想プライベート・データベースのポリシーを削除します。ユーザーVPDADMINで接続し、以下のスクリプトを実行します。

@mcp-app/vpd-setup/delete-vpd-prot.sql

SQL> @mcp-app/vpd-setup/delete-vpd-prot.sql 

SQL> 

SQL> begin

  2      dbms_rls.drop_policy(

  3          object_schema => 'hr'

  4          ,object_name => 'employees'

  5          ,policy_name => 'employee_in_same_department'

  6      );

  7      dbms_rls.drop_policy(

  8          object_schema => 'hr'

  9          ,object_name => 'employees'

 10          ,policy_name => 'employee_is_manager'

 11      );

 12  end;

 13  /


PL/SQLプロシージャが正常に完了しました。


SQL> 

SQL> drop function pred_employee_in_same_department;


Function PRED_EMPLOYEE_IN_SAME_DEPARTMENTが削除されました。


SQL> drop function pred_employee_is_manager;


Function PRED_EMPLOYEE_IS_MANAGERが削除されました。


SQL> 

SQL> drop package oj_mcp_vpd_config;


Package OJ_MCP_VPD_CONFIGが削除されました。


SQL> -- drop context emp_dept_ctx;

SQL> 


この状態でMCP Inspectorからツールのrun_sqlを実行します。

実行するSQLはselect * from hr.employees、limitに108を指定します。表HR.EMPLOYEESには107行のデータが含まれているため、Tool Resultには0から106までの107行の従業員が返されます。


再度ポリシーを有効にします。

ユーザーVPDADMINにて、以下のスクリプトを実行します。

@mcp-app/vpd-setup/create-vpd-prot.sql wksp_apexdev

続けて、ユーザーWKSP_APEXDEVにて、以下のスクリプトを実行します。
begin
    ords.define_handler(
        p_module_name    => 'sampleserver',
        p_pattern        => 'mcp',
        p_method         => 'POST',
        p_source_type    => 'plsql/block',
        p_source         => 'begin oj_mcp_vpd_post_handler(:body,:current_user,:status_code); end;'
    );
end;
/

SQL> begin

  2      ords.define_handler(

  3          p_module_name    => 'sampleserver',

  4          p_pattern        => 'mcp',

  5          p_method         => 'POST',

  6          p_source_type    => 'plsql/block',

  7          p_source         => 'begin oj_mcp_vpd_post_handler(:body,:current_user,:status_code); end;'

  8      );

  9  end;

 10* /


PL/SQLプロシージャが正常に完了しました。


SQL> exit

Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

Version 19.31.0.1.0から切断されました

mcp-salesadb % 


MCP Inspectorからツールのrun_sqlを実行します。実行するSQLは先ほどと同じです。


Tool Resultとして以下が返されます。

Alexander Jamesと同じ部署であるDEPARTMENT_IDが60の従業員が配列で返されています(ポリシーemployee_in_same_departmentが適用)。また、MANAGER_IDが103、つまりAlexander Jamesである従業員については、SALARYについてもデータとして含まれています(ポリシーemployee_is_managerが適用)。
[
  {
    "EMPLOYEE_ID": 103,
    "FIRST_NAME": "Alexander",
    "LAST_NAME": "James",
    "EMAIL": "AJAMES",
    "PHONE_NUMBER": "1.590.555.0103",
    "HIRE_DATE": "2016-01-03T00:00:00",
    "JOB_ID": "IT_PROG",
    "SALARY": null,
    "COMMISSION_PCT": null,
    "MANAGER_ID": 102,
    "DEPARTMENT_ID": 60
  },
  {
    "EMPLOYEE_ID": 104,
    "FIRST_NAME": "Bruce",
    "LAST_NAME": "Miller",
    "EMAIL": "BMILLER",
    "PHONE_NUMBER": "1.590.555.0104",
    "HIRE_DATE": "2017-05-21T00:00:00",
    "JOB_ID": "IT_PROG",
    "SALARY": 6000,
    "COMMISSION_PCT": null,
    "MANAGER_ID": 103,
    "DEPARTMENT_ID": 60
  },
  {
    "EMPLOYEE_ID": 105,
    "FIRST_NAME": "David",
    "LAST_NAME": "Williams",
    "EMAIL": "DWILLIAMS",
    "PHONE_NUMBER": "1.590.555.0105",
    "HIRE_DATE": "2015-06-25T00:00:00",
    "JOB_ID": "IT_PROG",
    "SALARY": 4800,
    "COMMISSION_PCT": null,
    "MANAGER_ID": 103,
    "DEPARTMENT_ID": 60
  },
  {
    "EMPLOYEE_ID": 106,
    "FIRST_NAME": "Valli",
    "LAST_NAME": "Jackson",
    "EMAIL": "VJACKSON",
    "PHONE_NUMBER": "1.590.555.0106",
    "HIRE_DATE": "2016-02-05T00:00:00",
    "JOB_ID": "IT_PROG",
    "SALARY": 4800,
    "COMMISSION_PCT": null,
    "MANAGER_ID": 103,
    "DEPARTMENT_ID": 60
  },
  {
    "EMPLOYEE_ID": 107,
    "FIRST_NAME": "Diana",
    "LAST_NAME": "Nguyen",
    "EMAIL": "DNGUYEN",
    "PHONE_NUMBER": "1.590.555.0107",
    "HIRE_DATE": "2017-02-07T00:00:00",
    "JOB_ID": "IT_PROG",
    "SALARY": 4200,
    "COMMISSION_PCT": null,
    "MANAGER_ID": 103,
    "DEPARTMENT_ID": 60
  }
]
以上で、サンプルのリモートMCPサーバーのsampleserverに、仮想プライベート・データベースによる保護を適用できました。

今回の記事は以上になります。