Slackのアカウント棚卸について


![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51774/8c2b61f4-3fde-d5e0-b365-b767fa32a00c.png)※一人実装したSlack運用スクリプトを管理してるリポジトリのロゴです :relaxed:

## この記事は

[Slack Advent Calendar 2019](https://qiita.com/advent-calendar/2019/slack) 14日目の記事です


## TL;DR

* 情シスにおけるSlack運用知見の共有 【アカウント棚卸編】
* 通常メンバ/マルチチャンネルゲストについては、課金ステータスが「非アクティブ」かどうかを取得し、それがどの程度継続しているかをカウントしておく
* シングルチャンネルゲストについては、アクセスログからアクセス履歴がないことを確認する
* 一発でさくっとうまくいく方法は残念ながら見つけられなかった（こうやってるよ！という良い方法あれば是非教えてください :pray_tone2:

# 前置き

社内Slackの運用を担当している情シスのみなさま、日々の業務お疲れさまです。
社内にSlackは浸透していますか？うまくいってるよ！という企業さんもいれば、うーん、導入してみたものの、、なところもあるでしょう。
または、「導入したいけど、上にごちゃごちゃ言われて、、」な人もいるかもしれません。
そんな方へ、「Slackの運用は（工夫&APIを活用すれば）こういうことができるよ」というヒントになれば幸いです。

# Slack運用の話(棚卸編)

## 前提、Slackの前に

どんなツール、サービスでもそうですが、社内でシステム運用すれば避けて通れない事柄がいくつかあります。そんな事柄の一つが**棚卸**です。
アカウント申請がきたから作成したけど、この人もう使ってなさそうだな、とか、この人は退職したからアカウント無効化しなきゃな、とか、当たり前ですが大切にしないといけないことです。

これを疎かにすると、よくある問題としては

* **コスト**
アカウント数ベースで費用が発生するサービスの場合、使ってないユーザは適切に棚卸しないと、余計なコストを発生させてしまいます。「コストセンター」と言われがちの情シスは、無駄なコスト発生には特に気を付けておきたいですね。

* **セキュリティ**
先日も、[こんなニュース](https://www3.nhk.or.jp/lnews/chiba/20191125/1080009150.html)がありましたが、情シス担当者としては他人事ではないな、、と思った方もいらっしゃるのではないでしょうか。退職された人のアカウントは即時BANしましょう。**社外の人間が社内へのアクセス権限を持っている状態が放置されている運用**が行われていれば、容易に想像できる内容です。事件の内容はともかく、情シスとしてはこの問題を正しく**運用で**防がなければいけません。

## Slackの場合はどうしたらいいの？

実際のところSlackはよくできており、上記2点に関してはデフォルトの機能でかなりカバーできています。

* **コスト**
Slackの利用料は[フェアビリングポリシー](https://slack.com/intl/ja-jp/help/articles/218915077)に則ります。
これは「使ってなさそうな人の分の費用は請求しないよ、使ってたらまた請求にのっけるよ」ということで、上記した**情シスで気にしなければいけない使ってなさそうな人の分の費用管理**を、Slack側が勝手にやってくれるよ、という内容です。おかげで情シスは「無駄なコスト発生」をあまり気にせず利用することができます。やったね。
※ただし、 EnterpriseGrid は例外のようです。詳しくは営業担当へお問合せください。

* **セキュリティ**  
こちらも各種設定で強化を図ることが可能です。おそらく多くのワークスペースでは適用されているであろう（されてるよね？？？）**2FAの強制適用**、**セッション有効期限の設定**をはじめ、EGで使えるEMMやEKMなど、インシデントを防ぐための機構も充実しています。 
Plusプラン以上であればSAML/SSOが使えるため、既になんらかのIdPを運用していれば、そちらで連携することも可能でしょう。  
/feedback **ただしauditlog、おまえはほんとEG以外でも有料プランなら取れるようにしてくれ**

## じゃあ、Slackは別に棚卸気にしなくてもいいんじゃない？

残念ながら、そうはいきません（というかぼくたちはダメだねという結論になりました）。
上記の設定を万全にしても、結局不要になったタイミングでのBANや定期的なチェックをしないと、有効なまま放置されたアカウントが残る可能性があり、そこをインシデントの足掛かりとされることは想定できます。SAMLしていても、**ゲストアカウントをSlackのローカル認証で作成している**可能性もあります。
これは、管理者たる情シスの責任で実施しなければなりません。がんばろう。

---

# 棚卸の手法について

Slackでは、利用しているユーザ情報やアクセスログなどがWebAPI経由で取得できます。
古き良き情シスにおけるアカウント管理手法としては、エクセル台帳管理が定番ですが、ことSlackにおいてはAPIをうまく活用して、自動化までしちゃいましょう。 ~~というかぼくが偉い人に「こういうことができます」って実際にやってもないのに言っちゃったのでやるハメになった~~

上記までを検討し、「やはり定期棚卸は必要だな」と判断した上で、ではどのようなアプローチをするかを、以下のように考えました。
本記事では下記に示す運用を前提としています。

* IdPとのSAML/SSOは行っておらず、すべてのSlackアカウントはSlackローカルで認証されています。
* **アカウント/チャンネルの管理用アカウントが存在します**。このアカウントは棚卸や調査のために存在し、必ず**すべてのプライベートチャンネルに所属しています**。
* API実行は、上記の管理用アカウントが持つtokenを通じて行われます。

また、ここで取得する情報は、ほぼすべてがPlusプラン以上で利用可能な[コーポレートエクスポート](https://slack.com/intl/ja-jp/help/articles/201658943)でも実施できます。
APIを使った自動化までは難しいな、、という方は、こちらの機能を検討されてもよいかと思います。

## やるべきこと

* **アカウントの棚卸**
これは必須です。具体的には、「使ってないアカウント」の無効化と、「退職等で不要になったアカウント」の無効化です。
後者に関しては、定期棚卸ではなく、退職時の処理フローに混ぜてもらうことで対応しました。  
以下、**アカウントの定期棚卸**にフォーカスを当てて説明します。

* その他、**棚卸したほうがよさそうなもの**
本記事では、末尾に[オマケ](https://qiita.com/mollinaca/private/ac6a8ec228c08ee8721d#%E3%82%AA%E3%83%9E%E3%82%B1%E3%81%AE%E8%A9%B1)としました。

# アプローチ

## 通常メンバ/マルチチャンネルゲストの課金対象ユーザについて

**課金ステータスが非アクティブになっていること**を`使っていないことの目安`とします。
課金ステータスが非アクティブかどうかは、 [team.billableInfo](https://api.slack.com/methods/team.billableInfo) で取得できます。レスポンスの例。

```
{
    "ok": true,
    "billable_info": {
        "U0632EWRW": {
            "billing_active": false
        },
        "U02UCPE1R": {
            "billing_active": true
        },
        "U02UEBSD2": {
            "billing_active": true
        }
    }
}
```
ここで、 `"billing_active": false `となっているユーザID一覧を取得、保持しておき、
それを1日1回実行するごとに1ずつカウントアップしていき、一定数を超えたらアカウント解除対象とする、としました。

ただし、課金が発生しない**シングルチャンネルゲスト**は、`billing_active`は常に`false`となります。
そこで、追加でシングルチャンネルゲストに対しては少々めんどうですが以下の対応を取ることとしました。

## シングルチャンネルゲストについて

### (1) Slackのアクセスログを定期的に取得しておく

アクセスログの取得には、 [team.accessLogs](https://api.slack.com/methods/team.accessLogs) が使えます。
ただ、[team.accessLogs](https://api.slack.com/methods/team.accessLogs) は、現在の指定では取得したいタイミングの「期間指定」ができません。代わりに、件数をベースとしたページネーションで、「いつから（デフォルトはAPI実行時点）1ページあたり何件を何ページ取得するか」という指定ができます。ワークスペースへのアクセス件数に応じて、適切な値を設定しましょう。
ぼくたちは今回、1日1回1ページあたり1000件を10ページで合計1万件とれば24時間分はカバーできそうということがわかったので、暫定的にその値で取得しています。レスポンスの例。

```
{
    "ok": true,
    "logins": [
        {
            "user_id": "U45678",
            "username": "alice",
            "date_first": 1422922864,
            "date_last": 1422922864,
            "count": 1,
            "ip": "127.0.0.1",
            "user_agent": "SlackWeb Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.35 Safari/537.36",
            "isp": "BigCo ISP",
            "country": "US",
            "region": "CA"
        },
        {
            "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"
        }
    ],
    "paging": {
        "count": 100,
        "total": 2,
        "page": 1,
        "pages": 1
    }
}
```
`page`には指定できる上限があるので、現時点からとれる最大の件数が決まっています。
それより以前のデータを取得したい場合は、 `before` のオプションを指定する必要があります。

### (2) 各シングルチャンネルゲストのアクセス履歴をログから確認する

1日1回上記ログと照らし合わせて、24時間以内（あるいはアクセスログ1万件以内）にアクセスがあったかどうかを確認します。
`アクセスがなければ使ってない`と判断し、この連続判断数が一定数を超えたら解除対象としました。

シングルチャンネルゲストは [users.list](https://api.slack.com/methods/users.list) で取得したデータから、 `"is_ultra_restricted": true,` となっているユーザを一覧で取得することで手に入ります。レスポンスの例。

```
{
    "ok": true,
    "members": [
        {
            "id": "W012A3CDE",
            "team_id": "T012AB3C4",
            "name": "spengler",
            "deleted": false,
            "color": "9f69e7",
            "real_name": "spengler",
            "tz": "America/Los_Angeles",
            "tz_label": "Pacific Daylight Time",
            "tz_offset": -25200,
            "profile": {
                "avatar_hash": "ge3b51ca72de",
                "status_text": "Print is dead",
                "status_emoji": ":books:",
                "real_name": "Egon Spengler",
                "display_name": "spengler",
                "real_name_normalized": "Egon Spengler",
                "display_name_normalized": "spengler",
                "email": "spengler@ghostbusters.example.com",
                "image_24": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
                "team": "T012AB3C4"
            },
            "is_admin": true,
            "is_owner": false,
            "is_primary_owner": false,
            "is_restricted": true,
            "is_ultra_restricted": true, ★コレ！
            "is_bot": false,
            "updated": 1502138686,
            "is_app_user": false,
            "has_2fa": false
        },
        {
            "id": "W07QCRPA4",
            "team_id": "T0G9PQBBK",
            "name": "glinda",
            "deleted": false,
            "color": "9f69e7",
            "real_name": "Glinda Southgood",
            "tz": "America/Los_Angeles",
            "tz_label": "Pacific Daylight Time",
            "tz_offset": -25200,
            "profile": {
                "avatar_hash": "8fbdd10b41c6",
                "image_24": "https://a.slack-edge.com...png",
                "image_original": "https://a.slack-edge.com...png",
                "first_name": "Glinda",
                "last_name": "Southgood",
                "title": "Glinda the Good",
                "phone": "",
                "skype": "",
                "real_name": "Glinda Southgood",
                "real_name_normalized": "Glinda Southgood",
                "display_name": "Glinda the Fairly Good",
                "display_name_normalized": "Glinda the Fairly Good",
                "email": "glenda@south.oz.coven"
            },
            "is_admin": true,
            "is_owner": false,
            "is_primary_owner": false,
            "is_restricted": false,
            "is_ultra_restricted": false,
            "is_bot": false,
            "updated": 1480527098,
            "has_2fa": false
        }
    ],
    "cache_ts": 1498777272,
    "response_metadata": {
        "next_cursor": "dXNlcjpVMEc5V0ZYTlo="
    }
}
```

/feedback **is_singleguest: true/false, is_multiguest: true/false みたいな表記にしてほしぃ**

上記2パターンの手法を組み合わせて、すべてのユーザに対して「使ってなさそうなアカウントを定時バッチ処理で検出し、あれば管理者チャンネルへ通知する、という運用にしました。

## 今回採用しなかった方法

* アナリティクスから確認できる**アクティブ日数**の項目  
この項目は「パブリックチャンネルを閲覧すること」がアクティブと判断される条件のようです。
例えば、プライベートチャンネルにしか所属しないアカウント（あんまりないと思いますが。。）に対しては適切にカウントができないと判断し採用しませんでした。

![analytics.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51774/36849a9a-862b-1174-0a17-bd244eae78f5.png)
※ぼくの私物Slackより


* [users.getPresence](https://api.slack.com/methods/users.getPresence) での `last_activity` の取得  
このAPIを使えば、各ユーザの在籍状況を確認できそうでしたが、なぜかこのAPI、自分の情報はとれるのに、自分以外はとれない仕様。。

レスポンス例（自分）

```
{
    "ok": true,
    "presence": "active",
    "online": true,
    "auto_away": false,
    "manual_away": false,
    "connection_count": 1,
    "last_activity": 1419027078
}
```
この `last_activity` 参照すれば、最後にSlack上でアクティブだったタイミングわかるかな～と思ったものの、自分以外を指定して実行すると、

レスポンス例（自分以外）

```
{
    "ok": true,
    "presence": "active"
}
```
となり、プレゼンス情報しかとれないという。。

/feedback （管理者権限必須でよいので） `admin.getPresence` みたいなAPI用意して、自分以外の `last_activity` も取れるようにしてほしい

# オマケの話

この記事では割愛しますが、アカウント以外にも以下のようなものを定期的にチェックし、問題がないかどうかを確認するようにしました。

### チャンネルの利用状況

使われていないチャンネルはアーカイブしましょう。
[conversations.history](https://api.slack.com/methods/conversations.history) で、最新書き込みのタイムスタンプ `ts` を確認します。

### マルチチャンネルゲストのチャンネル所属数

1チャンネルしか所属していないマルチチャンネルゲストは、シングルに格下げしましょう。余計なコストになります。
※この内容は、去年のぼくの記事でも触れました。 → [Slackを導入していて気付いたいくつかの小さなこと（2018冬）](https://qiita.com/mollinaca/items/1d1ca70d9972e1714c5f)
マルチゲストの一覧は [users.list](https://api.slack.com/methods/users.list) にて、ユーザごとのチャンネル所属数は [users.conversations](https://api.slack.com/methods/users.conversations) にて取得できます。

### パブリックでアップロードされたファイル

特にユーザ数が多いSlackであれば、実はパブリックであることがマズいファイルがしれっとパブリックで上がってしまっているケースがあると思います。[files.list](https://api.slack.com/methods/files.list) を使いファイルの一覧を取得し、ファイル名だけでいいので目視確認しましょう。

# まとめ

情シス運用者観点から、「コスト」と「セキュリティ」を守ってサービス運用を行うために必要そうな事項、検討した事項についてまとめてみました。
Slackはこういった開発が得意な利用者ベースで広がっている風潮が強く、「情シスでちゃんと運用して」とエライ人に言われたときに、実はあまりちゃんと検討されたことが少ない、かもしれません。もちろんちゃんとされてるところはちゃんとされていると思いますが。

もし、「現場が使いたいっていってるけど、どうやったらうまく運用できるかようわからん:disappointed_relieved:!
」という方がいましたら、参考になれば幸いです。


情シスの運用担当のみなさま、よいSlackライフを！![Slack_Mark_Web.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51774/220da25e-5ade-b89b-7e19-0e91ef09748b.png)