Zabbixの通知を追加投資なしでLINE WORKSに飛ばす方法(API2.0対応版)

## まずはじめに
前回の投稿同様、ネットに転がっている情報を切り貼りして作った**クソコード**なので、利用する場合は自己責任でお願いします。

## やりたいこと
* ZABBIXのサーバ監視の通知をLINE WORKSで受け取りたい
* コストかけずにやりたい

## 前提条件
* ZABBIXをインストールしたLinuxサーバがある
* ZABBIXのアクションとメディアタイプとユーザー＞メディアの設定が完了している
* LINE WORKSを使っていて管理者であること(フリープランでもOKらしい)
* 固定IPは不要
* ~~なんでトーク送信するだけのBotにまで認証させるのおぉぉ！~~
* ~~API2.0なんてきらい！！！~~

## 当方の構成(参考)
* AlmaLinux 8.8(仮想マシン)
* Zabbix 5.0.37
* LINE WORKS スタンダードプラン

## 全体の流れ
1. ZABBIX導入(適当)
1. ZABBIXの通知をカスタム、アクションとメディアタイプとメディアの設定をする(適当)
1. LINE WORKS Developer ConsoleのAPIからアプリを新規追加
1. LINE WORKS Developer ConsoleでBotを作る
1. ShellScriptをかく
1. 通知してみる

## ZABBIX導入(適当)
別に難しい作業はないと思ってましたが、なんかちょっとめんどくさそうなので、Qiitaで見つけたページを貼っておきます。

https://qiita.com/atanaka7/items/282a3499e135e00edcdc

## 2. ZABBIXの通知をカスタム、アクションとメディアタイプとメディアの設定をする(適当)
この辺は前回書いた記事と一緒。

https://qiita.com/Majide_Kichigai/items/d8982bcd7169e119b1d2



## LINE WORKS Developer ConsoleのAPIからアプリを新規追加
とても簡単

アプリの新規追加を押して
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/b51d2a9a-0b7b-7657-560a-05b92999bdcb.png)

アプリ名を入れて
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/c41aa6c2-0fc8-664f-9e26-9a1b6ea69314.png)

OAuth Scopeの中から「bot」を選んで
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/46739ccb-49a3-2c02-de62-d5f42ded05de.png)

保存
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/aebd7828-c127-9f84-0dfd-bf224e6e898c.png)
※よく考えたらすぐ消すのにClient IDとかモザイク入れる意味なかったデス。

やったね！1個アプリが増えました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/296a580f-7656-2a81-20fc-dcdad8f32b8a.png)

このままではService Accountが発行されてないので、発行ボタンを押して発行しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/49a26f5e-d68f-7347-c4cd-3ceebacb90c1.png)
最後にPrivate Keyを発行してAPI(アプリ)の設定は完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/91c011bd-d927-4251-3690-bc71d3fab4e2.png)
ダウンロードしたら、ShellScriptを設置するサーバに保存しておきます。
今回は`/usr/lib/zabbix/alertscripts/private.key`に保存してます。
その他にも`Client ID`と`Client Secret`と`Service Account`はコピーしておいてください。

## LINE WORKS Developer ConsoleでBotを作る
これも簡単

右上の登録を押して
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/985e71dc-e4cd-3d03-0277-8c9e0d9253e8.png)

必須項目(Bot名、説明、主担当)を入力して保存
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/da2651d3-a67c-a0e5-aecb-04b310957641.png)
※今回のBotはただ単に通知をするだけのものなので、固定メニュー、Callback URL、トークルームへの招待は利用しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/0a23e2a0-cfce-ec93-cf66-c2644833a0af.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/ae9ff22d-f64a-3569-fb7a-45e2eaf281de.png)
無事「通知するクン」(Bot番号：6531760)が追加されました。



## ShellScriptをかく
今回使っているコマンド類は以下の通り。
* date
* tr
* sed
* base64
* openssl
* curl
* jq

あと、今回ZABBIXから通知する際、スクリプトに以下の引数を渡す予定です。
* 第一引数：ZABBIXのアラートタイトル
* 第二引数：アラート本文
* 第三引数：Bot番号(今回の例なら6531760)
* 第四引数：通知を受けたいLINE WORKSのID

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426508/7d72d8dc-a0fe-c712-3749-ebd78a433468.png)
第三引数と第四引数をShellScriptに埋め込んでしまうことも可能ですが、複数人に通知したい場合はこっちの方がよいでしょう。

:::note
今更だけど、LINE WORKS DevelopersのドキュメントにあるService Account認証(JWT)のページを参照して、どういう風に動いているのかを把握しておいてください。
https://developers.worksmobile.com/jp/docs/auth-jwt
:::

本ShellScriptでは、以下の流れで動かしています。
1. JWTを生成する
1. 生成したJWTでAccess Tokenの取得をリクエストする
1. Access TokenとRefresh Tokenがレスポンスされる
1. Access Tokenを使ってTalkをSend

:::note warn
このとき、Access Tokenは24時間、Refresh Tokenは90日の有効期限で発行されます。
Refresh Tokenの有効期限内なら、何度でもAccess Tokenを再発行できるので、当初はRefresh Tokenを使ってAccess Tokenの再発行をリクエストしようと思っていました。
が、LINE WORKS Developersのコミュニティをサッと検索すると、Refresh Tokenを使ってAccess Tokenの再発行リクエストを行っても、Refresh Tokenの有効期限が伸びず、結局90日に1回以上JWTでのAccess Token発行リクエストを出さなければならないようです。
:::

