見出し画像

Djangoを使ったWebアプリの作り方 ⑧SSL(https)編

以下で独自ドメインでDjangoプロジェクトを公開しました。ただ、httpでの公開なので、今回はSSL通信(https)対応していきます。

SSL通信とは、WebサイトがSSL(Secure Sockets Layer)またはその後継であるTLS(Transport Layer Security)を使用して、ユーザーとサーバー間のデータ通信を暗号化することを指します。

これにより、通信内容が第三者に盗聴されたり改ざんされたりするリスクを低減し、ユーザーの個人情報や機密データを安全に保護します。

以下で、SSL通信の説明から具体的な設定方法を解説します。

1. SSL通信について

SSL通信の特徴や、SSL証明書の種類について説明します。

  • SSL通信の主な特徴として、暗号化、HTTPSの使用、信頼性の向上の3点が挙げられます。

    • 暗号化: SSL/TLSを使用することで、送信されるデータ(例えば、クレジットカード情報やログインパスワードなど)が暗号化されます。これにより、悪意のある第三者がデータを傍受しても、その内容を解読することができません。

    • HTTPSの使用: SSL対応のWebサイトは、URLが「http://」から「https://」に変更されます。この「s」は「secure(セキュア)」の略で、通信が暗号化されていることを示します。

    • 信頼性の向上: SSL対応のサイトは、ブラウザのアドレスバーに鍵のアイコンが表示され、ユーザーに対して安全な通信が行われていることを示します。これにより、訪問者はサイトの信頼性を感じやすくなります。

つまり、SSL通信は、通信するデータを暗号化して信頼性を向上(セキュア)するための方法です。

次に、SSL通信の具体的な流れについて説明します。
SSL通信の流れは以下で詳しく説明していますが、ポイントは、SSL通信をするには、SSL証明書が必要となる点です。

  1. SSL証明書の取得
    まず、Webサイトの運営者はSSL証明書を取得します。この証明書は、認証局(CA)によって発行され、サイトの運営者が正当であることを証明します。SSL証明書には、公開鍵が含まれています。

  2. ブラウザからの接続リクエスト
    ユーザーがWebサイトにアクセスすると、ブラウザはそのサイトのSSL証明書を要求します。このリクエストに対して、サーバーはSSL証明書をブラウザに送信します。

  3. SSL証明書の確認
    ブラウザは受け取ったSSL証明書を確認します。これにより、証明書が信頼できるものであるか、またサイトの運営者が正当であるかを検証します。問題がなければ、次のステップに進みます。

  4. セッション鍵の生成
    ブラウザとサーバーは、共通鍵(セッション鍵)を生成します。この鍵は、実際のデータ通信を暗号化するために使用されます。共通鍵は、ブラウザがサーバーの公開鍵を使って暗号化し、サーバーに送信されます。

  5. データの暗号化と送信
    サーバーは、受け取った暗号化された共通鍵を自分の秘密鍵を使って復号化します。これにより、サーバーとブラウザは同じ共通鍵を持つことになります。この共通鍵を使用して、以降のデータ通信が暗号化されます。これにより、悪意のある第三者が通信内容を盗み見たり改ざんしたりすることが非常に困難になります

SSL通信は、SSL証明書を使って行われます。

次にSSL証明書について説明します。SSL証明書には、ドメイン認証(DV)、組織認証(OV)、拡張認証(EV)の3種類があります。それぞれについて説明します。

  • ドメイン認証(DV)証明書

    • 説明:ドメイン名の所有者がそのドメインを管理していることを確認するための証明書です。最も基本的な認証方法で、発行が迅速です。

    •  特徴:認証プロセスが簡単で、数分で発行されることが多い。企業や個人の実在性は確認されないため、信頼度は低め。小規模なサイトや個人ブログに適しています。

  • 組織認証(OV)証明書

    •  説明:ドメイン名の所有者だけでなく、その組織が実在することも確認される証明書です。企業や団体向けに適しています。

    •  特徴:認証プロセスがDVよりも厳格で、組織の情報(会社名、所在地など)が確認される。発行には数日かかることがある。ユーザーに対して信頼性を高めるため、オンラインショップやビジネスサイトに適しています。

  • 拡張認証(EV)証明書

    • 説明:EV証明書は、最も厳格な認証プロセスを経て発行される証明書です。企業の実在性や法的な存在を確認するため、特に信頼性が高いです。

    • 特徴:認証プロセスが非常に厳格で、電話確認や書類の提出が必要な場合がある。ブラウザのアドレスバーが緑色になり、企業名が表示されることが多い。金融機関や大規模な企業サイトに適しています。

