Commit 7181dd49 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #50476 from caesarxuchao/plumb-proxy

Automatic merge from submit-queue (batch tested with PRs 51824, 50476, 52451, 52009, 52237) Plumbing the proxy dialer to the webhook admission plugin * Fixing https://github.com/kubernetes/kubernetes/issues/49987. Plumb the `Dial` function to the `transport.Config` * Fixing https://github.com/kubernetes/kubernetes/issues/52366. Let the webhook admission plugin sets the `TLSConfg.ServerName`. I tested it in my gke setup. I don't have time to implement an e2e test before 1.8 release. I think it's ok to add the test later, because *i)* the change only affects the alpha webhook admission feature, and *ii)* the webhook feature is unusable without the fix. That said, it's up to my reviewer to decide. Filed https://github.com/kubernetes/kubernetes/issues/52368 for the missing e2e test. ( The second commit is https://github.com/kubernetes/kubernetes/pull/52372, which is just a cleanup of client configuration in e2e tests. It removed a function that marshalled the client config to json and then unmarshalled it. It is a prerequisite of this PR, because this PR added the `Dial` function to the config which is not json marshallable.) ```release-note Fixed the webhook admission plugin so that it works even if the apiserver and the nodes are in two networks (e.g., in GKE). Fixed the webhook admission plugin so that webhook author could use the DNS name of the service as the CommonName when generating the server cert for the webhook. Action required: Anyone who generated server cert for admission webhooks need to regenerate the cert. Previously, when generating server cert for the admission webhook, the CN value doesn't matter. Now you must set it to the DNS name of the webhook service, i.e., `<service.Name>.<service.Namespace>.svc`. ```
parents b3e641d7 856a1db5
...@@ -251,7 +251,7 @@ func CreateNodeDialer(s *options.ServerRunOptions) (tunneler.Tunneler, *http.Tra ...@@ -251,7 +251,7 @@ func CreateNodeDialer(s *options.ServerRunOptions) (tunneler.Tunneler, *http.Tra
} }
// CreateKubeAPIServerConfig creates all the resources for running the API server, but runs none of them // CreateKubeAPIServerConfig creates all the resources for running the API server, but runs none of them
func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunneler.Tunneler, proxyTransport http.RoundTripper) (*master.Config, informers.SharedInformerFactory, clientgoinformers.SharedInformerFactory, *kubeserver.InsecureServingInfo, aggregatorapiserver.ServiceResolver, error) { func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunneler.Tunneler, proxyTransport *http.Transport) (*master.Config, informers.SharedInformerFactory, clientgoinformers.SharedInformerFactory, *kubeserver.InsecureServingInfo, aggregatorapiserver.ServiceResolver, error) {
// set defaults in the options before trying to create the generic config // set defaults in the options before trying to create the generic config
if err := defaultOptions(s); err != nil { if err := defaultOptions(s); err != nil {
return nil, nil, nil, nil, nil, err return nil, nil, nil, nil, nil, err
...@@ -269,8 +269,7 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele ...@@ -269,8 +269,7 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele
return nil, nil, nil, nil, nil, err return nil, nil, nil, nil, nil, err
} }
} }
genericConfig, sharedInformers, versionedInformers, insecureServingOptions, serviceResolver, err := BuildGenericConfig(s, proxyTransport)
genericConfig, sharedInformers, versionedInformers, insecureServingOptions, serviceResolver, err := BuildGenericConfig(s)
if err != nil { if err != nil {
return nil, nil, nil, nil, nil, err return nil, nil, nil, nil, nil, err
} }
...@@ -354,7 +353,7 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele ...@@ -354,7 +353,7 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele
} }
// BuildGenericConfig takes the master server options and produces the genericapiserver.Config associated with it // BuildGenericConfig takes the master server options and produces the genericapiserver.Config associated with it
func BuildGenericConfig(s *options.ServerRunOptions) (*genericapiserver.Config, informers.SharedInformerFactory, clientgoinformers.SharedInformerFactory, *kubeserver.InsecureServingInfo, aggregatorapiserver.ServiceResolver, error) { func BuildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transport) (*genericapiserver.Config, informers.SharedInformerFactory, clientgoinformers.SharedInformerFactory, *kubeserver.InsecureServingInfo, aggregatorapiserver.ServiceResolver, error) {
genericConfig := genericapiserver.NewConfig(api.Codecs) genericConfig := genericapiserver.NewConfig(api.Codecs)
if err := s.GenericServerRunOptions.ApplyTo(genericConfig); err != nil { if err := s.GenericServerRunOptions.ApplyTo(genericConfig); err != nil {
return nil, nil, nil, nil, nil, err return nil, nil, nil, nil, nil, err
...@@ -460,6 +459,7 @@ func BuildGenericConfig(s *options.ServerRunOptions) (*genericapiserver.Config, ...@@ -460,6 +459,7 @@ func BuildGenericConfig(s *options.ServerRunOptions) (*genericapiserver.Config,
sharedInformers, sharedInformers,
genericConfig.Authorizer, genericConfig.Authorizer,
serviceResolver, serviceResolver,
proxyTransport,
) )
if err != nil { if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("failed to create admission plugin initializer: %v", err) return nil, nil, nil, nil, nil, fmt.Errorf("failed to create admission plugin initializer: %v", err)
...@@ -476,7 +476,7 @@ func BuildGenericConfig(s *options.ServerRunOptions) (*genericapiserver.Config, ...@@ -476,7 +476,7 @@ func BuildGenericConfig(s *options.ServerRunOptions) (*genericapiserver.Config,
} }
// BuildAdmissionPluginInitializer constructs the admission plugin initializer // BuildAdmissionPluginInitializer constructs the admission plugin initializer
func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client internalclientset.Interface, externalClient clientset.Interface, sharedInformers informers.SharedInformerFactory, apiAuthorizer authorizer.Authorizer, serviceResolver aggregatorapiserver.ServiceResolver) (admission.PluginInitializer, error) { func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client internalclientset.Interface, externalClient clientset.Interface, sharedInformers informers.SharedInformerFactory, apiAuthorizer authorizer.Authorizer, serviceResolver aggregatorapiserver.ServiceResolver, proxyTransport *http.Transport) (admission.PluginInitializer, error) {
var cloudConfig []byte var cloudConfig []byte
if s.CloudProvider.CloudConfigFile != "" { if s.CloudProvider.CloudConfigFile != "" {
...@@ -510,6 +510,7 @@ func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client interna ...@@ -510,6 +510,7 @@ func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client interna
} }
pluginInitializer = pluginInitializer.SetServiceResolver(serviceResolver) pluginInitializer = pluginInitializer.SetServiceResolver(serviceResolver)
pluginInitializer = pluginInitializer.SetProxyTransport(proxyTransport)
return pluginInitializer, nil return pluginInitializer, nil
} }
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package admission package admission
import ( import (
"net/http"
"net/url" "net/url"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
...@@ -88,6 +89,12 @@ type ServiceResolver interface { ...@@ -88,6 +89,12 @@ type ServiceResolver interface {
ResolveEndpoint(namespace, name string) (*url.URL, error) ResolveEndpoint(namespace, name string) (*url.URL, error)
} }
// WantsProxyTransport defines a fuction that accepts a proxy transport for admission
// plugins that need to make calls to pods.
type WantsProxyTransport interface {
SetProxyTransport(proxyTransport *http.Transport)
}
type PluginInitializer struct { type PluginInitializer struct {
internalClient internalclientset.Interface internalClient internalclientset.Interface
externalClient clientset.Interface externalClient clientset.Interface
...@@ -99,8 +106,9 @@ type PluginInitializer struct { ...@@ -99,8 +106,9 @@ type PluginInitializer struct {
serviceResolver ServiceResolver serviceResolver ServiceResolver
// for proving we are apiserver in call-outs // for proving we are apiserver in call-outs
clientCert []byte clientCert []byte
clientKey []byte clientKey []byte
proxyTransport *http.Transport
} }
var _ admission.PluginInitializer = &PluginInitializer{} var _ admission.PluginInitializer = &PluginInitializer{}
...@@ -142,6 +150,12 @@ func (i *PluginInitializer) SetClientCert(cert, key []byte) *PluginInitializer { ...@@ -142,6 +150,12 @@ func (i *PluginInitializer) SetClientCert(cert, key []byte) *PluginInitializer {
return i return i
} }
// SetProxyTransport sets the proxyTransport which is needed by some plugins.
func (i *PluginInitializer) SetProxyTransport(proxyTransport *http.Transport) *PluginInitializer {
i.proxyTransport = proxyTransport
return i
}
// Initialize checks the initialization interfaces implemented by each plugin // Initialize checks the initialization interfaces implemented by each plugin
// and provide the appropriate initialization data // and provide the appropriate initialization data
func (i *PluginInitializer) Initialize(plugin admission.Interface) { func (i *PluginInitializer) Initialize(plugin admission.Interface) {
...@@ -186,4 +200,8 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) { ...@@ -186,4 +200,8 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
} }
wants.SetClientCert(i.clientCert, i.clientKey) wants.SetClientCert(i.clientCert, i.clientKey)
} }
if wants, ok := plugin.(WantsProxyTransport); ok {
wants.SetProxyTransport(i.proxyTransport)
}
} }
...@@ -21,6 +21,8 @@ import ( ...@@ -21,6 +21,8 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"net"
"net/http"
"sync" "sync"
"time" "time"
...@@ -106,6 +108,7 @@ type GenericAdmissionWebhook struct { ...@@ -106,6 +108,7 @@ type GenericAdmissionWebhook struct {
negotiatedSerializer runtime.NegotiatedSerializer negotiatedSerializer runtime.NegotiatedSerializer
clientCert []byte clientCert []byte
clientKey []byte clientKey []byte
proxyTransport *http.Transport
} }
var ( var (
...@@ -114,6 +117,10 @@ var ( ...@@ -114,6 +117,10 @@ var (
_ = admissioninit.WantsExternalKubeClientSet(&GenericAdmissionWebhook{}) _ = admissioninit.WantsExternalKubeClientSet(&GenericAdmissionWebhook{})
) )
func (a *GenericAdmissionWebhook) SetProxyTransport(pt *http.Transport) {
a.proxyTransport = pt
}
func (a *GenericAdmissionWebhook) SetServiceResolver(sr admissioninit.ServiceResolver) { func (a *GenericAdmissionWebhook) SetServiceResolver(sr admissioninit.ServiceResolver) {
a.serviceResolver = sr a.serviceResolver = sr
} }
...@@ -242,20 +249,27 @@ func (a *GenericAdmissionWebhook) hookClient(h *v1alpha1.ExternalAdmissionHook) ...@@ -242,20 +249,27 @@ func (a *GenericAdmissionWebhook) hookClient(h *v1alpha1.ExternalAdmissionHook)
return nil, err return nil, err
} }
var dial func(network, addr string) (net.Conn, error)
if a.proxyTransport != nil && a.proxyTransport.Dial != nil {
dial = a.proxyTransport.Dial
}
// TODO: cache these instead of constructing one each time // TODO: cache these instead of constructing one each time
cfg := &rest.Config{ cfg := &rest.Config{
Host: u.Host, Host: u.Host,
APIPath: u.Path, APIPath: u.Path,
TLSClientConfig: rest.TLSClientConfig{ TLSClientConfig: rest.TLSClientConfig{
CAData: h.ClientConfig.CABundle, ServerName: h.ClientConfig.Service.Name + "." + h.ClientConfig.Service.Namespace + ".svc",
CertData: a.clientCert, CAData: h.ClientConfig.CABundle,
KeyData: a.clientKey, CertData: a.clientCert,
KeyData: a.clientKey,
}, },
UserAgent: "kube-apiserver-admission", UserAgent: "kube-apiserver-admission",
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
ContentConfig: rest.ContentConfig{ ContentConfig: rest.ContentConfig{
NegotiatedSerializer: a.negotiatedSerializer, NegotiatedSerializer: a.negotiatedSerializer,
}, },
Dial: dial,
} }
return rest.UnversionedRESTClientFor(cfg) return rest.UnversionedRESTClientFor(cfg)
} }
...@@ -53,6 +53,7 @@ func (f *fakeHookSource) Run(stopCh <-chan struct{}) {} ...@@ -53,6 +53,7 @@ func (f *fakeHookSource) Run(stopCh <-chan struct{}) {}
type fakeServiceResolver struct { type fakeServiceResolver struct {
base url.URL base url.URL
path string
} }
func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) { func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) {
...@@ -60,7 +61,7 @@ func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, ...@@ -60,7 +61,7 @@ func (f fakeServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL,
return nil, fmt.Errorf("couldn't resolve service location") return nil, fmt.Errorf("couldn't resolve service location")
} }
u := f.base u := f.base
u.Path = name u.Path = f.path
return &u, nil return &u, nil
} }
...@@ -89,7 +90,6 @@ func TestAdmit(t *testing.T) { ...@@ -89,7 +90,6 @@ func TestAdmit(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
wh.serviceResolver = fakeServiceResolver{*serverURL}
wh.clientCert = clientCert wh.clientCert = clientCert
wh.clientKey = clientKey wh.clientKey = clientKey
...@@ -123,16 +123,16 @@ func TestAdmit(t *testing.T) { ...@@ -123,16 +123,16 @@ func TestAdmit(t *testing.T) {
type test struct { type test struct {
hookSource fakeHookSource hookSource fakeHookSource
path string
expectAllow bool expectAllow bool
errorContains string errorContains string
} }
ccfg := func(result string) registrationv1alpha1.AdmissionHookClientConfig { ccfg := registrationv1alpha1.AdmissionHookClientConfig{
return registrationv1alpha1.AdmissionHookClientConfig{ Service: registrationv1alpha1.ServiceReference{
Service: registrationv1alpha1.ServiceReference{ Name: "webhook-test",
Name: result, Namespace: "default",
}, },
CABundle: caCert, CABundle: caCert,
}
} }
matchEverythingRules := []registrationv1alpha1.RuleWithOperations{{ matchEverythingRules := []registrationv1alpha1.RuleWithOperations{{
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.OperationAll}, Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.OperationAll},
...@@ -148,66 +148,72 @@ func TestAdmit(t *testing.T) { ...@@ -148,66 +148,72 @@ func TestAdmit(t *testing.T) {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []registrationv1alpha1.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "nomatch", Name: "nomatch",
ClientConfig: ccfg("disallow"), ClientConfig: ccfg,
Rules: []registrationv1alpha1.RuleWithOperations{{ Rules: []registrationv1alpha1.RuleWithOperations{{
Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.Create}, Operations: []registrationv1alpha1.OperationType{registrationv1alpha1.Create},
}}, }},
}}, }},
}, },
path: "disallow",
expectAllow: true, expectAllow: true,
}, },
"match & allow": { "match & allow": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []registrationv1alpha1.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "allow", Name: "allow",
ClientConfig: ccfg("allow"), ClientConfig: ccfg,
Rules: matchEverythingRules, Rules: matchEverythingRules,
}}, }},
}, },
path: "allow",
expectAllow: true, expectAllow: true,
}, },
"match & disallow": { "match & disallow": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []registrationv1alpha1.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "disallow", Name: "disallow",
ClientConfig: ccfg("disallow"), ClientConfig: ccfg,
Rules: matchEverythingRules, Rules: matchEverythingRules,
}}, }},
}, },
path: "disallow",
errorContains: "without explanation", errorContains: "without explanation",
}, },
"match & disallow ii": { "match & disallow ii": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []registrationv1alpha1.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "disallowReason", Name: "disallowReason",
ClientConfig: ccfg("disallowReason"), ClientConfig: ccfg,
Rules: matchEverythingRules, Rules: matchEverythingRules,
}}, }},
}, },
path: "disallowReason",
errorContains: "you shall not pass", errorContains: "you shall not pass",
}, },
"match & fail (but allow because fail open)": { "match & fail (but allow because fail open)": {
hookSource: fakeHookSource{ hookSource: fakeHookSource{
hooks: []registrationv1alpha1.ExternalAdmissionHook{{ hooks: []registrationv1alpha1.ExternalAdmissionHook{{
Name: "internalErr A", Name: "internalErr A",
ClientConfig: ccfg("internalErr"), ClientConfig: ccfg,
Rules: matchEverythingRules, Rules: matchEverythingRules,
}, { }, {
Name: "invalidReq B", Name: "internalErr B",
ClientConfig: ccfg("invalidReq"), ClientConfig: ccfg,
Rules: matchEverythingRules, Rules: matchEverythingRules,
}, { }, {
Name: "invalidResp C", Name: "internalErr C",
ClientConfig: ccfg("invalidResp"), ClientConfig: ccfg,
Rules: matchEverythingRules, Rules: matchEverythingRules,
}}, }},
}, },
path: "internalErr",
expectAllow: true, expectAllow: true,
}, },
} }
for name, tt := range table { for name, tt := range table {
wh.hookSource = &tt.hookSource wh.hookSource = &tt.hookSource
wh.serviceResolver = fakeServiceResolver{base: *serverURL, path: tt.path}
err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, name, resource, subResource, operation, &userInfo)) err = wh.Admit(admission.NewAttributesRecord(&object, &oldObject, kind, namespace, name, resource, subResource, operation, &userInfo))
if tt.expectAllow != (err == nil) { if tt.expectAllow != (err == nil) {
......
...@@ -61,7 +61,7 @@ openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem ...@@ -61,7 +61,7 @@ openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem
# Create a server certiticate # Create a server certiticate
openssl genrsa -out serverKey.pem 2048 openssl genrsa -out serverKey.pem 2048
openssl req -new -key serverKey.pem -out server.csr -subj "/CN=${CN_BASE}_server" -config server.conf openssl req -new -key serverKey.pem -out server.csr -subj "/CN=webhook-test.default.svc" -config server.conf
openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf
# Create a client certiticate # Create a client certiticate
...@@ -104,4 +104,4 @@ done ...@@ -104,4 +104,4 @@ done
rm *.pem rm *.pem
rm *.csr rm *.csr
rm *.srl rm *.srl
rm *.conf rm *.conf
\ No newline at end of file
...@@ -114,6 +114,9 @@ type Config struct { ...@@ -114,6 +114,9 @@ type Config struct {
// The maximum length of time to wait before giving up on a server request. A value of zero means no timeout. // The maximum length of time to wait before giving up on a server request. A value of zero means no timeout.
Timeout time.Duration Timeout time.Duration
// Dial specifies the dial function for creating unencrypted TCP connections.
Dial func(network, addr string) (net.Conn, error)
// Version forces a specific version to be used (if registered) // Version forces a specific version to be used (if registered)
// Do we need this? // Do we need this?
// Version string // Version string
......
...@@ -18,6 +18,7 @@ package rest ...@@ -18,6 +18,7 @@ package rest
import ( import (
"io" "io"
"net"
"net/http" "net/http"
"path/filepath" "path/filepath"
"reflect" "reflect"
...@@ -236,6 +237,8 @@ func TestAnonymousConfig(t *testing.T) { ...@@ -236,6 +237,8 @@ func TestAnonymousConfig(t *testing.T) {
func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) { func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) {
r.Config = map[string]string{} r.Config = map[string]string{}
}, },
// Dial does not require fuzzer
func(r *func(network, addr string) (net.Conn, error), f fuzz.Continue) {},
) )
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
original := &Config{} original := &Config{}
......
...@@ -96,5 +96,6 @@ func (c *Config) TransportConfig() (*transport.Config, error) { ...@@ -96,5 +96,6 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
Groups: c.Impersonate.Groups, Groups: c.Impersonate.Groups,
Extra: c.Impersonate.Extra, Extra: c.Impersonate.Extra,
}, },
Dial: c.Dial,
}, nil }, nil
} }
...@@ -63,16 +63,20 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { ...@@ -63,16 +63,20 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
return http.DefaultTransport, nil return http.DefaultTransport, nil
} }
dial := config.Dial
if dial == nil {
dial = (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial
}
// Cache a single transport for these options // Cache a single transport for these options
c.transports[key] = utilnet.SetTransportDefaults(&http.Transport{ c.transports[key] = utilnet.SetTransportDefaults(&http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
MaxIdleConnsPerHost: idleConnsPerHost, MaxIdleConnsPerHost: idleConnsPerHost,
Dial: (&net.Dialer{ Dial: dial,
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
}) })
return c.transports[key], nil return c.transports[key], nil
} }
......
...@@ -16,7 +16,10 @@ limitations under the License. ...@@ -16,7 +16,10 @@ limitations under the License.
package transport package transport
import "net/http" import (
"net"
"net/http"
)
// Config holds various options for establishing a transport. // Config holds various options for establishing a transport.
type Config struct { type Config struct {
...@@ -52,6 +55,9 @@ type Config struct { ...@@ -52,6 +55,9 @@ type Config struct {
// config may layer other RoundTrippers on top of the returned // config may layer other RoundTrippers on top of the returned
// RoundTripper. // RoundTripper.
WrapTransport func(rt http.RoundTripper) http.RoundTripper WrapTransport func(rt http.RoundTripper) http.RoundTripper
// Dial specifies the dial function for creating unencrypted TCP connections.
Dial func(network, addr string) (net.Conn, error)
} }
// ImpersonationConfig has all the available impersonation options // ImpersonationConfig has all the available impersonation options
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment