LINE NotifyでPythonから自分にメッセージを送る

前回に続いて通知を作る話です。
今回はLINE通知を作ります。

この記事は、企業が運用している本格的な公式アカウントやBotサービスのようなものではなく、個人的に運用しているサーバーのバッチでエラーが起きたときなどに自分宛に通知するという小規模な利用を想定して書きます。

利用するサービスはこちらの LINE Notify です。
参考: https://notify-bot.line.me/ja/

この種のSNSに付随したサービスに対しては認証周りで面倒なコードを書かないといけないイメージがあったのですが、LINE Notifyは非常にコンパクトな実装で手軽に使えました。

事前準備として、こちらのサービスからトークンを入手します。

これ専用のアカウントは必要なく、普段使っているLINEのアカウントでログインできます。右上のログインリンクからログインしましょう。(LINE認証用のコードが表示され、LINEアプリから入力が必要なので、スマホも用意しておきましょう。)

ログインしたら、右上のログインリンクが自分のLINEアカウント名になっているので、それを押してマイページへ遷移します。
そして、「アクセストークンの発行(開発者向け) 」のところから「トークンを発行」ボタンをクリックします。

トークン名を入力し、通知を送るトークルームを選択して、「発酵する」ボタンをクリックするとトークンが発行されて1度だけ表示されます。もう二度と表示されないのでこの時点で確実に記録しておきましょう。

先に書いておきますが、通知を実装した後実際にLINEに届くメッセージは、
[トークン名] メッセージ本文
というフォーマットになります。トークン名が長いと毎回邪魔なのでコンパクトでわかりやすい名前にしておきましょう。

さて、トークンが発行できたらこれを使ってみます。
ドキュメントはこちらです。
参考: LINE Notify API Document
このドキュメントの「通知系」のところにある、https://notify-api.line.me/api/notify が通知を送るAPIです。

リクエストパラメーターがいろいろ書かれていますが、「必須」と指定されているのはmessageだけなので非常に簡単に使えます。

CURLで動かすサンプルコードもあるのでちょっとやってみましょう。{自分のトークン}の部分は先ほど発行したトークンを入れてください。

# コマンド
$ curl -X POST -H 'Authorization: Bearer {自分のトークン}' -F 'message=CURLで通知' https://notify-api.line.me/api/notify

# 結果
{"status":200,"message":"ok"}

これで、「[トークン名] CURLで通知」というメッセージが、 LINE Notify のアカウントから届きます。

あとは、このcurlコマンドをPythonに書き直していきましょう。使うライブラリはrequestsあたりで良いと思います。

import requests


line_notify_token = "{自分のトークン}"
api_url = "https://notify-api.line.me/api/notify"
message = "メッセージ本文"
headers = {"Authorization": f"Bearer {line_notify_token}"}
data = {"message": message}
requests.post(
    api_url,
    headers = headers,
    data = data,
)

たったこれだけで、LINEにメッセージが届きます。

もう少し丁寧に実装するなら、postの戻り値のstatusコードを確認してエラー処理を入れたりするとよさそうですね。

LINEのインターフェース的に、あまりにも長文を送ったりするのには適さず、長文になるなら先日のメール通知の方が良いかなと思うのですが、
メールよりLINEの方が通知に気付きやすいので、速報性が必要な場面で重宝しそうです。

PythonでYahooメールのアカウントからメール送信

個人的に運用しているソフトウェアに通知機能を作りたかったので、メールの送信方法を調べました。本当はSlack通知とかの方が使いやすいのですが、最近のSlackの無料プランは一定期間でメッセージが消えるなどイケてないですからね。

メールの利用を検討し出した当初はAmazon SESを使おうかとも思っていたのですが、標準ライブラリだけで実装できることと、料金もかからないのでPythonでやることにしました。

使用するライブラリは以下の二つです。
– SMTPプロトコルクライアント – smtplib
– メールメッセージの管理 – email
email の方は 使用例のページを見た方がいいです。

使い方はYahooメールやGmail, Outlookなどアカウントによって微妙に違うので今回はYahooメールを例に取り上げて説明します。

メールサーバーやポート番号の情報はこちらにあります。
参考: メールソフトで送受信するには(Yahoo!メールアドレス、@ymail.ne.jpアドレスの場合)

必要な情報は以下の2つです。
– 送信メール(SMTP)サーバー : smtp.mail.yahoo.co.jp
– 送信メール(SMTP)ポート番号 : 465

実はこの情報は、2020年8月に変わっていて、他所の古い技術記事等ではポート番号が違っていたりします。他サイトのコードをコピペしたが動かなかったという人は以下のアナウンスを読んでください。SMTPの通信方法がSSLになっているというのもライブラリで呼び出す関数が変わるので重要な点です。
参考: Yahoo!メールをより安全にご利用いただくためのメールソフト設定(送受信認証方式)変更のお願い

