Files
larksuite-cli/internal/transport/tls_ca.go
liangshuo-1 4710a294f5 refactor(transport): own all HTTP transport in internal/transport, fix util layering inversion (#1213)
internal/util imported internal/proxyplugin (SharedTransport, FallbackTransport,
NewHTTPClient, and WarnIfProxied via proxyPluginStatus), so a foundational util
package depended up into a feature package, pulling binding/core/vfs into the
transitive cone of every util importer.

Move internal/proxyplugin -> internal/transport and make it the single owner of
outbound transport: fold the two SharedTransport functions into one Shared()
(proxy-plugin override -> LARK_CLI_NO_PROXY -> http.DefaultTransport), and move
Fallback/NewHTTPClient/WarnIfProxied/DetectProxyEnv/noProxyTransport out of the
now-deleted internal/util/proxy.go into the new package. The proxy-plugin probe
is demoted to a private pluginTransport(); the duplicate redactProxyURL collapses
to one. internal/util keeps no proxy code and is a leaf again.

Re-point all consumers (registry, doctor, config, auth, cmdutil, update) to
internal/transport. Behavior-preserving: package move + symbol rename + dedup.
Two new tests lock the fail-closed contract (plugin overrides NO_PROXY; malformed
config never falls through to direct egress).
2026-06-02 16:10:35 +08:00

69 lines
2.1 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package transport
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"path/filepath"
"strings"
"github.com/larksuite/cli/internal/binding"
"github.com/larksuite/cli/internal/envvars"
"github.com/larksuite/cli/internal/vfs"
)
// applyExtraRootCA augments t with an additional PEM bundle used for configured proxy
// TLS interception.
func applyExtraRootCA(t *http.Transport, caPath string) error {
caPath = strings.TrimSpace(caPath)
if caPath == "" {
return nil
}
if !filepath.IsAbs(caPath) {
return fmt.Errorf("invalid %s %q: must be an absolute path to a PEM file", envvars.CliCAPath, caPath)
}
safeCAPath, err := binding.AssertSecurePath(binding.AuditParams{
TargetPath: caPath,
Label: envvars.CliCAPath,
AllowReadableByOthers: true,
})
if err != nil {
return fmt.Errorf("unsafe %s %q: %w", envvars.CliCAPath, caPath, err)
}
pemBytes, err := vfs.ReadFile(safeCAPath)
if err != nil {
return fmt.Errorf("failed to read %s %q: %w", envvars.CliCAPath, caPath, err)
}
// Augment the system trust store. Do NOT silently discard a SystemCertPool
// error: falling back to an empty pool would make this transport trust ONLY
// the extra CA (dropping all system roots), which narrows trust unexpectedly
// and could break TLS to legitimate endpoints. Fail closed instead.
pool, err := x509.SystemCertPool()
if err != nil {
return fmt.Errorf("failed to load system cert pool for %s: %w", envvars.CliCAPath, err)
}
if pool == nil {
pool = x509.NewCertPool()
}
if ok := pool.AppendCertsFromPEM(pemBytes); !ok {
return fmt.Errorf("invalid %s %q: no certificates parsed from PEM", envvars.CliCAPath, caPath)
}
if t.TLSClientConfig == nil {
t.TLSClientConfig = &tls.Config{}
} else {
// Clone to avoid mutating shared config from the base transport.
t.TLSClientConfig = t.TLSClientConfig.Clone()
}
if t.TLSClientConfig.MinVersion == 0 || t.TLSClientConfig.MinVersion < tls.VersionTLS12 {
t.TLSClientConfig.MinVersion = tls.VersionTLS12
}
t.TLSClientConfig.RootCAs = pool
return nil
}