mirror of
https://github.com/larksuite/cli.git
synced 2026-07-06 16:18:05 +08:00
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).
69 lines
2.1 KiB
Go
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
|
|
}
|