さしあたっては、「許可」する国を指定する必要はないと思うので、まずは「拒否」する方の設定を見ていくことにします。
先に紹介したBLOGでは、アクセス排除のために複数のスクリプトが用意されているのですが、まずはメインとなるスクリプト部分をターゲットに、該当部分を取り出して考えていきます。
01: # DROP_COUNTRY_MAKE関数定義
02: # 指定された国のIPアドレスからのアクセスを破棄するユーザ定義チェイン作成
03: DROP_COUNTRY_MAKE(){
04: for addr in `cat /tmp/cidr.txt|grep ^$1|awk ‘{print $2}’`
05: do
06: iptables -A DROP_COUNTRY -s $addr -m limit –limit 1/s -j LOG –log-prefix ‘[IPTABLES DENY_COUNTRY] : ‘
07: iptables -A DROP_COUNTRY -s $addr -j DROP
08: done
09: }
便宜上、行番号をつけてみました。
01行目:
コメント。これから定義する関数がDROP_COUNTRY_MAKEであることを説明しています。
02行目:
コメント。DROP_COUNTRY_MAKE関数が、指定された国のIPアドレスからのアクセスを破棄するユーザ定義チェイン作成であることを説明しています。
03行目:
DROP_COUNTRY_MAKE関数の始まり。関数定義は
関数名()
{
命令
}
という形で表記します。つまり03行目の”DROP_COUNTRY_MAKE(){“と09行目の”}”に囲まれている部分がこの関数の命令文になるわけですね。
04行目:
for文の始まり。for文は
for 値のリスト in 変数
do
命令
done
という表記で行います(原文ママ。値のリストと変数の位置は逆か?)。
for文は「値のリストを1つずつ指定された変数に代入し、命令を実行」するためのもので、「複数の引数が与えられた場合、それらの引数を一つずつ処理していくような用途で使用され」るとのことだそうです(『シェルスクリプト ポケットリファレンス bash編』、宮原 徹+河原龍人 著、技術評論社、2005年、43頁)。
つまり04行目の文は、
`cat /tmp/cidr.txt|grep ^$1|awk ‘{print $2}’`という(命令文で取り出される)値のリストを、1つずつaddrという変数に代入し、入れるごとに06行目、07行目の命令を実行し、終わったら次のリストの値をaddrに代入し・・・と続けていく、という命令になるわけです。
その`cat /tmp/cidr.txt|grep ^$1|awk ‘{print $2}’`ですが、これは3ブロックに分けられます。
一つ目はcat /tmp/cidr.txt。catはファイルを表示するコマンドですから(連結のコマンドでもある)、これは/tmp/cidr.txtを表示する、という意味になります。
それが「|」(パイプ)でつながれているので、この実行結果が次のブロックに引き渡されます。
二つ目はgrep ^$1。grepは文字列を検索するコマンドですが、問題は^$1の部分。これが一体何を意味するのかが実は分からなくて・・・orz
調べてみた限りでは、^も$も正規表現のようで、^は行頭を、$は文末を示すとのこと。そして^$の組み合わせで、空行を取り出す、というところまでは分かったのですが、だとすると最後の1は何・・・??
これが分からなくてずっと悩んでました。が、知恵袋で質問してみたら解決のヒントが。それはシェルスクリプトでは$1は第一引数を意味する、ということ。
これ、紹介されているスクリプトの全体を見るとわかるのですが(というか、後付けで分かったのですが)、このDROP_COUNTRY_MAKE関数は、後々第一引数を指定して実行されます。
つまり、この関数本体で指定している$1とは、この後々入ることになる(指定することになる)引数を意味していたわけです。
実際、シェルスクリプトで
#! /bin/bash
cat cidr.txt | grep ^$1>test.txt
cat test.txt
というスクリプトを組み(cidr.txtはカレントディレクトリに配置)、引数を与えて実行すると指定した引数、例えばCNならCNだけをgrepした実行結果がtest.txtに書き出され、それが表示されます。
これを理解するまで結構時間がかかってしまいました・・・orz
ま、まぁとにかくこの二つ目のコマンド部分では、指定した国コード(第一引数)で検索をかけて抽出、という作業を行うわけです。
そして最後のブロックであるawk ‘{print $2}’ですが、awkは「AWK は正確にはコマンドではなく、AWK スクリプト・インタプリタ」なんだそうです。この命令においては{print $2}が引数でありかつAWKのスクリプトのソースになるんだそうです。
要するに、awkの後で指定した$1とか$2とかで、第一フィールドの値を取り出す、とか、第二フィールドの値を取り出す、という命令になるんだそうです。
今回のコマンドでは第二フィールド、つまりネットワークアドレス部分を取り出す、ということになります。
つまり04行目で行っているのは、変数addrに、cidr.txtに対して後で指定する国コードで検索をかけ、取り出されたデータからネットワークアドレス部分だけを切り取った値を編入する、という操作を繰り返す、ということですね。
05行目(及び08行目):
doとdoneで行う操作をくくります。先の04行目の変数addrに値が入っている間、このdo~doneの操作を繰り返す、ということです。
06行目:
ここはiptablesでDROP_COUNTRY(ユーザ定義チェイン)に対してのLOG取得を記述しています。なお、DROP_COUNTRYはDROP_COUNTRY_MAKE関数定義の後に、iptables -N DROP_COUNTRYという形で定義されます(後述)。
私は今のところログ取得をさせる予定はない(あくまで排除のみ)ので、ここはちょっと割愛します。
07行目:
ここも06行目と同様、iptablesでDROP_COUNTRYユーザ定義チェインを実行します。ここでは-$addrという指定で、04行目で代入した変数の値をソースアドレスとして指定(そしてこの代入される変数は、関数自体に対して指定した第一引数によってgrepされたネットワークアドレスとなっている)し、それをDROPする、という操作になっています。
この06行目~07行目の一連の手順を、04行目で定義した変数addrに値が代入される間繰り返すことで、DROP_COUNTRY_MAKE関数に指定する引数、つまりCNやKRといった各国に割り振られているIPアドレスがある間繰り返され、当該ネットワークアドレスのすべてをDROPする、という段取りになっています。
DROP_COUNTRY_MAKE関数定義はこれで終了です。
次はIPアドレスリスト取得部分です。
01: # IPアドレスリスト取得
02: . /root/script/iptables_functions
03: IPLISTGET
ここも便宜上、行番号を振っています。
01行目:
コメント。ここでIPアドレスリストを取得します。
02行目:
行頭の“.”は外部ファイルを使用する際に使う記号で、sourceでも代用できるそうです。
つまり、source /root/script/iptables_functionsという書き方でもOKみたいですね。
03行目:
IPLISTGETを実行します。IPLISTGETは読み込んだiptables_functionsにて定義されている関数です。この関数で実際にcider.txtをネット上から読み取ってくる、という段取りですね。
そして最後が本丸です。
01: # 中国・韓国・台湾※からのアクセスをログを記録して破棄
02: # ※全国警察施設への攻撃元上位3カ国(日本・アメリカを除く)
03: # https://www.cyberpolice.go.jp/detect/observation.htmlより
04: iptables -N DROP_COUNTRY
05: DROP_COUNTRY_MAKE CN
06: DROP_COUNTRY_MAKE KR
07: DROP_COUNTRY_MAKE TW
08: iptables -A INPUT -j DROP_COUNTRY
01行目~03行目:
コメント。読んでそのままですね。
04行目:
ユーザ定義チェインを作成します。-Nは新しいチェイン作成のオプション。
05~07行目:
DROP_COUNTRY_MAKE関数を呼び出します。引数をつけて関数を実行することで、作成したユーザ定義チェインにルールを指定しています。
例えば05行目なら、引数(これが$1になるわけですね!)CNを与えてDROP_COUNTRY_MAKEを実行しているので、cidr.txtからCNの国コードを持つ行のネットワークアドレス部分だけが取り出され(xxx.xxx.xxx.xxx/24など)、取り出されたこの値(変数addrに格納される)に対して、
iptables -A DROP_COUNTRY -s $addr -j DROP
がルールとして設定されるわけです。
このルールは、04行目で定義したDROP_COUNTRYというユーザ定義チェインに、xxx.xxx.xxx.xxx/24をソースアドレスとして入ってくるパケットをDROPせよ、という命令になるわけですね。
これがCNだけでなく、KR(韓国)、TW(台湾)に対しても行われるので、ユーザ定義チェインDROP_COUNTRYはCN、KR、TWからのパケットを破棄せよ、という命令セットになるわけです。
08行目:
最後に、入ってくるパケットに対して、それをまずユーザ定義チェインDROP_COUNTRYに通させます。
ユーザ定義チェインはルールセットですから、実際には入ってきたパケットに対してこのルールセットへ誘導する記述が必要になるわけですね。
iptables -A INPUT -j DROP_COUNTRY
によって、まず入ってきたパケットはDROP_COUNTRYチェインに渡され、もしそのパケットがCN、KR、TWからのものであったら破棄する、そうでなければ次のチェインへ・・・という流れになっていきます。
ここで重要だと思われるのは、かつて私も失敗しましたが、iptablesではルールは上から順に実行されるということ。
仮にhttpsを通したい、でもCN、KR、TWからのパケット入れたくない、と言うときは、入れさせないためのルール→入れるためのルールという順番で記述しないとダメということです。
なので、参照先で紹介されているスクリプトでも、まずこのDROP_COUNTRYチェインへ通してから、各種サービスの公開設定を行う、という流れになっています。
これが、必要になる3つのスクリプトのうちの、メイン部分の説明です。
次は、IPアドレスリスト(つまりcidr.txt)を取得するためのスクリプトと、IPアドレスリストを自動更新するスクリプトを読み解いていきます。
04: for addr in `cat /tmp/cidr.txt|grep ^$1|awk ‘{print $2}’`
の部分ですが、inのあと(とその最後)にくる記号は、シングルクォートではなく、バッククオートです。したがって正しくは、
04: for addr in `cat /tmp/cidr.txt|grep ^$1|awk ‘{print $2}’`
となります。
シングルクォートはShift + 7(’)
バッククォートはShift + @(`)
です。
この違いに気づかずにトライして、cidr.txtがbad argumentと返されて困り果ててしまいました・・・(笑)