🦔

Azure AI Search の ADLS Gen2 ACL の利用

に公開

はじめに

2025 年の Build で発表された Azure AI Search の機能で、ドキュメントレベルのアクセス制御に、ADLS Gen2 の ACL/RBAC を利用できるというプレビュー機能があります。従来 Azure AI Search で、ドキュメントレベルのアクセス制御を実装するためには、セキュリティ フィルターを使用する必要があり、かなりの手間が必要でした。この機能を使用すれば、ADLS Gen2 側で設定したアクセス許可を基に実装ができるので、こうしたコントロールをしたいユーザにとって注目の機能でした。

Announcing enterprise-grade, Microsoft Entra-based document-level security in Azure AI Search

ということで、下記のドキュメントを基に動作検証をしていたのですが、発表直後は ACL の取り込みが行われず、うまく動作しませんでした......。

ADLS Gen2 インデクサーを使用してアクセス許可メタデータを取り込み、ユーザーアクセス権に基づいて検索結果をフィルター処理する

最近になって、ようやく取り込みが正常に行われるようになったので、動作の検証をしてみました。結果としては、期待通りに動作したわけですが、検証した内容についてここでまとめてみたいと思います。

検証シナリオ

基本的には下記の技術情報にあるシナリオを基に、公園の全体的な管理者と、特定の州の管理者で、アクセスできるドキュメントの範囲を変えて、制御がかかるか検証するというシナリオです。

チュートリアル: ADLS Gen2 のインデックスアクセス許可メタデータと、アクセス許可でフィルター処理された結果を使用したクエリ

登場人物は、以下の二人。

testuser:公園の全体的な管理者で、すべての情報にアクセス可能
testuser2:オレゴン州の管理者で、オレゴン州の情報のみアクセス可能

実際の運用を想定して、Entra ID に以下の 2 つのグループを作成して、そこに上記のユーザを登録しています。権限の付与については、この 2 つのグループに与えていく感じでの作業となります。

ParkAdministrator:全部の情報にアクセス可能
OregonParkManager:Oregon のみアクセス可能

ADLS Gen2 側のディレクトリ構成は、以下のように構成します。ParkAdministrator はすべてのディレクトリとファイルにアクセス許可を持ち、OregonParkManager は、parks ディレクトリ、oregon ディレクトリ、oregon_state_parks.csv ファイルへのアクセス許可を持つようにします。

/parks/oregon/oregon_state_parks.csv
/washington/washington_state_parks.csv

各 ファイルには、10 個ずつの公園に関する情報が含まれており、合計 20 個のドキュメントに対してインデックスが作成されます。

なお、検証としては Preview の RESTAPI を叩く必要があるので、下記のチュートリアルの方法(VS Code+REST クライアント拡張機能)を参考にして実行しています。

クイック スタート: フルテキスト検索

ADLS Gen2 の準備

それでは、まず ADLS Gen2 に、ディレクトリを作成して、2 つのグループに対してアクセス許可を与えていきます。まずは、階層型名前空間を有効にしたストレージ アカウントを作成して、ルートである parks コンテナを作成します。作成したコンテナの右側の省略記号ボタン([...])を押して、「ACL の管理」を選択します。

まずは、park コンテナに対して、ParkAdministrator、OregonParkManager の 2 つのグループにアクセス許可を与えます。ここでのポイントは、「読み取り」だけでなく「実行」も付与することです。

この後、作成されるディレクトリに ParkAdministrator の権限を自動で付与するために、「既定のアクセス許可」を選択して、「既定のアクセス許可の構成」を有効にします。ParkAdministrator グループに対して、アクセス許可を付与します。

保存が終わったら、parks コンテナの下に、oregon と washington のディレクトリを作成します。oregon ディレクトリには、既定に追加してる ParkAdministrator グループへの権限が付与されていることを確認します。

oregon ディレクトリには、OregonParkManager グループもアクセスする必要があるので、アクセス許可を追加します。

ついでに、このディレクトリ以下のファイルに既定でアクセス許可を付与したいので、既定のアクセス許可にも追加します。

それぞれのディレクトリにファイルをアップロードし、アクセス許可を確認します。oregon_state_parks.csv には、2 つのグループがアクセスできるようになっていることが確認できます。

これで、準備が整ったのですが、ACL が機能していることを確認するため、ストレージ アカウントのアクセス制御で、2 つのグループに対して閲覧者の権限を与えて、ポータルからテストを実施します。

