nginxへのアクセスをIPアドレスで制限する

nginxにて、IPアドレスを用いてアクセス制限する方法を、以下3パターンで記載します。

  1. ClinetIPにてアクセス制限を行う方法
  2. ロードバランサー等を経由している通信で、ClinetIPにてアクセス制限を行う方法
  3. ロードバランサー等を経由している通信で、経路途中の機器のIPにてアクセス制限を行う方法

環境

  • nginx version: nginx/1.15.12

1.ClinetIPにてアクセス制限を行う方法

nginx.configの、serverやlocationコンテキストにて、 allowdeny ディレクティブでIPアドレスを指定します。

Module ngx_http_access_module

以下の例では、指定したバーチャルホストにて、 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が記録されています。

f:id:goodbyegangster:20190420183657p:plain

アクセスログは以下のようになります。

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に変換されます。

Module ngx_http_realip_module

アクセスログは以下となります。

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と代入する形です。

Module ngx_http_map_module

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 というドキュメントがあり、あまり利用推奨されていないようです。

If Is Evil | NGINX

returnやrewriteの利用はif文中でも問題ないと、上記のページには記載ありますが、基本的にはif文の利用はイレギュラーなものなのだと思っています。