以前書いたエントリの続き。
管理しているサーバの1つが、同一IPから1000回以上のアタックを受けていたので、今度こそ自動的に拒否リストに追加する仕組みを考えてみる。
rubyで実装するとして、デーモンとして常駐するとかhosts.allowから呼び出すとかも考えたけど、あんま激しく呼び出されるとそれ自体が負荷をかけてしまう(ruby自体がけっこう重たいし)ということで、ヌルくcronから呼び出すことにする。

と言うわけで、要件はこんな感じ。

  • 一定時間ごとにcronで起動
  • /var/log/messagesを解析してアタックを行っているIPアドレスを抽出し、deny.listに追加する
  • 回数が閾値以下の場合は追加しない(自分で間違ってアクセスしたとたんにBANとかは避けたい)
  • ターンオーバーして圧縮されたログも解析できるようにする
  • IPアドレスは重複して登録しない、またできればdeny.listは毎回ソートする
  • ホワイトリストに登録されているIPアドレスは登録しない

半日くらいで書けた。名前はギブスンにあやかってIcewallとしてみた。すごくなんかと被ってそうです。
以下、工夫したこととか。


せっかくなのでoptparse使ってみた。
実は初めて。PerlでGetopt::Longとか使った朧気な記憶が。


opt = OptionParser.new
opt.on(‘-d’, ‘–deny=DENY_ADDRESSES’, String, ‘specify IP addresses to deny.’) {|var| @denyaddr << var } opt.on(‘-q’, ‘–quiet’, ‘quiet mode.’) { @quiet = true } opt.parse!(ARGV) [/ruby] とかやると、–helpを付けて起動すればそれっぽいヘルプを表示してくれる。 でもなんか複数のパラメタをちゃんと取れてない気がする。 IPアドレスの正規表現は、一発でマッチするように [ruby] str.scan(/(\d|[01]?\d\d|2[0-4]\d|25[0-5])\.(\d|[01]?\d\d|2[0-4]\d|25[0-5])\.(\d|[01]?\d\d|2[0-4]\d|25[0-5])\.(\d|[01]?\d\d|2[0-4]\d|25[0-5])/) [/ruby] としてみたけど、なんか誤動作する(^$でくくれば一致するんだけど)。あれこれ悩んだけど面倒になったので、 [ruby] str.scan(/\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}\b)/).select{|a| a.all?{|b| (0..255).include?(b.to_i)}}.map{|a| a.join(‘.’)} [/ruby] という超強引な方法で解決。ほ、ほら全部正規表現で書くより短いよ! ソートはこんな感じ。これは昔Perlでさんざんやった覚えがある。 [ruby] addresses.sort_by{|a| a.split(“.”).map {|c| c.to_i}.pack(“C4″)} [/ruby] その他、最初はパターンをコマンドラインに書いてたけど、やってみたらcronをいくつも書くのがめんどくさかったので、YAMLでレシピを書くようにしてみた。これなら新しい種類のアタックがあっても対応しやすい。 圧縮ログについては、bzip2ライブラリがよく分からなかったので、標準入力を受け付けるようにしてbzcatとパイプで繋げた。結果的にこの方が便利だけど若干敗北感。 APIをかっこよくしたくてActionHelperあたりのソースを読んだけど、それほど難しいことをしてるわけでもなく分かりやすかった。 あと、クラス書くときにrspec使ってみたりした。 さっそくcronで回して観測中。今のところうまく行ってるみたい。 ちゃんと動作して気が向いたら公開します。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>