File size: 2,501 Bytes
6d60378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package syncer

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"
)

// userAgent 出站标识。HF 与中转网关都会记录它,便于排障时区分是谁在同步。
const userAgent = "workbuddy2api-sync/1.0"

// newHTTPClient 构造同步用 HTTP 客户端。
//
// insecureSkipVerify 仅供本地抓包代理/自签证书排障(默认关闭);一旦开启会打一条
// 醒目警告——这条链路上流动的是账号 refreshToken,不能悄悄降级 TLS。
//
// caBundle 是额外的 PEM 证书包:某些抓包代理(DevSidecar / Reqable / ProxyPin)
// 会在系统信任库里装根证书,Go 在 Windows 上本来就吃系统库;但显式给一份也支持,
// 便于非 Windows 或证书没装进系统库的场景。
func newHTTPClient(timeout time.Duration, insecureSkipVerify bool, caBundle string) *http.Client {
	if timeout <= 0 {
		timeout = 60 * time.Second
	}
	tr := &http.Transport{
		Proxy:                 http.ProxyFromEnvironment,
		MaxIdleConns:          8,
		MaxIdleConnsPerHost:   4,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   20 * time.Second,
		ExpectContinueTimeout: 5 * time.Second,
		ForceAttemptHTTP2:     true,
	}

	tlsCfg := &tls.Config{MinVersion: tls.VersionTLS12}
	if caBundle != "" {
		pool, err := loadCABundle(caBundle)
		if err != nil {
			log.Printf("[syncer] 警告: 加载 ca_bundle 失败(%v),改用系统信任库", err)
		} else {
			tlsCfg.RootCAs = pool
		}
	}
	if insecureSkipVerify {
		log.Printf("[syncer] 警告: insecure_skip_verify 已开启,同步链路不再校验 TLS 证书")
		tlsCfg.InsecureSkipVerify = true // #nosec G402 —— 显式开关,默认关闭
	}
	tr.TLSClientConfig = tlsCfg

	return &http.Client{Timeout: timeout, Transport: tr}
}

// loadCABundle 读取一个 PEM 证书包,并在系统信任库之上追加它。
//
// 为什么要在系统库之上"追加"而不是替换:抓包代理的根证书只是多一个信任锚,
// 不该把公共 CA 全部丢掉——否则一旦代理临时停了,同步会连带访问不了任何 HTTPS。
func loadCABundle(path string) (*x509.CertPool, error) {
	pem, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("读取 %s: %w", path, err)
	}
	pool, err := x509.SystemCertPool()
	if err != nil || pool == nil {
		pool = x509.NewCertPool()
	}
	if !pool.AppendCertsFromPEM(pem) {
		return nil, fmt.Errorf("%s 里没有可解析的 PEM 证书", path)
	}
	return pool, nil
}