LoginSignup
10
4

More than 1 year has passed since last update.

はじめに

MYJLab Advent Calender 22日目の記事です。担当のセイです。

参加は2年目ですが、昨年と違って、今年は卒論に追われながら参加しています。

httpsの理念からSSL/TLSとか、非対称暗号とか、CAとルート証明書とか、man-in-the-middle attackとか、丁寧に語りたいが、なかなかそうも言っていられない状況ですので、超ざっくりで説明します。




つまり、理屈とかいいかとにかくワイのサイトに綺麗な🔒マークをつけるぞ!!!!




という勢いです。

なお、
この記事で作成したファイルは以下のリポジで公開しています。
https://github.com/KuroiCc/myjlab-advent-calendar-2022-day22/tree/main

作ったサイトは以下のlinkで公開しています。
気まぐれで(awsから請求が来たら)予告なく消す場合がありますので、見てみたい人はお早めに。
https://www.myjlab-advent-calendar-2022-day22.ml/

事前準備

  1. dockerがインストールされていて、Public IPでアクセルできるホスト
  2. ドメインをすでに持ってかつそのドメインは上記のホストのPublic IPに紐ついている

誰でも再現できるためにこの記事ではホストはAWS EC2で公開しています。OSはUbuntu, 22.04 LTS, amd64 、インスタンスタイプは t2.micro を使用しています。

ドメインはfreenomというサイトに無料でとりました。

HTTP(sじゃない方)で公開

新しいAWS EC2インスタンスを作ってsshで接続します。

まずは、apt更新とdockerのインストール

sudo apt update && apt upgrade
sudo apt install docker docker-compose

dockerインストールしたら、動いてるかどうか試してみよう

sudo docker run hello-world

image

よし、動いていそうですね

nginxをホストマシンにインストールするのは結構面倒臭いので、dockerでnginxサーバを立ち上げます。

今回のプロジェクトフォルダーを作って中に移動する

mkdir nginx-docker && cd nginx-docker

docker-compose.ymlファイルを作る

nano docker-compose.yml

中身はこんな感じ

services:
  nginx:
    image: nginx:latest
    ports:
      - 80:80 # ホストの80ポートをこのコンテナの80に転送する
      - 443:443 # ホストの443ポートをこのコンテナの443に転送する
    volumes:
      # マウントする必要のあるもの
      - /etc/letsencrypt:/etc/letsencrypt # あとで発行する証明書の置き場
      - ./conf:/etc/nginx/conf.d # 設定ファイルの置き場
      - ./logs:/var/log/nginx # nginxのlog等の置き場
      - ./html:/usr/share/nginx/html # 公開する内容の置き場
    environment:
      - TZ=Asia/Tokyo # コンテナ内のtimezone

image

次に公開するページを用意します。

mkdir html
nano html/index.html

これ以上はないほどにシンプルなページです。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>MYJLab Advent Calendar 2022 Day 22 - Work Page</title>
  <!-- Tailwind CSS CDN -->
  <script src="https://cdn.tailwindcss.com"></script>

</head>

<body>
  <!-- some cool tailwind page with just title generate by copilot -->
  <div class="flex flex-col justify-center min-h-screen py-6 bg-gray-100 sm:py-12">
    <div class="relative py-3 sm:max-w-xl sm:mx-auto">
      <div
        class="absolute inset-0 transform -skew-y-6 shadow-lg bg-gradient-to-r from-cyan-400 to-light-blue-500 sm:skew-y-0 sm:-rotate-6 sm:rounded-3xl">
      </div>
      <div class="relative px-4 py-10 bg-white shadow-lg sm:rounded-3xl sm:p-20">
        <div class="max-w-md mx-auto">
          <div class="flex items-center justify-between space-x-2">
            <img src="https://www.myjlab.org/img/logo-small.png" class="h-7" alt="MYJLab">
            <img src="https://www.myjlab.org/img/favicon.ico" class="h-7 rounded" alt="MYJLab">
          </div>
          <div class="divide-y divide-gray-200">
            <div class="py-8 space-y-4 text-base leading-6 text-gray-700 sm:text-lg sm:leading-7">
              <p>MYJLab Advent Calendar 2022 Day 22 - Work Page</p>
              <p>Thank you for visiting my site.</p>
              <p>Have a nice day.</p>
            </div>
            <div class="pt-6 text-base font-bold leading-6 sm:text-lg sm:leading-7">
              <p>MYJLab Advent Calendar 2022</p>
              <p><a href="https://qiita.com/advent-calendar/2022/myjlab"
                  class="text-cyan-600 hover:text-cyan-700">https://qiita.com/advent-calendar/2022/myjlab</a></p>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

</html>

image

あとでHTTPSのSSL証明書を発行ためのフォルダです。説明はあとでします。

mkdir html/_letsencrypt

次はniginxの設定ファイルを作成します。

mkdir conf
nano conf/default.conf

内容はこんな感じ

