Sửa lỗi x509: cannot validate certificate for IP because it doesn't contain any IP SANs

intermediate🔒 SSL/TLS2026-07-10| Go 1.15+, Kubernetes, kubectl, kubeadm, mọi TLS client kết nối qua địa chỉ IP

Error Message

x509: cannot validate certificate for 10.0.0.1 because it doesn't contain any IP SANs
#go#kubernetes#x509#san

Lỗi Gặp Phải

Lỗi này xuất hiện khi kết nối đến một dịch vụ qua địa chỉ IP — API server của Kubernetes, một gRPC endpoint nội bộ, một HTTPS service tùy chỉnh, hay bất kỳ thứ gì tương tự:

x509: cannot validate certificate for 10.0.0.1 because it doesn't contain any IP SANs

Quá trình TLS handshake thất bại. Go từ chối tiếp tục, và kubectl, grpc-go cũng như mọi client viết bằng Go khác đều có hành vi tương tự.

Nguyên Nhân

Chứng chỉ TLS xác định server thông qua hai cơ chế: trường Common Name (CN) truyền thống và extension Subject Alternative Names (SAN) hiện đại. SAN chứa các DNS name (DNS:) hoặc địa chỉ IP (IP:).

Người tạo chứng chỉ này chỉ đặt CN=10.0.0.1 rồi dừng lại — không có entry SAN nào cả. Cách này từng hoạt động nhiều năm vì các TLS client trước đây sẽ fallback về CN khi không có SAN. Go 1.15 đã deprecated cơ chế fallback đó. Go 1.16 loại bỏ hoàn toàn. Bây giờ nếu một chứng chỉ không có IP SAN khớp với địa chỉ bạn đang kết nối, Go sẽ từ chối — không có cách nào override ngoài việc tắt hẳn việc xác thực.

Xác nhận đây đúng là vấn đề:

openssl s_client -connect 10.0.0.1:6443 </dev/null 2>&1 | openssl x509 -noout -text | grep -A1 "Subject Alternative"

Nếu không có output bên dưới header nghĩa là không có SAN. Nguyên nhân gốc rễ đã được xác nhận.

Giải Pháp Tạm Thời — Bỏ Qua Xác Thực (Chỉ Dùng Khi Dev/Test)

Cần unblock môi trường dev ngay lúc này? Tắt xác thực chứng chỉ là một lựa chọn. Tuyệt đối không dùng cách này trên môi trường production.

Trong code Go:

import "crypto/tls"
import "net/http"

http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
    InsecureSkipVerify: true,
}

Với curl:

curl -k https://10.0.0.1:6443/healthz

Với kubectl:

kubectl --insecure-skip-tls-verify get nodes

Hãy coi đây chỉ là giải pháp tạm thời. Chứng chỉ vẫn cần được tạo lại đúng cách.

Giải Pháp Triệt Để — Tạo Lại Chứng Chỉ Với IP SANs

Phương án 1: Tự Ký Chứng Chỉ Bằng openssl (Không Dùng Kubernetes)

Điều quan trọng là dùng một file config với section [alt_names] rõ ràng. Truyền SAN qua tham số dòng lệnh rất dễ sai; dùng file .cnf giúp bạn không bao giờ quên:

# 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

Tạo key và chứng chỉ:

openssl req -x509 -newkey rsa:2048 -sha256 -days 365 \
  -keyout server.key \
  -out server.crt \
  -config san.cnf \
  -nodes

Xác minh SAN đã được nhúng vào:

openssl x509 -in server.crt -noout -text | grep -A2 "Subject Alternative Name"

Output mong đợi:

X509v3 Subject Alternative Name:
    IP Address:10.0.0.1, IP Address:127.0.0.1, DNS:myservice.internal

Phương án 2: API Server Kubernetes (kubeadm)

Đây là nguyên nhân phổ biến nhất gây ra lỗi này khi dùng kubectl. Chứng chỉ API server được cấp mà không có IP của node trong certSANs — thường vì không ai nghĩ đến việc thêm nó trước khi chạy kubeadm init.

Kiểm tra cấu hình kubeadm hiện tại trước:

kubectl -n kube-system get configmap kubeadm-config -o yaml

Tạo một patch config liệt kê mọi IP mà API server có thể được truy cập:

# 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"

Backup các chứng chỉ hiện có, rồi buộc tạo lại:

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

Khởi động lại API server. Nó chạy dưới dạng static pod, vì vậy việc xóa container sẽ để kubelet tạo lại:

sudo crictl rm $(sudo crictl ps --name kube-apiserver -q)
# kubelet tự động khởi động lại trong vài giây

Phương án 3: cert-manager (trong cluster)

Thêm IP vào trường ipAddresses trong resource Certificate của bạn:

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

Apply và cert-manager sẽ cấp một chứng chỉ mới với đúng SAN. Không cần thực hiện thủ công bằng openssl.

Kiểm Tra Lại

Sau khi thay chứng chỉ, xác nhận SAN đã có mặt:

openssl s_client -connect 10.0.0.1:6443 </dev/null 2>&1 | openssl x509 -noout -text | grep -A3 "Subject Alternative"

Với Kubernetes, bỏ flag insecure — nếu nhận được response bình thường nghĩa là chứng chỉ đã hợp lệ:

kubectl get nodes

Với Go HTTP client, xác nhận kết nối TLS bình thường hoạt động mà không cần InsecureSkipVerify:

resp, err := http.Get("https://10.0.0.1:6443/healthz")
if err != nil {
    log.Fatal(err) // không nên xảy ra nữa
}

Một lưu ý quan trọng nữa: nếu bạn đang dùng CA tùy chỉnh, Go client cần nạp chứng chỉ CA vào trust store — không chỉ chứng chỉ server. Thiếu chứng chỉ CA sẽ gây ra lỗi khác (x509: certificate signed by unknown authority), nhưng đây là điều hay gặp phải ở giai đoạn này.

Phòng Ngừa Cho Lần Sau

  • Luôn tạo chứng chỉ từ file .cnf. Chứng chỉ chỉ có CN đã bị Go từ chối từ phiên bản 1.16 — không có lý do gì để tạo theo kiểu đó nữa.
  • Luôn thêm 127.0.0.1localhost vào [alt_names] kể cả với remote service. Không tốn thêm gì và tránh được một buổi debug riêng khi test local.
  • Với Kubernetes cluster, liệt kê mọi IP mà API server có thể được truy cập — VIP của load balancer, IP của node, IP nội bộ trong cluster — vào certSANs trước lần kubeadm init đầu tiên. Thêm SAN sau khi đã init đồng nghĩa với việc phải rotate toàn bộ chứng chỉ.
  • Cân nhắc dùng cfssl hoặc step-cli thay vì openssl thuần. Cả hai đều xử lý SAN gọn gàng hơn và khó cấu hình sai hơn nhiều.

Related Error Notes