API Keyを使ったElasticsearchへのアクセス

# はじめに

まいどおなｊ（ｒｙ

# API Keyとは

Elasticsearch 6.7以降に追加されるAPI Key Serviceを使うと、BASIC認証の代わりにAPI Keyを使ってElasticsearchにアクセスできるようになります。
ドキュメントは、[このへん](https://www.elastic.co/guide/en/elasticsearch/reference/6.7/security-api.html#security-api-keys)です。

# 使い方
さっそく使ってみます。

## 準備
API Keyを使うには、Securityを有効にした状態のElasticsearchで、さらにTLS/SSLを有効にするか、`xpack.security.authc.api_key.enabled`をtrueにします。
細かいことは、[ここ](https://www.elastic.co/guide/en/elasticsearch/reference/6.7/security-api-create-api-key.html#_description_87)に書いてあります。

## API Keyの作成（その１）
準備ができたところで、API Keyを作成します。
Keyの作成は、以下のようなリクエストを送ります。
このリクエストでは、clusterに対するすべての権限を持ち、`test_idx*`というIndexに対して`read`権限を持つAPI Keyを作成しています。

```json
POST /_security/api_key
{
   "name": "api-key-1",
   "role_descriptors": {
     "role-a": {
       "cluster": ["all"],
       "index": [
         {
           "names": ["test_idx*"],
           "privileges": ["read"]
         }
       ]
     }
   }
}
```

すると、レスポンスとして`id`や`api key`が返ってきます。

```json
{
   "id" : "HW2CxWgBpzHQiGdVVGQo",
   "name" : "api-key-1",
   "api_key" : "AFBTRIOlS3eF1whp9qvnIw"
}
```

## API Keyの確認
確認してみます。

```json
GET _security/api_key?name=api-key-1
```

```json
{
   "api_keys" : [
     {
       "id" : "HW2CxWgBpzHQiGdVVGQo",
       "name" : "api-key-1",
       "creation" : 1549501879290,
       "invalidated" : false,
       "username" : "elastic",
       "realm" : "reserved"
     }
   ]
}
```

今回作成したAPI Keyは有効期限を指定していないので`expiration`の項目はありません。
よくよく見ると、API Key自体は返ってきません。なので、作成時のレスポンスに記載されたAPI Keyは忘れないように、どっかに保存しておきましょう。

## API Keyの作成（その２）

え？さっき作成したでしょ？
いえ。実際に使うには、「&lt;id&gt;:&lt;api key&gt;」をbase64でエンコードしたものを使います。
その辺の話は、[ここ](https://www.elastic.co/guide/en/elasticsearch/reference/6.7/security-api-create-api-key.html#_examples_102)の下の方に書いてあります。
というわけで早速API Keyを作ります。

```sh
# echo -n HW2CxWgBpzHQiGdVVGQo:AFBTRIOlS3eF1whp9qvnIw | base64
SFcyQ3hXZ0JwekhRaUdkVlZHUW86QUZCVFJJT2xTM2VGMXdocDlxdm5Jdw==
```

できましたね。この「SFcyQ3hXZ0JwekhRaUdkVlZHUW86QUZCVFJJT2xTM2VGMXdocDlxdm5Jdw==」も重要なのでどっかに保存しておきます。

## 検索してみる

では、作成したAPI Keyを使って検索してみます。

```sh
# curl -H 'Authorization: ApiKey SFcyQ3hXZ0JwekhRaUdkVlZHUW86QUZCVFJJT2xTM2VGMXdocDlxdm5Jdw==' -H 'Content-type: application/json' 'http://localhost:9200/test_idx1/_search?pretty' -d'{"size": 0}'
```

Securityが有効なので、通常であれば`-u`でユーザとパスワードを指定するところですが、上のリクエストでは書いてませんね。

でも、ちゃんと結果は返ってきます。

```json
{
   "took" : 1,
   "timed_out" : false,
   "_shards" : {
     "total" : 5,
     "successful" : 5,
     "skipped" : 0,
     "failed" : 0
   },
   "hits" : {
     "total" : 1,
     "max_score" : 0.0,
     "hits" : [ ]
   }
}

```

curlだと長ったらしいので、[HTTPie](https://httpie.org/)を使ってみます（若干癖がありますが）。

```sh
# http localhost:9200/test_idx1/_search Content-type:"application/json" Authorization:"ApiKey SFcyQ3hXZ0JwekhRaUdkVlZHUW86QUZCVFJJT2xTM2VGMXdocDlxdm5Jdw=="
```

ちゃんと結果が返ってきます。

```html
HTTP/1.1 200 OK
content-encoding: gzip
content-length: 202
content-type: application/json; charset=UTF-8

{
     "_shards": {
         "failed": 0,
         "skipped": 0,
         "successful": 5,
         "total": 5
     },
     "hits": {
         "hits": [
             {
                 "_id": "OVu3JmgBLVkCxyA_LNSK",
                 "_index": "test_idx1",
                 "_score": 1.0,
                 "_source": {
                     "foo": "bar"
                 },
                 "_type": "docs"
             }
         ],
         "max_score": 1.0,
         "total": 1
     },
     "timed_out": false,
     "took": 1
}
```

権限のないIndexを検索してみます。

```sh
# curl -H 'Authorization: ApiKey SFcyQ3hXZ0JwekhRaUdkVlZHUW86QUZCVFJJT2xTM2VGMXdocDlxdm5Jdw==' -H 'Content-type: application/json' 'http://localhost:9200/metricbeat-*/_search?pretty' -d'{"size": 0}'
```

検索結果がありませんね。ただ、エラーが出るわけじゃないですね。
ちょっとわかりにくいかも。

```json
{
   "took" : 0,
   "timed_out" : false,
   "_shards" : {
     "total" : 0,
     "successful" : 0,
     "skipped" : 0,
     "failed" : 0
   },
   "hits" : {
     "total" : 0,
     "max_score" : 0.0,
     "hits" : [ ]
   }
}
```

## API Keyの無効化

では、API Keyを無効化してみます。

```sh
# curl -XDELETE http://localhost:9200/_security/api_key -d'{"name": "api-key-1"}' -H 'Content-type: application/json' -u elastic:<elasticユーザのパスワード>
```

```json
{"invalidated_api_keys":["HW2CxWgBpzHQiGdVVGQo"],"previously_invalidated_api_keys":[],"error_count":0}
```

無事に無効化できました。
それでは、再度API Keyを使って検索してみます。

```sh
# http localhost:9200/test_idx1/_search Content-type:"application/json" Authorization:"ApiKey SFcyQ3hXZ0JwekhRaUdkVlZHUW86QUZCVFJJT2xTM2VGMXdocDlxdm5Jdw=="
```

ちゃんと無効化されていることがわかります。

```html
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="security" charset="UTF-8"
content-encoding: gzip
content-length: 177
content-type: application/json; charset=UTF-8

{
     "error": {
         "header": {
             "WWW-Authenticate": "Basic realm=\"security\" charset=\"UTF-8\""
         },
         "reason": "api key has been invalidated",
         "root_cause": [
             {
                 "header": {
                     "WWW-Authenticate": "Basic realm=\"security\" charset=\"UTF-8\""
                 },
                 "reason": "api key has been invalidated",
                 "type": "security_exception"
             }
         ],
         "type": "security_exception"
     },
     "status": 401
}
```

# おわりに

若干動きが不思議ちゃん（KibanaのConsoleから無効化ができない）とかありますが、色々と遊べそうな機能ですね。