ヘルプサイトへの検証済みアクセスを復旧する

サイバーセキュリティBeginner
オンラインで実践に進む

はじめに

サポートチームがヘルプサイトを公開しましたが、クライアントが HTTPS 接続を拒否しています。HTTPS は TLS を使い、通信を暗号化するとともに、応答したサーバーの身元を証明します。暗号化だけでは身元を証明できません。要求した名前が正しく、証明書の発行者を信頼できることも必要です。

実際の Nginx サーバーで、合成した学習用証明書を修復します。基本的なターミナル操作ができれば十分です。証明書の調査とサービス設定はここで説明します。Client と Server は同じ VM の別セッションとディレクトリであり、別のコンピューターではありません。Nginx は準備済みコンテナー内で動作します。任意の HTTPS Monitor は内部 TLS の実測結果を表示します。ブラウザーの鍵アイコンは別の外側の接続を示します。

信頼できる参照文書を取得する

このステップでは、正常なサービスから文書を取得してから、故障したヘルプサイトと比較します。ホスト名は要求するサービスを識別します。学習用の名前はすべて .lab.test で終わり、この演習内だけで使用します。

Client タブを使います。curl は URL の内容を取得します。--cacert root.crt は、証明書に署名する提供済みの認証局(CA)を信頼します。これは学習用のトラストアンカーであり、公開認証局ではありません。--resolve は名前とポートを IP アドレスに対応付け、TLS と HTTP では元の名前を維持します。127.0.0.1 はこの VM、ポート 8443 は準備済みの Nginx サービスを示します。--noproxy '*' は直接要求を送り、引用符はシェルによる * の展開を防ぎます。--fail は HTTP エラーを失敗とし、--output は後で使う実際のサービス文書を保存します。

curl --noproxy '*' --fail --cacert root.crt --resolve status.lab.test:8443:127.0.0.1 --output reference.txt https://status.lab.test:8443/

cat はファイルの内容を表示します。

cat reference.txt
status service: training content available

これは実際にダウンロードした参照文書であり、報告書ではありません。次に、本来のヘルプホスト名を要求します。このコマンドは失敗してプロンプトに戻る想定です。その後も続けてください。

curl --noproxy '*' --cacert root.crt --resolve help.lab.test:8443:127.0.0.1 https://help.lab.test:8443/
curl: (60) SSL: no alternative certificate subject name matches target host name 'help.lab.test'
...

同じ CA とアドレスで参照サービスは成功しました。このエラーはサーバーの停止ではなく、名前の不一致を示します。本来のホスト名を維持してください。URL を変えると別の身元を検証することになります。

証明書チェーンを調査して組み立てる

このステップでは、実際に提供される身元と準備済みの証明書を比較し、サーバーで使用できるバンドルを作ります。証明書には公開鍵と身元情報が含まれます。対応する秘密鍵はサーバーに保管します。秘密鍵を報告書に貼り付けたり、クライアントへ送ったりしないでください。

Client で実際の証明書を調べます。openssl s_client は TLS 接続を開きます。-connect はアドレス、-servername は Server Name Indication(SNI)で要求名を指定し、1 つの Nginx リスナーで複数サイトを提供できるようにします。< /dev/null は対話入力を終了します。2>/dev/null は今回の調査に限り接続診断を非表示にします。パイプ | は証明書を openssl x509 に渡します。-noout は符号化された証明書を省き、残りのオプションで必要な項目を表示します。この調査だけでは信頼性は証明できません。

openssl s_client -connect 127.0.0.1:8443 -servername help.lab.test </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
subject=CN = legacy.lab.test
issuer=CN = LabEx Training Issuing CA
notBefore=...
notAfter=...
X509v3 Subject Alternative Name:
    DNS:legacy.lab.test

サブジェクト代替名(SAN)は使用可能なホスト名を示します。issuer は署名者、日付は有効期間を示します。セットアップ時にこの VM 用の新しい証明書を生成し、リーフ証明書は 365 日間有効です。有効な日付でも名前の誤りは補えません。

Server に切り替えます。openssl x509 -in はネットワーク応答ではなくローカル証明書を読みます。

openssl x509 -in certs/help.crt -noout -subject -issuer -dates -ext subjectAltName

SAN は DNS:help.lab.test で、要求と一致します。必要な対応関係は次のとおりです。

項目 役割
help.crt と help.key ヘルプサイトの証明書と対応する秘密鍵
docs.crt と docs.key 文書サイトの証明書と対応する秘密鍵
issuing.crt 両サイトに署名した中間 CA
../client/root.crt クライアントが信頼するルート CA

チェーンはサイト証明書 → 中間 CA → 信頼済みルート CAです。Nginx はリーフを先に、その後に中間証明書を送ります。ルートはクライアントがすでに信頼しているため送る必要はありません。cat でこの公開バンドルを作ります。> は出力をファイルに書き、以前の内容を置き換えます。秘密鍵はバンドルに含めません。

cat certs/help.crt certs/issuing.crt > certs/help-fullchain.pem

openssl verify は指定した信頼済みルートに対してバンドルを検証します。-untrusted は中間証明書をチェーン候補として渡すもので、信頼するルートを追加するものではありません。-verify_hostname は本来の名前を検証します。