ページを公開するめに最低限の設定、server_name のところは自分のドメインの変更するように

server {
    listen 80;
    listen [::]:80; # 80ポートを監視
    server_name www.myjlab-advent-calendar-2022-day22.ml;

    # コンテナ内部の/usr/share/nginx/htmlのフォルダ(=./html)をroot path(`/`)として公開
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }

    # コンテナ内部の/usr/share/nginx/html/_letsencrypt(=./html/_letsencrypt)を/.well-known/acme-challengeのpathとして公開
    location /.well-known/acme-challenge/ {
        root /usr/share/nginx/html/_letsencrypt;
    }
}

image

次はdockerを起動すればサイトは公開されるはず。

sudo docker-compose up -d

httpでアクセスしてみると。。。。。アクセスできる!!!

だが、保護されない通信とある、これはいけません。

今からこのサイトに綺麗な🔒マークをつけていきます。

image

HTTPSでサイトを公開

HTTPSにするには一番手間なのは証明書の発行です。

今回は米国の非営利団体で運営ししている Let's Encrypt という認証局にSSL証明書を発行してもらいます。

実際はcertbotというLet's Encrypt公式推奨なツールで証明書を発行します。

Let's Encryptが発行する証明書の有効期限は90日しかないが、certbotは自動更新もしてくれます。素晴らしい!!!

自動で証明書発行原理はあとでちょっとだけ説明しますが、

、、、、とりあえず、、、まずは、コマンド打って発行してみよう!!!

certbotをインストール

sudo apt install certbot

インストールできたら以下のコマンドで会話的に証明書を発行できます。発行は成功しても失敗しても1日に上限がありますので、最初は --dry-run をつけてお試しに発行してみましょう

sudo certbot certonly --webroot --dry-run

最初はユーザ登録のためemailを要求してきます。

image

利用契約を同意してくださいという意味です、もちろんyyyyyyyyyyes!

image

申し込むドメインを入力します

image

webサイトのルートディレクトリを入力してくださいという意味です、

ここは ./html/_letsencrypt と入れます

image

このメッセージが表示されたら、発行成功です。

image

次は --dry-run 抜きで、正式に発行してみましょう!

sudo certbot certonly --webroot

image

と、、こういうメッセージが表示されたら成功です。

証明書は → /etc/letsencrypt/live/www.myjlab-advent-calendar-2022-day22.ml/fullchain.pem

キーは → /etc/letsencrypt/live/www.myjlab-advent-calendar-2022-day22.ml/privkey.pem

にそれぞれ保存だれています。

実は、dockerで設定しているときここには罠がございます。文章の最後に書きますので、興味ある人は読んでみてください。

次は、HTTPSに対応できるようniginxの設定ファイル変更します。

echo "" > conf/default.conf
nano conf/default.conf

内容は

server {
    # http 80番ポートでのリクエストをすべてhttpsにリダイレクト
    listen 80;
    listen [::]:80;
    server_name www.myjlab-advent-calendar-2022-day22.ml;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name www.myjlab-advent-calendar-2022-day22.ml;

    # SSL証明書の設定
    ssl_certificate /etc/letsencrypt/live/www.myjlab-advent-calendar-2022-day22.ml/fullchain.pem;
    # SSL秘密鍵の設定
    ssl_certificate_key /etc/letsencrypt/live/www.myjlab-advent-calendar-2022-day22.ml/privkey.pem;
    # SSLのプロトコルを指定
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # コンテナ内部の/usr/share/nginx/htmlのフォルダ(=./html)をroot path(`/`)として公開
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }

    # コンテナ内部の/usr/share/nginx/html/_letsencrypt(=./html/_letsencrypt)を/.well-known/acme-challengeのpathとして公開
    # 証明書自動更新用
    location /.well-known/acme-challenge/ {
        root /usr/share/nginx/html/_letsencrypt;
    }
}

dockerのコンテナを再起動すればうまくいくはずです。

sudo docker-compose restart

サイトをアクセスしてみると!!!!

image

綺麗な🔒マークが、、、、、ついてる!!!!!!!

証明書の中身は確認できている。すばら!

image

参考までに、ec2を立ち上げてから入れた全てのコマンドここに置いときます

ubuntu@ip-172-31-40-34:~/nginx-docker$ history 

1  sudo apt update && apt upgrade  
2  sudo apt install docker docker-compose  
3  sudo docker run hello-world  
4  mkdir nginx-docker && cd nginx-docker  
5  nano docker-compose.yml  
6  mkdir html  
7  nano html/index.html  
8  mkdir html/_letsencrypt  
9  mkdir conf  
10  nano conf/default.conf  
11  sudo docker-compose up -d  
12  sudo apt install certbot  
13  sudo certbot certonly --webroot --dry-run  
14  sudo certbot certonly --webroot  
15  nano conf/default.conf  
16  echo "" > conf/default.conf  
17  nano conf/default.conf  
18  sudo docker-compose restart  
19  history