OregonParkManager グループ に所属する testuser2 でポータルにログインし、oregon_state_parks.csv にアクセスできることを確認します。

一方、washington ディレクトリは権限がないため、エラーが発生してファイルが確認できません。期待した通りに、動作していることが確認できました。

AI Search の準備

AI Search のリソースをデプロイし、前提条件のために設定を変更します。

まずは、ロールベースのアクセス制御を有効にします。ここでは、「両方」を選択します。

続いて、マネージド ID を構成を構成します。ここでは、システム マネージド ID を作成して検証を行います。

作成したシステム マネージド ID で、ADLS Gen2 にアクセスするため、ストレージアカウントの「アクセス制御(IAM)」で、「ストレージ BLOB データ閲覧者」の権限を付与します。

インデクサーを実行するために、作業するアカウントに以下の 3 つの権限を付与します。

  • Search Service 共同作成者
  • 検索インデックス データ共同作成者
  • 検索インデックス データ閲覧者

以下のような感じで、3 つの権限を付与します。

最後に、後ほどテストで使用するため、検索インデックス データ閲覧者の権限を、2 つのグループに付与します。

インデックスの作成

リソースの準備ができたところで、インデックスを作成していきましょう。REST API を実行する準備として、Cloud Shell を利用してアクセストークンを取得します。

az account get-access-token --scope https://search.azure.com/.default

作成する http ファイルの最初に、以下の変数を定義します。AI Search の URL と上記で取得したトークン(accessToken)を、それぞれ置き換えて、それ以降で利用できるように定義します。

@endpoint=<AI Search の URL に置き換え>
@index=acl-sample
@search-token=<トークンに置き換え>
@api-version=2025-05-01-preview

まずは、インデックスを作成します。インデックスを作成する際に、permissionFilter を持つ 3 つのカラムを定義しています。ここに、必要なパーミッションの情報を格納します。

下記のコマンドを張り付け、「Send Request」を押下して、実行します。うまく動作すれば「HTTP/1.1 201 Created」が返されます。

PUT {{endpoint}}/indexes/{{index}}_index?api-version={{api-version}}
Authorization: Bearer {{search-token}}
Content-Type: application/json

{
  "name" : "{{index}}_index",
  "fields": [
    {
      "name": "id", "type": "Edm.String",
      "searchable": true, "filterable": false, "retrievable": true,
      "stored": true,
      "key": true
    },
    {
      "name": "name", "type": "Edm.String",
      "searchable": true, "filterable": false, "retrievable": true
    },
    {
      "name": "description", "type": "Edm.String",
      "searchable": true, "filterable": false, "retrievable": true    
    },
    {
      "name": "location", "type": "Edm.String",
      "searchable": true, "filterable": false, "retrievable": true
    },
    {
      "name": "state", "type": "Edm.String",
      "searchable": true, "filterable": false, "retrievable": true
    },
    {
      "name": "metadata_storage_path", "type": "Edm.String",
      "searchable": true, "filterable": false, "retrievable": true
    },
    { 
      "name": "UserIds", "type": "Collection(Edm.String)", 
      "permissionFilter": "userIds", 
      "searchable": true, "filterable": true , "retrievable": true
    },
    { 
      "name": "GroupIds", "type": "Collection(Edm.String)", 
      "permissionFilter": "groupIds", 
      "searchable": true, "filterable": true , "retrievable": true
    },
    { 
      "name": "RbacScope", "type": "Edm.String", 
      "permissionFilter": "rbacScope", 
      "searchable": true, "filterable": true , "retrievable": true
    }
  ],
  "permissionFilterOption": "enabled"
}

続いて、データソースを作成します。データソースの作成では、indexerPermissionOptions を指定して、パーミッションに関する情報を含めるように指示します。

下記コマンドのサブスクリプション ID、リソースグループ名、ストレージアカウント名を置き換えて実行します。


PUT {{endpoint}}/datasources('{{index}}ds')?api-version={{api-version}}
Authorization: Bearer {{search-token}}
Content-Type: application/json

