FastAPI + uvicorn + NginxでWebページを表示（SSL/HTTPS化 ）

# 0. はじめに

- [FastAPI + uvicorn + nginxをdocker-composeで構築](https://qiita.com/junkor-1011/items/02ed76b94c60deba8282)
- [FastAPI + uvicorn + NginxでWebページを表示（Jinja2によるTemplates機能 ）](https://qiita.com/junkor-1011/items/0a1a2a486a779461e757)

の続編として、更にNginxの設定を編集・追加してSSL化(HTTPS)とBASIC認証をつけるところまでやってみる

※前提：「[FastAPI + uvicorn + NginxでWebページを表示（Jinja2によるTemplates機能 ）](https://qiita.com/junkor-1011/items/0a1a2a486a779461e757)」の状態からスタートする

# 1. SSL

ディレクトリ構造（抜粋）

```bash
$ tree 
.
├── app
├── docker-compose.yml
└── web
    ├── conf.d
    │   └── app.conf
    └── ssl
        ├── server.crt
        └── server.key
```

- `docker-compose.yml`の修正
- `web`ディレクトリ内のファイルを修正・追加
    - `conf.d/app.conf`の修正
    - 証明書・鍵の用意(`ssl/`ディレクトリ)

がそれぞれ必要

詳細は以下の通り：

## `docker-compose.yml`

以下のように修正：

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

services:
  web:
    container_name: web
    image: nginx:alpine
    depends_on:
      - app
    ports:
      # - "80:80"
      - "${PORT:-8443:443}"
    volumes:
      - ./web/conf.d:/etc/nginx/conf.d
      - ./web/ssl:/etc/nginx/ssl
    networks:
      - nginx_network

  app:
    container_name: app
    image: test_fastapi_app
    build:
      context: ./app
      dockerfile: Dockerfile
    expose:
      - 8000
    networks:
      - nginx_network
    # volumes:
    #   - ./app/app:/app/app
    # command: "uvicorn app.main:app --host 0.0.0.0 --proxy-headers --forwarded-allow-ips * --reload"

networks:
  nginx_network:
    driver: bridge
```

差分のあるところだけ抜き出すと、

```yaml:docker-compose.yml(抜粋)
services:
  web:
    ports:
      # - "80:80"
      - "${PORT:-8443:443}"
    volumes:
      - ./web/conf.d:/etc/nginx/conf.d
      - ./web/ssl:/etc/nginx/ssl
```

- SSL用にポートマッピングを変更している
    - ホストOSの8443ポート（デフォルト、環境変数で設定）とコンテナ（Nginx）の443ポート
    - なお、環境変数を使って8443にしているのは個人的な都合で深い意味は無いので、必要に応じて変更する
        - [docker-composeドキュメント](https://docs.docker.jp/compose/environment-variables.html)など参照
- 証明書・鍵の入った`web/ssl`ディレクトリはNginxコンテナの`/etc/nginx/ssl`にマウントしている
    - ここのパス・ファイル名も後でNginxの設定内で使用する

## `web/ssl`（鍵・証明書）

```
web
└── ssl
    ├── server.crt
    └── server.key
```

↑の`server.crt`と`server.key`に対応するものを予め用意しておいて`web/ssl`ディレクトリにそれぞれ配置しておく

今回の例ではスクリプト（ワンライナー）でオレオレ証明書を自動生成したものをそのまま配置している：

```sh:make_key.sh
#!/usr/bin/env sh

openssl req -batch -new -x509 -newkey rsa:4096 -nodes -sha256 \
  -subj /CN=example.com/O=example -days 3650 \
  -keyout ./server.key \
  -out ./server.crt
```

## `web/conf.d/app.conf`

Nginxの設定ファイルを書き換える。

```conf:conf.d/app.conf
upstream backend {
    server app:8000;
}

server {
    listen 443 ssl;
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols        TLSv1.2 TLSv1.3;

    # server_name  _;
    index index.html index.htm;

    location / {
        proxy_set_header    Host    $http_host;
        proxy_set_header    X-Real-IP    $remote_addr;
        proxy_set_header    X-Forwarded-Host      $http_host;
        proxy_set_header    X-Forwarded-Server    $http_host;
        proxy_set_header    X-Forwarded-Server    $host;
        proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto  $scheme;
        proxy_redirect      http:// https://;

        proxy_pass http://backend;
    }

    # log
    # access_log /var/log/nginx/access.log;
    # error_log /var/log/nginx/error.log;
}

server_tokens off;
```

クリティカルな変更点：

- `listen 80`だった部分を以下のように書き換えることで、コンテナ内にvolumeマウントして配置した鍵・証明書を指定してHTTPSにしている
- ついでにTLSのバージョンも制限している

```
    listen 443 ssl;
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols        TLSv1.2 TLSv1.3;
```

- `location /`内の`proxy_***`を修正・追加などしている
   - やったのがそこそこ前なのと、かなり試行錯誤した関係で詳細はあまり覚えていない。。。
   - なので不要な設定などが混じっているかもしれない。。。

```
        proxy_set_header    Host    $http_host;
        proxy_set_header    X-Real-IP    $remote_addr;
        proxy_set_header    X-Forwarded-Host      $http_host;
        proxy_set_header    X-Forwarded-Server    $http_host;
        proxy_set_header    X-Forwarded-Server    $host;
        proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto  $scheme;
        proxy_redirect      http:// https://;
```

とりあえずこれで
`docker-compose up -d`
を実行して、 https://localhost:8443 (ポートは自分で設定したもの)などにアクセスするとhttps通信化されていることが確認出来る


# 2. （おまけ）NginxでBASIC認証

ついでにBASIC認証くらいまでかけておく

- 予め`.htpasswd`を生成しておく
  - 例えば`echo "user:"$(openssl passwd password) >> /path/to/.htpasswd`のような感じ
  - なお、`.htpasswd`の内容自体はApache httpdなどと同様
- 置き場所はある程度任意だが、今回は`web/conf.d`内に置いた：

```bash
.
├── app
├── docker-compose.yml
└── web
    ├── conf.d
    |   ├── .htpasswd
    │   └── app.conf
    └── ssl
```

- `web/conf.d/app.conf`に追記：

```conf:conf.d/app.conf
upstream backend {
    server app:8000;
}

server {
    listen 443 ssl;
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols        TLSv1.2 TLSv1.3;

    # basic-auth
    auth_basic "BASIC AUTH";
    auth_basic_user_file /etc/nginx/conf.d/.htpasswd;

    # server_name  _;
    index index.html index.htm;

    location / {
        proxy_set_header    Host    $http_host;
        proxy_set_header    X-Real-IP    $remote_addr;
        proxy_set_header    X-Forwarded-Host      $http_host;
        proxy_set_header    X-Forwarded-Server    $http_host;
        proxy_set_header    X-Forwarded-Server    $host;
        proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto  $scheme;
        proxy_redirect      http:// https://;

        proxy_pass http://backend;
    }

    # log
    # access_log /var/log/nginx/access.log;
    # error_log /var/log/nginx/error.log;
}

server_tokens off;
```

↑追記したのは以下：

```conf:app.conf（追記分）
    # basic-auth
    auth_basic "BASIC AUTH";
    auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
```
- `auth_basic`の内容は認証を求められるときの文字列として出てくることがある（多分ブラウザに依る）ので、適宜書き換えておく
- `auth_basic_user_file`でコンテナ内にvolumeマウントしてある`.htpasswd`のパスを指定して読み込んでいる

これで、例えば以下のような感じで認証がつく：

![スクリーンショット 2020-08-16 14-50-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/96ef4f8f-7d3c-f2ec-9b58-b06d7c19a4cf.png)

(※今回はオレオレ証明書なので警告が出ている)

# 参考

- Nginx リバースプロキシ設定
  - https://qiita.com/uezo/items/847e1911ac486f5a89c4
  - https://qiita.com/imoimo2009/items/515ceaeea5aeb1bade3a
  - https://qiita.com/tnoce/items/ded6d3d298da5972ab63
  - https://ja.nuxtjs.org/faq/nginx-proxy/
  - https://blog.akagi.jp/archives/3883.html
  - https://www.busterworks.co.jp/archives/64
  - https://threetreeslight.com/posts/2014/03/14/nginx%E3%81%A7ssl-reverse-proxy%E8%A8%AD%E5%AE%9A/
  - https://tech-lab.sios.jp/archives/9922

- Nginxでhttpからhttpsへリダイレクト
  - https://qiita.com/fisherman08/items/e39e67e85ef07a1ef3db

- NginxにおけるBASIC認証
  - http://vdeep.net/nginx-basic-authentication
  - https://www.yoheim.net/blog.php?q=20170804
  - https://qiita.com/kotarella1110/items/be76b17cdbe61ff7b5ca
  - https://qiita.com/STSynthe/items/5387bc33e7be315ae338
  - https://qiita.com/ekzemplaro/items/0385c34c4325da2f2af6
  - https://note.com/club_cloud/n/n581d1995139f

- `.htpasswd`生成
  - https://asakawajunya.hatenablog.com/entry/20130706/1373046305
  - https://qiita.com/shell/items/5606d37a802e39479036
  - https://www.junk-works.science/generate-htpasswd-with-openssl/
  - https://www.luft.co.jp/cgi/htpasswd.php

- オレオレ証明書のワンライナー
  - https://qiita.com/ngyuki/items/27e56196fbf2d68a9f7f
  - https://qiita.com/marcy-terui/items/2f63d7f170ff82531245
  - https://nekonenene.hatenablog.com/entry/2019/08/10/153430