Azure AI Search の結果をトリミングするためのセキュリティ フィルター

Azure AI Search はドキュメント レベルのアクセス許可を提供しておらず、ユーザーのアクセス許可によって同じインデックス内からの検索結果を変えることはできません。 回避策として、グループまたはユーザー ID を含む文字列に基づいて検索結果をトリミングするフィルターを作成できます。

この記事では、次の手順を含むセキュリティ フィルター処理のパターンについて説明します。

  • 必要なコンテンツが含まれたソース ドキュメントを作成する
  • プリンシパル識別子のフィールドを作成する
  • インデックス作成のためにドキュメントを検索インデックスにプッシュする
  • search.in フィルター関数を使用してインデックスに対するクエリを実行する

セキュリティ フィルター パターンについて

Azure AI Search は、インデックス内のコンテンツにアクセスするためのセキュリティ サブシステムと統合されていませんが、ドキュメント レベルのセキュリティ要件を持つ多くのお客様は、フィルターによって自分のニーズを満たせることに気付いています。

Azure AI Search では、セキュリティ フィルターは、一致する値に基づいて検索結果を含めたり除外したりする通常の OData フィルターです。ただし、セキュリティ フィルターでは、条件はセキュリティ プリンシパルで構成される文字列です。 セキュリティ プリンシパルによる認証や認可はありません。 プリンシパルは単なる文字列であり、検索結果にドキュメントを含めたり除外したりするためにフィルター式で使用されます。

セキュリティ フィルター処理を実現するには、いくつかの方法があります。 その 1 つは、Id eq 'id1' or Id eq 'id2' など、等値式の複雑な論理和演算を使用する方法です。 この方法は誤りが発生しやすく、保守が困難です。また、一覧の値の数が数百、数千単位の場合、クエリの応答時間が何秒も遅くなります。

この記事で説明するように、推奨される解決策として、セキュリティ フィルターに search.in 関数を使用します。 等値式ではなく search.in(Id, 'id1, id2, ...') を使用すると、1 秒未満の応答時間を期待できます。

前提条件

  • グループまたはユーザー ID を含むフィールドは、filterable 属性を持つ文字列でなければなりません。 これは、コレクションである必要があります。 null を許容することはできません。

  • 同じドキュメント内の他のフィールドは、そのグループまたはユーザーがアクセスできるコンテンツを提供する必要があります。 以下の JSON ドキュメントでは、"security_id" フィールドにセキュリティ フィルターで使用される ID が含まれており、呼び出し元の ID がドキュメントの "security_id" と一致する場合、名前、給与、配偶者の有無が含まれます。

    {  
        "Employee-1": {  
            "id": "100-1000-10-1-10000-1",
            "name": "Abram",   
            "salary": 75000,   
            "married": true,
            "security_id": "10011"
        },
        "Employee-2": {  
            "id": "200-2000-20-2-20000-2",
            "name": "Adams",   
            "salary": 75000,   
            "married": true,
            "security_id": "20022"
        } 
    }  
    

    Note

    プリンシパル識別子を取得し、それらの文字列を Azure AI Search でインデックス付けできるソース ドキュメントに挿入するプロセスについては、この記事では説明しません。 識別子の取得に関するヘルプについては、ID サービス プロバイダーのドキュメントを参照してください。

セキュリティ フィールドを作成する

検索インデックスでは、フィールド コレクション内に、前の例の架空の "security_id" フィールドと同様の、グループまたはユーザー ID を含む 1 つのフィールドが必要です。

  1. セキュリティ フィールドを Collection(Edm.String) として追加します。 ユーザーが持っているアクセス権に基づいて検索結果がフィルター処理されるように、その filterable 属性が true に設定されていることを確認します。 たとえば、file_name が "secured_file_b" のドキュメントで group_ids フィールドを ["group_id1, group_id2"] に設定した場合、グループ ID "group_id1" または "group_id2" に属するユーザーのみが、このファイルに対して読み取りアクセス権を持ちます。

    検索要求の一部として返されないように、フィールドの retrievable 属性を false に設定します。

  2. インデックスにはドキュメント キーが必要です。 "file_id" フィールドがこの要件を満たしています。 インデックスには、検索可能なコンテンツも含まれている必要があります。 この例では、"file_name" と "file_description" のフィールドがそれを表しています。

    POST https://[search service].search.windows.net/indexes/securedfiles/docs/index?api-version=2023-11-01
    {
         "name": "securedfiles",  
         "fields": [
             {"name": "file_id", "type": "Edm.String", "key": true, "searchable": false },
             {"name": "file_name", "type": "Edm.String", "searchable": true },
             {"name": "file_description", "type": "Edm.String", "searchable": true },
             {"name": "group_ids", "type": "Collection(Edm.String)", "filterable": true, "retrievable": false }
         ]
     }
    

