はじめに
前回はCaddyをReverse Proxyとして導入し、
http://flask.home.arpa
http://kuma.home.arpa
というHost名でFlaskとUptime Kumaへアクセスできるようにしました。
構成は、
Windows PC
|
| HTTP :80
v
Caddy
|
+----> Flask
|
+----> Uptime Kuma
となっています。
今回はこの通信をHTTPS化します。
最終的には、
https://flask.home.arpa
https://kuma.home.arpa
へ証明書警告なしでアクセスできるようにします。
HTTPSにすると何が変わるのか
HTTPではClientとServer間の通信がTLSによって暗号化されていません。
HTTPSでは、
HTTP
+
TLS
=
HTTPS
という形でTLSを使って通信を保護します。
今回の構成ではCaddyがHTTPSを担当します。
Windows PC
|
| HTTPS
v
Caddy
|
| HTTP
v
Flask
Flask自身をHTTPS化するのではありません。
CaddyでTLS通信を終了し、内部では通常のHTTPでFlaskへReverse Proxyします。
このような構成を**TLS Termination(TLS終端)**と呼びます。
今回はCaddyのInternal CAを使う
今回使用しているHost名は、
flask.home.arpa
kuma.home.arpa
です。
これは家庭内ネットワークで使用している名前であり、公開Webサイト用のドメインではありません。
そこで今回は、Caddyが持つInternal CAを利用します。
イメージとしては、
Caddy Internal CA
|
+---- 証明書 ---> flask.home.arpa
|
+---- 証明書 ---> kuma.home.arpa
となります。
公開Webサイトで利用するLet's Encryptなどの公開CAとは異なり、今回のCAは自分の環境内で使用するCAです。
CaddyfileをHTTPS対応にする
前回はHTTPSを無効にするため、
http://flask.home.arpa {
reverse_proxy web:5000
}
http://kuma.home.arpa {
reverse_proxy uptime-kuma:3001
}
としていました。
これを次のように変更しました。
flask.home.arpa {
tls internal
reverse_proxy web:5000
}
kuma.home.arpa {
tls internal
reverse_proxy uptime-kuma:3001
}
tls internalによってCaddyのInternal CAを利用します。
443番Portを公開する
HTTPSでは通常443番Portを使用します。
Caddyのcompose.yamlを変更します。
services:
caddy:
image: caddy:latest
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
restart: unless-stopped
networks:
- default
- docker-lab
- uptime-kuma
networks:
docker-lab:
external: true
name: docker-lab_default
uptime-kuma:
external: true
name: uptime-kuma_default
volumes:
caddy-data:
caddy-config:
今回は、
- "80:80"
- "443:443"
を公開しています。
80番はHTTP、443番はHTTPSで使用します。
CaddyのデータをVolumeへ保存する
今回新しく、
- caddy-data:/data
- caddy-config:/config
を追加しました。
そして、
volumes:
caddy-data:
caddy-config:
としています。
特に/dataには、Caddyが管理する証明書やInternal CAに関係する重要なデータが保存されます。
Containerは再作成できますが、CAまで毎回作り直してしまうと、Windows側が信頼しているCAと一致しなくなる可能性があります。
そのため、CaddyのデータもVolumeへ永続化します。
設定を確認する
Compose設定を確認しました。
sudo docker compose config
問題がなければ反映します。
sudo docker compose up -d
確認します。
sudo docker compose ps
結果として、
0.0.0.0:80->80/tcp
0.0.0.0:443->443/tcp
が確認できました。
Host側でも確認します。
sudo ss -lntp | grep -E ':80 |:443 '
80番と443番の両方がListenしていました。
Caddyが証明書を発行したことを確認する
Caddyのログを確認します。
sudo docker compose logs --tail=50 caddy
ログには、
enabling automatic HTTP->HTTPS redirects
が表示されました。
つまりCaddyがHTTPからHTTPSへのRedirectを自動設定しています。
さらに、
enabling automatic TLS certificate management
も確認できました。
証明書についても、
certificate obtained successfully
となっており、
flask.home.arpa
kuma.home.arpa
の証明書が正常に発行されました。
HTTPSへアクセスしてみる
Windowsから、
https://flask.home.arpa
へアクセスしました。
しかし、この段階ではブラウザに証明書警告が表示されました。
Caddyが証明書を発行できているのに、なぜ警告されるのでしょうか。
理由は、
Caddy
|
| 「この証明書は私が保証します」
v
Windows
「そのCAをまだ信用していない」
という状態だからです。
証明書を発行できることと、Clientがその発行元を信頼することは別です。
CaddyのRoot CAを確認する
Caddy Container内を確認しました。
sudo docker compose exec caddy \
ls -l /data/caddy/pki/authorities/local/
次のファイルが確認できました。
intermediate.crt
intermediate.key
root.crt
root.key
ここで非常に重要なのが、
root.crt
と、
root.key
の違いです。
root.crtはRoot CAの公開証明書です。
一方、
root.key
は秘密鍵です。
秘密鍵が漏れると、そのCAを使って不正な証明書を作られる可能性があります。
そのため、
root.crt → Clientへ配布してよい
root.key → 外へ出さない
という扱いが必要です。
Root CAをWindowsへコピーする
Caddy ContainerからRaspberry Piへroot.crtをコピーしました。
sudo docker cp \
caddy:/data/caddy/pki/authorities/local/root.crt \
./root.crt
sudo docker cpで作成したため、必要に応じて所有者と権限を変更しました。
sudo chown pi:pi ~/caddy/root.crt
chmod 644 ~/caddy/root.crt
Windowsからscpを使って取得します。
scp pi@192.168.1.93:/home/pi/caddy/root.crt .\root.crt
重要なのは、コピーするのはroot.crtだけという点です。
root.keyはコピーしません。
WindowsにRoot CAを信頼させる
管理者としてPowerShellを起動し、Root CAをWindowsの信頼されたRoot証明書ストアへ登録しました。
certutil -addstore -f Root "$env:USERPROFILE\root.crt"
これによって、
Caddy Internal CA
|
| root.crt
v
Windows
「このCAを信頼する」
という状態になります。
ブラウザを再起動して、
https://flask.home.arpa
へアクセスすると、今度は証明書警告が表示されませんでした。
https://kuma.home.arpa
も同様にアクセスできました。
HTTPからHTTPSへ自動転送される
次に、
http://flask.home.arpa
へアクセスしました。
すると、自動的に、
https://flask.home.arpa
へ移動しました。
これはCaddyのAutomatic HTTPSによるRedirectです。
通信の流れは、
http://flask.home.arpa
|
| HTTP :80
v
Caddy
|
| HTTPSへRedirect
v
https://flask.home.arpa
|
| HTTPS :443
v
Caddy
となります。
実際の証明書をOpenSSLで確認する
HTTPSで使われている証明書をOpenSSLで確認しました。
openssl s_client \
-connect 127.0.0.1:443 \
-servername flask.home.arpa \
</dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
結果は、
subject=
issuer=CN=Caddy Local Authority - ECC Intermediate
notBefore=Aug 30 05:30:39 2026 GMT
notAfter=Aug 30 17:30:39 2026 GMT
となりました。
issuerを見ると、
Caddy Local Authority - ECC Intermediate
となっています。
つまりCaddyのInternal CAによって発行された証明書です。
証明書の有効期限が約12時間だった
今回確認した証明書は、
notBefore=Aug 30 05:30:39 2026 GMT
notAfter=Aug 30 17:30:39 2026 GMT
となっており、有効期間は約12時間でした。
ここで、
12時間後にHTTPSが使えなくなるのでは?
と思いました。
しかし、Caddyは証明書を自動管理します。
そのため、通常は有効期限が来る前にCaddyが新しい証明書へ更新します。
つまり、
証明書の有効期限が12時間
だからといって、
12時間後にHTTPSが使えなくなる
という意味ではありません。
今回caddy-data Volumeを永続化していることも重要です。
Caddy Container
|
X 再作成
|
v
caddy-data Volume
|
+---- Internal CA
+---- 証明書関連データ
Containerを再作成しても、CaddyのPKI関連データを保持できます。
ただし、caddy-data Volumeまで削除してInternal CAを作り直した場合は注意が必要です。
新しいRoot CAが作られると、Windowsへ登録した古いRoot CAとは別のCAになる可能性があります。
その場合は、新しいroot.crtをWindowsへ登録し直す必要があります。
SANを確認する
証明書が本当にflask.home.arpa用なのか確認しました。
openssl s_client \
-connect 127.0.0.1:443 \
-servername flask.home.arpa \
</dev/null 2>/dev/null \
| openssl x509 -noout -text \
| grep -A2 "Subject Alternative Name"
結果は、
X509v3 Subject Alternative Name: critical
DNS:flask.home.arpa
Signature Algorithm: ecdsa-with-SHA256
となりました。
SANは、
Subject Alternative Name
の略です。
ここに、
DNS:flask.home.arpa
が入っています。
ブラウザは証明書の名前とアクセス先のHost名が一致しているかを確認します。
アクセス先
flask.home.arpa
↓ 比較
証明書SAN
DNS:flask.home.arpa
↓
一致
この確認も、HTTPSでServerを検証するための重要な仕組みです。
CAの信頼関係
今回の証明書の信頼関係を簡略化すると、
Caddy Root CA
|
v
Caddy Intermediate CA
|
v
flask.home.arpa用証明書
となります。
WindowsにはRoot CAを信頼させました。
そのため、
Windows
|
| Root CAを信頼
v
Caddy Root CA
|
v
Intermediate CA
|
v
flask.home.arpa
という信頼のChainを検証できます。
TLSで何が行われているのか
細かな仕組みは非常に複雑ですが、今回の実験で重要な部分を簡略化すると、
1. ClientがServerへ接続する
2. Serverが証明書を提示する
3. Clientが証明書を検証する
4. TLSで暗号化通信に必要な情報を安全に確立する
5. 暗号化されたHTTP通信を行う
という流れになります。
証明書には単に「暗号化する」という役割だけでなく、
接続しているServerが意図したHost名のServerなのかを検証する
という重要な役割があります。
TLS Termination
今回の構成では、HTTPSを処理しているのはFlaskではありません。
Caddyです。
Windows PC
|
| HTTPS / TLS
v
Caddy
|
| HTTP
v
Flask
CaddyでTLS通信を終了しているため、
TLS Termination
と呼ばれます。
Flask側ではこれまで通り、
web:5000
でHTTP Serverを動かしています。
Caddyが、
- 証明書
- HTTPS
- TLS
- Redirect
をまとめて担当します。
複数のWeb Applicationがある場合でも、それぞれに個別でTLS設定を実装する必要がなく、Reverse Proxy側へ集約できます。
今回分かったこと
今回の実験では、次のことを確認できました。
- HTTPSではTLSを利用する
- CaddyはInternal CAを持てる
- Caddyが証明書を自動発行できる
- Client側がCAを信頼していなければ証明書警告が出る
- Root CAをWindowsへ登録すると信頼できる
-
root.crtとroot.keyはまったく扱いが違う - HTTPからHTTPSへ自動Redirectできる
- 証明書には有効期限がある
- Caddyは証明書を自動管理・更新する
- SANに証明書の対象Host名が入っている
- CaddyでTLS Terminationできる
- Flask自身をHTTPS化しなくてもClientからHTTPSでアクセスできる
現在の構成
最終的な通信経路は次のようになりました。
Windows PC
|
| HTTP :80
v
Caddy
|
| Redirect
v
HTTPS :443
|
| TLS
v
Caddy
|
| Docker Network / HTTP
|
+----> Flask :5000
|
+----> Uptime Kuma :3001
ClientからCaddyまではHTTPSで暗号化されています。
Caddyから各ContainerまではDocker Network内をHTTPで通信しています。
おわりに
前回はReverse Proxyを使ってWebサービスの入口をCaddyへまとめました。
今回はさらにCaddyへTLSを担当させ、
http://
から、
https://
へ移行しました。
実際に証明書警告が出る状態から始めて、Root CAをWindowsへ登録すると警告が消えるところまで確認したことで、
証明書がある
だけでは不十分で、
その証明書を発行したCAをClientが信頼できる
ことが重要だと分かりました。
Dockerの学習から始めましたが、Reverse Proxyを導入したことで、
Docker Network
Reverse Proxy
DNS / hosts
HTTP
HTTPS
TLS
CA
Certificate
がどのようにつながっているのかも見えてきました。
次回は、この環境をさらに実運用へ近づけるための構成を試していきます。