NIKKEI TECHNOLOGY AND CAREER

Slack Botを使用したGitHubアカウント管理

はじめに

こんにちは。新卒一年目、APIチームの成田です。

今回はSaaSアカウント、特にGitHubアカウントの管理についてのお話です。 社内でのSaaSのアカウント管理は非常に重要であり、もしサボってしまうと、 辞めたはずの人が社内情報にアクセスできてしまう、というような事態にもなりかねません。(*1)

日経では社員や協力会社の方々の各SaaSアカウントをデータベースで管理しています。 下図のように、氏名、会社名、Emailアドレスを持った親アカウントをつくり、 その下に、Slack、nulab、kibela、GitHub等のアカウントを紐づけています。 このように紐づけておくと、退職時のアカウント削除処理の自動化なども可能になります。

アカウント構成

現在では、まず最初にSlack招待時に下図のようなフォームを利用させ、この情報から親アカウントを作っています。 各SaaSのアカウントは各APIで取得し、EmailアドレスをKeyとして紐づけます。

Slack招待BOT

しかし、この方法ではGitHubのアカウントを紐づけるのは困難です。 なぜなら、私用Emailアドレスを使用しているアカウントが多いうえ、 そもそもEmailアドレスがAPIで取得できない場合がほとんどだからです。 さらに、アカウント名から本名を連想できないケースが多く、管理者が人力で紐づけることすら難しくなっています。

そのため、詳しい人に「これ誰?」と聞く必要があるのですが、これを人の手でやるのは大変で、 溜まれば溜まるほど面倒なことになる(実際なった)ので、これを自動化しようというのが、今回のエントリーです。

こんなのつくりました

GitHubにアカウントが追加されると、リアルタイムで下図のようなメッセージが送信されます。 メンションされる人は招待を行ったユーザーです。

GitHubアカウント紐づけBOT

メンションされた人を含め任意の人が、追加されたユーザーのSlackアカウントを下図のように選択することで、 Slackアカウントと同じ親アカウントにGitHubアカウントが紐づけられます。

Slack紐付け

もしなければ下図フォームで氏名等の情報を入力することで、その場で新しい親アカウントを作成し、 その下にGitHubアカウントが紐づけられます。

自由入力紐付け

紐づけが完了すると、メッセージが下図のように書き換えられ、ログとして参照出来るようになります。

紐付け完了

概観

概観は下図のようになります。今回データベースにはMySQLを利用していますが、そこまで大掛かりにせず、 Google Spreadsheetでも代用可能です。

Botの構成

まず、GitHub OrganizationのWebhookを利用して、アカウント追加情報をリアルタイムにサーバに送ります。 hookするイベントはOrganizatios/member_addedイベントです。Outside collaboratorの追加も見たいときは Collaborator add, remove, or changed/addedをhookします。 ただし、このイベントはcollaboratorを新しくレポジトリに追加するたびに発火するので、サーバーでうまく処理する必要があります。 送信されるpayloadには追加ユーザーのGitHub IDや、招待したユーザーのGitHub IDが含まれています。

イベント情報を受信したサーバーは、まずデータベースに招待したユーザーのSlack IDを問い合わせます。 そのため、少なくとも招待する権限を持ったユーザーのGitHub IDとSlack IDの紐づけは完了している必要があります。 取得したSlack IDと追加ユーザーのGitHub IDを利用してメッセージをSlackにPOSTします。 Slack側で紐づけが行われると、サーバーにそのSlack IDまたは氏名、会社名、Emailアドレス情報が送信されます。 情報を受信したサーバーはそれをデータベースに書き込み、Slack側にログを送信します。

Slack Botの詳細

今回、投稿フォームには今年新しく導入されたメッセージUIフレームワーク、Block kitを使っています。 Block kitは画像、テキスト入力フォーム、date picker、ボタンなどの要素を積み重ねて、 リッチなメッセージを送信することが出来ます。

今回の例では下図のような簡単な構成となっており、以下のようなpayloadをSlackにPOSTします。 blocksに使いたい要素の配列を渡します。