REST API を使用してインデックスにデータをプッシュする

インデックスの URL エンドポイントのドキュメント コレクションに HTTP POST 要求を送信します (「ドキュメント - インデックス」を参照)。 HTTP 要求の本文は、インデックスを作成するドキュメントの JSON レンダリングです。

POST https://[search service].search.windows.net/indexes/securedfiles/docs/index?api-version=2023-11-01

要求の本文に、ドキュメントの内容を指定します。

{
    "value": [
        {
            "@search.action": "upload",
            "file_id": "1",
            "file_name": "secured_file_a",
            "file_description": "File access is restricted to the Human Resources.",
            "group_ids": ["group_id1"]
        },
        {
            "@search.action": "upload",
            "file_id": "2",
            "file_name": "secured_file_b",
            "file_description": "File access is restricted to Human Resources and Recruiting.",
            "group_ids": ["group_id1", "group_id2"]
        },
        {
            "@search.action": "upload",
            "file_id": "3",
            "file_name": "secured_file_c",
            "file_description": "File access is restricted to Operations and Logistics.",
            "group_ids": ["group_id5", "group_id6"]
        }
    ]
}

グループの一覧を使用して既存のドキュメントを更新する必要がある場合は、merge または mergeOrUpload アクションを使用できます。

{
    "value": [
        {
            "@search.action": "mergeOrUpload",
            "file_id": "3",
            "group_ids": ["group_id7", "group_id8", "group_id9"]
        }
    ]
}

クエリでセキュリティ フィルターを適用する

group_ids のアクセス権に基づいてドキュメントをトリミングするには、group_ids/any(g:search.in(g, 'group_id1, group_id2,...')) フィルターを含む検索クエリを発行する必要があります (この "group_id1, group_id2,..." は、検索要求の発行元が属するグループです)。

このフィルターは、指定された識別子のいずれかが group_ids フィールドに含まれるすべてのドキュメントと一致します。 Azure AI Search を使用してドキュメントを検索する方法の詳細については、ドキュメントの検索に関するページを参照してください。

このサンプルでは、POST 要求を使用してクエリを設定する方法を示します。

HTTP POST 要求を発行します。

POST https://[service name].search.windows.net/indexes/securedfiles/docs/search?api-version=2020-06-30
Content-Type: application/json  
api-key: [admin or query key]

要求の本文でフィルターを指定します。

{
   "filter":"group_ids/any(g:search.in(g, 'group_id1, group_id2'))"  
}

group_ids に "group_id1" または "group_id2" が含まれるドキュメントが返されます。 つまり、要求の発行元が読み取りアクセス権を持っているドキュメントが取得されます。

{
 [
   {
    "@search.score":1.0,
     "file_id":"1",
     "file_name":"secured_file_a",
   },
   {
     "@search.score":1.0,
     "file_id":"2",
     "file_name":"secured_file_b"
   }
 ]
}

次のステップ

この記事では、ユーザー ID と search.in() 関数に基づいて結果をフィルター処理するためのパターンについて説明します。 この関数を使用して、各ターゲット ドキュメントに関連付けられたプリンシパルの識別子と一致する要求元のユーザーについて、プリンシパルの識別子を渡すことができます。 検索要求が処理されると、search.in 関数によって、ユーザーのプリンシパルが読み取りアクセス権を持っていない検索結果は除外されます。 プリンシパルの識別子は、セキュリティ グループ、ロール、さらにはユーザー自身の ID を表す場合があります。

Microsoft Entra ID に基づく代替パターン、または他のセキュリティ機能を再確認する方法については、次のリンクを参照してください。