エラーの内容
IPアドレスでサービスに接続する際に発生します — KubernetesのAPIサーバー、内部のgRPCエンドポイント、カスタムHTTPSサービスなど、あらゆるケースで起こりえます:
x509: cannot validate certificate for 10.0.0.1 because it doesn't contain any IP SANs
TLSハンドシェイクが失敗します。Goはこれ以上処理を進めることを拒否し、kubectl、grpc-go、その他すべてのGoベースのクライアントも同様です。
原因
TLS証明書はサーバーを2つの仕組みで識別します: 旧来の**Common Name (CN)フィールドと、現代的なSubject Alternative Names (SAN)**拡張です。SANにはDNS名(DNS:)またはIPアドレス(IP:)が格納されます。
この証明書を生成した人物はCN=10.0.0.1を設定しただけで終わっており、SANエントリが一切ありません。SANが存在しない場合にTLSクライアントがCNにフォールバックしていた時代があったため、これは長年にわたって機能していました。**Go 1.15でそのフォールバックは非推奨になり、Go 1.16で完全に削除されました。**現在では、接続先のアドレスに一致するIP SANが証明書に含まれていない場合、Goは証明書を拒否します — 検証を完全に無効化する以外に回避策はありません。
これが原因かどうかを確認するには:
openssl s_client -connect 10.0.0.1:6443 </dev/null 2>&1 | openssl x509 -noout -text | grep -A1 "Subject Alternative"
ヘッダーの下に何も出力されなければ、SANが存在しないことを意味します。根本原因が確認されました。
応急処置 — 検証をスキップする(開発・テスト環境のみ)
今すぐ開発環境のブロックを解除する必要がありますか? 証明書の検証を無効化することも選択肢の一つです。本番環境では絶対に使用しないでください。
Goコードの場合:
import "crypto/tls"
import "net/http"
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
curlの場合:
curl -k https://10.0.0.1:6443/healthz
kubectlの場合:
kubectl --insecure-skip-tls-verify get nodes
これはあくまでも一時的な対応です。証明書は適切に再生成する必要があります。
恒久的な修正 — IP SANを含む証明書の再生成
オプション1: opensslによる自己署名証明書(Kubernetes以外)
重要なのは、明示的な[alt_names]セクションを含む設定ファイルです。SANをコマンドラインフラグとして渡す方法はミスが起きやすいですが、.cnfファイルを使えば指定漏れを防げます:
# san.cnf
[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[dn]
CN = 10.0.0.1
[req_ext]
subjectAltName = @alt_names
[alt_names]
IP.1 = 10.0.0.1
IP.2 = 127.0.0.1
DNS.1 = myservice.internal
鍵と証明書を生成します:
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 \
-keyout server.key \
-out server.crt \
-config san.cnf \
-nodes
SANが埋め込まれたことを確認します:
openssl x509 -in server.crt -noout -text | grep -A2 "Subject Alternative Name"
期待される出力:
X509v3 Subject Alternative Name:
IP Address:10.0.0.1, IP Address:127.0.0.1, DNS:myservice.internal
オプション2: Kubernetes APIサーバー(kubeadm)
これはkubectlでこのエラーが発生する最も一般的なケースです。APIサーバーの証明書が、ノードのIPをcertSANsに含めずに発行されています — 多くの場合、kubeadm initを実行する前に誰も追加することを考えていなかったためです。
まず現在のkubeadm設定を確認します:
kubectl -n kube-system get configmap kubeadm-config -o yaml
APIサーバーがアクセス可能なすべてのIPをリストしたパッチ設定を作成します:
# kubeadm-patch.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
apiServer:
certSANs:
- "10.0.0.1"
- "127.0.0.1"
- "kubernetes"
- "kubernetes.default"
- "kubernetes.default.svc"
- "kubernetes.default.svc.cluster.local"
既存の証明書をバックアップし、強制的に再生成します:
sudo mv /etc/kubernetes/pki/apiserver.crt /etc/kubernetes/pki/apiserver.crt.bak
sudo mv /etc/kubernetes/pki/apiserver.key /etc/kubernetes/pki/apiserver.key.bak
sudo kubeadm init phase certs apiserver --config kubeadm-patch.yaml
APIサーバーを再起動します。スタティックPodとして動作しているため、コンテナを削除するとkubeletが自動的に再作成します:
sudo crictl rm $(sudo crictl ps --name kube-apiserver -q)
# kubeletが数秒以内に自動的に再起動します
オプション3: cert-manager(クラスター内)
CertificateリソースのipAddressesフィールドにIPを追加します:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-service-cert
spec:
secretName: my-service-tls
ipAddresses:
- 10.0.0.1
dnsNames:
- myservice.internal
issuerRef:
name: my-ca-issuer
kind: ClusterIssuer
適用すると、cert-managerが正しいSANを含む新しい証明書を発行します。opensslの手動操作は不要です。
確認
証明書を入れ替えた後、SANが存在することを確認します:
openssl s_client -connect 10.0.0.1:6443 </dev/null 2>&1 | openssl x509 -noout -text | grep -A3 "Subject Alternative"
Kubernetesの場合は、insecureフラグを外してください — ここで正常なレスポンスが返れば証明書が有効であることを意味します:
kubectl get nodes
GoのHTTPクライアントの場合、InsecureSkipVerifyなしで通常のTLS接続が機能することを確認します:
resp, err := http.Get("https://10.0.0.1:6443/healthz")
if err != nil {
log.Fatal(err) // これ以上トリガーされないはずです
}
もう一つの注意点: カスタムCAを使用している場合、GoクライアントはサーバーCertだけでなく、CA証明書をトラストストアに読み込む必要があります。CA証明書が不足していると別のエラー(x509: certificate signed by unknown authority)が発生しますが、この段階でよくつまずくポイントです。
再発防止策
- 常に
.cnfファイルから証明書を生成してください。CN only証明書はGo 1.16以降で動作しません — 今後作成する理由はありません。 - リモートサービスであっても、
[alt_names]に127.0.0.1とlocalhostを含めてください。コストはゼロで、ローカルテスト中の別のデバッグセッションを回避できます。 - Kubernetesクラスターの場合は、APIサーバーに到達できる可能性のあるすべてのIP — ロードバランサーのVIP、ノードIP、内部クラスターIP — を、最初の
kubeadm initの前にcertSANsにリストしてください。後からSANを追加するには証明書のフルローテーションが必要になります。 - 生の
opensslの代わりにcfsslやstep-cliの使用を検討してください。どちらもSANの扱いがよりわかりやすく、設定ミスが格段に起きにくい設計になっています。