どのSSL証明書を取得するかは、Webサイトの規模や必要なセキュリティレベルに応じて選択しますが、今回は、簡単なDV証明書にて設定を進めていきます。

具体的には、無料でDV証明書を提供する認証局であるLet's Encryptを使っていきます。Let's Encryptは、ドメインの所有を確認するだけで無料DV証明書を発行します。
これにより、個人や小規模なビジネスでも手軽にSSL証明書を導入できます。

2. SSL証明書の取得

今回は、Let's Encryptという無料の認証局が提供する証明書を利用します。SSL/TLS証明書を自動的に取得・更新するツールとして、Certbot(オープンソース)があります。
Certbotは、Let's Encryptの無料証明書を利用するために設計されたツールです。

Certbotは、取得した証明書をWebサーバー(Nginxなど)に自動的に設定します。これにより、手動で設定ファイルを編集する必要がなくなります。

また、 Let's EncryptのSSL証明書は90日間の有効期限があるため、定期的な更新が必要です。Certbotは、SSL証明書の更新を自動化する機能を提供しており、これにより手動での更新作業を省くことができます

2.1 Certbotのインストール

さくらvpsのサーバーにてCertbotをインストールします。その前に、epel-releaseというパッケージをインストールします。これにより、EPEL(Extra Packages for Enterprise Linux)リポジトリがシステムで使えるようになります。

ちなみに、EPEL(Extra Packages for Enterprise Linux)リポジトリは、CentOSやRed Hat Enterprise Linux(RHEL)といったLinuxディストリビューション向けの追加ソフトウェアパッケージの集まりです。通常、これらのディストリビューションでは、安定性やサポートを重視するために、公式リポジトリに含まれるソフトウェアが限定されています。そのため、最新のソフトウェアやニッチなツールが公式リポジトリには含まれていないことがあります。EPELを使うと、公式リポジトリにはないソフトウェア(例:htop、nginx、moshなど)を簡単にインストールできます。
ただ、EPELのパッケージは公式サポート対象外のため、自己責任の利用となります。また、Ubuntuなど他のディストリビューションでは使えません。

EPELは、この制限を補うために、Fedora Project(Red Hat傘下のオープンソースプロジェクト)が提供するリポジトリで、以下の特徴があります:

EPELをインストールしたのち、Certbotをインストールします。
(Centos9環境での設定を説明します)

sudo dnf install epel-release -y
sudo dnf install certbot -y

'''
# コードの説明
epel-releaseをインストールする理由は、Certbotが標準のCentOSリポジトリには含まれておらず、EPELリポジトリが必要だからです。
-yは、すべての「Yes/No」の確認に自動的に「Yes」と答えるオプションです。

certbot: Let's EncryptのSSL証明書を発行・管理するためのツールです。
     HTTPS通信を有効にするためのSSL証明書を取得・更新します。

'''

2.2 補足

本筋ではないですが、Certbotをインストールしているときに以下エラーが発生しました。

[centos@***-***-***** *****]$ sudo dnf install certbot -y
Extra Packages for Enterprise Linux 9 - x86_64   10 MB/s |  23 MB     00:02
Killed

このエラーは、プロセスが実行中にサーバーのメモリ不足で、システムが強制終了されたたためです。
(今回契約したさくらvpsサーバーのスペックが低いものだったため)

以下のように、メモリを確認すると、物理メモリが、現在82MBしか空いていません。

[centos@***-***-***** *****]$ free -h
               total        used        free      shared  buff/cache   available
Mem:           457Mi       256Mi        82Mi        25Mi       163Mi       200Mi
Swap:             0B          0B          0B

また、スワップ領域も設定されていないため、Certbotをインストールしているとき、メモリが不足してプロセスが強制終了されたと考えられます。

ハイスペックのvpsに変更してもいいのですが、今回は、スワップ領域を追加し、メモリ不足時にディスクを一時的にメモリとして利用する方法で対処します。

sudo fallocate -l 2G /swapfile      # 2GBのスワップファイル作成
sudo chmod 600 /swapfile           # セキュリティ設定
sudo mkswap /swapfile              # スワップ領域としてフォーマット
sudo swapon /swapfile              # スワップを有効化 

echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab # 再起動後も有効にする

以下のように、2Gのスワップ領域が作成されていることがわかります。

[centos@***-***-***** *****]$ free -h    # メモリとスワップの状態を確認 
               total        used        free      shared  buff/cache   available