Botの構成
{
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "hogehoge"
            },
        ...
        },
        {
            "type": "actions",
            "elements": [
                {
                    "type": "users_select",
                    "action_id": "select_xxx",
            ...
                },
                {
                    "type": "button",
                    "action_id": "bind_xxx.",
            ...
                },
            ],
        ...
        },
        {
            "type": "actions",
            "elements": [
                {
                    "type": "button",
                    "action_id": "modal_xxx",
            ...
                }
            ],
        ...
        }
    ]
}

ユーザーを選ぶ、ボタンを押す等のactionを行うと以下のようなpayloadがSlackからサーバーに送信されます。 response_urlはメッセージを更新するために、trigger_idはmodalと呼ばれる入力フォームを送信するために利用します。

{
    "type": "block_actions",
    "user": {
        "id": "U0XXXXX",
    ...
    },
    "trigger_id": "123646734323.XXXXXXX",
    "response_url": "https://www.xxxxxx",
    "actions": [
        {
            "action_id": "hoge_xxx",
      ...
        }
    ]
  ...
}

ますSlack IDを紐づける場合を考えます。 users_selectでworkspace内のユーザーを選択し, 隣のbuttonを押すことで、サーバー上で紐づけが行われ、メッセージ更新用payloadがSlackにPOSTされます。 buttonを押すときのpayloadには何もしないとusers_selectで選んだユーザーの情報は送られないので、 users_selectでユーザーを選ぶたび、その情報をaction_idに変数として格納しておきます。 pythonならば例えば以下のようにaction_idを利用できます。

# 送信時
action_id =  f"bind_{selected_user}"
# 受信時
selected_user = res.json()["payload"]["actions"][0]["action_id"].split("_", 1)[1]

一番下のbuttonを押すと、入力フォームが立ち上がりますが、 これはmodalと呼ばれる機能で、これ自体もBlock kitを使用します。 以下のようなpayloadをSlackにPOSTします。先ほどと同様に、blocksとして使用したい要素の配列を渡します。

{
    "trigger_id": "123455666.xxxxxxx",
    ...
    "view": {
        "type": "modal",
        "private_metadata": "https://www...(response_url)",
        "blocks": [
            {
                "type": "input",
                "element": {
                    "type": "plain_text_input",
                    "action_id": "name_input",
            ...
                },
                "label": {
                    "type": "plain_text",
                    "text": "氏名"
                },
                "block_id": "name"
            },
            {
                "type": "input",
                "block_id": "company",
          ...
            },
            {
                "type": "input",
                "block_id": "email",
          ...
            }
        ]
    }
}

今回はシンプルなテキスト入力フォーム、plain_text_inputを3つ利用しました。 またmodalにはprivate_metadataというfieldが存在し、 このfieldにメッセージを更新するためのresponse_urlなどの情報を格納出来ます。 紐づけるボタンを押すことで入力された内容が以下のようなpayloadがサーバーに送信されます。 受け取ったprivate_metadataのresponse_urlに新しいメッセージを送信することでメッセージの更新が出来ます。 実際には招待された人のGitHub IDなどもprivate_metadataに保存して、メッセージの更新に使用します。

{
    "type": "view_submissins",
    "user": {
        "id": "U0XXXXX",
      ...
    },
    "private_metadata": "https://www...(resppnse_url)",
    "state": {
        "values": {
            "name": {
                "name_input": {
                    "value": "日経 太郎",
            ...
                }
            },
            "comapany": {
                "company_input": {
                    "value": "日本経済新聞社",
            ...
                }
            },
            "email": {
                "email_input": {
                    "value": "hoge@example.com",
            ...
                }
            }
        }
    ...
    }
}

終わりに

今回はGitHubのアカウント管理方法の提案をさせていただきました。 Slackのapi周りの充実度は素晴らしく、このBlock kitの登場によってかなりかゆいところに手が届くようになりました。 今後のアップデートによってさらに使い方が増えるそうなので、楽しみです。

*1 SAML等で統一できるところは少しずつ進めていますが、運用上すぐに変更できなかったり、SAMLに対応していないサービスも結構あったりします

Entry

各種エントリーはこちらから

キャリア採用
Entry
新卒採用
Entry
カジュアル面談
Entry