Dockerコンテナ上でnginxをリバースプロキシとして動作させる

## 概要

nginxをリバースプロキシとして動作させます。
TLSによる暗号化、およびクライアント認証を行う手順を記載します。

動作確認用にアプリケーションとして **PHP+Apache** を使いました。

## 前提条件

本記事で使用する環境・ソフトウェアはDocker、docker-compose、nginxです。

## nginxの設定

nginxの設定は **.template** ファイルを読み込ませることで、コンテナ起動時に自動生成させます。
ローカル環境で作成した **templates** ディレクトリをnginxコンテナの **/etc/nginx/templates** へマウントします。

nginxは起動時に **/etc/nginx/templates** ディレクトリ内部にある **.template** ファイルを読み込むためです。

参考：[docker hub nginx](https://hub.docker.com/_/nginx)

```nginx:nginx.conf.template
server {
    server_name www.example.com:443;
    root   /var/www/;
    index  index.php index.html;
    listen 443 ssl;
    location / {
        proxy_pass http://backend.example.com;
    }
    
    ssl_certificate     /ssl/server/server.crt;
    ssl_certificate_key /ssl/server/server.key;
    ssl_protocols       TLSv1.2;
    ssl_ciphers         ECDHE+AESGCM:DHE+aRSA+AESGCM:ECDHE+AESCCM:DHE+aRSA+AESCCM:+AES256:ECDHE+CHACHA20:DHE+aRSA+CHACHA20:+DHE:ECDHE+AES128:ECDHE+CAMELLIA128:ECDHE+AES:ECDHE+CAMELLIA:+ECDHE+SHA:DHE+aRSA+AES128:DHE+aRSA+CAMELLIA128:DHE+aRSA+AES:DHE+aRSA+CAMELLIA:+DHE+aRSA+SHA;
    
    ssl_verify_client on;
    ssl_stapling on;
    ssl_client_certificate /ssl/ca/ca.pem;
}
```

以下、個々の設定を説明します。

### SSL/TLSに関する設定

```nginx
server {
    server_name www.example.com;
    listen 443 ssl;
    
    ssl_certificate     /ssl/server/server.crt;
    ssl_certificate_key /ssl/server/server.key;
    ssl_protocols       TLSv1.2;
    ssl_ciphers         ECDHE+AESGCM:DHE+aRSA+AESGCM:ECDHE+AESCCM:DHE+aRSA+AESCCM:+AES256:ECDHE+CHACHA20:DHE+aRSA+CHACHA20:+DHE:ECDHE+AES128:ECDHE+CAMELLIA128:ECDHE+AES:ECDHE+CAMELLIA:+ECDHE+SHA:DHE+aRSA+AES128:DHE+aRSA+CAMELLIA128:DHE+aRSA+AES:DHE+aRSA+CAMELLIA:+DHE+aRSA+SHA;
}
```

* server
    * [server_name](http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name)：仮想サーバー名（FQDN）を指定
    * [listen](http://nginx.org/en/docs/http/ngx_http_core_module.html#listen)：この仮想サーバーが通信を待ち受けるポートを指定。 **ssl** オプションはHTTPとHTTPS構成を簡単に設定するために使用
    * [ssl_certificate](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate)：SSL証明書名
    * [ssl_certificate_key](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key)：SSL証明書の対となる秘密鍵名
    * [ssl_protocols](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols)：使用可能なSSLプロトコルバージョン
    * [ssl_ciphers](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers)：使用可能な暗号化プロトコル

### リバースプロキシ

```nginx
server {
    location / {
        proxy_pass backend.example.com;
    }
}
```

* location
    * [proxy_pass](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass)：プロキシされているサーバー（ここではバックエンドとなるアプリケーション）のホスト名（FQDN）

バックエンドのアプリケーションへhttpヘッダーを使って情報を渡したい場合は、 **proxy_set_header** を使用します。
以下に参考例を記載します。

```nginx
server {
    location / {
        proxy_set_header Host               $host;
        proxy_set_header X-Forwarded-Host   $host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  https;
    }
}

```

### クライアント認証

```nginx
server {
    ssl_verify_client on;
    ssl_stapling on;
    ssl_client_certificate /ssl/ca/ca.pem;
}
```

* [ssl_verify_client](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_verify_client)：クライアント認証を有効化
* [ssl_stapling](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_stapling)：証明書の失効チェック
* [ssl_client_certificate](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_client_certificate)：クライアント証明書名


## docker-compose

```yaml:docker-compose.yaml
version: '3'

services:
   httpd:
     image: php:7.2-apache
     volumes:
       - ./index.php:/var/www/html/index.php
     ports:
       - "80:80"
     restart: always
   nginx:
     image: nginx:latest
     volumes:
       - ./templates:/etc/nginx/templates
       - ./server.crt:/ssl/server/server.crt
       - ./server.key:/ssl/server/server.key
     ports:
       - "443:443"
     restart: always
     environment:
       NGINX_PROXY_PATH: backend.example.com:80
```

httpヘッダ確認のために以下ファイルを使用しました。
[getallheaders — 全てのHTTPリクエストヘッダを取得する](https://www.php.net/manual/ja/function.getallheaders.php)

```php:index.php
<?php

foreach (getallheaders() as $name => $value) {
    echo "$name: $value\n";
}

?>
```


ディレクトリ構成は以下になる。

```
- docker-compose.yaml
- index.php
- server.crt
- server.key
- templates #ディレクトリ
    - nginx.conf.template
```

## コンテナ起動

以下のコマンドでコンテナを起動します。

> docker-compose up -d

## 確認

### クライアント認証

ブラウザから **ホスト名** へ接続してクライアント認証画面が出ることを確認します。
> ブラウザでクライアント証明書について聞かれない場合は、直近の履歴を削除してみると良いです

※以下はFirefoxの場合

![rect817-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302006/21013377-ab91-8de2-9b72-7312e0f028ce.png)

### httpヘッダー

接続したページにヘッダー情報が記載されているため確認します。

## コンテナ停止

以下のコマンドでコンテナを停止します。

> docker-compose stop