上記の事情で本ShellScriptでは毎回JWTを生成してAccess Tokenの発行をリクエストしています。
(要検証だが90日も待てないわよアタシ)

ということで、コードは以下。
`sed`を使うな！とか、めちゃくちゃなコードじゃねーか！とかそういうご指摘はあろうかと思いますけど、本職はPCのキッティングとかしてる底辺社内SEなので許してね。
てか、誰かコード直してクレメンス

```Bash:LineWorksApi2.0TalkSendBot.sh
# 今の時間(UNIX秒)
iat=`date +%s`

# 1時間後(UNIX秒)
exp=`date +%s -d "1 hour"`

# LINEWORKS Developer ConsoleのアプリのClient ID
iss="<Client ID>"

# LINEWORKS Developer ConsoleのアプリのService Account
sub="<Service Account>"

# LINEWORKS Developer ConsoleのアプリのClient Secret
c_sec="<Client Secret>"

# 認証リクエストヘッダ(JSON)
head="{\"alg\":\"RS256\",\"typ\":\"JWT\"}"

# 認証用JSON Claims Set(JSON)
claims="{\"iss\":\"$iss\",\"sub\":\"$sub\",\"iat\":$iat,\"exp\":$exp}"

# 秘密鍵(LINEWORKS Developer Consoleのアプリからダウンロードしたもの)
p_key="/usr/lib/zabbix/alertscripts/private.key"

# タイトル(ダブルクォーテーション削除、半角スペースエスケープ)
title=`echo $1 | tr -d '"' | tr ' ' '\ '`

# 本文(ダブルクォーテーション削除、半角スペースエスケープ)
body=`echo $2 | tr -d '"' | tr ' ' '\ '`

# 送信メッセージ全体
msg=$title'\n'$body

# Bot番号
bot=$3

# LINEWORKS_account
lwacc=$4

# ヘッダとクレームのbase64エンコード(URLセーフ化を含む)
head64=$(echo -n $head | base64 -w 0 | sed -e 's/=$//' -e 's/+/-/' -e 's/\//_/')
claims64=$(echo -n $claims | base64 -w 0 | sed -e 's/=$//' -e 's/+/-/' -e 's/\//_/')

# ヘッダとクレームを連結
head_claims="$head64.$claims64"

# ヘッダとクレームをデジタル署名してbase64エンコード(URLセーフ化を含む)
# ちゃんとしたいけどこれで動いてるから触りたくない(ここで結構ハマった)
jwt_sig=$(echo -n $head_claims \
        | openssl dgst -sign $p_key -sha256 -binary \
        | base64 -w 0 | sed -e 's/=$//' -e 's/+/-/' -e 's/\//_/'\
        | tr '+' '-' | tr '/' '_'\
        | sed -e 's/=$//' -e 's/+/-/' -e 's/\//_/')

# ヘッダ、クレーム、シグネチャを連結してJWTを生成
jwt="$head64.$claims64.$jwt_sig"

# 生成したJWTを使ってAccess Tokenを発行
jws=$(curl --location --request POST 'https://auth.worksmobile.com/oauth2/v2.0/token' \
 --header 'Content-Type: application/x-www-form-urlencoded' \
 --data-urlencode "assertion=${jwt}" \
 --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
 --data-urlencode "client_id=${iss}" \
 --data-urlencode "client_secret=${c_sec}" \
 --data-urlencode "scope=bot")

# 認証リクエストした結果のうち、Access Tokenだけを変数に代入
acctkn=`echo $jws|jq -r '.access_token'`

# トークの中身（コンテンツ）
cont="{\"content\":{\"type\":\"text\",\"text\":\"$msg\"}}"

# トーク送信用URL
url="https://www.worksapis.com/v1.0/bots/${bot}/users/${lwacc}/messages"

# 送信処理
curl -v -H "Content-Type: application/json; charset=UTF-8" \
 -H "Authorization: Bearer ${acctkn}" \
 -X POST --data-binary "$cont" \
 $url

exit 0

```

## 通知してみる
```
[User@Hostname alertscripts]# sudo sh LineWorksApi2.0TalkSendBot.sh "タイトル" "本文" 6531760 <LINE WORKSのID>
```

どうでしょう？
通知できましたか？

## さいごに
実は、API2.0への変更のタイミングで、もうZABBIXのLINE WORKS通知は諦めようと思っていました。
実際、旧APIが停止した7月くらいから10月までZABBIXの通知を止めていました。
しかし、障害発生時に通知が来ないのは不便、、というか危険だなと思い、重い腰を上げた次第です。

Botはプログラマさんが作るもの、という認識は間違いではないと思いますが、なにかの通知をトークに送信するだけの簡易なBotなら、私のようなニワカSEでも作れるくらい簡単です。
でも、その簡単なものもドキュメントやサンプルコードが整備されていないと途端に難しくなる。
特に、LINE WORKSのBotを簡易に動かすためのサンプルコードが不足していると感じます。

この記事が僅かでも誰かの役に立てば嬉しく思います。