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

2 回复

我找到了解决方案。原来是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可以看出,客户端证书握手失败。可能的原因包括:

  1. 证书格式或路径问题
  2. TLS配置不完整
  3. 服务器证书验证失败

解决方案

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握手过程中的任何错误信息。

回到顶部