ツールとして仕上げていく

前回はsshdのログファイルを解析して不正ログインを試みてきたユーザー名とIPアドレスを閲覧表示するシェルスクリプトを作成した。今回はこのスクリプトにさらに手を加えて、日付ごとに不正アクセス元IPアドレスとその記録回数をグラフにして出力するスクリプトに変更する。

先に成果物を掲載しておく。次のようなスクリプトを「ssh-check」という名前でつくった。

#!/bin/sh

logfile=/var/log/auth.log

# ログデータを取得
cat $logfile                        |
#
# sshdに関するログのみを選択
grep    -E 'sshd\[[0-9]*\]'                 |
#
# 必要なデータのみを抽出
#   1列目:    月
#   2列目:    日
#   3列目:    時:分:秒
#   12列目:   ユーザー名
#   14列目:   アクセス元IPアドレス
awk '{print $1,$2,$3,$12,$14}'              |
#
# 必要なログのみを選択
grep -E '[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}'     |
#
# 日付データを正規化(年月日時分秒)
while read mon day time user ip
do
    date=$(date --date="$mon $day $time" +%Y%m%d%H%M%S)
    echo "$date $user   $ip"
done                                |
# 1:年月日時分秒 2:ユーザー名 3:アクセス元IPアドレス
#
# 年月日時分秒を年月日に変更
awk '{print substr($1, 1, 8),$3}'               |
# 1:年月日 2:アクセス元IPアドレス
#
# 日付とアクセス元IPアドレスごとに数を集計
sort    -k1,2                           |
count   1 2                         |
# 1:年月日 2:アクセス元IPアドレス 3:集計数
#
# 集計数を横棒グラフに変更
awk '{
    bar=""
    for (i=0; i<$3; i++)
        bar=bar "*"
    print $1,$2,bar
}'                              |
# 1:年月日 2:アクセス元IPアドレス 3:棒グラフ
#
# 出力を若干調整
sed -e 's/ \([0-9]\)[.]/   \1./'                \
    -e 's/ \([0-9][0-9]\)[.]/  \1./'            |
sed -e 's/[.]\([0-9]\)\([. ]\)/.  \1\2/g'           \
    -e 's/[.]\([0-9][0-9]\)\([. ]\)/. \1\2/g'       |
sed -e 's/[.]\([0-9]\)\([. ]\)/.  \1\2/g'           \
    -e 's/[.]\([0-9][0-9]\)\([. ]\)/. \1\2/g'

このスクリプトを実行すると次のような結果を得ることができる。

% ./ssh-check
20190731 170. 80.226. 29 **
20190731  27. 41.190. 25 *
20190802 113.109.110. 51 *
20190802 223.199.221.178 *
20190802  59.125. 69. 40 ***
20190803 113. 17. 31. 61 *
20190803  14.118.206.120 *
20190803 183.128.224.219 *
20190804 115.213.143.250 *
20190804 170. 80.227. 99 *
20190804 217. 92.127.208 *
20190804  27.222.223.201 *
20190804  58. 35. 16.  9 *
20190804  71.202.241.115 *
20190805 113.215.220. 89 *
20190805  31.181.215. 22 *
20190806 101.235.114.131 *
20190806 113.215.193.142 *
20190806 121. 25. 24. 86 *
20190806 177.184.189.209 **
20190806 183.159.195. 55 *
20190806  76. 20. 69.183 *
20190806  91.250. 22.133 *
20190807 115.213.128.113 *
20190807 115. 59.120. 27 *
20190807 125. 42.179.246 *
20190807 188. 92. 75.248 *********************
20190807  49.130. 34. 61 *
20190807  49. 69. 83. 42 *
(略)
20190831 112. 93.138. 80 *
20190831 115.200.120. 56 *
20190831 123.145. 79. 20 *
20190831 123.157.113.170 *
20190831 218. 57. 96.113 *
20190831  39. 65.132. 59 *
20190831  39. 87.180. 98 *
20190831  69.115.101.133 *
%

このくらいであれば、書き換えにかかる時間は10分から1時間もあれば十分だと思う。

追加したコマンドについて

個々のコマンドの使い方がわかっていれば特に説明はいらないと思うが、追加したコマンドをざっくり説明しておこうと思う。

「YYYYmmddHHMMSS」というデータを「YYYYmmdd」に変換する処理は次のように実装した。

