Elasticsearchのドキュメントごとのアクセス制御(Document Level Security)

BLOG

はじめに

これまで3回にわたって、インデックスごとのロールベースアクセス制御(RBAC) の設定方法について解説してきました。

今回は、ドキュメントレベルセキュリティ(Document Level Security: DLS) について説明します。

DLSを利用すると、以下のようなきめ細やかな権限設定が可能です。

  • ユーザーAはこのドキュメントを参照できる。
  • グループBはこのドキュメントを参照できる。
  • ユーザーCとグループDはこのドキュメントを参照できる。

対象者

  • Elasticsearch 中級者

前提条件

  • Elasticsearch Platinum License または Enterprise License (DLS機能は Basic License では利用できません。)
  • Elasticsearch v9.0.3 (他のバージョンでも動作する可能性はありますが、筆者は Enterprise License v9.0.3 で動作確認済みです。)
  • Docker 実行環境 (筆者は Rancher Desktop 1.19.3 で動作確認済みです。)
  • Python 3.13
  • streamlit 1.42.2
  • streamlit-authenticator 0.4.2

ドキュメントレベルセキュリティの概要

ドキュメントレベルセキュリティの概要を図に示します。

  1. まず、ドキュメントをインデックスに登録する際に、そのドキュメントを参照可能なユーザー名やグループ名も一緒に格納します。
  2. 次に、アクセスするユーザーのユーザー名とグループ名を記載した APIキー を発行します。
  3. 発行されたAPIキーを使用して検索を実行します。
  4. APIキーが保持するユーザー名、グループ名で読み取り可能なドキュメントのみがヒットし、結果として返却されます。

この図だけでは分かりにくいかもしれないため、実際にサンプルアプリを動かして動作を確認してみましょう。

サンプルアプリ

今回紹介するサンプルアプリは、GitHubリポジトリで公開しています。ソースコードの詳細はそちらを参照してください。

ソースコードの取得方法

  1. 右記のURLにアクセスします。 : https://github.com/sios-elastic-tech/blogs
  2. [<> Code] から Download ZIP を選択してダウンロードします。
  3. ダウンロードしたblogs-main.zipファイルを展開します。
  4. 2025-07-dlsフォルダに移動します。
cd 2025-07-dls

注記: 準備作業として必要なリクエストもこのフォルダに含まれています。

インデックスの作成

Document Level Security の動作確認を目的とした簡易的なインデックスのため、本文の形態素解析などは省略します。

以下のリクエストを Dev Tools の Console から発行してください。

PUT /dls_sample_202507
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 1
    },
    "analysis": {
      "filter": {
        "en-stop-words-filter": {
          "type": "stop",
          "stopwords": "_english_"
        }
      },
      "analyzer": {
        "iq_text_base": {
          "filter": [
            "cjk_width",
            "lowercase",
            "asciifolding",
            "en-stop-words-filter"
          ],
          "tokenizer": "standard"
        }
      }
    }
  }
}

インデックスへのマッピングの登録

必要最低限のフィールドのみを生成します

  • 本文用フィールド: content
  • アクセス制御用フィールド: _allow_access_control

以下のリクエストを Dev Tools の Console から発行してください。

PUT /dls_sample_202507/_mapping
{
  "properties": {
    "content": {
      "type": "text"
    },
    "_allow_access_control": {
      "type": "text",
      "index_options": "freqs",
      "analyzer": "iq_text_base",
      "fields": {
        "enum": {
          "type": "keyword",
          "ignore_above": 2048
        }
      }
    }
  }
}

ドキュメントの登録

検索対象となるドキュメントを登録します。その際、そのドキュメントを検索可能なユーザー名、グループ名も登録します。

読み取り権限は以下の表のようにしておきます。

ドキュメント読み取り能なユーザー名、グループ名
doc1: …user1@example.com または group1
doc2: …user2@example.com または group2
doc3: …user1@example.com
doc4: …user2@example.com
doc5: …group1
doc6: …group2
doc7: …全員

注記: doc7 には読み取り可能なユーザー、グループを明示していません。明示しない場合は、全員が読み取り可能とします。

以下のリクエストを Dev Tools の Console から発行してください。

POST /dls_sample_202507/_doc
{
  "content": "doc1: This document can be read by user1 or group1.",
  "_allow_access_control": [
    "user1@example.com",
    "group1"
  ]
}

POST /dls_sample_202507/_doc
{  "content": "doc2: This document can be read by user2 or group2.",
  "_allow_access_control": [
    "user2@example.com",
    "group2"
  ]
}

POST /dls_sample_202507/_doc
{
  "content": "doc3: This document can be read by user1.",
  "_allow_access_control": [
    "user1@example.com"
  ]
}

POST /dls_sample_202507/_doc
{
  "content": "doc4: This document can be read by user2.",
  "_allow_access_control": [
    "user2@example.com"
  ]
}

