ブロックリスト自動生成その2(素人のサーバー管理)

手動起動によるメンテナンス機能をまとめると(順不同)

  1. 指定した(規定の)ログファイルを検索して不正アクセスのパターンにマッチしたIPアドレスを収集する
  2. 規定回数(例:5回)以上の不正アクセスが確認できたIPアドレスをリスト化
  3. 現行ブロックリスト(ufwの場合:/etc/ufw/before.rules)より現行のブロックリストを読み出し、新しく検出したIPアドレスリストを重複しないようにマージ(基本的には重複しないはず)
  4. オプション機能としてブロックリストの整理ーアクセスの多いIPアドレスのノードを指定幅ネットマスクでまとめる
  5. 新しいブロックリストを更新し、ファイヤーウォールを再起動 ー オプション:基本的には確認のみ

ipv4adrsetクラス

1のIPアドレスのパターンマッチは普通に正規表現と比較だけですむが、ブロックリストにはネットマスクでグループ化した形式(xxx.yyy.zzz.0/24)などに指定アドレスが含まれるかどうかの判定も必要になる。おそらく便利なライブラリがあるとは思うが、ここは勉強を兼ねてクラスを作成した

class ipv4adrset:
    def __init__(self):
        self.ips = []
        self.msk = 32
    def __init__(self, line = None):
        self.set_line(line)

    def set_line(self, line):
        if line is None:
            self.ips = []
            self.msk = 32
        else:
            res = re.match('^([0-9\.]+)\/([0-9]+)$', line)
            (ip, msk0) = (res.group(1), res.group(2)) if res else (line, 32)
            self.ips = list(map(lambda n: int(n), ip.split('.')))
            self.msk = int(msk0)

    def set_mask(self, mask):
        if self.msk < mask:
            return self
        msks = list(map(lambda n: int('1'*n+'0'*(8-n), 2), [8 if (mask - i*8) >= 8 else (mask - i*8) if (mask - i*8) > 0 else 0 for i in range(4)]))
        rip = list(np.array(self.ips) & np.array(msks))
        ret = ipv4adrset()
        ret.ips = rip
        ret.msk = mask
        return ret

    def __eq__(self, other):
        smsks = list(map(lambda n: int('1'*n+'0'*(8-n), 2), [ 8 if (self.msk - i*8) >= 8 else (self.msk - i*8) if (self.msk - i*8) > 0 else 0 for i in range(4)]))
        omsks = list(map(lambda n: int('1'*n+'0'*(8-n), 2), [ 8 if (other.msk - i*8) >= 8 else (other.msk - i*8) if (other.msk - i*8) > 0 else 0 for i in range(4)]))
        sip = list(np.array(self.ips) & np.array(smsks) & np.array(omsks))
        oip = list(np.array(other.ips) & np.array(smsks) & np.array(omsks))
        return sip == oip

    def __str__(self):
        return '.'.join(map(lambda i: str(i), self.ips)) + ('/' + str(self.msk) if self.msk < 32 else '')

  • メンバ変数self.ipsは4要素整数の配列でアドレスを示しており、self.mskはネットマスクピット数でデフォルトは全マスクで32
  • メゾッドset_lineはIPアドレスのマスク有り無しの文字列を読み込みオブジェクトの値をセットする
  • メゾッドset_maskはマスクピット幅をセットする。マスク外のビットはゼロにする
  • メゾッド__eq__は比較関数だが、ネットマスク幅の短い方で比較する。指定したアドレスがあるネットマスクに含まれるかどうかを判別出来る
  • メゾッド__str__は文字列変換関数

改めて見てみるとリスト内包表記で無理に一行で書こうとしている感じで可読性が非常に悪いですね。。。

とくに1文メチャクチャ長いリスト内包表記があるので、覚書きついでに解説

msks = list(map(lambda n: int('1'*n+'0'*(8-n), 2), [8 if (mask - i*8) >= 8 else (mask - i*8) if (mask - i*8) > 0 else 0 for i in range(4)]))

これは、整数値のネットマスク幅を、IPアドレスリストと同じ形式のマスクビット配列に変換する1文となる。(例えば24 → 11111111.11111111.11111111.00000000

[8 if (mask - i*8) >= 8 else (mask - i*8) if (mask - i*8) > 0 else 0 for i in range(4)]

これが1から32までの数字mskに対して、8ビットずつ4分割し、足りない分は余りと、ゼロとする構文。mskを8で割って、配列の上から「商」の数だけ8として、次に「余り」残りをゼロとする。

map(lambda n: int('1'*n+'0'*(8-n), 2), LIST)

上記配列の要素に対し上位を1で埋めたビット表記に変換して、2進数で数値化

例)3 → 11100000 → 0xE0

うーん、コーディング進めている間にこんなふうにまとめてしまった。デバッグとかも難しそうだし、どうするつもりだったんだろう(笑)

コメントを残す

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

*

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)