Slackログイン情報をKibanaで可視化する

チームメンバーのSlackログイン情報（ユーザ名、IP等）を可視化する必要がある。Slack API からデータを取り出し、ELKのkibanaで可視化方法を紹介する

### 環境
- ELK 7
- Slack有償バージョン

### Slackから情報を取り出す
Slackからログイン情報についてどこまで出せるかわからなかったので調べてみた。[Slack APIページ](https://api.slack.com/methods/team.accessLogs)から以下の例を見つけた

```json
...
        {
            "user_id": "U12345",
            "username": "white_rabbit",
            "date_first": 1422922493,
            "date_last": 1422922493,
            "count": 1,
            "ip": "127.0.0.1",
            "user_agent": "SlackWeb Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4",
            "isp": "BigCo ISP",
            "country": "US",
            "region": "CA"
        }
...
```
みた通り、面白い情報が色々入った。IP情報以外、`user_agent`や接続する`ISP`情報も見える。ログにおいて重要な時間情報は`date_first`と`date_last`となる。これらのフィルドはどんな意味するかもうちょっと調べた結果、時刻 `date_first`(Unix epoch)から時刻 `date_last`の間に `count` イベントが発生したことがわかった。このAPIでは、詳細のアクションは記録されないが、ログイン数のみは記録されている。

既存の環境でAPIを叩いてみる結果は以下である。ここで`$TOKEN`はslackから発行されたAPI tokenで、`xoxp-xxxxxxxxx-xxxx`のフォーマットである。

``` shell
$ curl -s "https://slack.com/api/team.accessLogs?token=$TOKEN&pretty=1"
{
    "ok": true,
    "logins": [
        {
            "user_id": "XXXXXXXXX",
            "username": "user1",
            "date_first": 1583519339,
            "date_last": 1583519340,
            "count": 3,
            "ip": "x.x.x.x",
            "user_agent": "SlackWeb\/0 Mozilla\/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko\/20100101 Firefox\/73.0",
            "isp": null,
            "country": null,
            "region": null
        },
        {
            "user_id": "XXXXXXXXX",
            "username": "user2",
            "date_first": 1583518935,
            "date_last": 1583518935,
            "count": 3,
            "ip": "x.x.x.x",
            "user_agent": "SlackWeb\/0 Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit\/537.36 (KHTML, like Gecko) Slack\/4.3.3 Chrome\/78.0.3904.130 Electron\/7.1.11 Safari\/537.36 MacAppStore\/19.3.0 Sonic Slack_SSB\/4.3.3",
            "isp": null,
            "country": null,
            "region": null
        },
...
```
注意: このAPI使えるのは有償バージョンのSlackのみである。

このログ情報をELKにインジェストすれば、自由に可視化できる。しかし、ログ情報はどこまで遡って取れるかわからない。API仕様をよく確認すると、`count`と`page`のオプションもある。なお、`count`はデフォルトで`100`、最大で`1000`の値を設定でき、`page`は`デフォルトで`1`、最大`100`である。ということは最大、`100x1000=10万`のエントリーを出力（遡る）可能である。

実際、50人ぐらいのチームだと、5分間隔で1000エントリを取れば十分である。5分ごと、APIで直近の1000エントリーをとると、重複の可能性がある。APIの仕様によって、ログを取得期間を指定することができないことは悩ましい。但し、同じエントリーを`上書きすれば`、問題ない。解決として、同じepoch時刻で、複数ユーザがログインしないと前提し、`first_date`の値をELKドキュメントのindexにした。

### ELKでインジェストする
##### テンプレート作成
日付ごとに `slack-logins-2020-03-05`のようなindexを自動作成するのテンプレートを登録する。

``` shell
$ curl -s -XPUT https://example.com:9200/_template/slack-logins-template
{
  "index_patterns": ["slack-logins-*"],
  "settings" : {
        "number_of_shards" : 3,
        "number_of_replicas" : 2
    },
    "mappings" : {
      "properties": {
          "user_id" : { "type" : "keyword" },
          "username" : { "type" : "keyword" },
          "ip" : { "type" : "keyword" },
          "isp" : { "type" : "keyword" },
          "country" : { "type" : "keyword" },
          "region" : { "type" : "keyword" },
          "date_first" : { "type" : "date", "format": "epoch_second" },
          "date_last" : { "type" : "date", "format": "epoch_second" }
      }
    }
}
```
ここで、時間フィルド `date_first`、`date_last`を`{ "type" : "date", "format": "epoch_second" }`にするのはポイントである。（ELK7からドキュメントの`type`は指定しなくて良い）

##### 登録スクリプト

上記 Slack APIの出力結果はjsonフォーマットであるため、`jq`を使い、各ログインエントリを取り出す、indexする。登録スクリプトの例（update.sh）は以下である

``` shell:update.sh
#!/bin/sh
TOKEN=xoxp-xxxxx-xxxx

# 日付からindex名を決定
INDEX=$(date +%Y-%m-%d)

# APIの結果
TMP=data.json

# Slackログインデータ取得
curl -s -x $PROXY "https://slack.com/api/team.accessLogs?token=$TOKEN&count=1000" | jq ' .logins' > $TMP

# ログインエントリを取り出し、ELKへ登録する
LEN=$(cat $TMP | jq length)
for i  in $(seq 0 $(($LEN - 1)) ); do
    ID=$(cat $TMP | jq ".[$i] | .date_first")
    cat $TMP | jq ".[$i]" | curl -s -XPOST "https://example.com:9200/slack-logins-$INDEX/_doc/$ID" -H 'Content-Type: application/json' -d @-
done
```
尚、`-d @-` は`stdin`からjsonデータを入力できるcurlの便利なオプションである。ELK7からtypeを指定の代わりに、`_doc`を使う。


上記のスクリプトを crontabへ登録すれば、定期的にデータをELKへ登録できる（スクリプトの絶対パスは
`/home/robot/work/elk/slack-login/update.sh` とする）

``` cron
*/5 * * * *             /home/robot/work/elk/slack-login/update.sh > /dev/null 2>&1
```

### 可視化する
KibanaのDiscoverツールでindexを作成されることが確認できたら、`date_first`をドキュメントのtimestampとしてindex patternを登録すれば、従来の手順で、グラフやdashboardを作成する。

例えば、我々はこのようなdashboardを作成した。
<img width="1149" alt="スクリーンショット 2020-03-07 5.04.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310585/ee4e9ab9-f623-9e09-69b8-dfb129a61013.png">



### まとめ
Slack APIを用い、チームのログイン情報をELKで可視化するの例をまとめた。ログイン情報において、ユーザ名やIP情報などの他に接続ISPやagentの種類なども入っていた。

### 参考情報
- [Slack assess log API](https://api.slack.com/methods/team.accessLogs)
- [jqの便利な使い方](https://qiita.com/nardtree/items/0b65b792430bc0be9d02)
- [Elasticsearchのdocument typeについての面白い記事（英語)]( https://medium.com/@federicopanini/elasticsearch-6-0-removal-of-mapping-types-526a67ff772)