POST /dls_sample_202507/_doc
{
  "content": "doc5: This document can be read by group1.",
  "_allow_access_control": [
    "group1"
  ]
}

POST /dls_sample_202507/_doc
{
  "content": "doc6: This document can be read by group2.",
  "_allow_access_control": [
    "group2"
  ]
}

POST /dls_sample_202507/_doc
{
  "content": "doc7: This document can be read by everybody."
}

APIキーの発行

動作確認として、以下の4パターンのAPIキーを発行します。

APIキー名ユーザー、グループ有効期限
user1_api_key_20250702user1@example.com, group11日
user2_api_key_20250702user2@example.com, group21日
user3_api_key_20250702user3@example.com, group11日
user4_api_key_20250702user4@example.com, group21日


まず、user1用の以下のリクエストを Dev Tools の Console から発行してください。(*脚注1)1

POST /_security/api_key
{
  "name": "user1_api_key_20250702",
  "expiration": "1d",
  "role_descriptors": {
    "dls_sample_user1": {
      "cluster": ["monitor"],
      "index": [
        {
          "names": [
            "dls_sample_202507"
          ],
          "privileges": [
            "read"
          ],
          "query": {
            "template": {
              "params": {
                "access_control": [
                  "user1@example.com",
                  "group1"
                ]
              },
              "source": """
              {
                "bool": {
                  "should": [
                    {
                      "bool": {
                        "must_not": {
                          "exists": {
                            "field": "_allow_access_control"
                          }
                        }
                      }
                    },
                    {
                      "terms": {
                        "_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}
                      }
                    }
                  ]
                }
              }
              """
            }
          }
        }
      ]
    }
  }
}

※このリクエストを見ると、なんとなく想像がつくと思いますが、超端的に言えば、検索クエリーの発行時に _allow_access_control フィールドを使って強制的にフィルタをかけるような動きになります。

※_allow_access_control フィールドがないドキュメントは、全員読み取り可能とします。


成功すると、以下のようなレスポンスが返却されます。

{
  "id": "xxxxxxxxxxxxxxxxxx",
  "name": "user1_api_key_20250702",
  "expiration": xxxxxxxxxxxxxxx,
  "api_key": "xxxxxxxxxxxxxxxxxx",
  "encoded": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=="
}

このうち、encoded の値をコピーして、config.yaml の user1 の encoded_api_key に貼り付けます。

config.yaml:

elasticsearch:
  endpoint: url_of_elasticsearch_endpoint
cookie:
  expiry_days: 1
  key: some_signature_key
  name: some_cookie_name
credentials:
  usernames:
    user1:
      name: user1
      password: secret_password
      encoded_api_key: dummy==
    user2:
      name: user2
      password: secret_password
      encoded_api_key: dummy==
    user3:
      name: user3
      password: secret_password
      encoded_api_key: dummy==
    user4:
      name: user4
      password: secret_password
      encoded_api_key: dummy==


同様に、user2用のAPIキーを発行し、encoded の値を user2 の encoded_api_key の欄に貼り付けます。

POST /_security/api_key
{
  "name": "user2_api_key_20250702",
  "expiration": "1d",
  "role_descriptors": {
    "dls_sample_user2": {
      "cluster": ["monitor"],
      "index": [
        {
          "names": [
            "dls_sample_202507"
          ],
          "privileges": [
            "read"
          ],
          "query": {
            "template": {
              "params": {
                "access_control": [
                  "user2@example.com",
                  "group2"
                ]
              },
              "source": """
              {
                "bool": {
                  "should": [
                    {
                      "bool": {
                        "must_not": {
                          "exists": {
                            "field": "_allow_access_control"
                          }
                        }
                      }
                    },
                    {
                      "terms": {
                        "_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}
                      }
                    }
                  ]
                }
              }
              """
            }
          }
        }
      ]
    }
  }
}

同様にuser3用のAPIキーを発行し、user3 の encoded_api_key の欄に encoded の値を貼り付けます。

POST /_security/api_key
{
  "name": "user3_api_key_20250702",
  "expiration": "1d",
  "role_descriptors": {
    "dls_sample_user3": {
      "cluster": ["monitor"],
      "index": [
        {
          "names": [
            "dls_sample_202507"
          ],
          "privileges": [
            "read"
          ],
          "query": {
            "template": {
              "params": {
                "access_control": [
                  "user3@example.com",
                  "group1"
                ]
              },
              "source": """
              {
                "bool": {
                  "should": [
                    {
                      "bool": {
                        "must_not": {
                          "exists": {
                            "field": "_allow_access_control"
                          }
                        }
                      }
                    },
                    {
                      "terms": {
                        "_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}
                      }
                    }
                  ]
                }
              }
              """
            }
          }
        }
      ]
    }
  }
}

同様にuser4用のAPIキーを発行し、user4 の encoded_api_key の欄に encoded の値を貼り付けます。

