nginxにて、IPアドレスを用いてアクセス制限する方法を、以下3パターンで記載します。
- ClinetIPにてアクセス制限を行う方法
- ロードバランサー等を経由している通信で、ClinetIPにてアクセス制限を行う方法
- ロードバランサー等を経由している通信で、経路途中の機器のIPにてアクセス制限を行う方法
環境
- nginx version: nginx/1.15.12
1.ClinetIPにてアクセス制限を行う方法
nginx.configの、serverやlocationコンテキストにて、 allow
や deny
ディレクティブでIPアドレスを指定します。
以下の例では、指定したバーチャルホストにて、 223.XXX.XXX.XXX
のみアクセスできるサイトとなります。
server { listen 8000; server_name sample1.example.com; access_log /var/log/nginx/8000-access.log main; error_log /var/log/nginx/8000-error.log; allow 223.XXX.XXX.XXX; deny all; location / { root /www/dir; index index.html index.htm; } }
記載した順番で上から評価されるので、223.XXX.XXX.XXXをallowした後に、allをdenyしています。
アクセスログの出力内容です。
223.XXX.XXX.XXX - - [18/Apr/2019:15:16:10 +0000] "GET / HTTP/1.1" 200 39 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36" "-" 103.4.10.234 - - [18/Apr/2019:15:20:26 +0000] "GET / HTTP/1.1" 403 556 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36" "-"
2.ロードバランサー等を経由している通信で、ClinetIPにてアクセス制限を行う方法
例えば、AWSで以下のようにCloudFrontとELBを介してnginxに接続してくる場合、ClinetIPにはELBのIPが登録され、X-Forwarded-Forに実際のブラウザを表示しているPCのIPが記録されています。
アクセスログは以下のようになります。
172.31.34.50 - - [20/Apr/2019:04:24:54 +0000] "GET / HTTP/1.1" 200 39 "-" "Amazon CloudFront" "223.XXX.XXX.XXX, 52.46.50.66"
こういった場合に、PCのIPにてアクセス制限する方法です。
ngx_http_realip_module
を利用することになるので、このモジュールが有効になっているかどうか、まず確認します。通常はデフォルトで有効になっています。
$ nginx -V 2>&1 | grep -o -e '--with-http_realip_module' --with-http_realip_module
nginx.confに、設定を追加します。
server { listen 8000; server_name sample1.example.com; set_real_ip_from 0.0.0.0/0; real_ip_header X-Forwarded-For; real_ip_recursive on; allow 223.XXX.XXX.XXX; deny all; access_log /var/log/nginx/8000-access.log main; error_log /var/log/nginx/8000-error.log; location / { root /www/dir; index index.html index.htm; }
set_real_ip_from
には、ForwardされてくるIPアドレス情報を登録します。今回は0.0.0.0/0を指定して、全アドレスを許可しています。 real_ip_header X-Forwarded-For
とすることで、実際のClinetIPをX-Forwarded-Forヘッダーから拾ってきてくれます。real_ip_recursive
を有効にすることで、ClientPCのIPと変換してくれます。無効にしていると、CloudFrontのIPに変換されます。
アクセスログは以下となります。
223.XXX.XXX.XXX - - [20/Apr/2019:04:42:51 +0000] "GET / HTTP/1.1" 200 39 "-" "Amazon CloudFront" "223.XXX.XXX.XXX, 52.46.50.63"
3.ロードバランサー等を経由している通信で、経路途中の機器のIPにてアクセス制限を行う方法
つまり、X-Forwarded-forに、特定のIPアドレスが入っている場合にのみ、WEBサーバへアクセス許可したい、といった場合の方法となります。
コンフィグ参考例です。
http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; map $http_x_forwarded_for $allowed { default deny; ~52.46.50.63 allow; } server { listen 8000; server_name sample1.example.com; access_log /var/log/nginx/8000-access.log main; error_log /var/log/nginx/8000-error.log; location / { if ( $allowed = "deny" ) { return 403; } root /www/dir; index index.html index.htm; } location /healthcheck { allow 172.31.0.0/16; deny all; alias /www/dir/; index index.html index.htm; } } }
httpコンテクストにて、mapモジュールにてmap関数を指定しています。 $http_x_forwarded_for
の値に応じて $allowed
に値を代入しています。52.46.50.63というのはCloudFrontで利用しているIPなので、その場合にのみallowと代入する形です。
serverコンテクストの /
を指定したlocationコンテクストにて、if文を指定しています。 $allowed
変数の値がdenyの場合、403Errorを返す処理をしています。これで52.46.50.63を経由したIPのみを許可することができます。
Module ngx_http_rewrite_module
/healthcheck
を指定しているlocationコンテクストは、ELBのヘルスチェック用のものになります。
アクセスログは、以下のようになっています。ClinetIPに記載されている172.31.17.181はELBのIPアドレスです。
172.31.17.181 - - [20/Apr/2019:06:31:52 +0000] "GET / HTTP/1.1" 200 39 "-" "Amazon CloudFront" "223.XXX.XXX.XXX, 52.46.50.63"
メモ1
パターン3にて、CloudFrontのIPアドレスを指定してアクセス制限を設けていますが、CloudFrontで利用されるIPアドレスは動的に更新されます。そのため実際の利用する場合は、CloudFrontのIPアドレスが変更される都度、nginx.confを変更する必要が出てきます。当然そんなめんどくさい事できないので、AWS WAFを用いカスタムヘッダを用いた管理方法が紹介されています。
AWS WAFを利用してCloudFrontのELBオリジンへ直接アクセスを制限してみた | DevelopersIO
メモ2
nginx.confの中でmap関数やif文が使えるとは、知りませんでした。ただし、if文の利用には公式に If Is Evil
というドキュメントがあり、あまり利用推奨されていないようです。
returnやrewriteの利用はif文中でも問題ないと、上記のページには記載ありますが、基本的にはif文の利用はイレギュラーなものなのだと思っています。