Shieldを使用してユーザーの権限に応じた検索結果を取得する

Elasticsearchで検索サービスを開発しているお客様から、検索を行うユーザーの権限に応じて検索結果を変えたいという要件を、しばしば伺います。例えば、企業の財務に関するドキュメントは、経理部門のスタッフ以外には、検索結果として表示したくない。顧客情報が含まれる文書は、営業部門やコールセンター部門にアクセスを限定したい、といったケースです。

本プログでは、ユーザーが閲覧を許可されたドキュメントのみ、検索結果として表示させる方法をご紹介します。Elasticのサブスクリプションに含まれるプラグインShieldを使用します。

対象とするElasticsearchのバージョンは2.3.xです。

Shieldのインストール

ElasticsearchやKibanaを起動している場合には、一旦停止します。ターミナルなどでElasticsearchをインストールしたディレクトリに移動し、Shieldをインストールします。

$ bin/plugin install license
$ bin/plugin install shield

すでに他のプラグインなどを使用していて、licenseプラグインがインストール済みである場合には、Shieldのインストールのみ、行ってください。

Shieldの設定

ユーザー、ロールなどの追加は、ElasticsearchのAPIを使用して行うことができますが、APIを実行するユーザーは事前に認証されている必要があります。esusersコマンドで管理者ユーザーを追加します。

$ bin/shield/esusers useradd admin -r admin -p [password]

[password]には、パスワードとしてふさわしい任意の文字列を指定してください。
Elasticsearchを起動します。

$ bin/elasticsearch

まず、Shieldの認証が正しく機能していることを確認します。curlコマンドを使用して、ユーザー、パスワードを指定せずにアクセスを試みます。HTTP/1.1 401 Unauthorizeが、メッセージとともに返されます。

$ curl --include localhost:9200
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="shield"
Content-Type: application/json; charset=UTF-8
Content-Length: 329
{"error":{"root_cause":[{"type":"security_exception","reason":"missing authentication token for REST request [/]","header":{"WWW-Authenticate":"Basic realm=\"shield\""}}],"type":"security_exception","reason":"missing authentication token for REST request [/]","header":{"WWW-Authenticate":"Basic realm=\"shield\""}},"status":401}%

次に、先ほど作成したユーザーadminでアクセスを試みます。

$ curl localhost:9200 --include --user admin:[password]
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 305
{
  "name" : "Sushi",
  "cluster_name" : "v233",
  "version" : {
    "number" : "2.3.3",
    "build_hash" : "218bdf10790eef486ff2c41a3df5cfa32dadcfde",
    "build_timestamp" : "2016-05-17T15:40:04Z",
    "build_snapshot" : false,
    "lucene_version" : "5.5.0"
  },
  "tagline" : "You Know, for Search"
}

HTTP/1.1 200 OKが返されます。

ロールとユーザーの作成

managersusersのふたつのロールがある組織を想定します。managersロールを割り当てるメンバーとしてjohn_doeusersロールを割り当てるメンバーとしてfred_bloggsがいます。managersロールを割り当てられたメンバーには、managersのみが閲覧可能なドキュメントと、usersが閲覧可能なドキュメントの両方を検索結果として表示します。usersのメンバーには、明示的にusersメンバーの閲覧が許されたドキュメントだけを検索結果として返します。

ロールの作成

managersロールを作成します。managersロールを割り当てられたユーザーは、すべてのindexのaccess_controlフィールドにmanagersもしくはusersが含まれるドキュメントのみ、閲覧することができます。

$ curl -XPOST localhost:9200/_shield/role/managers --user admin:[password] -d '
{
  "cluster": [
    "all"
  ],
  "indices": [
    {
      "names": [
        "sample"
      ],
      "privileges": [
        "read"
      ],
      "query": {
        "terms": {
          "access_control": ["managers", "users"]
        }
      }
    }
  ]
}'

同様に、usersロールを作成します。usersロールを割り当てられたユーザーは、すべてのindexのaccess_controlフィールドに、usersが含まれるドキュメントのみ、閲覧することができます。

curl -XPOST localhost:9200/_shield/role/users --user admin:[password] -d '
{
  "cluster": [
    "all"
  ],
  "indices": [
    {
      "names": [
        "sample"
      ],
      "privileges": [
        "read"
      ],
      "query": {
        "terms": {
          "access_control": ["users"]
        }
      }
    }
  ]
}'

ユーザーの作成

ユーザーjohn_doefred_bloggsを作成します。[password]には、パスワードにふさわしい任意の文字列を指定してください。