# 年月日時分秒を年月日に変更
awk '{print substr($1, 1, 8),$3}'               |

これはawkコマンドに用意されているsubstr()関数の機能で、1列目の1文字目から8文字分を切り出して出力するという内容になっている。この手のシェルスクリプトとawkは相性がよく、結構便利に使用できる。

次の処理は日付とIPアドレスをごとにデータを集計して、日付ごとにアクセス元IPアドレスからのログ記録回数を集計するものだ。

# 日付とアクセス元IPアドレスごとに数を集計
sort    -k1,2                           |
count   1 2                         |
# 1:年月日 2:アクセス元IPアドレス 3:集計数

ここでは「count」というコマンドを使って処理を行っている。countはあまり広く使われているコマンドではないので、処理系によっては存在しないこともある。その場合は、awkコマンドで同じような働きをする処理を記述すればよいだろう。

次の処理は集計した数を棒グラフにする処理だ。

# 集計数を横棒グラフに変更
awk '{
    bar=""
    for (i=0; i<$3; i++)
        bar=bar "*"
    print $1,$2,bar
}'                              |
# 1:年月日 2:アクセス元IPアドレス 3:棒グラフ

awkで数値を文字列に変換している。数の分だけ棒グラフを表現する文字を増やしているだけだ。

最後に棒グラフが見やすくなるように、IPアドレスを長さを揃えたドット区切りで表示されるように調整している。

# 出力を若干調整
sed -e 's/ \([0-9]\)[.]/   \1./'                \
    -e 's/ \([0-9][0-9]\)[.]/  \1./'            |
sed -e 's/[.]\([0-9]\)\([. ]\)/.  \1\2/g'           \
    -e 's/[.]\([0-9][0-9]\)\([. ]\)/. \1\2/g'       |
sed -e 's/[.]\([0-9]\)\([. ]\)/.  \1\2/g'           \
    -e 's/[.]\([0-9][0-9]\)\([. ]\)/. \1\2/g'

シェルスクリプトはコマンドのスキルでもある。コマンドの使い方をよく知っているほど、こうした処理を簡単に記述することができる。

1つの考え方として

ここで紹介しているようなシェルスクリプトは、Pythonで全部書くこともできるし、awkで書くこともできる。どちらかというかawkで書くほうがいいかもしれない。

なぜここで紹介したような感じでシェルスクリプトに書いているかというと、後からよく使う処理を別のコマンドとして実装しやすくなるからだ。例えば、今回書いたシェルスクリプトだが、個々の機能を個別のコマンドとして実装すれば、次のような感じでシェルスクリプトをシンプルに書くことができるようになる。

#!/bin/sh

logfile=/var/log/auth.log

# ログデータを取得
cat $logfile                        |
#
# sshdに関するログのみを選択
grep_sshd                           |
#
# 必要なデータのみを抽出
#   1列目:    月
#   2列目:    日
#   3列目:    時:分:秒
#   12列目:   ユーザー名
#   14列目:   アクセス元IPアドレス
retusel 1 2 3 12 14                     |
#
# 必要なログのみを選択
grep_ipaddr                         |
#
# 日付データを正規化(年月日時分秒)
datefmt 1=1+2+3                         |
retusel 1 5                         |
# 1:年月日時分秒 2:アクセス元IPアドレス
#
# 年月日時分秒を年月日に変更
datefmt 1.%Y%m%d%H%M%S.%Y%m%d
# 1:年月日 2:アクセス元IPアドレス
#
# 日付とアクセス元IPアドレスごとに数を集計
aggregate 1 2                           |
# 1:年月日 2:アクセス元IPアドレス 3:集計数
#
# 集計数を横棒グラフに変更
num2bar 3                           |
# 1:年月日 2:アクセス元IPアドレス 3:棒グラフ
#
# 出力を若干調整
ipfmt   2.%3d%3d%3d%3d

業務で必要になる処理は、似たような処理であることが多い。このため「こういうコマンドがあればな」という部分が経験を重ねるにつれてはっきりしてくる。そうなってきたら、その部分をコマンドとして実装すればよい。Pythonで書いてもよいし、awkで書いてもいいだろう。処理速度が必要になったらC言語で書き換えればよい。

こんな感じでシェルスクリプトで処理を組んでいくときは、将来的には自分でコマンドを開発することも視野に入れておくとよい。うまくいけば業務に必要なスクリプトをすごく簡単に書けるようになる。