まずはじめに
前回の投稿同様、ネットに転がっている情報を切り貼りして作ったクソコードなので、利用する場合は自己責任でお願いします。
やりたいこと
- ZABBIXのサーバ監視の通知をLINE WORKSで受け取りたい
- コストかけずにやりたい
前提条件
- ZABBIXをインストールしたLinuxサーバがある
- ZABBIXのアクションとメディアタイプとユーザー>メディアの設定が完了している
- LINE WORKSを使っていて管理者であること(フリープランでもOKらしい)
- 固定IPは不要
なんでトーク送信するだけのBotにまで認証させるのおぉぉ!API2.0なんてきらい!!!
当方の構成(参考)
- AlmaLinux 8.8(仮想マシン)
- Zabbix 5.0.37
- LINE WORKS スタンダードプラン
全体の流れ
- ZABBIX導入(適当)
- ZABBIXの通知をカスタム、アクションとメディアタイプとメディアの設定をする(適当)
- LINE WORKS Developer ConsoleのAPIからアプリを新規追加
- LINE WORKS Developer ConsoleでBotを作る
- ShellScriptをかく
- 通知してみる
ZABBIX導入(適当)
別に難しい作業はないと思ってましたが、なんかちょっとめんどくさそうなので、Qiitaで見つけたページを貼っておきます。
2. ZABBIXの通知をカスタム、アクションとメディアタイプとメディアの設定をする(適当)
この辺は前回書いた記事と一緒。
LINE WORKS Developer ConsoleのAPIからアプリを新規追加
とても簡単
保存
※よく考えたらすぐ消すのにClient IDとかモザイク入れる意味なかったデス。
このままではService Accountが発行されてないので、発行ボタンを押して発行しておきます。
最後にPrivate Keyを発行してAPI(アプリ)の設定は完了です。
ダウンロードしたら、ShellScriptを設置するサーバに保存しておきます。
今回は/usr/lib/zabbix/alertscripts/private.key
に保存してます。
その他にもClient ID
とClient Secret
とService Account
はコピーしておいてください。
LINE WORKS Developer ConsoleでBotを作る
これも簡単
必須項目(Bot名、説明、主担当)を入力して保存
※今回のBotはただ単に通知をするだけのものなので、固定メニュー、Callback URL、トークルームへの招待は利用しません。
無事「通知するクン」(Bot番号:6531760)が追加されました。
ShellScriptをかく
今回使っているコマンド類は以下の通り。
- date
- tr
- sed
- base64
- openssl
- curl
- jq
あと、今回ZABBIXから通知する際、スクリプトに以下の引数を渡す予定です。
- 第一引数:ZABBIXのアラートタイトル
- 第二引数:アラート本文
- 第三引数:Bot番号(今回の例なら6531760)
- 第四引数:通知を受けたいLINE WORKSのID
第三引数と第四引数をShellScriptに埋め込んでしまうことも可能ですが、複数人に通知したい場合はこっちの方がよいでしょう。
今更だけど、LINE WORKS DevelopersのドキュメントにあるService Account認証(JWT)のページを参照して、どういう風に動いているのかを把握しておいてください。
https://developers.worksmobile.com/jp/docs/auth-jwt
本ShellScriptでは、以下の流れで動かしています。
- JWTを生成する
- 生成したJWTでAccess Tokenの取得をリクエストする
- Access TokenとRefresh Tokenがレスポンスされる
- Access Tokenを使ってTalkをSend
このとき、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なので許してね。
てか、誰かコード直してクレメンス
# 今の時間(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を簡易に動かすためのサンプルコードが不足していると感じます。
この記事が僅かでも誰かの役に立てば嬉しく思います。