2016年のnginx advent calendarです。
ギリギリまでトピック決まらず、簡単な内容になってしまいました・・・
nginxでのアクセス制御
なんらかのAPIのフロントエンドにnginxを使用している場合、パブリックなセグメントからは特定のHTTPメソッド(POSTやDELETEなど)を禁止し、GETのみを許可したい場合があると思います。
サーバーをパブリック用とプライベート用に分けるという事も考えられますが、今回は同一サーバーにてパブリックなアクセスを制限書けたいと思います。
前提
前提 1
server {
listen 80;
# 省略
location / {
proxy_pass http://backend;
}
}
このような形で、httpのリクエストをTCP80番でリクエストを受け、backendに流すという構成を想定します。便宜上backendはどのメソッドのリクエストも200で返すようになってます。
前提 2
セグメント | CIDR |
---|---|
プロダクション(制限を書けたくない) | 10.XX.0.0/16 |
会社のセグメント(制限をかけたい) | 192.168.YY.0/22 |
特に制限をかけていない状態
curlでリクエストを送ってみてアクセスログを見てみると、どのセグメントからもGET
PUT
DELETE
すべてHTTP Status200が返っています。アクセス制限かけてないので当たり前ですね。
127.0.0.1 - - [03/Dec/2016:07:57:08 +0000] "GET / HTTP/1.0" 200 4 "-" "curl/7.47.1" "-"
127.0.0.1 - - [03/Dec/2016:07:58:12 +0000] "PUT / HTTP/1.1" 200 14 "-" "curl/7.47.1" "-"
127.0.0.1 - - [03/Dec/2016:07:58:17 +0000] "DELETE / HTTP/1.1" 200 14 "-" "curl/7.47.1" "-"
10.XX.0.ZZ - - [03/Dec/2016:07:58:36 +0000] "GET / HTTP/1.1" 200 14 "-" "curl/7.49.1" "27.110.37.252"
10.XX.0.ZZ - - [03/Dec/2016:07:58:45 +0000] "DELETE / HTTP/1.1" 200 14 "-" "curl/7.49.1" "27.110.37.252"
10.XX.0.ZZ - - [03/Dec/2016:07:59:02 +0000] "PUT / HTTP/1.1" 200 14 "-" "curl/7.49.1" "27.110.37.252"
192.168.YY.ZZZ - - [03/Dec/2016:08:08:56 +0000] "GET / HTTP/1.1" 200 14 "-" "curl/7.49.1" "-"
192.168.YY.ZZZ - - [03/Dec/2016:08:09:03 +0000] "PUT / HTTP/1.1" 200 14 "-" "curl/7.49.1" "-"
192.168.YY.ZZZ - - [03/Dec/2016:08:09:08 +0000] "DELETE / HTTP/1.1" 200 14 "-" "curl/7.49.1" "-"
制限をかける
limit_exceptディレクティブを使用することで簡単に制限をかけることができます。
server {
listen 80;
location / {
proxy_pass http://backend;
limit_except GET {
allow 127.0.0.1;# localhostからは確認用
allow 10.XX.0.0/16;# プロダクションのセグメント
deny all;
}
}
}
GET以外のメソッドも許可する場合は、メソッドを羅列します。なお、HEADのリクエストはGETが許可されていれば明示的に書かなくても許可されれます。
limit_except GET PUT POST{
}
公式ドキュメント :ngx_http_core_module.html#limit_except にも書いたりますが、ngx_http_access_module(allow〜〜や、deny〜〜)、ngx_http_auth_basic_module(basic認証の設定)を使用することもできます。
制限がかかったのでアクセスしてみる
再びcurlで適当にリクエストを叩いてみて、アクセスログを見てみます。
localhost(127.0.0.1)とプロダクション(10.XX.0.0/16)からのアクセスは制限なく許可されていて(http status200が返されていて)、今回制限をかけたかったセグメント(192.168.YY.0/22)では、GETのみが許可され、PUTとDELETEには403 Forbiddenを返しています。
127.0.0.1 - - [03/Dec/2016:08:09:59 +0000] "GET / HTTP/1.1" 200 14 "-" "curl/7.47.1" "-"
127.0.0.1 - - [03/Dec/2016:08:10:01 +0000] "PUT / HTTP/1.1" 200 14 "-" "curl/7.47.1" "-"
127.0.0.1 - - [03/Dec/2016:08:10:05 +0000] "DELETE / HTTP/1.1" 200 14 "-" "curl/7.47.1" "-"
10.XX.0.ZZ - - [03/Dec/2016:08:10:19 +0000] "GET / HTTP/1.1" 200 14 "-" "curl/7.40.0" "-"
10.XX.0.ZZ - - [03/Dec/2016:08:10:24 +0000] "PUT / HTTP/1.1" 200 14 "-" "curl/7.40.0" "-"
10.XX.0.ZZ - - [03/Dec/2016:08:10:25 +0000] "DELETE / HTTP/1.1" 200 14 "-" "curl/7.40.0" "-"
192.168.YY.ZZZ - - [03/Dec/2016:08:10:33 +0000] "GET / HTTP/1.1" 200 14 "-" "curl/7.49.1" "-"
192.168.YY.ZZZ - - [03/Dec/2016:08:10:34 +0000] "PUT / HTTP/1.1" 403 169 "-" "curl/7.49.1" "-"
192.168.YY.ZZZ - - [03/Dec/2016:08:10:36 +0000] "DELETE / HTTP/1.1" 403 169 "-" "curl/7.49.1" "-"
ロードバランサーやnginxの手前に別のサーバーが置いてある場合には注意
そのままの設定では、ロードバランサや手前のサーバーのIPアドレスで判別してしまいます。ngx_http_realip_module を使用することで、クライアントのIPアドレスで判別することができます。(アクセスログにも適用されるので便利です。)
AWSでELBを使用している場合の例
http {# httpのコンテクスト
set_real_ip_from 10.XX.0.0/16;# VPCのCIDR
real_ip_header X-Forwarded-For;
}
まとめ
limit_exceptディレクティブを使うことで簡単にHTTPのメソッドによる制限をかけることができました。iptablesやnginxのifを使用して制限することも可能ですが、その場合設定が複雑になります。
蛇足ですが、IPのヘッダーは偽装可能です。より厳密に制御したい場合は、全体的なインフラ構成でパブリックからのアクセスを許可するセグメントと許可しないセグメントをしっかり定義して運用すると良いでしょう。