メールソフトで送受信するにはのページに記載がありますが、「Yahoo! JAPAN公式サービス以外からのアクセスも有効にする」の設定をやっておかないと動かないので気をつけてください。(とはいえ、普段スマホでメールを見れるようにしているのであれば設定済みだと思います。)

それではやっていきましょう。自分のスクリプトから自分のメールアドレス宛の通知としての利用を想定しているので飾りも何もないテキストメールを送ります。

まず、各変数に必要な値を格納しておきます。悪用は厳禁ですが、toだけでなくfromのメールアドレスも自由に設定できます。まともに使うならfromは自分のアドレスでしょう。

# Yahooのログイン情報
username = "{YahooのユーザーID}"
password = "{Yahooのパスワード}"

# メールアドレス情報
from_address = "{差出人のアドレス}@yahoo.co.jp"
to_address = "{宛先のアドレス}@yahoo.co.jp"

# SMTPサーバーの情報。値はYahooメールのヘルプページから取得。
smtp_host = "smtp.mail.yahoo.co.jp"
smtp_port = 465

続いて、メールの情報を作ります。smtpのドキュメント末尾では文字列で直接データを作ってますが、その直下の注釈でemailパッケージを推奨されているのでそちらに従います。

from email.message import EmailMessage


msg = EmailMessage()
msg.set_content("メール本文")
msg["Subject"] = "メールタイトル"
msg["From"] = from_address
msg["To"] = to_address

メールデータできたので、これを送信します。ドキュメントの一番下のサンプルコードではローカルのSMTPサーバーでメール送信しているのでログインも何もしていませんが、Yahooメールを使うなら最初にログインが必要です。smtplib.SMTP ではなく、smtplib.SMTP_SSLを使うのもポイントですね。コードは以下のようになります。

import smtplib


server = smtplib.SMTP_SSL(smtp_host, smtp_port)
server.login(username, password)
server.send_message(msg)
server.quit()

たったこれだけでメール送信が実装できました。

WordPressのログインページのURLを変更する

前回の記事で書いてる通り、機械的にログインを試みる攻撃を受けていたので対策を施しました。特定IPアドレスからの攻撃だったのでそのIPをブロックしようかと思ったのですが、他のIPに変えられるたびにやるのも面倒なので、Wordpressのセキュリティ策としてよく挙げられているログインURLの変更を実施しました。

初期設定のログインURLはWordpressのドキュメントを見れば分かっちゃいますからね。

方法はいろいろありますが、手軽な方法としてプラグインを使うことにしました。

選んだのは、 All In One WP Security です。もっとシンプルな、URLの変更に特化したやつとかもあるのですが今後別の対策を考える時があったら使いまわせるのがいいと思ったのでこれを選びました。(結果的にURL変更だけで攻撃が収まったのですが、対策前の時点ではそれだけで完了するかどうわかりませんでしたし。)

WordPressの管理画面からプラグインの画面を開き、新規追加から検索してインストールします。そして有効化します。

有効化したら左ペインのメニューに「WPセキュリテイ」というのができるのでここから設定します。

「総当たり攻撃」というカテゴリ(英語だとBrute force)の中の、Rename login page タブがログインURLの変更です。それを開きます。

Enable rename login page feature: のチェックボックスにチェックを入れ、Login page URL: のテキストボックスの中にこれから使うURLを設定しSaveするとそちらが新しいログインページになります。

これで、デフォルトのログインページにアクセスしてみてフォームが出てこないことを確認したら完成です。

WordPressは未ログイン状態でログイン後の管理画面のURLにアクセスするとログインURLにリダイレクトされたりするのですが、このツールでURLを変更するとその動作もなくなり、リダイレクトによってURLがバレるということも防いでくれています。気がききますね。

そして、肝心の攻撃に対する効果ですが、apacheのログを見ると該当の攻撃者に404エラーが出た段階でピタリとアクセスが止まってるのを確認できました。Lightsailのリソースも回復しており良い感じです。

今回の事象への対応はこれで完了ですが、時々不自然にアクセスが集中している時間があったりなどの不穏な動きはまぁまぁあるので必要に応じて今回導入したAll-In-One Securityの各機能を活用して対策して行こうと思います。

Lightsailで立てたWordPressサーバーのapacheログについて

新年のご挨拶でちょっと書きましたが、このブログが昨年末に攻撃を受けていたようで、過剰なアクセスによりCPUリソースが枯渇する事態となっていました。

下にコンソールで確認したCPUリソースの画像を貼りますが、パーストキャパシティがなくなってますね。この時間帯、ブログの表示が非常に遅くなってしまっていました。最終的にどうやって対策し事象を解消したたかは次の記事に書くとして、この時の状況調査のためにログファイルを確認したのでその時調べたあれこれを記事にまとめておきます。