curl -XPOST localhost:9200/_shield/user/john_doe --user admin:[password] -d '
{
  "password" : "[password]",
  "roles" : [ "managers" ],
  "full_name" : "John Doe"
}'
curl -XPOST /_shield/user/fred_bloggs --user admin:[password] -d '
{
  "password" : "[password]",
  "roles" : [ "users" ],
  "full_name" : "Fred Bloggs"
}'

動作の確認

ドキュメントの作成

managerusers双方が閲覧可能なドキュメント、managersのみが閲覧可能なドキュメント、usersのみ明示的に閲覧が許可されたドキュメントをそれぞれ作成します。

curl -XPUT localhost:9200/sample/doc/1 --user admin:[password] -d '
{
  "access_control": ["managers", "users"],
  "content": "This document should be readable for managers and users"
}'
curl -XPUT localhost:9200/sample/doc/2 --user admin:[password] -d '
{
  "access_control": ["managers"],
  "content": "This document should be readable for managers only"
}'
curl -XPUT localhost:9200/sample/doc/3 --user admin:[password] -d '
{
  "access_control": ["users"],
  "content": "This document should be readable for managers and users"
}'

それぞれのユーザーが閲覧可能なドキュメントの確認

最初に、usersロールを割り当てられたfred_bloggsによる検索結果を取得してみます。

curl 'localhost:9200/sample/_search?pretty' --user fred_bloggs:[password]
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "sample",
      "_type" : "doc",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "access_control" : [ "managers", "users" ],
        "content" : "This document should be readable for managers and users"
      }
    }, {
      "_index" : "sample",
      "_type" : "doc",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "access_control" : [ "users" ],
        "content" : "This document should be readable for managers and users"
      }
    } ]
  }
}

access_controlusersが含まれるドキュメントのみ検索結果として現れることが確認できました。次にmanagersロールを割り当てられたjohn_doeで、同様に検索結果を確認します。

$ curl 'localhost:9200/sample/_search?pretty' --user john_doe:[password]
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "sample",
      "_type" : "doc",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "access_control" : [ "managers", "users" ],
        "content" : "This document should be readable for managers and users"
      }
    }, {
      "_index" : "sample",
      "_type" : "doc",
      "_id" : "2",
      "_score" : 1.0,
      "_source" : {
        "access_control" : [ "managers" ],
        "content" : "This document should be readable for managers only"
      }
    }, {
      "_index" : "sample",
      "_type" : "doc",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "access_control" : [ "users" ],
        "content" : "This document should be readable for managers and users"
      }
    } ]
  }
}

managersおよびusersaccess_controlに含まれるドキュメントが、検索結果に含まれることが確認できました。

ユーザー、ロール、およびドキュメントの追加

ユーザーを追加する場合には、そのユーザーに正しいロールを割り当てます。ロールを追加する際には、access_controlを使用してふさわしいクエリーが出来るよう検討します。合わせて、すでにインデックスされているドキュメントの扱いについても考慮します。Elasticsearchにドキュメントを追加する場合には、access_controlに閲覧を許可するロールを付加し、インデックスします。

Active Directory連携

Active Directory連携を行う場合には、Shield上でのロールの定義を不要にすることも可能です。Shieldの認証レルムの設定でunmapped_groups_as_roles: trueを指定します。本設定により、Shieldにロールが存在しない場合には、ユーザーが属するActive Directoryのセキュリティグループ名を、Shieldのロールとして採用します。ユーザーとロールの対応付けが不要になり、運用がより容易になることが期待されます。

shield:
  authc:
    realms:
      active_directory:
        type: active_directory
        order: 0
        domain_name: example.com
        url: ldap://ad.example.com:389
        unmapped_groups_as_roles: true

まとめ

ロールに基づいたアクセス制御は、Shieldのドキュメントに、より詳しい記載があります。また、Active Directory連係については、こちらのページが参考になります。

Shieldは、当社のサブスクリプションユーザーに提供しているプラグインのひとつです。本エントリにて紹介したアクセス制御はDocument Level Securityと呼ぶもので、Platinumサブスクリプションをご契約のお客様がご利用になれます。サブスクリプションをご契約頂きますと、Shieldの他、特定の条件に応じた際にアラートや通知を行うWatcher、既存のElasticsearchのインデックスを利用して、UIでのデータ探索を可能にするGraphなどもご利用いただけます。当社のElastic Stackの開発者にお問い合わせいただけるほか、お客様の開発工数を削減する、これらの便利なプラグインにもアクセス可能ですので、ぜひご利用ください。