POST /_security/api_key
{
  "name": "user4_api_key_20250702",
  "expiration": "1d",
  "role_descriptors": {
    "dls_sample_user4": {
      "cluster": ["monitor"],
      "index": [
        {
          "names": [
            "dls_sample_202507"
          ],
          "privileges": [
            "read"
          ],
          "query": {
            "template": {
              "params": {
                "access_control": [
                  "user4@example.com",
                  "group2"
                ]
              },
              "source": """
              {
                "bool": {
                  "should": [
                    {
                      "bool": {
                        "must_not": {
                          "exists": {
                            "field": "_allow_access_control"
                          }
                        }
                      }
                    },
                    {
                      "terms": {
                        "_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}
                      }
                    }
                  ]
                }
              }
              """
            }
          }
        }
      ]
    }
  }
}

※user1, user2, user3, user4 の Streamlit へのログインパスワードも config.yaml に記載してください。あくまでもサンプルアプリケーションですので、暗号化などは考慮していません。

ElasticsearchエンドポイントURLの取得

Elastic Kibana の Home 画面から Elasticsearch のエンドポイントURLを取得し、config.yamlファイルの endpoint に転記してください。

ビルド~コンテナとの接続

ビルド

docker-compose.yml があるディレクトリに移動します。

cd app

docker-compose.ymlがあるディレクトリで、以下のコマンドを実行します。

docker compose build

コンテナの起動

docker compose up -d

コンテナとの接続

docker exec -it dls_sample_202507 /bin/bash

(dls_sample_202507はコンテナ名です)

サンプルプログラムの実行

ログイン画面の表示

dls_sample_202507コンテナ上の bash から次のコマンドを実行します。

streamlit run src/app.py


Webブラウザから http://localhost:8501/ にアクセスしてください。

ユーザーごとの動作確認

user1での動作確認

まず、user1でログインします。パスワードは config.yaml に記載したものを入力してください。

ログインに成功すると、検索画面へ遷移します。

[Search]ボタンを押してください。

user1が参照可能なドキュメントのみが検索されます。

動作確認が終わったら、[Logout]ボタンを押してログアウトします。

user2での動作確認

同様に、user2でも動作確認します。

user2でログインします。パスワードは config.yaml に記載したものを入力してください。

ログインに成功すると、検索画面へ遷移します。

[Search]ボタンを押してください。

user2が参照可能なドキュメントのみが検索されます。

[Logout]ボタンを押してログアウトします。

user3での動作確認

同様に、user3でも動作確認します。

user3でログインします。パスワードはconfig.yamlに記載したものを入力してください。

ログインに成功すると、検索画面へ遷移します。

[Search]ボタンを押してください。

user3が参照可能なドキュメントのみが検索されます。

[Logout]ボタンを押してログアウトします。

user4での動作確認

最後に、user4で動作確認します。

user4でログインします。パスワードはconfig.yamlに記載したものを入力してください。

ログインに成功すると、検索画面へ遷移します。

[Search]ボタンを押してください。

user4が参照可能なドキュメントのみが検索されます。

[Logout]ボタンを押してログアウトします。

注記: 停止ボタンは用意していないため、停止させたい場合はCtrl+Cを押すなどの操作を行ってください。

関連情報

Connector における Document Level Security

Elasticsearchには、Connectorという機能があります。

Connectorを利用すると、Amazon S3 や SharePoint Online など外部に存在するファイルをElasticsearch に取り込んで検索できるようになります。

その際、いくつかの Connector では Document Level Security に対応しており、ユーザーの読み取り権限に応じた検索が可能です。

例)ServiceNowなど。

詳細は、Elasticsearch の Connector のドキュメントを参照してください。
https://www.elastic.co/docs/reference/search-connectors/

Field Level Security

Document Level Security 以外にも、Field Level Security の機構も用意されています。

これは、「このフィールドに対しては、このユーザーは参照可能、このユーザーは参照不可」といった制御が可能です。

詳細は、Elasticsearch の Field Level Security のドキュメントを参照してください。
https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#field-level-security

まとめ

Elasticsearch の Document Level Security 機能を使うと、ユーザーの読み取り権限に応じてドキュメントが検索されることを確認できました。

前回のインデックス単位の権限設定と組み合わせることで、情報漏洩を防ぎつつ、適切な権限を持つユーザーのみが検索できるように設定することが可能です。


  1. ここでは、分かりやすくするために、Dev Tools の Console からAPIキーの生成リクエストを発行する方法を採用しました。

    なお、https://www.elastic.co/docs/reference/search-connectors/es-dls-e2e-guide の例では、
    1) .search-acl-filter-source1および.search-acl-filter-source2インデックスに各ユーザーごとの権限情報をドキュメントとして格納しておく。
    2) その内容(権限情報)をGET /index-name/_doc/userId で取得する。
    3) 取得した内容(権限情報)を元に Create API Key のリクエストを実行して一時的なAPIキーを生成する。
    といった手順で APIキーを生成しています。
    ↩︎