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
}
|