GoogleCloudのVMで立てたdockerのサーバー（Nginx）に最小コストでSSL/TLS証明書を適用する

# やろうとしたこと

GoogleCloudのVMにdockerでWebサーバーを立てて、HTTPSでアクセスできるようにしたかった。
HTTPS通信にするためにはロードバランサを使用しSSL/TLS証明書（以下、証明書と呼ぶ）を適用する必要がある。ロードバランサはどう頑張ってもコストがかかるので、できれば使わずに無料でHTTPS化したい。**無料でHTTPSを使う**、ここ大事！

# 前提

- 使用するdocker関連ファイルはCTFdを流用
- 使用するドメイン名は取得済み
- HTTPを使ったWeｂサーバーからのレスポンスは正常なことを確認済み

# 手順

1. Nginxの設定ファイルを修正する
    
    CTFdだと~/conf/nginx/http.confを修正することになる
    ```bash
    worker_processes 4;
    
    events {
    
      worker_connections 1024;
    }
    
    http {
    
      # Configuration containing list of application servers
      upstream app_servers {
    
        server ctfd:8000;
      }
    
        server {
            # HTTPからHTTPSへのリダイレクト
            listen 80;
            server_name <取得しているドメイン名>;  # ドメイン名を指定
    
            # リダイレクト設定
            return 301 https://$host$request_uri;
        }
    
        server {
            listen 443 ssl;  # SSLを有効にする
            server_name <取得しているドメイン名>;  # ドメイン名を指定
    
            # SSL証明書のパスを指定
            ssl_certificate /etc/letsencrypt/live/cyber-ctf.xyz/fullchain.pem;
            ssl_certificate_key /etc/letsencrypt/live/cyber-ctf.xyz/privkey.pem;
    
        gzip on;
    
        client_max_body_size 4G;
    
        # Handle Server Sent Events for Notifications
        location /events {
    
          proxy_pass http://app_servers;
          proxy_set_header Connection '';
          proxy_http_version 1.1;
          chunked_transfer_encoding off;
          proxy_buffering off;
          proxy_cache off;
          proxy_redirect off;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Host $server_name;
        }
    
        # Proxy connections to the application servers
        location / {
    
          proxy_pass http://app_servers;
          proxy_redirect off;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Host $server_name;
        }
      }
    }
    ```
    
2. docker-compose.yamlを修正する
    
    ```bash
    services:
      ctfd:
        build: .
        user: root
        restart: always
        ports:
          - "8000:8000"
        environment:
          - UPLOAD_FOLDER=/var/uploads
          - DATABASE_URL=mysql+pymysql://ctfd:ctfd@db/ctfd
          - REDIS_URL=redis://cache:6379
          - WORKERS=1
          - LOG_FOLDER=/var/log/CTFd
          - ACCESS_LOG=-
          - ERROR_LOG=-
          - REVERSE_PROXY=true
        volumes:
          - .data/CTFd/logs:/var/log/CTFd
          - .data/CTFd/uploads:/var/uploads
          - .:/opt/CTFd:ro
        depends_on:
          - db
        networks:
            default:
            internal:
    
      nginx:
        image: nginx:stable
        restart: always
        volumes:
          - ./conf/nginx/http.conf:/etc/nginx/nginx.conf
          - /etc/letsencrypt:/etc/letsencrypt　# 証明書をマウントさせるため追記
        ports:
          - 443:443  # HTTPS接続させるため変更
    
    	# 証明書を使うためcertbotの記述を追加
      certbot:
        image: certbot/certbot
        volumes:
          - ./certs:/etc/letsencrypt
        entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
        depends_on:
          - ctfd
    
      db:
        image: mariadb:10.11
        restart: always
        environment:
          - MARIADB_ROOT_PASSWORD=ctfd
          - MARIADB_USER=ctfd
          - MARIADB_PASSWORD=ctfd
          - MARIADB_DATABASE=ctfd
          - MARIADB_AUTO_UPGRADE=1
        volumes:
          - .data/mysql:/var/lib/mysql
        networks:
            internal:
        # This command is required to set important mariadb defaults
        command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --wait_timeout=28800, --log-warnings=0]
    
      cache:
        image: redis:4
        restart: always
        volumes:
        - .data/redis:/data
        networks:
            internal:
    
    networks:
        default:
        internal:
            internal: true
    ```
    
3. Certbotを使用して証明書を取得する
    
    ```bash
    sudo docker-compose run --rm certbot certonly --nginx -d <ドメイン名>
    又は
    sudo docker-compose run --rm certbot certonly --standalone -d <ドメイン名>
    ```
    
4. dockerを起動する
    
    ```bash
    sudo docker compose up
    ```
    

これでdocker上のNginxとHTTPS通信が可能になる上、HTTP通信を試みるとHTTPSにリダイレクトされる！

# 補足（上手くいかない時）

自分の場合は手順３を実施した時に、証明書が何も取得できなかった。これはdockerに適用する前にVMでテストするため以下のコマンドで証明書を既に入手していたから。

```bash
sudo apt install python3-certbot-nginx
sudo certbot --nginx
```

証明書を既に持っているにも関わらずdocker compose upをすると証明書のエラーが出て起動できない状態が続いた。
この場合の原因はdocker-compose.yamlファイルにあった。当初はファイルの記述内容を下記のようにしていた

```bash
nginx:
    image: nginx:stable
    restart: always
    volumes:
      - ./conf/nginx/http.conf:/etc/nginx/nginx.conf
      - ./:/etc/letsencrypt　# ここの記述がエラーの原因だった
    ports:
      - 443:443 
```

証明書が格納されているファイルを調べると/etc/letsencrypt/だった。
つまり、上記のコードだと、証明書がdocker上の/etc/letsencryptにマウントされず証明書エラーが出てしまう。
事前にVM上で証明書を取ったためにエラーが出てしまい時間を溶かしてしまった。

# さいごに

思い返せばdockerで出るエラーはほとんどvolumesに関連してる。
他の人はどうなんだろう？
自分は今度からエラーが出たら真っ先にvolumesが正しいか確認することから始めようかな。