内部TLSの問題:自己署名証明書の混乱と証明書の期限切れ
深夜にサーバーがSSHブルートフォース攻撃を受けて以来、見て見ぬふりをしてきたすべての攻撃面を徹底的に封じることへの執念が生まれた。あの事件で身に染みてわかったことがある——ファイアウォールとfail2banは境界線を守ってくれるが、内部トラフィックは平文で流れていた。認証情報も、セッショントークンも、すべてが。
内部TLSに対して、ほとんどのチームは2つのアプローチのどちらかに落ち着く。暗号化を完全にスキップして内部ネットワークは安全だと信頼するか、自己署名証明書をあちこちにばらまくかだ。どちらも解決策に見える。どちらも問題を積み重ねていく。
内部TLSをスキップすれば、ホストが1台侵害されただけでサービスメッシュ全体の平文認証情報が筒抜けになる。自己署名証明書を使えば、永遠に続くブラウザ警告の管理、サービス停止前の期限切れ証明書の追跡、そしてチームがセキュリティプロンプトを読まずにクリックする習慣を少しずつ身につけることになる。
根本的な問題は、内部インフラに対して信頼された認証局が存在しないことだ。Let’s EncryptのようなパブリックCAは、公開解決可能なドメイン名に対してしか証明書を発行しない。api.internal、db.prod.local、vault.homelabといった内部ホスト名は、永遠に対象外だ。
Step-CAがそのギャップを埋める。これはACMEプロトコル——Let’s Encryptが使用しているのと同じプロトコル——に対応したオープンソースのプライベート認証局だ。既存のACMEクライアント(certbot、acme.sh、Caddy)をプライベートCAに向けるだけで、コードの変更は不要。制御下にあるどのホスト名に対しても、証明書が自動的に発行され、期限切れ前に更新される。
インストール前に理解しておくべき基本概念
Step-CAとは
Step-CA(Smallstep製)は、自分のインフラ上で動作するCAサーバーだ。ルートCA証明書を管理し、正しく認証されたクライアントにリーフ証明書を発行する。Microsoft ADCSのような従来のエンタープライズCAとは異なり、Step-CAは小規模チーム向けに設計されており、モダンなDevOpsツールと相性がいい。サーバー全体が単一バイナリとして提供される——データベースも外部依存もない。
ACMEプロトコル——Let’s Encryptを支えるエンジン
ACME(Automated Certificate Management Environment、RFC 8555)は、無料の自動TLSを主流にしたプロトコルだ。クライアントはチャレンジ——HTTP-01、DNS-01、またはTLS-ALPN-01——を通じてドメインの制御を証明し、CAが証明書を発行する。プライベートCAを使えば、両側を自分でコントロールできる。つまり、内部ホスト名の検証ルールはすべて自分で設定できるということだ。
信頼チェーン
ブラウザ警告なしにStep-CA証明書を信頼させるには、マシンごとに1回だけStep-CAのルート証明書をOSのトラストストアにインストールすればいい。それだけだ。CAが今後発行するすべての証明書は、そのマシンで自動的に信頼される——証明書ごとの設定は不要だ。
実践:Step-CAをACMEとともにデプロイする
ステップ1:Step-CAとstep CLIをインストールする
Step-CAは単一バイナリとして提供される。Debian/Ubuntuの場合:
# step CLI(証明書管理クライアント)をインストール
wget https://dl.smallstep.com/cli/docs-cli-install/latest/step-cli_amd64.deb
sudo dpkg -i step-cli_amd64.deb
# step-caサーバーをインストール
wget https://dl.smallstep.com/certificates/docs-ca-install/latest/step-ca_amd64.deb
sudo dpkg -i step-ca_amd64.deb
# バージョン確認
step version
step-ca version
RHEL/Rocky/AlmaLinuxの場合は、SmallstepのGitHubリリースページからRPMパッケージを入手してください。
ステップ2:プライベートCAを初期化する
専用のシステムユーザーを作成し、そのホームディレクトリ配下でCAを初期化する:
sudo useradd --system --home /etc/step-ca --shell /bin/false step
sudo mkdir -p /etc/step-ca
sudo chown step:step /etc/step-ca
# stepユーザーとしてCAを初期化
sudo -u step env STEPPATH=/etc/step-ca step ca init \
--name="社内プライベートCA" \
--dns="ca.internal,ca.yourdomain.local" \
--address=":9000" \
--provisioner="[email protected]"
これにより、/etc/step-ca/certs/配下にルートおよび中間CA証明書が、/etc/step-ca/secrets/配下に秘密鍵が生成される。CAの秘密鍵用パスワードの設定を求めるプロンプトが表示される。HashiCorp Vault、AWS Secrets Manager、あるいは最低限でもCAホスト上の0400権限ファイル(stepユーザーのみ読み取り可能)など、安全な場所に保管すること。
ステップ3:ACMEプロビジョナーを追加する
Step-CAはデフォルトでJWKプロビジョナーを使用する。標準的なACMEクライアントが証明書をリクエストできるよう、ACMEプロビジョナーを追加する:
sudo -u step env STEPPATH=/etc/step-ca \
step ca provisioner add acme --type ACME
実行後に/etc/step-ca/config/ca.jsonを確認すると、プロビジョナー配列に"type": "ACME"のエントリが追加されているはずだ。ACMEディレクトリURLはhttps://ca.internal:9000/acme/acme/directoryになる。
ステップ4:Step-CAをsystemdサービスとして実行する
cat <<EOF | sudo tee /etc/systemd/system/step-ca.service
[Unit]
Description=step-ca プライベート認証局
After=network.target
[Service]
Type=simple
User=step
Group=step
Environment=STEPPATH=/etc/step-ca
ExecStart=/usr/bin/step-ca /etc/step-ca/config/ca.json --password-file /etc/step-ca/password.txt
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now step-ca
sudo systemctl status step-ca
CAのパスワードを/etc/step-ca/password.txtに保存し、権限を制限する:
sudo chmod 600 /etc/step-ca/password.txt
sudo chown step:step /etc/step-ca/password.txt
ステップ5:クライアントの信頼を設定する
CAを信頼する必要があるすべてのマシンにルート証明書をインストールしなければならない。step CLIでこれを数秒で完了できる:
# CAフィンガープリントを取得(CAサーバーで1回だけ実行)
step certificate fingerprint /etc/step-ca/certs/root_ca.crt
# 各クライアントマシンで実行——信頼を設定する
step ca bootstrap \
--ca-url https://ca.internal:9000 \
--fingerprint YOUR_FINGERPRINT_HERE
# ルート証明書をシステムトラストストアにインストール
sudo step certificate install $(step path)/certs/root_ca.crt
フィンガープリントをAnsible、Salt、またはPuppetを使ってクライアントマシンに配布し、一回限りのブートストラップタスクとして実行する。一度実行すれば、そのマシンはCAが署名するすべての証明書——数か月後に発行されたものも含めて——を自動的に信頼するようになる。再設定は不要だ。
ステップ6:ACMEで証明書を発行する
任意のACMEクライアントをプライベートCAエンドポイントに向けられるようになった。certbotを使う場合——certbotはACMEサーバーのTLS証明書をシステムトラストストアと照合するため、先にステップ5を完了させること:
certbot certonly \
--standalone \
--server https://ca.internal:9000/acme/acme/directory \
--domain api.internal \
--email [email protected] \
--agree-tos
certbotがACMEディレクトリに接続できない場合、最も多い原因はルートCA証明書がシステムストアにインストールされていないことだ。REQUESTS_CA_BUNDLEで明示的に指定する:
REQUESTS_CA_BUNDLE=/etc/step-ca/certs/root_ca.crt certbot certonly \
--standalone \
--server https://ca.internal:9000/acme/acme/directory \
--domain api.internal \
--email [email protected] \
--agree-tos
acme.shを使う場合(certbotが使えないときや、証明書発行をスクリプト化したいときに便利):
acme.sh --issue \
--server https://ca.internal:9000/acme/acme/directory \
--domain api.internal \
--standalone \
--ca-bundle /etc/step-ca/certs/root_ca.crt
Caddyユーザーはこれを自動的に利用できる——CaddyfileでStep-CAディレクトリURLをacme_caに設定すれば、あとはCaddyがすべて処理してくれる。
ステップ7:証明書の有効期間と更新を設定する
Step-CAはデフォルトで有効期間24時間のACME証明書を発行する。これは意図的に短く設定されている。最初から自動化を強制し、秘密鍵が漏洩した場合のリスクを最小化するためだ——24時間で失効する漏洩証明書は、1年間有効なものよりはるかに被害が少ない。ca.jsonのACMEプロビジョナーのクレームを更新サイクルに合わせて調整する:
{
"type": "ACME",
"name": "acme",
"claims": {
"minTLSCertDuration": "24h",
"maxTLSCertDuration": "720h",
"defaultTLSCertDuration": "168h"
}
}
certbotのsystemdタイマーが更新を自動で処理してくれる。step CLIを直接使っている場合、renewデーモンモードがcronジョブなしで証明書を最新の状態に保つ:
# バックグラウンドデーモンとして実行——期限切れ前に積極的に更新
step ca renew --daemon \
/etc/ssl/private/api.internal.crt \
/etc/ssl/private/api.internal.key
動作確認
# 発行された証明書を確認
step certificate inspect /etc/letsencrypt/live/api.internal/cert.pem
# 信頼を設定済みのクライアントからTLSをテスト
curl https://api.internal
# ルートCAに対して証明書チェーンを明示的に検証
openssl verify -CAfile /etc/step-ca/certs/root_ca.crt \
/etc/letsencrypt/live/api.internal/cert.pem
次のステップ
これで、内部インフラが信頼するプライベートCAが構築され、標準的なACMEを通じてTLS証明書が自動的に発行・更新されるようになった。自己署名証明書の警告はもうない。誰かが証明書の更新を忘れてサービスが停止することもない。「どうせ内部トラフィックだから」という平文通信の言い訳も通用しなくなる。
広く展開する前に対処しておくべきことがいくつかある。ルートCAの秘密鍵をオフラインかつ複数の場所にバックアップすること——失ってしまうと、インフラ全体のすべての証明書を最初からやり直しになる。高可用性のためには、共有中間CAを使ったセカンダリStep-CAインスタンスを追加で稼働させること。CAホストを廃止する前に、どのマシンが信頼設定済みかを監査すること——そうしないと、フリート全体でTLSエラーを追いかける羽目になる。
基本構成の先に目を向けると、Step-CAはOIDCプロビジョナー(GoogleまたはGitHubのアイデンティティに紐づいた証明書)、SSH認証局モード(静的な鍵管理を置き換える短期間有効なSSH証明書)、PKCS#11を介したハードウェア鍵ストレージもサポートしている。ACMEの基盤がうまく稼働し始めたら探ってみる価値がある——各機能は実際の問題を解決しており、まだ必要のない運用負荷を増やすことなく導入できる。

