mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #81443 from mikedanese/socks5
rest.Config: support configuring an explict proxy URL
This commit is contained in:
commit
ba35704b51
@ -1034,7 +1034,7 @@ func TestServeExecInContainerIdleTimeout(t *testing.T) {
|
||||
|
||||
url := fw.testHTTPServer.URL + "/exec/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?c=ls&c=-a&" + api.ExecStdinParam + "=1"
|
||||
|
||||
upgradeRoundTripper := spdy.NewSpdyRoundTripper(nil, true, true)
|
||||
upgradeRoundTripper := spdy.NewRoundTripper(nil, true, true)
|
||||
c := &http.Client{Transport: upgradeRoundTripper}
|
||||
|
||||
resp, err := c.Do(makeReq(t, "POST", url, "v4.channel.k8s.io"))
|
||||
|
@ -76,19 +76,20 @@ var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{}
|
||||
var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{}
|
||||
var _ utilnet.Dialer = &SpdyRoundTripper{}
|
||||
|
||||
// NewRoundTripper creates a new SpdyRoundTripper that will use
|
||||
// the specified tlsConfig.
|
||||
func NewRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) httpstream.UpgradeRoundTripper {
|
||||
return NewSpdyRoundTripper(tlsConfig, followRedirects, requireSameHostRedirects)
|
||||
// NewRoundTripper creates a new SpdyRoundTripper that will use the specified
|
||||
// tlsConfig.
|
||||
func NewRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) *SpdyRoundTripper {
|
||||
return NewRoundTripperWithProxy(tlsConfig, followRedirects, requireSameHostRedirects, utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment))
|
||||
}
|
||||
|
||||
// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use
|
||||
// the specified tlsConfig. This function is mostly meant for unit tests.
|
||||
func NewSpdyRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) *SpdyRoundTripper {
|
||||
// NewRoundTripperWithProxy creates a new SpdyRoundTripper that will use the
|
||||
// specified tlsConfig and proxy func.
|
||||
func NewRoundTripperWithProxy(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool, proxier func(*http.Request) (*url.URL, error)) *SpdyRoundTripper {
|
||||
return &SpdyRoundTripper{
|
||||
tlsConfig: tlsConfig,
|
||||
followRedirects: followRedirects,
|
||||
requireSameHostRedirects: requireSameHostRedirects,
|
||||
proxier: proxier,
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,11 +117,7 @@ func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
|
||||
// dial dials the host specified by req, using TLS if appropriate, optionally
|
||||
// using a proxy server if one is configured via environment variables.
|
||||
func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) {
|
||||
proxier := s.proxier
|
||||
if proxier == nil {
|
||||
proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
|
||||
}
|
||||
proxyURL, err := proxier(req)
|
||||
proxyURL, err := s.proxier(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ func TestRoundTripAndNewConnection(t *testing.T) {
|
||||
t.Fatalf("%s: Error creating request: %s", k, err)
|
||||
}
|
||||
|
||||
spdyTransport := NewSpdyRoundTripper(testCase.clientTLS, redirect, redirect)
|
||||
spdyTransport := NewRoundTripper(testCase.clientTLS, redirect, redirect)
|
||||
|
||||
var proxierCalled bool
|
||||
var proxyCalledWithHost string
|
||||
@ -425,7 +425,7 @@ func TestRoundTripRedirects(t *testing.T) {
|
||||
t.Fatalf("Error creating request: %s", err)
|
||||
}
|
||||
|
||||
spdyTransport := NewSpdyRoundTripper(nil, true, true)
|
||||
spdyTransport := NewRoundTripper(nil, true, true)
|
||||
client := &http.Client{Transport: spdyTransport}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
@ -38,6 +38,7 @@ go_test(
|
||||
"//staging/src/k8s.io/client-go/transport:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
||||
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
|
||||
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
|
@ -18,16 +18,16 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
v1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
@ -37,6 +37,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
type TestParam struct {
|
||||
@ -252,7 +254,7 @@ func validate(testParam TestParam, t *testing.T, body []byte, fakeHandler *utilt
|
||||
|
||||
}
|
||||
|
||||
func TestHttpMethods(t *testing.T) {
|
||||
func TestHTTPMethods(t *testing.T) {
|
||||
testServer, _, _ := testServerEnv(t, 200)
|
||||
defer testServer.Close()
|
||||
c, _ := restClient(testServer)
|
||||
@ -283,6 +285,57 @@ func TestHttpMethods(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPProxy(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testServer, fh, _ := testServerEnv(t, 200)
|
||||
fh.ResponseBody = "backend data"
|
||||
defer testServer.Close()
|
||||
|
||||
testProxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
to, err := url.Parse(req.RequestURI)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
w.Write([]byte("proxied: "))
|
||||
httputil.NewSingleHostReverseProxy(to).ServeHTTP(w, req)
|
||||
}))
|
||||
defer testProxyServer.Close()
|
||||
|
||||
t.Logf(testProxyServer.URL)
|
||||
|
||||
u, err := url.Parse(testProxyServer.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse test proxy server url: %v", err)
|
||||
}
|
||||
|
||||
c, err := RESTClientFor(&Config{
|
||||
Host: testServer.URL,
|
||||
ContentConfig: ContentConfig{
|
||||
GroupVersion: &v1.SchemeGroupVersion,
|
||||
NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
|
||||
},
|
||||
Proxy: http.ProxyURL(u),
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
request := c.Get()
|
||||
if request == nil {
|
||||
t.Fatalf("Get: Object returned should not be nil")
|
||||
}
|
||||
|
||||
b, err := request.DoRaw(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if got, want := string(b), "proxied: backend data"; !cmp.Equal(got, want) {
|
||||
t.Errorf("unexpected body: %v", cmp.Diff(want, got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBackoffManager(t *testing.T) {
|
||||
|
||||
theUrl, _ := url.Parse("http://localhost")
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
gruntime "runtime"
|
||||
@ -128,6 +129,13 @@ type Config struct {
|
||||
// Dial specifies the dial function for creating unencrypted TCP connections.
|
||||
Dial func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
|
||||
// Proxy is the the proxy func to be used for all requests made by this
|
||||
// transport. If Proxy is nil, http.ProxyFromEnvironment is used. If Proxy
|
||||
// returns a nil *URL, no proxy is used.
|
||||
//
|
||||
// socks5 proxying does not currently support spdy streaming endpoints.
|
||||
Proxy func(*http.Request) (*url.URL, error)
|
||||
|
||||
// Version forces a specific version to be used (if registered)
|
||||
// Do we need this?
|
||||
// Version string
|
||||
@ -560,6 +568,7 @@ func AnonymousClientConfig(config *Config) *Config {
|
||||
Burst: config.Burst,
|
||||
Timeout: config.Timeout,
|
||||
Dial: config.Dial,
|
||||
Proxy: config.Proxy,
|
||||
}
|
||||
}
|
||||
|
||||
@ -601,5 +610,6 @@ func CopyConfig(config *Config) *Config {
|
||||
RateLimiter: config.RateLimiter,
|
||||
Timeout: config.Timeout,
|
||||
Dial: config.Dial,
|
||||
Proxy: config.Proxy,
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -32,12 +33,12 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/transport"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
fuzz "github.com/google/gofuzz"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -274,8 +275,13 @@ func (n *fakeNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder,
|
||||
var fakeDialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return nil, fakeDialerError
|
||||
}
|
||||
|
||||
var fakeDialerError = errors.New("fakedialer")
|
||||
|
||||
func fakeProxyFunc(*http.Request) (*url.URL, error) {
|
||||
return nil, errors.New("fakeproxy")
|
||||
}
|
||||
|
||||
type fakeAuthProviderConfigPersister struct{}
|
||||
|
||||
func (fakeAuthProviderConfigPersister) Persist(map[string]string) error {
|
||||
@ -318,8 +324,12 @@ func TestAnonymousConfig(t *testing.T) {
|
||||
func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) {
|
||||
r.Config = map[string]string{}
|
||||
},
|
||||
// Dial does not require fuzzer
|
||||
func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {},
|
||||
func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
|
||||
*r = fakeDialFunc
|
||||
},
|
||||
func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
|
||||
*r = fakeProxyFunc
|
||||
},
|
||||
)
|
||||
for i := 0; i < 20; i++ {
|
||||
original := &Config{}
|
||||
@ -350,13 +360,22 @@ func TestAnonymousConfig(t *testing.T) {
|
||||
if !reflect.DeepEqual(expectedError, actualError) {
|
||||
t.Fatalf("AnonymousClientConfig dropped the Dial field")
|
||||
}
|
||||
} else {
|
||||
actual.Dial = nil
|
||||
expected.Dial = nil
|
||||
}
|
||||
actual.Dial = nil
|
||||
expected.Dial = nil
|
||||
|
||||
if !reflect.DeepEqual(*actual, expected) {
|
||||
t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not: %s", diff.ObjectGoPrintDiff(expected, actual))
|
||||
if actual.Proxy != nil {
|
||||
_, actualError := actual.Proxy(nil)
|
||||
_, expectedError := expected.Proxy(nil)
|
||||
if !reflect.DeepEqual(expectedError, actualError) {
|
||||
t.Fatalf("AnonymousClientConfig dropped the Proxy field")
|
||||
}
|
||||
}
|
||||
actual.Proxy = nil
|
||||
expected.Proxy = nil
|
||||
|
||||
if diff := cmp.Diff(*actual, expected); diff != "" {
|
||||
t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -396,6 +415,9 @@ func TestCopyConfig(t *testing.T) {
|
||||
func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
|
||||
*r = fakeDialFunc
|
||||
},
|
||||
func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
|
||||
*r = fakeProxyFunc
|
||||
},
|
||||
)
|
||||
for i := 0; i < 20; i++ {
|
||||
original := &Config{}
|
||||
@ -410,10 +432,10 @@ func TestCopyConfig(t *testing.T) {
|
||||
// function return the expected object.
|
||||
if actual.WrapTransport == nil || !reflect.DeepEqual(expected.WrapTransport(nil), &fakeRoundTripper{}) {
|
||||
t.Fatalf("CopyConfig dropped the WrapTransport field")
|
||||
} else {
|
||||
actual.WrapTransport = nil
|
||||
expected.WrapTransport = nil
|
||||
}
|
||||
actual.WrapTransport = nil
|
||||
expected.WrapTransport = nil
|
||||
|
||||
if actual.Dial != nil {
|
||||
_, actualError := actual.Dial(context.Background(), "", "")
|
||||
_, expectedError := expected.Dial(context.Background(), "", "")
|
||||
@ -423,6 +445,7 @@ func TestCopyConfig(t *testing.T) {
|
||||
}
|
||||
actual.Dial = nil
|
||||
expected.Dial = nil
|
||||
|
||||
if actual.AuthConfigPersister != nil {
|
||||
actualError := actual.AuthConfigPersister.Persist(nil)
|
||||
expectedError := expected.AuthConfigPersister.Persist(nil)
|
||||
@ -433,8 +456,18 @@ func TestCopyConfig(t *testing.T) {
|
||||
actual.AuthConfigPersister = nil
|
||||
expected.AuthConfigPersister = nil
|
||||
|
||||
if !reflect.DeepEqual(*actual, expected) {
|
||||
t.Fatalf("CopyConfig dropped unexpected fields, identify whether they are security related or not: %s", diff.ObjectReflectDiff(expected, *actual))
|
||||
if actual.Proxy != nil {
|
||||
_, actualError := actual.Proxy(nil)
|
||||
_, expectedError := expected.Proxy(nil)
|
||||
if !reflect.DeepEqual(expectedError, actualError) {
|
||||
t.Fatalf("CopyConfig dropped the Proxy field")
|
||||
}
|
||||
}
|
||||
actual.Proxy = nil
|
||||
expected.Proxy = nil
|
||||
|
||||
if diff := cmp.Diff(*actual, expected); diff != "" {
|
||||
t.Fatalf("CopyConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -564,10 +597,11 @@ func TestConfigSprint(t *testing.T) {
|
||||
RateLimiter: &fakeLimiter{},
|
||||
Timeout: 3 * time.Second,
|
||||
Dial: fakeDialFunc,
|
||||
Proxy: fakeProxyFunc,
|
||||
}
|
||||
want := fmt.Sprintf(
|
||||
`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p)}`,
|
||||
c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc,
|
||||
`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`,
|
||||
c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc,
|
||||
)
|
||||
|
||||
for _, f := range []string{"%s", "%v", "%+v", "%#v"} {
|
||||
|
@ -85,7 +85,8 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
|
||||
Groups: c.Impersonate.Groups,
|
||||
Extra: c.Impersonate.Extra,
|
||||
},
|
||||
Dial: c.Dial,
|
||||
Dial: c.Dial,
|
||||
Proxy: c.Proxy,
|
||||
}
|
||||
|
||||
if c.ExecProvider != nil && c.AuthProvider != nil {
|
||||
|
@ -82,6 +82,17 @@ type Cluster struct {
|
||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||
// +optional
|
||||
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||
// ProxyURL is the URL to the proxy to be used for all requests made by this
|
||||
// client. URLs with "http", "https", and "socks5" schemes are supported. If
|
||||
// this configuration is not provided or the empty string, the client
|
||||
// attempts to construct a proxy configuration from http_proxy and
|
||||
// https_proxy environment variables. If these environment variables are not
|
||||
// set, the client does not attempt to proxy requests.
|
||||
//
|
||||
// socks5 proxying does not currently support spdy streaming endpoints (exec,
|
||||
// attach, port forward).
|
||||
// +optional
|
||||
ProxyURL string `json:"proxy-url,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
|
@ -75,6 +75,17 @@ type Cluster struct {
|
||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||
// +optional
|
||||
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||
// ProxyURL is the URL to the proxy to be used for all requests made by this
|
||||
// client. URLs with "http", "https", and "socks5" schemes are supported. If
|
||||
// this configuration is not provided or the empty string, the client
|
||||
// attempts to construct a proxy configuration from http_proxy and
|
||||
// https_proxy environment variables. If these environment variables are not
|
||||
// set, the client does not attempt to proxy requests.
|
||||
//
|
||||
// socks5 proxying does not currently support spdy streaming endpoints (exec,
|
||||
// attach, port forward).
|
||||
// +optional
|
||||
ProxyURL string `json:"proxy-url,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
|
@ -237,6 +237,7 @@ func autoConvert_v1_Cluster_To_api_Cluster(in *Cluster, out *api.Cluster, s conv
|
||||
out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
|
||||
out.CertificateAuthority = in.CertificateAuthority
|
||||
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
|
||||
out.ProxyURL = in.ProxyURL
|
||||
if err := Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -255,6 +256,7 @@ func autoConvert_api_Cluster_To_v1_Cluster(in *api.Cluster, out *Cluster, s conv
|
||||
out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
|
||||
out.CertificateAuthority = in.CertificateAuthority
|
||||
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
|
||||
out.ProxyURL = in.ProxyURL
|
||||
if err := Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -20,16 +20,17 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"k8s.io/klog"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
clientauth "k8s.io/client-go/tools/auth"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -150,6 +151,13 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
|
||||
clientConfig := &restclient.Config{}
|
||||
clientConfig.Host = configClusterInfo.Server
|
||||
if configClusterInfo.ProxyURL != "" {
|
||||
u, err := parseProxyURL(configClusterInfo.ProxyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientConfig.Proxy = http.ProxyURL(u)
|
||||
}
|
||||
|
||||
if len(config.overrides.Timeout) > 0 {
|
||||
timeout, err := ParseTimeout(config.overrides.Timeout)
|
||||
|
@ -23,10 +23,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
func TestMergoSemantics(t *testing.T) {
|
||||
@ -330,6 +330,84 @@ func TestCertificateData(t *testing.T) {
|
||||
matchByteArg(keyData, clientConfig.TLSClientConfig.KeyData, t)
|
||||
}
|
||||
|
||||
func TestProxyURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
proxyURL string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
desc: "no proxy-url",
|
||||
},
|
||||
{
|
||||
desc: "socks5 proxy-url",
|
||||
proxyURL: "socks5://example.com",
|
||||
},
|
||||
{
|
||||
desc: "https proxy-url",
|
||||
proxyURL: "https://example.com",
|
||||
},
|
||||
{
|
||||
desc: "http proxy-url",
|
||||
proxyURL: "http://example.com",
|
||||
},
|
||||
{
|
||||
desc: "bad scheme proxy-url",
|
||||
proxyURL: "socks6://example.com",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "no scheme proxy-url",
|
||||
proxyURL: "example.com",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
desc: "not a url proxy-url",
|
||||
proxyURL: "chewbacca@example.com",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.proxyURL, func(t *testing.T) {
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "https://localhost:8443",
|
||||
ProxyURL: test.proxyURL,
|
||||
}
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{}
|
||||
config.Contexts["clean"] = &clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
config.CurrentContext = "clean"
|
||||
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if test.expectErr {
|
||||
if err == nil {
|
||||
t.Fatal("Expected error constructing config")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error constructing config: %v", err)
|
||||
}
|
||||
|
||||
if test.proxyURL == "" {
|
||||
return
|
||||
}
|
||||
gotURL, err := clientConfig.Proxy(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error from proxier: %v", err)
|
||||
}
|
||||
matchStringArg(test.proxyURL, gotURL.String(), t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuthData(t *testing.T) {
|
||||
username := "myuser"
|
||||
password := "mypass" // Fake value for testing.
|
||||
|
@ -18,6 +18,7 @@ package clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@ -33,3 +34,17 @@ func ParseTimeout(duration string) (time.Duration, error) {
|
||||
}
|
||||
return 0, fmt.Errorf("Invalid timeout value. Timeout must be a single integer in seconds, or an integer followed by a corresponding time unit (e.g. 1s | 2m | 3h)")
|
||||
}
|
||||
|
||||
func parseProxyURL(proxyURL string) (*url.URL, error) {
|
||||
u, err := url.Parse(proxyURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse: %v", proxyURL)
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "http", "https", "socks5":
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported scheme %q, must be http, https, or socks5", u.Scheme)
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
@ -227,6 +227,11 @@ func validateClusterInfo(clusterName string, clusterInfo clientcmdapi.Cluster) [
|
||||
validationErrors = append(validationErrors, fmt.Errorf("no server found for cluster %q", clusterName))
|
||||
}
|
||||
}
|
||||
if proxyURL := clusterInfo.ProxyURL; proxyURL != "" {
|
||||
if _, err := parseProxyURL(proxyURL); err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("invalid 'proxy-url' %q for cluster %q: %v", proxyURL, clusterName, err))
|
||||
}
|
||||
}
|
||||
// Make sure CA data and CA file aren't both specified
|
||||
if len(clusterInfo.CertificateAuthority) != 0 && len(clusterInfo.CertificateAuthorityData) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("certificate-authority-data and certificate-authority are both specified for %v. certificate-authority-data will override.", clusterName))
|
||||
|
@ -52,6 +52,7 @@ type tlsCacheKey struct {
|
||||
nextProtos string
|
||||
dial string
|
||||
disableCompression bool
|
||||
proxy string
|
||||
}
|
||||
|
||||
func (t tlsCacheKey) String() string {
|
||||
@ -59,7 +60,7 @@ func (t tlsCacheKey) String() string {
|
||||
if len(t.keyData) > 0 {
|
||||
keyText = "<redacted>"
|
||||
}
|
||||
return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, getCert: %s, serverName:%s, dial:%s disableCompression:%t", t.insecure, t.caData, t.certData, keyText, t.getCert, t.serverName, t.dial, t.disableCompression)
|
||||
return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, getCert: %s, serverName:%s, dial:%s disableCompression:%t, proxy: %s", t.insecure, t.caData, t.certData, keyText, t.getCert, t.serverName, t.dial, t.disableCompression, t.proxy)
|
||||
}
|
||||
|
||||
func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
|
||||
@ -83,7 +84,7 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
|
||||
return nil, err
|
||||
}
|
||||
// The options didn't require a custom TLS config
|
||||
if tlsConfig == nil && config.Dial == nil {
|
||||
if tlsConfig == nil && config.Dial == nil && config.Proxy == nil {
|
||||
return http.DefaultTransport, nil
|
||||
}
|
||||
|
||||
@ -104,9 +105,14 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
|
||||
go dynamicCertDialer.Run(wait.NeverStop)
|
||||
}
|
||||
|
||||
proxy := http.ProxyFromEnvironment
|
||||
if config.Proxy != nil {
|
||||
proxy = config.Proxy
|
||||
}
|
||||
|
||||
// Cache a single transport for these options
|
||||
c.transports[key] = utilnet.SetTransportDefaults(&http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Proxy: proxy,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
MaxIdleConnsPerHost: idleConnsPerHost,
|
||||
@ -130,6 +136,7 @@ func tlsConfigKey(c *Config) (tlsCacheKey, error) {
|
||||
nextProtos: strings.Join(c.TLS.NextProtos, ","),
|
||||
dial: fmt.Sprintf("%p", c.Dial),
|
||||
disableCompression: c.DisableCompression,
|
||||
proxy: fmt.Sprintf("%p", c.Proxy),
|
||||
}
|
||||
|
||||
if c.TLS.ReloadTLSFiles {
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -157,3 +158,23 @@ func TestTLSConfigKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSConfigKeyFuncPtr(t *testing.T) {
|
||||
keys := make(map[tlsCacheKey]struct{})
|
||||
makeKey := func(p func(*http.Request) (*url.URL, error)) tlsCacheKey {
|
||||
key, err := tlsConfigKey(&Config{Proxy: p})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error creating cache key: %v", err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
keys[makeKey(http.ProxyFromEnvironment)] = struct{}{}
|
||||
keys[makeKey(http.ProxyFromEnvironment)] = struct{}{}
|
||||
keys[makeKey(http.ProxyURL(nil))] = struct{}{}
|
||||
keys[makeKey(nil)] = struct{}{}
|
||||
|
||||
if got, want := len(keys), 3; got != want {
|
||||
t.Fatalf("Unexpected number of keys: got=%d want=%d", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Config holds various options for establishing a transport.
|
||||
@ -68,6 +69,13 @@ type Config struct {
|
||||
|
||||
// Dial specifies the dial function for creating unencrypted TCP connections.
|
||||
Dial func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
|
||||
// Proxy is the the proxy func to be used for all requests made by this
|
||||
// transport. If Proxy is nil, http.ProxyFromEnvironment is used. If Proxy
|
||||
// returns a nil *URL, no proxy is used.
|
||||
//
|
||||
// socks5 proxying does not currently support spdy streaming endpoints.
|
||||
Proxy func(*http.Request) (*url.URL, error)
|
||||
}
|
||||
|
||||
// ImpersonationConfig has all the available impersonation options
|
||||
|
@ -38,7 +38,11 @@ func RoundTripperFor(config *restclient.Config) (http.RoundTripper, Upgrader, er
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true, false)
|
||||
proxy := http.ProxyFromEnvironment
|
||||
if config.Proxy != nil {
|
||||
proxy = config.Proxy
|
||||
}
|
||||
upgradeRoundTripper := spdy.NewRoundTripperWithProxy(tlsConfig, true, false, proxy)
|
||||
wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
Loading…
Reference in New Issue
Block a user