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行目はルールを記載するテーブル として filter を指定している。これがデフォルトのテーブルであり、この備忘録ではおまじないと考えることにする。
- 2行目では INPUT チェインのポリシーを DROP に設定。入ってきたパケットは全て捨てるポリシーを指定している。この後、受け取りたいパケットのルール(例外)を登録する。
- 3行目では FORWARD チェインのポリシーを DROP に設定。転送はしない。
- 4行目はVPS から外へ出て行くパケットは全て許可。
- 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行目は http を開けている。5行目と同様に INPUT チェインに対してルールを設定している。
- 7行目は ping を受けとる。
- 8行目は自分自身へのパケットを受け取るようにしている。
- 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行目はおまじない
- 2行目は -F で nginx というユーザ定義チェインのルールをフラッシュしている。この nginx というユーザ定義チェインについては後でまた触れる。
- 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