エラーメッセージ
x509: certificate is valid for example.com, not api.example.com
2 つの名前には、失敗ごとに実際の値が入ります。先に証明書がカバーする名前、次にクライアントが要求した名前です。
Go crypto/x509 — HostnameError from VerifyHostname / TLS dials
このエラーの意味
TLS 接続は証明書を読み取れるところまでは進みましたが、証明書のサブジェクト代替名(SAN)のどれも、クライアントが接続したホスト名と一致しません。エラーには両方の名前が示されるため、食い違いはすぐにわかります。証明書がカバーしているのは X で、要求したのは Y です。なお、現在の検証では SAN だけが使われ、従来のコモンネームフィールドは無視されます。
発生する理由
使用中の名前が SAN リストにない
よくあるexample.com に対して発行された証明書は api.example.com をカバーしません(ワイルドカード *.example.com がカバーするのも 1 階層だけで、example.com 自体や a.b.example.com はカバーしません)。新しいサブドメインには、証明書の再発行が必要です。
内部のエイリアスに接続している
よくあるdocker-compose のサービス名やその他の内部エイリアスに接続すると、サーバーは自身が想定している名前の証明書を提示します。SAN に含まれるのはパブリックな DNS 名で、内部のエイリアスではないため、照合に失敗します。IP アドレスを直接指定して接続した場合は、代わりに同系統の別のメッセージ「x509: cannot validate certificate for 10.0.0.5 because it doesn't contain any IP SANs」が出ます。
誤った証明書またはデフォルトの証明書が返されている(SNI)
ときどきマルチテナントの Ingress は、SNI で証明書を選択します。ServerName を省略したクライアントや、そのホストに対応するルールがない Ingress では、デフォルトの証明書が返されます。多くの場合、それは「Kubernetes Ingress Controller Fake Certificate」のようなダミーの証明書です。
修正方法
- 1.
実際に使うすべての名前を含めて再発行する
発行時にすべてのホスト名を SAN として追加してください。現時点でカバーされている名前は、現在の SAN リストを調べると確認できます。
bash echo | openssl s_client -connect host:443 -servername api.example.com 2>/dev/null \ | openssl x509 -noout -ext subjectAltName # request with SANs: openssl req -new -key key.pem -out req.csr \ -subj "/CN=example.com" -addext "subjectAltName=DNS:example.com,DNS:api.example.com" - 2.
名前で接続するか、ServerName を明示的に設定する
ネットワーク上のアドレスを証明書の名前と別にしなければならない場合(VIP、ポートフォワード、サイドカー)は、どの ID(証明書上の名前)を期待しているかを TLS に伝え、検証が本来の役割を果たすようにしてください。
go conn, err := tls.Dial("tcp", "10.0.0.5:443", &tls.Config{ ServerName: "api.example.com", // matched against SANs, sent as SNI })