openssl verify -CAfile ../client/root.crt -untrusted certs/help-fullchain.pem -verify_hostname help.lab.test certs/help-fullchain.pem
certs/help-fullchain.pem: OK

失敗した場合は入力ファイルの順序と綴りを確認し、cat を再実行します。これで準備したバンドルは検証できましたが、稼働中のサーバーはまだ使用していません。

ヘルプサイトの身元を適用する

このステップでは、状態サイトを維持したまま Nginx に正しい身元を提供させます。Server で cat を使って設定を表示します。

cat conf/help.conf

server_name はサイト、ssl_certificate は公開チェーン、ssl_certificate_key は秘密鍵を指定します。/tls/ から始まるパスは Nginx コンテナー内の場所で、このディレクトリの certs/ に対応します。return 200 行は実際の学習用ヘルプ内容を返します。リスナー、名前、応答は変更しません。

テキストエディター nano で開きます。

nano conf/help.conf

矢印キーで 2 つの TLS ディレクティブを見つけ、その 2 行だけを次のように置き換えます。

    ssl_certificate /tls/help-fullchain.pem;
    ssl_certificate_key /tls/help.key;

セミコロンは Nginx ディレクティブの終わりです。Ctrl+O、Enter で保存し、Ctrl+X で終了します。パスを誤入力したら同じファイルを開き直して修正します。

sudo は管理者権限で実行します。docker exec https-edge は後続のコマンドを準備済みサービスコンテナー内で実行します。nginx -t は設定を適用せずにテストします。

sudo docker exec https-edge nginx -t

syntax is ok と test is successful を確認します。失敗したら、示されたファイルと行を修正してから続けます。nginx -s reload は稼働中のサービスにテスト済み設定の再読み込みを要求します。

sudo docker exec https-edge nginx -s reload

Client に戻り、本来の要求を繰り返します。再読み込みには少し時間がかかる場合があります。古い証明書が返る場合は同じ要求を再実行します。

curl --noproxy '*' --fail --cacert root.crt --resolve help.lab.test:8443:127.0.0.1 https://help.lab.test:8443/
help service: training content available

参照サイトの正常動作も維持します。

curl --noproxy '*' --fail --cacert root.crt --resolve status.lab.test:8443:127.0.0.1 https://status.lab.test:8443/

この成功では、ヘルプ応答を受け取る前に実際のホスト名、チェーン、有効期間を検証しています。使われていない証明書ファイルを編集しただけでは成功しません。

不足する中間証明書を修復する

このステップでは、不完全なチェーンと修復済みの名前不一致を区別します。Client のまま、準備済みの文書サイトを要求します。次の失敗は意図的なものです。

curl --noproxy '*' --cacert root.crt --resolve docs.lab.test:8443:127.0.0.1 https://docs.lab.test:8443/
curl: (60) SSL certificate problem: unable to get local issuer certificate
...

どちらの失敗も終了コードは 60 です。併記された説明を読みます。同じ形式のコマンドで証明書を調査します。

openssl s_client -connect 127.0.0.1:8443 -servername docs.lab.test </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

今回は SAN が DNS:docs.lab.test です。名前は正しくても Nginx がリーフしか送らないため、クライアントは発行者をルートへ結び付けられません。

比較のため、-k で一度だけ証明書検証を省略します。信頼が壊れたままでも内容を取得できることが分かります。

curl --noproxy '*' -k --resolve docs.lab.test:8443:127.0.0.1 https://docs.lab.test:8443/

ここで docs service: training content available が表示されても修復ではありません。Server に戻り、稼働設定のファイルを調べて開きます。

cat conf/docs.conf
nano conf/docs.conf

準備済みの docs-fullchain.pem はリーフと中間証明書を順に含みます。ssl_certificate 行だけを変更します。

    ssl_certificate /tls/docs-fullchain.pem;

秘密鍵は /tls/docs.key のままにします。前と同じ方法で保存して終了し、テストと再読み込みを行います。

sudo docker exec https-edge nginx -t
sudo docker exec https-edge nginx -s reload

Client で 3 つの名前を -k なしで要求します。ループは列挙した値を順に host に代入します。$host はその値を挿入し、二重引用符は生成された各引数をまとめます。

for host in help docs status; do
  curl --noproxy '*' --fail --cacert root.crt --resolve "$host.lab.test:8443:127.0.0.1" "https://$host.lab.test:8443/"
done

対応する 3 つのサービス応答が返ります。HTTPS Monitor を更新し、独立した実測と比較しても構いません。エラーは該当設定に戻って修正し、テストして再読み込みします。クライアントの信頼ルートや本来の名前は変えません。実運用では組織の発行手順で承認された証明書を取得します。この演習では診断に集中できるよう合成候補を提供しています。

まとめ

信頼できる参照文書を取得し、実際の証明書の名前と発行者を調べ、リーフと中間証明書のバンドルを作り、テスト済みの Nginx 再読み込みで適用しました。正常なサービスを維持しながら名前不一致と不完全なチェーンを修復しました。次のチャレンジでは別のポータルでこれらの判断を組み合わせます。