IPアドレスブロックを集中管理する方法と、その活用法

やりたいこと

  • 携帯3キャリアのIPアドレスブロックは比較的頻繁に変わるので自動更新したい
  • 自宅やオフィスその他のIPアドレスは、変わることがあまりないので手動管理でいい
  • これらIPアドレスブロックの情報は、後述する通りいろいろなところで使いたい
    • 即ち、いろいろな形式で表現したい
  • このように、頻度の差こそあれ、IPアドレスブロックは増減したり新しいブロックが追加したりするので、簡素な機構で包括的な管理をしたい。

実現方法

スクリプトを2つ書きました。いずれもgithubにあります。

図も用意しました。


update-mobilejp-cidr

キャリアのサイトをスクレイピングして、指定されたディレクトリにその情報を書き出します。

その形式は、1行1アドレスブロック、コメントは "#" です。

$ cat plain/docomo
124.146.174.0/24
124.146.175.0/24
...
$ cat plain/softbank
123.108.236.0/24
123.108.237.0/27
...
generate-cidr-files

指定されたディレクトリからIPアドレスブロックを読み込み、いくつかの形式に変換して書き出します。

読み込める形式は、1行1アドレスブロック、コメントは "#"、つまり update-mobilejp-cidr が書き出す形式です。

手動で変更を反映するIPアドレスブロックファイル(図中だと「office」「home」)も、同じ形式で update-mobilejp-cidr が書き出すのとディレクトリに置いておきます。

つまり、generate-cidr-filesは、いろいろな方法(自動、手動)で変更されるがその形式は統一されたファイル群が収められている、とあるディレクトリ (/etc/ip.d/plain/) からデータを読み込みます。

読み込んだデータは、情報はそのままに形式を違えてまた別なディレクトリにファイルとして書き出します。

今のところ、サポートしているのは YAML (単一ファイル) と Pair (CIDR ブロック名;の形式で単一ファイル) の2種類です。

$ cat yaml/cidr
---
docomo:
  - 124.146.174.0/24
  - 124.146.175.0/24
...
softbank:
  - 123.108.236.0/24
  - 123.108.237.0/27
...
office:
  - XXX.XXX.XXX.XXX
home:
  - YYY.YYY.YYY.YYY

$ cat pair/cidr
124.146.174.0/24   docomo;
124.146.175.0/24   docomo;
...
123.108.236.0/24   softbank;
123.108.237.0/27   softbank;
...
XXX.XXX.XXX.XXX    office;
YYY.YYY.YYY.YYY    home;
運用

意識して変更するのは /etc/ip.d/plain/ の下のファイルのみです。

「office」「home」のように手動更新のものは、更新したのち、手動で generate-cidr-files を実行して他の形式に反映します。

携帯3キャリアのは、cronで update-mobilejp-cidr を定期的に実行するようにすれば、plain/* の更新は自動化できますし、update-mobilejp-cidr は更新があった場合は終了コード 0 で終了するので、このようにすれば必要に応じて generate-cidr-files を実行することができます。

update-mobilejp-cidr -o /etc/ip.d/plain/ && generate-cidr-files -i /etc/ip.d/plain/ -o /etc/ip.d/

活用法

plain

mod_cidr_lookup の入力データとして使えます。

mod_cidr_lookupは、当該IPアドレスがどのIPアドレスブロックに属するかを非常に高速に検索できる点、IPアドレスブロックの数が多くなっても検索スピードが劣化しない点が特長です。(実測していませんが) 原理的に、ApacheのAllowディレクティブを使って大量の Allow from XXX を列挙するのより、かなり高速なはずです。

mod_cidr_lookup の使い方は、そのプロジェクトページに詳しいです。

yaml

多くの言語ではYAMLを扱うライブラリ等がありますので、アプリケーションレイヤでIPアドレスブロックの情報を扱いたい場合には、この形式がよいでしょう。

pair

Nginxのgeoモジュール の入力データとして使えます。

追いきれてませんが、ざっくり実装を見た感じでは、geoモジュールはひとつの木構造IPアドレスブロック群の情報を格納し、検索しているようなので、遅くはないと思います。Nginxで、Apacheのmod_cidr_lookup相当のことを行うには、geoモジュールを使うのはリーズナブルな選択だと思います。

使い方はこんな感じです。

http {
  # HTTPクライアントのIPアドレスについて、/etc/ip.d/pair/cidr の
  # データにマッチすればそのIPアドレスブロック名を、マッチしなけ
  # れば (defaultで指定している) 「no」を、$clienttype に格納します。
  ...
  geo $clienttype {
    default no;
    include /etc/ip.d/pair/cidr;
  }

  # 変数 $clienttype は、
  # ログに出力したり、
  log_format  full  '$msec [$time_local] $remote_addr $remote_user '
                    '"$request" $status '
                    '$request_length $body_bytes_sent $request_time '
                    '"$http_referer" "$http_user_agent" $clienttype';

  # アクセス制御に使えます。
  server {
    ...
    location / {
      # モバイルとオフィス以外からは拒否
      if ($clienttype !~* '^(docomo|softbank|ezweb|office)$') {
        return 403;
      }
    ...