nginxで各環境（開発環境/本番環境）ごとにアクセス制御を行う

個人の開発環境、検証環境、本番環境など異なる環境において、環境に応じてアクセス制御を実施したいことがあると思います。
例えば、`API`を内部のアプリにのみ公開していて、`Swagger UI`を本番環境のみ非公開にしたいケースなどです。

本投稿では、`nginx`で上記課題に対応する方法を紹介します。

## 設定

`nginx`のmapモジュール（[ngx_http_map_module](http://nginx.org/en/docs/http/ngx_http_map_module.html "ngx_http_map_module")）で行います。

```:nginx.conf
map $http_host $name {
    hostnames;

    default       0;

    hoge.com   1;
    *.hoge.com 1;
    hoge.org   2;
    *.hoge.org 2;
    .hoge.net  3;
}
```

公式では`Host`ヘッダでのマッピング制御の例が掲載されていますが、`nginx`では`envsubst`（軽量なテンプレートエンジン）がサポートされているので、こちらを利用して設定していきます。

### nginx.conf

```:nginx.conf
# 開発時のみデバッグモード on(=1) の設定を追加
map $PROFILE $debugEnable {
    default 0; # production deny
    dev 1;
}

server {
    listen       80;
    server_name  localhost;

    # デバッグモード off の場合、 swagger-ui を 403 で返す
    location /swagger-ui.html {
        if ( $debugEnable != 1 ) {
            return 403;
        }
    }
（……省略……）
```

### 起動方法

+ 開発時

```:bash
PROFILE=dev envsubst '$PROFILE' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
```

+ 本番時

```:bash
PROFILE=prod envsubst '$PROFILE' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
```

オンプロミス（仮想サーバ）で動かす場合は、`systemd`ののサービスファイル`/usr/lib/systemd/system/nginx.service`などに設定、コンテナで動かす場合は、環境変数をコンテナに引き渡して設定を切り分けましょう。

## 確認

`Docker`を使って稼働確認してみます。
ベースイメージは`vim`が入っている`nginx:alpine`を用います。

### コンテナ起動（@ローカルPC）

```:bash
% docker run -ti --rm -p 8080:80 nginx:alpine ash
```

### 設定確認（@コンテナ内）

#### テンプレートファイル生成

```:ash
# cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.template
```

#### テンプレートファイル編集（@コンテナ内）

`vim`で上記で作成したファイルを以下のとおり編集します。

```:default.conf.template
/ # diff -u /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.template
--- /etc/nginx/conf.d/default.conf
+++ /etc/nginx/conf.d/default.conf.template
@@ -1,9 +1,20 @@
+map $PROFILE $debugEnable {
+    default 0; # production deny
+    dev 1;
+}
+
 server {
     listen       80;
     server_name  localhost;

     #charset koi8-r;
     #access_log  /var/log/nginx/host.access.log  main;
+
+    location /hoge {
+        if ( $debugEnable != 1 ) {
+            return 403;
+        }
+    }

     location / {
         root   /usr/share/nginx/html;
```

全量は以下です。

```:ash
/ # cat /etc/nginx/conf.d/default.conf.template
map $PROFILE $debugEnable {
    default 0; # production deny
    dev 1;
}

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location /hoge {
        if ( $debugEnable != 1 ) {
            return 403;
        }
    }

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}
```

#### 開発モードの確認

##### 開発モードで起動（@コンテナ内）

```:ash
# PROFILE=dev envsubst '$PROFILE' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
```

##### 開発モードの動作確認（@ローカルPC）

`httpie`で確認します。
→判定を抜けて、期待どおりHTTPステータス404が返ってきます。

```:bash
% http localhost:8080/hoge
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 153
Content-Type: text/html
Date: Thu, 23 Jan 2020 01:27:33 GMT
Server: nginx/1.17.8

<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
```

##### 開発モードログ（@コンテナ内）

```:ash
/ # PROFILE=dev envsubst '$PROFILE' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
2020/01/23 01:27:33 [error] 16#16: *1 open() "/etc/nginx/html/hoge" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /hoge HTTP/1.1", host: "localhost:8080"
172.17.0.1 - - [23/Jan/2020:01:27:33 +0000] "GET /hoge HTTP/1.1" 404 153 "-" "HTTPie/1.0.2" "-"
```

#### 本番モードの確認

##### 本番モードで起動（@コンテナ内）

```:ash
# PROFILE=prod envsubst '$PROFILE' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
```

##### 本番モードの動作確認（@ローカルPC）

`httpie`で確認します。
→判定に引っ掛かり、期待どおりHTTPステータス403が返ってきます。

```:bash
% http localhost:8080/hoge
HTTP/1.1 403 Forbidden
Connection: keep-alive
Content-Length: 153
Content-Type: text/html
Date: Thu, 23 Jan 2020 01:48:17 GMT
Server: nginx/1.17.8

<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
```

##### 本番モードログ（@コンテナ内）

```:ash
/ # PROFILE=prod envsubst '$PROFILE' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
172.17.0.1 - - [23/Jan/2020:01:48:17 +0000] "GET /hoge HTTP/1.1" 403 153 "-" "HTTPie/1.0.2" "-"
```

## 最後に

本番環境をコンテナで運用している場合も、環境変数の設定(`k8s ConfigMap`,`AWS ECS use Parameter Store`など)は容易になっているので、使いどころは多いと思います。

[ConfigMap | Kubernetes Engine のドキュメント | Google Cloud](https://cloud.google.com/kubernetes-engine/docs/concepts/configmap?hl=ja "ConfigMap | Kubernetes Engine のドキュメント | Google Cloud")
[Amazon ECS タスクで秘密情報や機密情報をコンテナに安全に渡す方法を教えてください。](https://aws.amazon.com/jp/premiumsupport/knowledge-center/ecs-data-security-container-task/ "Amazon ECS タスクで秘密情報や機密情報をコンテナに安全に渡す方法を教えてください。")