Mem:           457Mi       251Mi       6.0Mi        25Mi       235Mi       205Mi
Swap:          2.0Gi          0B       2.0Gi

以上を行うと、問題なくCertbotをインストールできます。

2.3 SSL証明書の取得

Certbotを使い、SSL証明書を取得します。もし、すでにDjangoプロジェクトが公開されている(80番ポートを使用している)場合はいったん以下コードを実行して止めます。
(理由は、今回は、Certbotを使いstandaloneと言う方法でSSL証明書を取得します。この場合、Certbotが一時的にサーバーを自動で起動(80番ポート)して、Let’s Encryptと通信します。そのため、Nginxで80番ポートがすでに起動していると競合して上手くいかないので、Djangoプロジェクトを最初に停止します。)

docker-compose -f docker-compose.prod.yml down -v

次に、以下コマンドを実行して、SSL証明書を取得します。

sudo certbot certonly --standalone -d example.example.jp

'''
# コードの説明
・sudo
 >>管理者権限でコマンドを実行します。

・certbot
 >>Certbotは、Let's EncryptのSSL証明書を簡単に取得・更新できるツールです。

・certonly
 >>このオプションは、「証明書を取得するだけ」という意味です。
 サーバーの設定(NginxやApache)を自動的に変更せず、証明書だけ取得します。
 後で自分で設定を行いたい場合に使います。

・--standalone
 >>スタンドアロンモード でCertbotを動作させます。
 Certbot自身が一時的にWebサーバーとして動作し、Let's Encryptが証明書を発行するための認証を行います。
 Webサーバー(NginxやApache)が停止している場合や、設定していない場合に便利です。

・-d example.example.jp
 >>-d は「ドメイン名」を指定するオプションです。
 ここでは、example.example.jp の証明書を取得します。(独自ドメインを設定してください)
'''

上記コマンドを実行すると、Emailアドレスを入力、利用規約への同意を求められます。問題なく設定していくとSSL証明書が"/etc/letsencrypt/live/独自ドメイン/"に保存されます。
保存された証明書の中で、privkey.pem と fullchain.pemを使ってSSL通信の設定を行います。

・privkey.pem(秘密鍵):暗号化通信の鍵
・fullchain.pem(証明書チェーン):Webサイトが信頼できることを証明するデータ。自分のサイトの証明書と、信頼の連鎖を保証する中間証明書が含まれています。

privkey.pem と fullchain.pemは現在root権限以外ではアクセスできないのでユーザーに権限を追加します。今回はcentosユーザーに読み取りと実行の権限を追加します。

sudo setfacl -R -m u:centos:rX /etc/letsencrypt/live/
sudo setfacl -R -m u:centos:rX /etc/letsencrypt/archive/

権限が追加されたかを確認します。

[centos@***-***-***** *****]$ getfacl /etc/letsencrypt/live/
getfacl: Removing leading '/' from absolute path names
# file: etc/letsencrypt/live/
# owner: root
# group: root
user::rwx
user:centos:r-x
group::---
mask::r-x
other::---

すると、centos ユーザーに読み取り(r)と実行(x)の権限がある事がわかります(問題ない状態)。

3. Nginxの設定

取得した証明書(privkey.pem と fullchain.pem)を使ってSSL通信の設定を行います。

3.1 Nginxの設定ファイル(default.conf)の修正

NginxがWebサーバーとしてどのようにリクエストを処理するかを設定して
いるファイル(./containers/nginx/conf.d/default.conf)を修正します。

upstream djang {
    server app:8000;
}

server {
    listen 80;
    server_name example.example.jp;

    location / {                                                               # 追加
        return 301 https://$host$request_uri; # http接続をhttpsにリダイレクト    # 追加
    }                                                                          # 追加

}