証明書の自動発行の原理及びACME

Let's Encrypt はACME(アクミー, Automatic Certificate Management Environment)というプロトコルで証明書を発行している。それについてすごくざっくり説明します。

とあるドメインに対して証明書を発行するには、認証局にそのドメインは確かに自分がコントロールしていることを証明する必要があります。

以前は人工的にやる必要があるが、それを自動化するため、ACMEというプロトコルが提出されました。

原理は、まず証明書の利用者がACMEクライアント(この記事の場合はcertbot)のコマンドを実行して、ホストに暗号ファイルを置きます。

次に、ACMEサーバ(認証局、この記事の場合はLet's Encrypt)がドメインをアクセルし、暗号ファイルを取得して答え合わせをします。答え合わせができたら、利用者は確かにそのドメインをコントロールしていることが証明され、証明書は発行されます。

検証

証明書を更新するコマンドを実行します

sudo certbot renew --webroot --dry-run

すると提供したルートディレクトリ(この記事の場合は ./html/_letsencrypt)にこんなファイルが生成された

image

それと同時にnginx-docker/logs/access.log からもこんなアクセル履歴を確認できます

23.178.112.106 - - [22/Dec/2022:22:43:06 +0900] "GET /.well-known/acme-challenge/VxVUs8mtN14xNYYomMK-cTP8p6nxLMlAkrPh9WP_gkg HTTP/1.1" 200 87 "http://www.myjlab-advent-calendar-2022-day22.ml/.well-known/acme-challenge/VxVUs8mtN14xNYYomMK-cTP8p6nxLMlAkrPh9WP_gkg" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" "-"

認証用nginx設定

    # コンテナ内部の/usr/share/nginx/html/_letsencrypt(=./html/_letsencrypt)を/.well-known/acme-challengeのpathとして公開
    location /.well-known/acme-challenge/ {
        root /usr/share/nginx/html/_letsencrypt;
    }

前にあとで説明したというこの設定はこの自動認証を通すために設定です。

今回のように静的サイトの場合静的サイトの場合はURLのパスはそのままディレクトリのpathに変換されますので、この設定を消して、webルートディレクトリーをほんとのルートディレクター ./html に設定しても認証チャレンジ自体は通ることができます。

<your domain>/any/path/here に対してアクセルすると

./html/any/path/here というファイルのアクセルに変換されます

認証チャレンジの時は

./html/.well-known/acme-challenge/<暗号ファイル with long hash as its name> にこの場合暗号ファイルは生成されて、

<your domain>/.well-known/acme-challenge/<暗号ファイル with long hash as its name> のurl対するアクセルはこのままファイルのアクセルに変換されます。

ところが、webサイトとして公開する内容はReactなどのSPAの場合は、URLのpath関係なく全部のアクセルは index.htmlのアクセスにリダイレクトされる。

<your domain>/any/path/here
<your domain>/どんな/パス/であっても に対するアクセルはすべて

./html/index.html に転送されます。

この場合は認証チャレンジを通ることはできないので、特別に /.well-known/acme-challenge/ というパスへのアクセルするときのみ、認証専用のフォルダに転送するよう設定する必要があります。

docker nginxにLet's Encrypt 利用時の落とし穴

証明書を発行成功するとき

image

証明書は → /etc/letsencrypt/live/www.myjlab-advent-calendar-2022-day22.ml/fullchain.pem
キーは → /etc/letsencrypt/live/www.myjlab-advent-calendar-2022-day22.ml/privkey.pem

に保存されましてよというメッセージが表示されます。

これをみるとこういう風に docker-compose.yml を書いてしまいがちです。

volumes:
  - /etc/letsencrypt/live:/etc/letsencrypt/live

あるいは

volumes:
  - /etc/letsencrypt/live/<your.domain>:/etc/letsencrypt/live/<your.domain>

でもこの書き方は間違いです、ダメです、バツ❌です。

nginxのコンテナは証明書を見つかりませんとかいうエラーを吐いて、一瞬で終了しちゃいます。

その原因は、実は証明書とそのキーはメッセージで表示された場所には保存していませんからです。

ls -al で証明書が保存されるとしているフォルダの中身を確認してみると

image

そのフォルダの中身は証明書の本体ではなく、本体へのソフトリンクでした

上記のように docker-compose.yml を書くと /etc/letsencrypt/aechive はコンテナにマウントされないので、当然証明書は見つからないわけだ!

ということで、必ず

volumes:
  - /etc/letsencrypt:/etc/letsencrypt

と書かないといけない。

まとめ

certbotを使えばLet's Encryptから簡単にSSLの証明書を発行することができて、自分のサイトに綺麗な🔒マークをつけさせる、、いかがでしたか?

クラウドインフラが主流の今ではhttpsなんてやってくれるところが多いのであんまり必要ないかもしれないね

眠いのでここまでにしときます。

では僕が無事卒業できることを祈って、またいつかでお会いしましょう!

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4