このCPUが異常に利用されていた時なのですが、ブラウザではなくコマンドかプログラムか何かしら機械的なアクセスがされていたようで、Google Analyticsでは特にアクセスの増加等が見られませんでした。GAはブラウザでアクセスしてJavaScriptが動かないとデータが取れませんからね。

ということで、サーバー側のログを調べる必要性が発生したわけです。

通常の構成であれば、apacheのログはデフォルトでは、/var/log/ の配下にあるそうです。
/var/log/apatche か、 /var/log/httpd/ のどちらかの下に。

ただし、LightsailのWordpressはbitnamiというパッケージが使われており、apache自体が通常と違う場所にあって、ログファイルも普通と違う場所にあります。ちなみにapacheがインストールされている場所は次のようにして確認できます。

$ which httpd
/opt/bitnami/apache2/bin/httpd

/opt/bitnami の配下にあることがわかりますね。

そして、ログファイルもこの近辺にあります。/opt/bitnami/apache2/log ってディレクトリがあるのです。一応中見ておきますか。

$ ls /opt/bitnami/apache2
bin  bnconfig  build  cgi-bin  conf  error  htdocs  icons  include  logs  modules  scripts  var


$ ls /opt/bitnami/apache2/logs/
access_log              access_log-20210801.gz  error_log-20200223.gz  error_log-20210808.gz
access_log-20200223.gz  access_log-20210808.gz  error_log-20200302.gz  error_log-20210816.g
access_log-20200302.gz  access_log-20210816.gz  error_log-20200308.gz  error_log-20210822.gz
access_log-20200308.gz  access_log-20210822.gz  error_log-20200316.gz  error_log-20210829.gz
#########
#  中略  #
#########
access_log-20210704.gz  access_log-20221218.gz  error_log-20210712.gz  error_log-20221226.gz
access_log-20210712.gz  access_log-20221226.gz  error_log-20210718.gz  error_log-20230101.gz
access_log-20210718.gz  access_log-20230101.gz  error_log-20210726.gz  httpd.pid
access_log-20210726.gz  error_log               error_log-20210801.gz  pagespeed_log

名前から明らかですが、access_logがアクセスログで、error_logがエラーログであり、日付がついて拡張子が.gzになっているのがログローテションで圧縮された古いログです。

ちなみにこのログファイルのパスは、次の設定ファイルで設定されています。

$ vim /opt/bitnami/apache2/conf/httpd.conf

# 中略

<IfModule log_config_module>
    #
    # The following directives define some format nicknames for use with
    # a CustomLog directive (see below).
    #
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common

    <IfModule logio_module>
      # You need to enable mod_logio.c to use %I and %O
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
    </IfModule>

    #
    # The location and format of the access logfile (Common Logfile Format).
    # If you do not define any access logfiles within a <VirtualHost>
    # container, they will be logged here.  Contrariwise, if you *do*
    # define per-<VirtualHost> access logfiles, transactions will be
    # logged therein and *not* in this file.
    #
    CustomLog "logs/access_log" common

    #
    # If you prefer a logfile with access, agent, and referer information
    # (Combined Logfile Format) you can use the following directive.
    #
    #CustomLog "logs/access_log" combined
</IfModule>

# 中略
#
# ErrorLog: The location of the error log file.
# If you do not specify an ErrorLog directive within a <VirtualHost>
# container, error messages relating to that virtual host will be
# logged here.  If you *do* define an error logfile for a <VirtualHost>
# container, that host's errors will be logged there and not here.
#
ErrorLog "logs/error_log"

#
# LogLevel: Control the number of messages logged to the error_log.
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
#
LogLevel warn

CustomLog / ErrorLog がファイルパスの指定で、LogFormatとしてログの出力書式も指定されていますね。
書式の%hとかの意味はこちらのドキュメントにあります。
参考: mod_log_config – Apache HTTP サーバ バージョン 2.4

あとは中身を確認したら良いです。access_log とかのファイルはapacheがアクセスがあるたびにバリバリ書き込んでる物なので、ロックがかからないようにこれを直接開くのは避けて、どこかにコピーして開きましょう。scp等でローカルに持ってきちゃうのが良いと思います。

拡張子が.gzのものは、gzip コマンドに -d オプションをつけて実行すると解答できます。

# *(アスタリスク)を使ってまとめて解凍しちゃうと楽。
$ gzip -d *.gz
# .gzファイルが無くなり、解凍済みファイルだけが残ります。

あとはただのテキストファイルなので、出来上がったファイルを確認したら良いです。

この結果、冒頭に挙げた攻撃を受けてた時間帯は、ログインを試みるアクセスが特定のIPアドレスから7万回も発生していたのがわかりました。

