外部からのアクセスに対するセキュリティを少しでも高める為に、以下のようにFWを更新する仕組みを考えました。
これを実現するスクリプトを作成して記事で紹介しています。
結果としてうまく動作していて、自宅サーバはすこぶる快調です。
SSHDの不正なログイン試行の形跡はすっかりなくなりました。
本記事では、このスクリプトの処理の概要について解説したいと思います。
興味があれば目を通してコメント等頂けると幸いです。
fail2banは禁句です
Contents
スクリプト
作成したスクリプトを再掲します。
1 |
cat /path/to/script/deny-ip |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
#! /bin/bash ################################################################## # <概要> 作成日:2020/11/1 # /var/log/auth.logの認証失敗履歴から接続元IPアドレスを抽出し, # iptablesのINPUTチェインに接続拒否ルールを追加するスクリプト. # ・固定的に拒否するIPアドレスは,DENY_IP_LIST_FILE に記載する. # ・固定的に許可するIPアドレスは,ALLOW_IP_LIST_FILE に記載する. # ・ローカルIPアドレスは接続許可する(ALLOW_IP_REGEXで指定). # iptablesのコマンドはIPTABLES_SCRIPT_FILEに残る. # crontabに設定済み. # #<実行コマンド> # command: bash deny-ip update|remove|status|dump" # option: # update iptablesのルールを更新 # remove iptablesのルールをを削除 # status iptables追加ルールの内容をターミナルに出力 # dump 拒否対象のIPアドレスをDENY_IP_LIST_FILEに出力 ################################################################## ######## 変数定義 ############ # 引数 MODE=$1 # iptablesのチェイン名 CHAIN_NAME=DENY-IP # このスクリプトのディレクトリ CURRENT=$(cd $(dirname $0);pwd) # 作業ファイルを配置するディレクトリ TMP_DIR=${CURRENT}/deny-ip.d # ssh接続を許可するIPアドレス接頭辞(正規表現) ALLOW_IP_REGEX="^10\.|^172\.|^192\.168\." # ssh接続を拒否するIPアドレスファイル(リスト形式)のパス DENY_IP_LIST_FILE=${TMP_DIR}/deny_ip_list # ssh接続を許可するIPアドレスファイル(リスト形式)のパス ALLOW_IP_LIST_FILE=${TMP_DIR}/allow_ip_list # iptablesルール追加スクリプト IPTABLES_SCRIPT_FILE=${TMP_DIR}/iptables-sh ######## mainスクリプト ######## if [ $# -lt 1 ]; then echo "command: bash $0 update|remove|status|dump" # 引数がdumpの時 elif [ $MODE = "dump" ]; then echo "iptables dump script." echo "# iptables -nL ${CHAIN_NAME} | awk '{print \$4}'| grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sort | uniq > ${DENY_IP_LIST_FILE}" iptables -nL ${CHAIN_NAME} | awk '{print $4}'| grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sort | uniq > ${DENY_IP_LIST_FILE} # 引数がstatusの時 elif [ $MODE = "status" ]; then echo "iptables show script." echo "# iptables -nvL ${CHAIN_NAME}" iptables -nvL ${CHAIN_NAME} # 引数がremoveの時 elif [ $MODE = "remove" ]; then echo "iptables restore script." # INPUTチェインからルールを削除 echo "# iptables -D INPUT -j ${CHAIN_NAME}" iptables -D INPUT -j ${CHAIN_NAME} # チェインを削除 echo "# iptables -F ${CHAIN_NAME}" iptables -F ${CHAIN_NAME} echo "# iptables -X ${CHAIN_NAME}" iptables -X ${CHAIN_NAME} # 引数がupdateの時 elif [ $MODE = "update" ]; then echo "iptables set script." # 作業ファイルを配置するディレクトリを作成 mkdir -p ${TMP_DIR} touch ${DENY_IP_LIST_FILE} touch ${ALLOW_IP_LIST_FILE} touch ${IPTABLES_SCRIPT_FILE} # 接続を拒否するIPアドレスリストを作成(grep Failed|Invalid) cat /var/log/auth.log* | grep -E "Failed|Invalid" | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sort | uniq | grep -v -E $ALLOW_IP_REGEX > ${IPTABLES_SCRIPT_FILE}.buf cat ${DENY_IP_LIST_FILE} ${IPTABLES_SCRIPT_FILE}.buf | sort | uniq > ${DENY_IP_LIST_FILE}.tmp # iptables(チェイン)の既設定IPアドレスを抽出 iptables -nL ${CHAIN_NAME} | awk '{print $4}' | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' > ${DENY_IP_LIST_FILE}.def # 接続を拒否するIPアドレスを設定するスクリプトを作成 comm --nocheck-order -23 ${DENY_IP_LIST_FILE}.tmp ${DENY_IP_LIST_FILE}.def > ${IPTABLES_SCRIPT_FILE}.buf comm --nocheck-order -23 ${IPTABLES_SCRIPT_FILE}.buf ${ALLOW_IP_LIST_FILE} | sed "s/^\(.*\)/iptables -A ${CHAIN_NAME} -s \1 -j DROP/g" | sort | uniq > ${IPTABLES_SCRIPT_FILE} # 接続を許可するIPアドレスの設定を消すスクリプトを作成 comm --nocheck-order -13 ${DENY_IP_LIST_FILE}.tmp ${DENY_IP_LIST_FILE}.def > ${IPTABLES_SCRIPT_FILE}.buf comm --nocheck-order -12 ${IPTABLES_SCRIPT_FILE}.buf ${ALLOW_IP_LIST_FILE} | sed "s/^\(.*\)/iptables -D ${CHAIN_NAME} -s \1 -j DROP/g" | sort | uniq >> ${IPTABLES_SCRIPT_FILE} comm --nocheck-order -12 ${DENY_IP_LIST_FILE}.tmp ${DENY_IP_LIST_FILE}.def > ${IPTABLES_SCRIPT_FILE}.buf comm --nocheck-order -12 ${IPTABLES_SCRIPT_FILE}.buf ${ALLOW_IP_LIST_FILE} | sed "s/^\(.*\)/iptables -D ${CHAIN_NAME} -s \1 -j DROP/g" | sort | uniq >> ${IPTABLES_SCRIPT_FILE} rm ${IPTABLES_SCRIPT_FILE}.buf ${DENY_IP_LIST_FILE}.def ${DENY_IP_LIST_FILE}.tmp # チェインを作成 echo "# iptables -N ${CHAIN_NAME}" iptables -N ${CHAIN_NAME} # ルールを作成 echo "# bash ${IPTABLES_SCRIPT_FILE}" cat ${IPTABLES_SCRIPT_FILE} | sed "s/\(.*\)/\+\1/g" bash ${IPTABLES_SCRIPT_FILE} # INPUTチェインに追加 echo "# iptables -C INPUT -j ${CHAIN_NAME} || iptables -A INPUT -j ${CHAIN_NAME}" iptables -C INPUT -j ${CHAIN_NAME} || iptables -A INPUT -j ${CHAIN_NAME} else bash $0 fi |
コメントと空行を除けば、53行のスクリプトです。
Debian系のLinux(Ubuntu、Raspbian)を想定していますので、RedHat系のLinux(CentOS)で実行する場合はログの場所(91行目)のパスを/var/log/secureに修正してください。
使い方については以下の記事をご覧いただければと思います。
解説
updateの動作がメインなので、update(82行目以降)に絞って解説したいと思います。
update時にやっていることは、以下のベン図でいうAのiptablesルール追加と、G,Eの iptablesルール削除です。
スクリプトの内容について解説します。
前処理(84~87行目)
ここでは、以下の処理をしています。
1 2 3 4 5 |
# 作業ファイルを配置するディレクトリを作成 mkdir -p ${TMP_DIR} touch ${DENY_IP_LIST_FILE} touch ${ALLOW_IP_LIST_FILE} touch ${IPTABLES_SCRIPT_FILE} |
- 作業ファイルを配置するディレクトリを作成(./deny-ip.d/)
- 作成したディレクトリ配下にファイルが存在しない場合は、各種ファイルを生成
- ①allow_ip_list
- ②deny_ip_list
- ③iptables-sh
拒否したいIPアドレス群を抽出する部分(91,92行目)
ベン図でいうA,D,F,Gの部分の抽出です。
1 2 3 |
# 接続を拒否するIPアドレスリストを作成(grep Failed|Invalid) cat /var/log/auth.log* | grep -E "Failed|Invalid" | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sort | uniq | grep -v -E $ALLOW_IP_REGEX > ${IPTABLES_SCRIPT_FILE}.buf cat ${DENY_IP_LIST_FILE} ${IPTABLES_SCRIPT_FILE}.buf | sort | uniq > ${DENY_IP_LIST_FILE}.tmp |
- /var/log/auth.logを読み込み、拒否したいIPアドレス群を抽出(sshdログインに失敗した接続元IPアドレスを取得し、ローカルIPアドレスを除く)
- 上記から、拒否したいIPアドレス群(②)とマージして、リストを保持・・・②’deny_ip_list.tmp
拒否したIPアドレス群を抽出する部分(95行目)
ベン図でいうB,D,E,Gの部分の抽出です。
1 2 |
# iptables(チェイン)の既設定IPアドレスを抽出 iptables -nL ${CHAIN_NAME} | awk '{print $4}' | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' > ${DENY_IP_LIST_FILE}.def |
- iptablesに設定済みのルールを読み込み、拒否した(既設定の)IPアドレス群を抽出・・・②”deny_ip_list.def
追加の拒否ルールを設定するコマンドを作成する部分(98,99行目)
ベン図でいうAの部分の抽出とルール作成です。
1 2 3 |
# 接続を拒否するIPアドレスを設定するスクリプトを作成 comm --nocheck-order -23 ${DENY_IP_LIST_FILE}.tmp ${DENY_IP_LIST_FILE}.def > ${IPTABLES_SCRIPT_FILE}.buf comm --nocheck-order -23 ${IPTABLES_SCRIPT_FILE}.buf ${ALLOW_IP_LIST_FILE} | sed "s/^\(.*\)/iptables -A ${CHAIN_NAME} -s \1 -j DROP/g" | sort | uniq > ${IPTABLES_SCRIPT_FILE} |
- 拒否したいIPアドレス群のうち、既設定にないIPアドレス群(②’と②”を並べて、②’にしかないIPアドレス群)を抽出・・・③’iptables-sh.buf
- 上記の③’から許可したいIPアドレス群を除くIPアドレス群(③’と①を並べて、③’にしかないIPアドレス群)に対して、iptablesのルール追加コマンドを作成・・・④iptables-sh
既存の拒否ルールを削除するコマンドを作成する部分 (102,103行目)
ベン図でいうEの部分の抽出とルール作成です。
1 2 3 |
# 接続を許可するIPアドレスの設定を消すスクリプトを作成 comm --nocheck-order -13 ${DENY_IP_LIST_FILE}.tmp ${DENY_IP_LIST_FILE}.def > ${IPTABLES_SCRIPT_FILE}.buf comm --nocheck-order -12 ${IPTABLES_SCRIPT_FILE}.buf ${ALLOW_IP_LIST_FILE} | sed "s/^\(.*\)/iptables -D ${CHAIN_NAME} -s \1 -j DROP/g" | sort | uniq >> ${IPTABLES_SCRIPT_FILE} |
- 拒否したいIPアドレス群のうち、既設定にしかないIPアドレス群(②’と②”を並べて、②”にしかないIPアドレス群)を抽出・・・③’iptables-sh.buf
- 上記の③’から許可したいIPアドレス群(③’と①を並べて、①にしかないIPアドレス群)に対して、iptablesのルール削除コマンドを作成(append)・・・④iptables-sh 追加
既存の拒否ルールを削除するコマンドを作成する部分(105,106行目)
ベン図でいうGの部分の抽出とルール作成です。
1 2 3 |
# 接続を許可するIPアドレスの設定を消すスクリプトを作成 comm --nocheck-order -12 ${DENY_IP_LIST_FILE}.tmp ${DENY_IP_LIST_FILE}.def > ${IPTABLES_SCRIPT_FILE}.buf comm --nocheck-order -12 ${IPTABLES_SCRIPT_FILE}.buf ${ALLOW_IP_LIST_FILE} | sed "s/^\(.*\)/iptables -D ${CHAIN_NAME} -s \1 -j DROP/g" | sort | uniq >> ${IPTABLES_SCRIPT_FILE} |
- 拒否したいIPアドレス群のうち、既設定と共通するIPアドレス群(②’と②”を並べて、②’と②”両方にあるIPアドレス群)を抽出・・・③’iptables-sh.buf
- 上記の③’から許可したいIPアドレス群(③’と①を並べて、①にしかないIPアドレス群)に対して、iptablesのルール削除コマンドを作成(append)・・・④iptables-sh 追加
この時点で、④のiptables-shには、全て(=ベン図でいうところのA追加コマンドとE,G削除)のiptables設定コマンドが記述されてます。
設定処理(108~122行目)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
rm ${IPTABLES_SCRIPT_FILE}.buf ${DENY_IP_LIST_FILE}.def ${DENY_IP_LIST_FILE}.tmp # チェインを作成 echo "# iptables -N ${CHAIN_NAME}" iptables -N ${CHAIN_NAME} # ルールを作成 echo "# bash ${IPTABLES_SCRIPT_FILE}" cat ${IPTABLES_SCRIPT_FILE} | sed "s/\(.*\)/\+\1/g" bash ${IPTABLES_SCRIPT_FILE} # INPUTチェインに追加 echo "# iptables -C INPUT -j ${CHAIN_NAME} || iptables -A INPUT -j ${CHAIN_NAME}" iptables -C INPUT -j ${CHAIN_NAME} || iptables -A INPUT -j ${CHAIN_NAME} |
- 計算過程で作成した②’、③’、③”のファイルを削除(deny_ip_list.tmp、iptables-sh.buf、deny_ip_list.def)
- iptablesのチェイン(DENY-IP)を生成
- チェイン(DENY-IP)にルールを流し込む(④をシェルで実行)
- チェイン(DENY-IP)をINPUTに組み込む(重複設定とならないように、確認もしとく)
以上
まとめ
E,Gの設定では設定対象のIPアドレスを抽出する処理をしていますが、iptablesにはルール存在確認のコマンドがあるのでこれを使えば楽だったのを最後に気が付きました。
1 |
iptables -C DENY-IP -s ${許可したいIPアドレス} -j DROP || iptables -D DENY-IP -s ${許可したいIPアドレス} -j DROP |
ただ、これだと全ての許可したいIPアドレスに対して、ルール設定時に毎回チェックを行うので非効率かもしれません。
なので、このスクリプトの処理の方が健全かと。
参考になれば幸いです。
コメント