Golang客户端与Apache SSL连接问题排查
Golang客户端与Apache SSL连接问题排查 我遇到了一个Go客户端访问Apache SSL服务器的奇怪问题。我需要用我的X509证书对客户端进行身份验证。以下是客户端代码:
// helper function to create a client
func HttpClient() *http.Client {
uckey := os.Getenv("X509_USER_KEY")
ucert := os.Getenv("X509_USER_CERT")
cert, err := tls.LoadX509KeyPair(ucert, uckey)
if err != nil {
panic(err.Error())
}
certs := []tls.Certificate{cert}
// root CA
caCert, err := ioutil.ReadFile("my-grid-CA.pem")
if err != nil {
panic(err.Error())
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{Certificates: certs, RootCAs: caCertPool}
tlsConfig.BuildNameToCertificate()
tr := &http.Transport{TLSClientConfig: tlsConfig}
return &http.Client{Transport: tr}
}
然后我使用以下代码进行HTTPS调用:
rurl := "MY_URL"
req, _ := http.NewRequest("GET", rurl, nil)
req.Header.Add("Accept-Encoding", "identity") // I setup other headers in a similar way
client := HttpClient()
resp, err := client.Do(req)
我遇到了身份验证错误,因为我的客户端证书没有传播到Apache服务器。
从Apache服务器我发现mod_ssl提取了我的服务器CA并创建了SSL_SERVER_CERT和类似的SSL_SERVER头信息,但对于客户端证书,它只创建了:
SSL_CLIENT_VERIFY: NONE
SSL_CLIENT_CERT:
没有其他内容。如果我使用Python代码或普通的curl,我确实看到Apache正确识别了客户端证书,提取了我的DN等,也就是说它设置了SSL_CLIENT_S_DN和其他头信息,这些后来被身份验证代码使用。
如何在Go代码中转储请求以及传递的证书?我使用了httputil.DumpRequestOut,但它没有打印我的证书,只提供了请求头信息。
我还遗漏了什么? 我将非常感谢任何帮助。
更多关于Golang客户端与Apache SSL连接问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我找到了解决方案。原来是Apache服务器的配置问题,它本应能够识别Go客户端提供的客户端证书链。令人惊讶的是,curl和Python客户端没有这个问题,当我使用它们时,Apache服务器能够正确识别客户端证书。Apache的实际配置需要以下参数:
SSLCECertificatePath /path/certificates
SSLCARevocationPath /path/certificates
SSLCARevocationCheck chain
没有这些选项,Apache将无法识别Go客户端传递的x509证书的DN(可识别名称)。
更多关于Golang客户端与Apache SSL连接问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
您遇到的问题是客户端证书未正确传递到Apache服务器,导致服务器无法识别客户端身份验证。以下是排查和解决方案:
问题分析
从Apache的响应头SSL_CLIENT_VERIFY: NONE和空的SSL_CLIENT_CERT可以看出,客户端证书握手失败。可能的原因包括:
- 证书格式或路径问题
- TLS配置不完整
- 服务器证书验证失败
解决方案
1. 增强TLS配置
修改您的HttpClient函数,添加更完整的TLS配置:
func HttpClient() *http.Client {
uckey := os.Getenv("X509_USER_KEY")
ucert := os.Getenv("X509_USER_CERT")
// 加载客户端证书
cert, err := tls.LoadX509KeyPair(ucert, uckey)
if err != nil {
panic(fmt.Sprintf("Failed to load key pair: %v", err))
}
// 加载CA证书
caCert, err := ioutil.ReadFile("my-grid-CA.pem")
if err != nil {
panic(fmt.Sprintf("Failed to read CA cert: %v", err))
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
panic("Failed to parse CA certificate")
}
// 完整的TLS配置
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
ServerName: "your-server-name", // 添加服务器名称
MinVersion: tls.VersionTLS12, // 设置最低TLS版本
}
tr := &http.Transport{
TLSClientConfig: tlsConfig,
TLSHandshakeTimeout: 30 * time.Second,
}
return &http.Client{
Transport: tr,
Timeout: 60 * time.Second,
}
}
2. 添加详细的调试信息
创建调试函数来检查证书和请求详情:
func debugTLSInfo(tlsConfig *tls.Config) {
fmt.Printf("Number of client certificates: %d\n", len(tlsConfig.Certificates))
for i, cert := range tlsConfig.Certificates {
fmt.Printf("Certificate %d:\n", i)
if len(cert.Certificate) > 0 {
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
fmt.Printf(" Failed to parse certificate: %v\n", err)
continue
}
fmt.Printf(" Subject: %s\n", x509Cert.Subject)
fmt.Printf(" Issuer: %s\n", x509Cert.Issuer)
fmt.Printf(" Not Before: %s\n", x509Cert.NotBefore)
fmt.Printf(" Not After: %s\n", x509Cert.NotAfter)
}
}
if tlsConfig.RootCAs != nil {
fmt.Printf("Root CAs configured: %d certificates\n", len(tlsConfig.RootCAs.Subjects()))
}
}
// 在HttpClient函数中添加调试
func HttpClient() *http.Client {
// ... 之前的代码 ...
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
ServerName: "your-server-name",
}
// 调试信息
debugTLSInfo(tlsConfig)
// ... 其余代码 ...
}
3. 增强请求调试
使用自定义Transport来捕获TLS握手详情:
type debugTransport struct {
transport http.RoundTripper
}
func (d *debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
fmt.Printf("Making request to: %s\n", req.URL)
fmt.Printf("Headers: %v\n", req.Header)
// 使用httputil.DumpRequestOut获取请求详情
dump, err := httputil.DumpRequestOut(req, true)
if err != nil {
fmt.Printf("Error dumping request: %v\n", err)
} else {
fmt.Printf("Request dump:\n%s\n", string(dump))
}
resp, err := d.transport.RoundTrip(req)
if err != nil {
fmt.Printf("Request error: %v\n", err)
return nil, err
}
fmt.Printf("Response Status: %s\n", resp.Status)
fmt.Printf("Response Headers: %v\n", resp.Header)
return resp, err
}
// 修改HttpClient使用调试transport
func HttpClient() *http.Client {
// ... TLS配置代码 ...
tr := &http.Transport{TLSClientConfig: tlsConfig}
debugTr := &debugTransport{transport: tr}
return &http.Client{Transport: debugTr}
}
4. 验证证书有效性
添加证书验证函数:
func validateCertificates(ucert, uckey string) error {
// 验证证书文件存在
if _, err := os.Stat(ucert); os.IsNotExist(err) {
return fmt.Errorf("certificate file not found: %s", ucert)
}
if _, err := os.Stat(uckey); os.IsNotExist(err) {
return fmt.Errorf("key file not found: %s", uckey)
}
// 加载并验证证书
cert, err := tls.LoadX509KeyPair(ucert, uckey)
if err != nil {
return fmt.Errorf("failed to load certificate: %v", err)
}
if len(cert.Certificate) == 0 {
return fmt.Errorf("no certificates found in key pair")
}
// 解析第一个证书
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return fmt.Errorf("failed to parse certificate: %v", err)
}
// 检查证书是否过期
now := time.Now()
if now.Before(x509Cert.NotBefore) {
return fmt.Errorf("certificate not yet valid (NotBefore: %s)", x509Cert.NotBefore)
}
if now.After(x509Cert.NotAfter) {
return fmt.Errorf("certificate expired (NotAfter: %s)", x509Cert.NotAfter)
}
fmt.Printf("Certificate validated: Subject=%s, Issuer=%s\n",
x509Cert.Subject, x509Cert.Issuer)
return nil
}
// 在HttpClient开始时调用
func HttpClient() *http.Client {
uckey := os.Getenv("X509_USER_KEY")
ucert := os.Getenv("X509_USER_CERT")
if err := validateCertificates(ucert, uckey); err != nil {
panic(err.Error())
}
// ... 其余代码 ...
}
5. 完整的调试版本
这是包含所有调试功能的完整版本:
func HttpClient() *http.Client {
uckey := os.Getenv("X509_USER_KEY")
ucert := os.Getenv("X509_USER_CERT")
// 验证证书
if err := validateCertificates(ucert, uckey); err != nil {
panic(err.Error())
}
// 加载证书
cert, err := tls.LoadX509KeyPair(ucert, uckey)
if err != nil {
panic(fmt.Sprintf("Failed to load key pair: %v", err))
}
// 加载CA
caCert, err := ioutil.ReadFile("my-grid-CA.pem")
if err != nil {
panic(fmt.Sprintf("Failed to read CA cert: %v", err))
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
panic("Failed to parse CA certificate")
}
// TLS配置
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
ServerName: "your-server-name",
MinVersion: tls.VersionTLS12,
}
// 调试TLS信息
debugTLSInfo(tlsConfig)
tr := &http.Transport{
TLSClientConfig: tlsConfig,
TLSHandshakeTimeout: 30 * time.Second,
}
debugTr := &debugTransport{transport: tr}
return &http.Client{
Transport: debugTr,
Timeout: 60 * time.Second,
}
}
运行这个增强版本将提供详细的调试信息,帮助您识别客户端证书未传递的具体原因。重点关注证书验证输出和TLS握手过程中的任何错误信息。