server {                                                                       # 追加
    listen 443 ssl http2;                                                      # 追加
    listen [::]:443 ssl http2;                                                 # 追加
    server_name example.example.jp;                                             # 追加

    ssl_certificate /etc/letsencrypt/live/example.example.jp/fullchain.pem;     # 追加
    ssl_certificate_key /etc/letsencrypt/live/example.example.jp/privkey.pem;   # 追加

    location / {                                                               # 追加
        proxy_pass http://django;                                              # 追加
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;           # 追加
        proxy_set_header Host $host;                                           # 追加
        proxy_redirect off;                                                    # 追加
    }                                                                          # 追加

    location /static/ {                                                        # 追加
        alias /static/;                                                        # 追加
    }                                                                          # 追加

'''
# ディレクトリ構成
├── Django_Project                   # << カレントディレクトリ
    ├── manage.py
    ├── db.sqlite3
    ├── Pipfile
    ├── Pipfile.lock
    ├── docker-compose.yml
    ├── .env
    ├── .gitignore 
    ├── containers/
    |   ├── django
    |      ├── Dcokerfile
    |      ├── entorypoint.sh
    |   ├── postgres
    |      ├── Dcokerfile
    |   ├── nginx/
    |      ├── Dcokerfile
    |      ├── conf.d/
    |         ├── default.conf              # << 修正
    │
    ├── mysite/
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    │   └── asgi.py
    │
    └── myapp/
        ├── migrations/
        ├── templates/
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── forms.py
        ├── models.py
        ├── tests.py
        ├── urls.py
        └── views.py

# コードの説明
・return 301 https://$host$request_uri;
 >>すべての HTTP リクエストを HTTPS(ポート 443)にリダイレクトします。

・listen 443 ssl http2;
 >>HTTPS 通信をポート 443 で受け付けます。
  http2 は HTTP/2 プロトコルを有効化する設定です(より高速な通信が可能)。

・ssl_certificate と ssl_certificate_key
 >>Let's Encrypt の SSL 証明書と秘密鍵を指定しています。これにより、HTTPS 通信が暗号化されます。

・proxy_pass http://django;
 >>リクエストをバックエンド(ここでは upstream django で定義した app:8000)に転送します。

・proxy_set_header
 >>転送時に以下のヘッダー情報を追加します:
 >>X-Forwarded-For: クライアント(ユーザー)の IP アドレス。
 >>Host: クライアントがアクセスしたホスト名。

・proxy_redirect off;
 >>バックエンドからのリダイレクトを Nginx 側で処理しない設定です。

・location /static/
 >>URL が /static/ で始まるリクエストを処理します。

・alias /static/;
 >>実際の静的ファイルが保存されているディレクトリ(ここでは /static/)を指定します。
  Nginx が直接静的ファイルを返します。
'''

3.1 Nginxの設定変更(docker-compose.prod.yml)の修正

docker-compose.prod.ymlのnginx部分の設定を変更していきます。
privkey.pem と fullchain.pemファイルは、ホスト(さくらvps)の/etc/letsencrypt/フォルダ内にあるため、ホストの/etc/letsencrypt/フォルダとコンテナの/etc/letsencrypt/を共有します。
また、https通信には、443ポートを使うので、ホストとコンテナの443ポートを接続します。

--省略
  web:
    container_name: web

    build:
      context: .
      dockerfile: ./containers/nginx/Dockerfile

    volumes:
      - ./static:/static
      - /etc/letsencrypt/:/etc/letsencrypt         # 追加

    ports:
      - "80:80"
      - "443:443"          # 追加

--省略
'''
# コードの説明
・- /etc/letsencrypt/:/etc/letsencrypt
 >>/etc/letsencrypt/(ホスト側のディレクトリ) をコンテナ内の /etc/letsencrypt/ とリンク(共有)します。
 これにより、ホスト上の証明書ファイルがコンテナから使用できるようになります。

・- "443:443"
>>443:443
 左側(ホスト側のポート): 443 番ポートを公開します。
 右側(コンテナ側のポート): コンテナ内で動作しているサービスの 443 番ポートに接続します。
'''

4. Settings.pyの修正

Django4.0以上の場合、settings.pyのCSRF_TRUSTED_ORIGINSに独自ドメインのURLを指定しないと、POSTリクエスト時に403 Forbiddenエラーが発生します。

Django 4.0以降では、セキュリティ強化のためにCSRF対策がアップデートされました。CSRF(クロスサイトリクエストフォージェリ)とは、悪意のあるサイトがユーザーになりすまして、意図しないリクエストを送る攻撃のことです。Djangoは、これを防ぐためにCSRFトークンを使い、リクエストが本当に正しいかどうかを確認しています。

CSRF保護の一環として、リクエストのOriginヘッダーが導入されました。これにより、リクエストの発信元(オリジン)が信頼できるものであるかを確認するようになりました。
具体的には、Originヘッダーの値がCSRF_TRUSTED_ORIGINSに含まれていない場合、Djangoはそのリクエストを拒否し、403 Forbiddenエラーを返します。

CSRF_TRUSTED_ORIGINSの設定方法
このエラーを防ぐためには、settings.pyファイルにCSRF_TRUSTED_ORIGINSを設定し、信頼できるオリジンを指定する必要があります。設定方法はsettings.pyに以下を追加します。

CSRF_TRUSTED_ORIGINS = [
    'https://example.example.jp',
    'https://www.example.example.jp',
]

ここで、https://example.example.jpの部分は、実際の独自ドメインに置き換えてください。重要なのは、プロトコル(httpやhttps)を含めて指定することです。

5. さくらのVPSでポートの許可(443番)

さくらのVPSではポートが制限されてるので、コントロールパネルから、さくらVPSで443番ポートを許可します。
フィルターの種類:カスタム、プロトコル:TCP、ポート番号:443 でパケットフィルターを追加します。
追加すると以下のように表示されます。

パケットフィルター設定画面

6. コンテナのビルドと起動

以下のコマンドを実行して、Dockerイメージをビルドし、コンテナを起動します。今回は、docker-compose.prod.ymlファイルからビルドを行うため、以下コードを実行します。

docker-compose -f docker-compose.prod.yml up -d --build

'''
# コードの説明
docker-compose up:
>>Docker Composeで定義されたサービス(コンテナ)を起動します。

-d(デタッチドモード):
>>コンテナをバックグラウンドで実行します。
 つまり、ターミナルを占有せずに他の作業ができます。

--build:
>>各サービスのDockerイメージをビルドしてから起動します。
 Dockerfileやコードに変更があった場合、新しいイメージを作成します。

-f:
>>使用するComposeファイルを指定します。
通常、Docker Composeはデフォルトでdocker-compose.ymlを使用します。
ここではdocker-compose.prod.ymlという別のファイルを指定しています。
'''

以上を行うと、作成したDjangoプロジェクトをhttpsで公開することができます。
https://さくらvpsのIPアドレス(IPv4):443 にアクセスするとアプリが正常に起動しているのが確認できます。

Djangoプロジェクト画面

7. SSL証明書の自動更新

今回取得した、SSL証明書(Let's Encrypt発行)の有効期限は90日間です。そのため、有効期限が過ぎる前にSSLの証明書を更新する必要があります。

Certbotを使ってSSL証明書を更新します。Certbotを使ってSSL証明書を更新しますが、手動更新は更新忘れのリスクがあるため、crontabを使い自動化します。

crontab(クローンタブ)は、LinuxやUnix系のシステムで特定のコマンドやスクリプトを決まったスケジュールで自動的に実行するための設定ファイルや仕組みのことです。

Certbotを使ってSSLを更新するコードをcrontabで定期実行するようにします。コードは以下となります。

0 2 * * 0 sudo /usr/bin/certbot renew --deploy-hook "/usr/local/bin/docker-compose -f /home/centos/**django_project**/docker-compose.prod.yml restart web" >> /tmp/certbot-renew.log 2>&1

上記では、1週間ごと(毎週日曜日午前2時)に、CertbotでSSL証明書の更新を行っています。
Certbotはデフォルトで、証明書の有効期限が30日以下になった場合のみ更新を試みます。それよりも有効期限が長い場合は、「更新する必要はありません」と判断して実際の更新処理をスキップします。
(1週間ごと更新していますが、有効期限が30日以上ある場合は処理がスキップされます)

あと特に重要なのは、certbotやdocker-composeのパスをフルパスで指定しているところです。はまりどころだと思うので、注意ください。ちなみに、フルパスの確認は、以下コードを実行すれば確認できます。

[centos***-***-***** ***]$ which docker-compose
/usr/local/bin/docker-compose


定期実行ではなく、単に下記コードを実行すると、更新の必要がないと表示されました。(問題なく実行されていることがわかります。)
定期実行の前に、処理が正常に行われるか確認する事をお勧めします。

[centos***-***-***** ***]$ sudo /usr/bin/certbot renew --deploy-hook "/usr/local/bin/docker-compose -f /home/centos/**django_project**/docker-compose.prod.yml restart web"
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/example.example.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certificate not yet due for renewal

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certificates are not due for renewal yet:
  /etc/letsencrypt/live/example.example.jp/fullchain.pem expires on 2025-03-01 (skipped)
No renewals were attempted.
No hooks were run.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

以上でSSL証明書の自動更新設定は完了です。

7. まとめ

以上で、さくらvpsでSSL通信(https)でDjangoプロジェクトを公開する流れを説明しました。少し手順が多くなりましたが、無料でSSL証明書を取得し、セキュアな通信を実現できました。

8. 参考


いいなと思ったら応援しよう!