{
    "name" : "{{index}}ds",
    "type": "adlsgen2",
    "indexerPermissionOptions": ["userIds", "groupIds", "rbacScope"],
    "credentials": {
    "connectionString": "ResourceId=/subscriptions/<サブスクリプション ID>/resourceGroups/<リソースグループ名>/providers/Microsoft.Storage/storageAccounts/<ストレージアカウント名>/;"
    },
    "container": {
    "name": "parks",
    "query": null
    }
}

最後に、インデクサーを作成します。インデクサーの作成では、fieldMappings を使用して、メタデータをどこのフィールドに格納するかを指示します。

下記のコマンドを実行し、「HTTP/1.1 201 Created」が返ることを確認します。この実行がエラーになる場合、データソースの情報に間違いがないか確認してみて下さい。

PUT {{endpoint}}/indexers('{{index}}indexer')?api-version={{api-version}}
Authorization: Bearer {{search-token}}
Content-Type: application/json

{
  "name" : "{{index}}indexer",
  "dataSourceName" : "{{index}}ds",
  "targetIndexName" : "{{index}}_index",
  "parameters": {
    "batchSize": null,
    "maxFailedItems": 0,
    "maxFailedItemsPerBatch": 0,
    "configuration": {
      "dataToExtract": "contentAndMetadata",
      "parsingMode": "delimitedText",
      "firstLineContainsHeaders": true,
      "delimitedTextDelimiter": ",",
      "delimitedTextHeaders": ""
      }
  },
  "fieldMappings": [
    { "sourceFieldName": "metadata_user_ids", "targetFieldName": "UserIds" },
    { "sourceFieldName": "metadata_group_ids", "targetFieldName": "GroupIds" },
    { "sourceFieldName": "metadata_rbac_scope", "targetFieldName": "RbacScope" }
    ]
}    

Azure ポータルから、AI Search のインデクサーを確認します。20 件のドキュメントが作成されていれば、期待通りに動作しています。

インデックスで作成したインデックスを表示し、条件を指定せずに「検索」を実行します。実行結果を確認すると、"state" が "OR" のレコードには、2 つのグループ ID が割り当てられており、"WA" には 1 つしか割り当てられていないことが確認できます。検索が実行される際に、この情報を基に参照の可否を判断してくれる動作になります。

動作確認

それでは最後に動作確認をしてみましょう。ACL によるアクセス制御も、Preview の API を使用する必要があるので、同様にテスト用のファイルを作成して実行します。

まずは、testuser でアクセストークンを取得して、下記のコマンドを実行します。

POST  {{endpoint}}/indexes/{{index}}_index/docs/search?api-version={{api-version}}
Authorization: Bearer {{search-token}}
x-ms-query-source-authorization: {{search-token}}
Content-Type: application/json

{
    "search": "*",
    "count": true,
    "top": 0
}

結果として、20 件のデータが参照できていることをが確認できます。

{
"@odata.context": "https://tttestaisearch.search.windows.net/indexes('acl-sample_index')/$metadata#docs(*)",
"@odata.count": 20,
"value": []
}

次に下記コマンドを実行し、実際のドキュメントを取得します。"state" が "OR" と "WA" のドキュメントが取得できていることが確認できます。

POST  {{endpoint}}/indexes/{{index}}_index/docs/search?api-version={{api-version}}
Authorization: Bearer {{search-token}}
x-ms-query-source-authorization: {{search-token}}
Content-Type: application/json

{
    "search": "*",
    "select": "state,name,description,location,GroupIds",
    "orderby": "name asc"
}

今度は、testuser2 で同じコマンドを実行します。こちらは、オレゴン州のレコードのみが確認できるのが期待された動作です。

期待通り、1 つ目の実行では、10 件しか参照できていないことが確認できます。

{
"@odata.context": "https://tttestaisearch.search.windows.net/indexes('acl-sample_index')/$metadata#docs(*)",
"@odata.count": 10,
"value": []
}

2 つ目の実行も、"state" が "OR" のドキュメントのみが返されていることが確認できます。

この結果から、期待通りに ADLS Gen2 の ACL に従ったドキュメント検索が実現できていることが確認できました!

最後に

ということで、一旦、Preview 機能の動作確認ができました。後から追加したアクセス許可をどう反映させるのか等、実際の運用で必要な操作がいろいろあるかと思いますが、まずは検証できる環境ができたことを喜びたいと思います。

いや、マジで 5 月下旬から検証してて、設定は正しいのにエラーで動作しなくて、苦しんでたんで.....。

Microsoft (有志)

Discussion