mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 09:11:44 +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).
174 lines
5.7 KiB
Go
174 lines
5.7 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package transport
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// mustCreateTestCertPEM generates a short-lived self-signed CA certificate for tests.
|
|
func mustCreateTestCertPEM(t *testing.T) []byte {
|
|
t.Helper()
|
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
t.Fatalf("GenerateKey() error = %v", err)
|
|
}
|
|
|
|
der, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{
|
|
CommonName: "proxyplugin-test-ca",
|
|
},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
|
IsCA: true,
|
|
BasicConstraintsValid: true,
|
|
}, &x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{
|
|
CommonName: "proxyplugin-test-ca",
|
|
},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
|
IsCA: true,
|
|
BasicConstraintsValid: true,
|
|
}, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatalf("CreateCertificate() error = %v", err)
|
|
}
|
|
|
|
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
|
|
}
|
|
|
|
// TestApplyExtraRootCA_EmptyPathIsNoop verifies that an empty CA path leaves the transport unchanged.
|
|
func TestApplyExtraRootCA_EmptyPathIsNoop(t *testing.T) {
|
|
tr := &http.Transport{}
|
|
|
|
if err := applyExtraRootCA(tr, " "); err != nil {
|
|
t.Fatalf("applyExtraRootCA() error = %v", err)
|
|
}
|
|
if tr.TLSClientConfig != nil {
|
|
t.Fatalf("TLSClientConfig = %#v, want nil", tr.TLSClientConfig)
|
|
}
|
|
}
|
|
|
|
// TestApplyExtraRootCA_RejectsRelativePath verifies that CA paths must be absolute.
|
|
func TestApplyExtraRootCA_RejectsRelativePath(t *testing.T) {
|
|
tr := &http.Transport{}
|
|
|
|
err := applyExtraRootCA(tr, "ca.pem")
|
|
if err == nil || !strings.Contains(err.Error(), "must be an absolute path") {
|
|
t.Fatalf("applyExtraRootCA() error = %v, want absolute-path error", err)
|
|
}
|
|
}
|
|
|
|
// TestApplyExtraRootCA_RejectsMissingFile verifies missing PEM bundles fail before file reads.
|
|
func TestApplyExtraRootCA_RejectsMissingFile(t *testing.T) {
|
|
tr := &http.Transport{}
|
|
|
|
err := applyExtraRootCA(tr, filepath.Join(t.TempDir(), "missing.pem"))
|
|
if err == nil || !strings.Contains(err.Error(), "unsafe") {
|
|
t.Fatalf("applyExtraRootCA() error = %v, want unsafe path error", err)
|
|
}
|
|
}
|
|
|
|
// TestApplyExtraRootCA_RejectsInvalidPEM verifies validation of malformed PEM bundles.
|
|
func TestApplyExtraRootCA_RejectsInvalidPEM(t *testing.T) {
|
|
caPath := filepath.Join(t.TempDir(), "invalid.pem")
|
|
writeFile(t, caPath, []byte("not a pem"), 0600)
|
|
|
|
tr := &http.Transport{}
|
|
err := applyExtraRootCA(tr, caPath)
|
|
if err == nil || !strings.Contains(err.Error(), "no certificates parsed from PEM") {
|
|
t.Fatalf("applyExtraRootCA() error = %v, want invalid PEM error", err)
|
|
}
|
|
}
|
|
|
|
// TestApplyExtraRootCA_RejectsInsecureCAPath verifies CA paths are safety-checked
|
|
// before reading the configured file.
|
|
func TestApplyExtraRootCA_RejectsInsecureCAPath(t *testing.T) {
|
|
caPath := filepath.Join(t.TempDir(), "ca.pem")
|
|
writeFile(t, caPath, mustCreateTestCertPEM(t), 0600)
|
|
if err := os.Chmod(caPath, 0666); err != nil {
|
|
t.Fatalf("Chmod() error = %v", err)
|
|
}
|
|
|
|
tr := &http.Transport{}
|
|
err := applyExtraRootCA(tr, caPath)
|
|
if err == nil || !strings.Contains(err.Error(), "unsafe") {
|
|
t.Fatalf("applyExtraRootCA() error = %v, want unsafe path error", err)
|
|
}
|
|
if tr.TLSClientConfig != nil {
|
|
t.Fatalf("TLSClientConfig = %#v, want nil", tr.TLSClientConfig)
|
|
}
|
|
}
|
|
|
|
// TestApplyExtraRootCA_SetsTLSConfigWhenMissing verifies initialization of TLSClientConfig when absent.
|
|
func TestApplyExtraRootCA_SetsTLSConfigWhenMissing(t *testing.T) {
|
|
caPath := filepath.Join(t.TempDir(), "ca.pem")
|
|
writeFile(t, caPath, mustCreateTestCertPEM(t), 0600)
|
|
|
|
tr := &http.Transport{}
|
|
if err := applyExtraRootCA(tr, caPath); err != nil {
|
|
t.Fatalf("applyExtraRootCA() error = %v", err)
|
|
}
|
|
if tr.TLSClientConfig == nil {
|
|
t.Fatal("TLSClientConfig = nil, want initialized config")
|
|
}
|
|
if tr.TLSClientConfig.RootCAs == nil {
|
|
t.Fatal("RootCAs = nil, want cert pool")
|
|
}
|
|
}
|
|
|
|
// TestApplyExtraRootCA_ClonesExistingTLSConfig verifies cloning when the base transport already has TLS settings.
|
|
func TestApplyExtraRootCA_ClonesExistingTLSConfig(t *testing.T) {
|
|
caPath := filepath.Join(t.TempDir(), "ca.pem")
|
|
writeFile(t, caPath, mustCreateTestCertPEM(t), 0600)
|
|
|
|
original := &tls.Config{ServerName: "open.feishu.cn"}
|
|
tr := &http.Transport{TLSClientConfig: original}
|
|
if err := applyExtraRootCA(tr, caPath); err != nil {
|
|
t.Fatalf("applyExtraRootCA() error = %v", err)
|
|
}
|
|
if tr.TLSClientConfig == original {
|
|
t.Fatal("TLSClientConfig pointer reused, want clone")
|
|
}
|
|
if tr.TLSClientConfig.ServerName != original.ServerName {
|
|
t.Fatalf("ServerName = %q, want %q", tr.TLSClientConfig.ServerName, original.ServerName)
|
|
}
|
|
if tr.TLSClientConfig.RootCAs == nil {
|
|
t.Fatal("RootCAs = nil, want cert pool")
|
|
}
|
|
}
|
|
|
|
// TestApplyExtraRootCA_PreservesHigherTLSMinVersion verifies that adding a CA
|
|
// does not relax an existing stricter TLS version floor.
|
|
func TestApplyExtraRootCA_PreservesHigherTLSMinVersion(t *testing.T) {
|
|
caPath := filepath.Join(t.TempDir(), "ca.pem")
|
|
writeFile(t, caPath, mustCreateTestCertPEM(t), 0600)
|
|
|
|
tr := &http.Transport{TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS13}}
|
|
if err := applyExtraRootCA(tr, caPath); err != nil {
|
|
t.Fatalf("applyExtraRootCA() error = %v", err)
|
|
}
|
|
if tr.TLSClientConfig.MinVersion != tls.VersionTLS13 {
|
|
t.Fatalf("MinVersion = %x, want %x", tr.TLSClientConfig.MinVersion, tls.VersionTLS13)
|
|
}
|
|
}
|