はじめに
初めまして、私は現在リンクアンドモチベーションにてバックエンドエンジニアをしている中﨑です。
先日、私はIPアドレス制限機能の実装を任されましたが、お恥ずかしながらIPアドレスについてほとんど知りませんでした、、、
そんな私がリリース完了するまでに学んだことを記載していきたいなと思っています。
私と同じように「IPアドレスについてあんまり分かっていないな」という方や、「IPアドレス制限の実装をするよ」という方の参考になれば幸いです。
目次
基本知識
IPアドレスの基本的な知識を身につけるために、以下の記事を読ませていただきました
IPアドレスでよく出てくる用語に関して、わからない部分があれば個別のインプット記事を読むと良いと思います
全体的なインプット
【図解】IPアドレスの仕組み(クラス分類からサブネットまで)
IPアドレスの基礎知識
個別のインプット
IPアドレス
CIDR表記
アドレスクラス
サブネットマスク
グローバルアドレスとプライベートアドレス
ネットワーク部とホスト部
IPアドレスに関わるライブラリ
ipaddressの基本的な機能
標準ライブラリのipaddrは割愛させていただきます
require 'ipaddress'
# オブジェクトの作成
ipaddress = IPAddress("192.0.2.0/24")
=> #<IPAddress::IPv4:0x000000000421e8d8 @address="192.0.2.0", @prefix=24, @octets=[192, 0, 2, 0], @u32=3221225984>
# アドレスの取得
ipaddress.address
=> "192.0.2.0"
# prefixの取得
ipaddress.prefix
=> 24
# ネットマスクの取得
ipaddress.netmask
=> "255.255.255.0"
# cidr表記で取得(cidr表記でないものは/32が補完される)
ipaddress.to_string
=> "192.0.2.0/24"
# binaryでの取得
ipaddress.bits
=> "11000000000000000000001000000000"
# 32bitでの取得
ipaddress.to_u32
=> 3221225984
# ipv4形式→ipv6形式へ変換
ipaddress.to_ipv6
=> "c000:0200"
# IPv4クラスを指定してオブジェクトの作成
ipv4 = IPAddress::IPv4.new("192.0.2.0/24")
=> #<IPAddress::IPv4:0x00000000080fd1d0 @address="192.0.2.0", @prefix=24, @octets=[192, 0, 2, 0], @u32=3221225984>
# IPv6クラスを指定してオブジェクトの作成
ipv6 = IPAddress::IPv6.new("2001:db8::/32")
=> #<IPAddress::IPv6:0x0000000007f66e70 @groups=[8193, 3512, 0, 0, 0, 0, 0, 0], @address="2001:0db8:0000:0000:0000:0000:0000:0000", @compressed="2001:db8::", @prefix=32>
※より詳細な使用方法やメソッドについてはドキュメントを参照ください
IPアドレス制限を実装した時のこと
前提
取得したクライアントIPは完全に信用できるものではないので、あくまでアクセス制限するための1つの要素です。
今回はIP制限する際に必要なことの一部を記載しております。セキュアにクライアントIPを取得するための方法などは下記を参照ください。
参照
https://www.m3tech.blog/entry/x-forwarded-for
https://mrk21.hatenablog.com/entry/2020/08/06/214922
##IPアドレスの入力チェック(バリデーション)
主に、前章で記載したipaddressライブラリを用いて、どのようにバリデーションをしていくかを記載しております。
IPのフォーマットとして正しいか
不正なフォーマットでIPAddressのオブジェクトを作成しようとするとArgumentErrorが発生するのを利用した
IPv4/IPv6のフォーマットの違いの考慮や正規表現を使わなくていいのがありがたい、、、
require 'ipaddress'
def validate_ip_format(ips)
ips.each { |ip| IPAddress(ip) }
true
rescue ArgumentError
false
end
実行結果
# IPの形式が正しいとき
ips = ["192.0.2.0/24", "198.51.100.0/24"]
validate_ip_format?(ips)
=> true
# 不正なフォーマットのとき
ips = ["192", "198.51.100.0/24"]
validate_ip_format?(ips)
=> false
ips = ["hoge", "198.51.100.0/24"]
validate_ip_format?(ips)
=> false
# prefixが不正なとき
ips = ["192.0.2.0/33", "198.51.100.0/24"]
validate_ip_format?(ips)
=> false
重複しているものがないか
cidr表記に変換してくれる"#to_string"メソッドを用いることで重複を除く
"192.0.2.0"と"192.0.2.0/32"を重複しているとみなしてくれるのがありがたい、、、
require 'ipaddress'
def validate_ip_uniqueness(ips)
check_ips = ips.map { |ip| IPAddress(ip).to_string }
check_ips == check_ips.uniq
end
実行結果
# 重複がない時
ips = ["192.0.2.0/24", "198.51.100.0/24"]
validate_ip_uniqueness(ips)
=> true
# 重複があるとき
ips = ["192.0.2.0/24", "192.0.2.0/24"]
validate_ip_uniqueness(ips)
=> false
# 個別IPとcidr表記で重複があるとき
ips = ["192.0.2.0", "192.0.2.0/32"]
validate_ip_uniqueness(ips)
=> false
IP制限検証時の性能について
お客様が登録したいIPアドレス192.0.2.0/24
だと仮定する
※コードにすると複雑になるため割愛させていただいてます
悪い例
192.0.2.0/24
の範囲は192.0.2.0 ~ 192.0.2.255
だから1つ1つ合計256個保存する
(***.***.***.***
のIPアドレスからアクセスがきた)
登録されている256個のIPアドレスの中に***.***.***.***
が含まれているか検証する
○実際の住所のイメージ
登録された住所が東京都中央区銀座1丁目〇〇 ~ 東京都中央区銀座8丁目△△
だから全部登録する
(東京都****
の人がやってきた)
東京都中央区銀座1丁目〇〇 ~ 東京都中央区銀座8丁目△△
の中にこの人が含まれているか検証する
※東京都銀座は1丁目から8丁目まで
何が良くないのか
- 許可するIPをメモリにキャッシュしている場合
- メモリにキャッシュする際に時間がかかってしまう
- メモリにキャッシュするデータ容量が大きくなり、逼迫してしまう
- 検証時に時間がかかってしまい、ユーザー体験が悪くなってしまう
良い例
192.0.2.0/24
のネットワーク部分は110000000000000000000010
(***.***.***.***
のIPアドレスからアクセスがきた)
***.***.***.***
のアドレスをbitに変換して、先頭24文字目までが110000000000000000000010
と一致しているか検証する
○実際の住所のイメージ
登録された住所が東京都中央区銀座1丁目〇〇 ~ 東京都中央区銀座8丁目△△
だから東京都中央区銀座
かどうかを検証すればいい
(東京都****
の人がやってきた)
東京都中央区銀座
に一致しているか検証する
何が良いか
- binaryでの検証になるので、速度が速い
- キャッシュするデータ容量が少なくて済む
ドキュメント用に記載するIPアドレスについて
RFCによって定められている下記のIPを使用すると良い
IPv4
192.0.2.0/24
198.51.100.0/24
203.0.113.0/24
IPv6
2001:db8::/32
参照
RFC5737 IPv4 Address Blocks Reserved for Documentation
RFC3849 IPv6 Address Prefix Reserved for Documentation
さいごに
ここまで、記事を読んでくださりありがとうございます。
私自身、エンジニアとして2年目になりますが、
お恥ずかしながら、IPアドレスの機能化の実装を任されて初めてちゃんとIPアドレスについての知識をインプットしました。
IPアドレスのようによく聞くけど、実はあんまりわかっていないことがまだまだたくさんあることにも気づかされました。例えばプロキシとか、、、、
私と同じように、聞いたことあるけど実はあまり知らないな。という人に少しでも参考になっていますと幸いです。
弊社アドベントカレンダーではたくさんの記事が投稿されています!よかったらご覧ください!