基本的に通常のアクセス分析はGoogle Analyticsを見れば済む話なのでapacheのログに意識をはらってきませんでしたが、今回調査してみてもっと使いやすいフォーマットで出力するように設定しておけば良かったなと思いました。csvではないのでpandasでのパースも面倒でしたし、User Agentなど取れるはずなのに取ってない情報も多かったので。そしてタイムゾーンも日本時間じゃないんですよね。これも地味に扱いにくいです。

2023年のご挨拶

新年明けましておめでとうございます。本年もこのブログをよろしくお願いします。

早速ですが今年のこのブログの更新方針を決めました。昨年同様に今年もしっかりインプットの時間を確保し、ブログへのアウトプットは少なめに週1回の更新を目指していきたいと思っています。

今年は新年早々から統計数理研究所の講座受講も2件決まっていたり、参加したいセミナーやミートアップも既にいくつかあるので積極的に動いていきたいです。昨年からすうがくぶんか社のセミナーも受講していますが、今年も何か面白そうなのを探して受講しようと思います。また、書籍についても昨年後半出た本が複数あり、まだ追いついていないので順次読んでいきます。

昨年はインプットを増やすと言って一番増えたのがビジネス系Youtubeの視聴時間だったので、今年はちゃんと読書時間を増やしたいです。Youtubeは最初は良かったのですが、冷静に見ると似たようなネタの繰り返しが多くてそろそろ減らしていいかなと思ってます。

このブログはネタ帳を用意していてそこに常時数十個のテーマを列挙しており、そこからその日の気分でピックアップして書いています。大体その執筆時点で新しく知ったばかりのことを優先的に選んで書いてるのですが、そうやって場当たり的に書いていると、タイミングを逸していつか書きたいと思ったまま放置状態になってしまっているテーマがたくさん残ってしまいました。この点は反省していて、そのうち書こうと思っていたけど放置してた系の記事をもっと書くようにしたいなと思っています。

例えば以下のような内容がいつか書かねばと思って放置された状態です。物によってはブログ開設前(2018年)にリストアップしてその時からずっと放置しています。全部書けるかというと難しそうなのですが少なくとも半分程度はクリアしたい。
– グラフのコミュニティー検出
– AWSの各サービスについて(DynamoDB/ personalize/ Forecast/ SageMaker など)
– opencv
– 生存分析(カプラン・マイヤー法やCOX回帰など)
– node2vec
– scikit-learn等のライブラリの最近の新機能
– 因果探索(LiNGAMなど)/因果推論
– 時系列データの異常検知や変化検知
– 状態空間モデル(カルマンフィルター)
– JavaScriptのデータ可視化関数(特にワードクラウド)
– Word Mover’s Distance などの自然言語処理の小ネタ
– jupyter lab
– J-Quants API
その他、numpy, scipy, pandas, matplotlib, tableauなどの小ネタなどが多数。

今年は今年で新ネタは出ると思いますし、更新回数が50回程度と考えるともう1年分のネタは確実に確保できそうです。あとは実際に執筆する時間とモチベーションを維持できるかという点が問題ですね。(何せ、書ける状態なのに書かなかったネタたちなので1つ1つがちょっと重い。)
できる範囲で頑張って書いていこうと思いますのでよろしくお願いします。

この他、昨年目標に入れていてあまり手をつけなかったこのブログ自体のメンテナンスもやらなければなりません。PHPやバージョンアップとか。これLightsail使ってるとすごく面倒なんですよね。
また、この記事を書いてる時点で海外から攻撃を受けているようでして、どこかの誰かが執拗にLoginを試みていてそのアクセスでCPUリソースが枯渇しているようです。
ここがそんなハッキングする価値のあるブログだとは思えなのですが、攻撃してくる人がいる以上はセキュリティ面の強化等も進めなければなりませんので、何かやったら記事にしていこうと思います。
アクセスが重くなっていることがあり、訪問者の方にはご不便をおかけします。

訪問者の方にはあまり関係ないことなのですが、Google Analyticsの旧バージョン、ユニバーサル アナリティクスが今年終了するというのもブログ関係では大きなイベントですね。
後継のGA4をしっかり学んで、継続して分析ができるようにしたいと思います。
(ただ、現時点のGA4は明らかにUAに劣るように感じているので、他の分析ツールへの乗り換えも視野に入れたい。これから改善するといいのですが。)

ブログ以外では、昨年からやっている投資ツール開発の個人プロジェクトももっと進めていきます。プログラムはほぼ動くものが揃ってきているのであとは手動で実行から自動実行への切り替えとか自動実行に伴うエラー通知の仕組み構築とかが残課題です。

以上のような方針で今年も頑張っていこうと思いますので、本年もよろしくお願いいたします。