2015年11月12日木曜日

iptable の備忘録

iptable で http ポートへのアクセス制限をした記録

VPS で運用している nginx を自宅と sim 端末からだけアクセスできるようにした。

事前準備として必要なことが1点。iptables の設定ミスで ssh が不通になった場合に備え、VPS のコンソールが取れ、 root でログインできることを確認すること。

まず自宅から VPS で動かしている sshd と nginx へアクセスできるようにするには、こんな感じの設定ファイルを作成する。
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -s <Home IPAddress> -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -s <Home IPAddress> -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
COMMIT

  1. 1行目はルールを記載するテーブル として filter を指定している。これがデフォルトのテーブルであり、この備忘録ではおまじないと考えることにする。
  2. 2行目では INPUT チェインのポリシーを DROP に設定。入ってきたパケットは全て捨てるポリシーを指定している。この後、受け取りたいパケットのルール(例外)を登録する。
  3. 3行目では FORWARD チェインのポリシーを DROP に設定。転送はしない。
  4. 4行目はVPS から外へ出て行くパケットは全て許可。
  5. 5行目は ssh を開けている。 -A INPUT では、ルールを登録するチェインとして INPUT を指定している。チェインはパケットの選択ルールを登録するためのリストである。デフォルトテーブル filter で VPS へのアクセス制限をする場合、組み込みチェインは INPUT, OUTPUT, FORWARD の3つになる。この他にユーザ定義チェインを作ることもできるが、それについては一旦置いておく。この行では VPS に入るパケットを選ぶルールを指定したいので、INPUT チェーンに対してルールを設定する。-s でアクセス元の自宅 IP アドレスを指定、ssh を 22 番ポートで動かしているので --dport 22 としている。末尾の -j オプションは --jump の省略形で、ACCEPT という組み込みターゲットを指定している。入ってきたパケットが -s で指定したアドレスからで、22 番ポート宛であれば、この行がマッチして、 -j で ACCEPT と指定しているので、パケットは VPS 内の sshd に届く。
  6. 6行目は http を開けている。5行目と同様に INPUT チェインに対してルールを設定している。
  7. 7行目は ping を受けとる。
  8. 8行目は自分自身へのパケットを受け取るようにしている。
  9. 9行目はVPS から外部ホストへアクセスした結果、返ってくるパケットを受けとるようにしている。
設定ファイルを例えば /etc/iptables.up.rules として保存して、 iptables-restore で以下のように読み込ませれば、その時点からアクセス制限がかかる。
iptables-restore < /etc/iptables.up.rules
但し iptables-restore コマンドは、オプションを指定しないと、すでに登録されていたルールが全て消える。例えば ssh 許可のルールが登録されている状況で、オプション無しで実行すると、ssh が不通になる。既存のルールをフラッシュせずにルールを追加するにはオプション --noflush を付ける。

次にSIM 端末からのアクセスはどうするか。現在使っている SIM は iijmio で、APN をみると vmobile.jp とある。そこで WHOIS で IIJNET を調べ、そのうちで vmobile.jp である IP アドレスを調べてみる
whois -h whois.apnic.net IIJNET | grep inetnum
 ずらずらと出てくる(結果は省略)。この中から vmobile.jp なアドレスを拾う ruby スクリプトを作った。
require 'ipaddr'

NSLOOKUP =         '/usr/bin/nslookup'
WHOIS    =         '/usr/bin/whois'
GREP     =         '/bin/grep'

class IPAddr
  class << self

    def ipcount(ip1, ip2)
      bit = 0
      ip = ip1.dup
      loop {
        while ! ( (ip >> bit) == (ip1 >> bit) )
          bit += 1
        end

        if ip == ip2
          break
        end
        ip = ip.succ
      }
      "#{ip1}/#{32 - bit}"
    end

  end
end

module Net
  class << self

    def hostname(ip)
      `#{NSLOOKUP} #{ip}` =~ /name = (\S+)\./m
      $1
    end

    def get_ranges(netname)
      ranges = `#{WHOIS} -h whois.apnic.net #{netname} | #{GREP} inetnum`.split(/\n/)
      ranges.map {|line|
        line =~ /inetnum:\s+(\S+) - (\S+)/
        (IPAddr.new($1) .. IPAddr.new($2))
      }
    end

  end
end

rules = open('/etc/iptables.d/nginx.rules', 'w')

rules.puts <
*filter
-F nginx
EOF

ranges = Net.get_ranges('IIJNET')
ranges.each do |rg|
  host = Net.hostname(rg.first.to_s)
  if host && host =~ /vmobile/
    m = IPAddr.ipcount(rg.first, rg.last)
    rules.puts "-A nginx -s #{m} -p tcp -m tcp -j ACCEPT"
  end
end

rules.puts <<EOF
COMMIT

EOF

rules.close

このスクリプトを動かすと下記の /etc/iptables.d/nginx.rules というファイルが作られる。
*filter
-F nginx
-A nginx -s <IPAddress range 1> -p tcp -m tcp -j ACCEPT
-A nginx -s <IPAddress range 2> -p tcp -m tcp -j ACCEPT
-A nginx -s <IPAddress range 3> -p tcp -m tcp -j ACCEPT
COMMIT


  1. 1行目はおまじない
  2. 2行目は -F で nginx というユーザ定義チェインのルールをフラッシュしている。この nginx というユーザ定義チェインについては後でまた触れる。
  3. 3行目以降でユーザ定義チェイン nginx にたいしルールを追加している。iijmio のアドレスレンジは幾つかあるので、それぞれを -s で指定し、-j ACCEPT で許可している。
このルールをロードする前に、ユーザ定義チェイン nginx を作成する。ユーザ定義チェインの作成は -N <チェイン名> で行う。詳細は後述するが、-N nginx でチェインを作成したら以下のコマンドでチェイン nginx にルールを登録できる。
iptables-restore --noflush < /etc/iptables.d/nginx.rules
この段階では、チェイン nginx が評価されることはない。このチェインを評価させるにはチェイン INPUT で、ポート 80 番へのパケットが来た時にチェイン nginx に jump するよう指示すればよい。そこで一番最初に作った設定ファイル /etc/iptables.up.rules に2行追加する。
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-N nginx
-A INPUT -s <Home IPAddress> -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -s <Home IPAddress> -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT                     -p tcp -m tcp --dport 80 -j nginx
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
COMMIT

追加したのは 5 行目の -N nginx と 8 行目である。5 行目で チェイン nginx を作成し、8 行目では、80番ポートに来たパケットのうち自宅 IP アドレスにマッチしなかった( 7 行目でマッチしなかった)ものは、-j nginx で、チェイン nginx に飛ばすように指定している。

後は、起動時に /etc/iptables.up.rules と /etc/iptables.d/nginx.rules を順に読み込むスクリプトを書くなりすればいい。

#!/bin/bash
/sbin/iptables-restore < /etc/iptables.up.rules
/sbin/iptables-restore --noflush < etc/iptables.d/nginx.rules

0 件のコメント:

コメントを投稿