mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-02 16:29:21 +00:00
Merge pull request #16126 from krousey/client_breakup
Auto commit by PR queue bot
This commit is contained in:
commit
f0b07c9c68
83
pkg/client/transport/cache.go
Normal file
83
pkg/client/transport/cache.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TlsTransportCache caches TLS http.RoundTrippers different configurations. The
|
||||||
|
// same RoundTripper will be returned for configs with identical TLS options If
|
||||||
|
// the config has no custom TLS options, http.DefaultTransport is returned.
|
||||||
|
type tlsTransportCache struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
transports map[string]*http.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsCache = &tlsTransportCache{transports: make(map[string]*http.Transport)}
|
||||||
|
|
||||||
|
func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
|
||||||
|
key, err := tlsConfigKey(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we only create a single transport for the given TLS options
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
// See if we already have a custom transport for this config
|
||||||
|
if t, ok := c.transports[key]; ok {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the TLS options for this client config
|
||||||
|
tlsConfig, err := TLSConfigFor(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// The options didn't require a custom TLS config
|
||||||
|
if tlsConfig == nil {
|
||||||
|
return http.DefaultTransport, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache a single transport for these options
|
||||||
|
c.transports[key] = &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
}
|
||||||
|
return c.transports[key], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsConfigKey returns a unique key for tls.Config objects returned from TLSConfigFor
|
||||||
|
func tlsConfigKey(c *Config) (string, error) {
|
||||||
|
// Make sure ca/key/cert content is loaded
|
||||||
|
if err := loadTLSFiles(c); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Only include the things that actually affect the tls.Config
|
||||||
|
return fmt.Sprintf("%v/%x/%x/%x", c.TLS.Insecure, c.TLS.CAData, c.TLS.CertData, c.TLS.KeyData), nil
|
||||||
|
}
|
114
pkg/client/transport/cache_test.go
Normal file
114
pkg/client/transport/cache_test.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTLSConfigKey(t *testing.T) {
|
||||||
|
// Make sure config fields that don't affect the tls config don't affect the cache key
|
||||||
|
identicalConfigurations := map[string]*Config{
|
||||||
|
"empty": {},
|
||||||
|
"basic": {Username: "bob", Password: "password"},
|
||||||
|
"bearer": {BearerToken: "token"},
|
||||||
|
"user agent": {UserAgent: "useragent"},
|
||||||
|
"transport": {Transport: http.DefaultTransport},
|
||||||
|
"wrap transport": {WrapTransport: func(http.RoundTripper) http.RoundTripper { return nil }},
|
||||||
|
}
|
||||||
|
for nameA, valueA := range identicalConfigurations {
|
||||||
|
for nameB, valueB := range identicalConfigurations {
|
||||||
|
keyA, err := tlsConfigKey(valueA)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error for %q: %v", nameA, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyB, err := tlsConfigKey(valueB)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error for %q: %v", nameB, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if keyA != keyB {
|
||||||
|
t.Errorf("Expected identical cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure config fields that affect the tls config affect the cache key
|
||||||
|
uniqueConfigurations := map[string]*Config{
|
||||||
|
"no tls": {},
|
||||||
|
"insecure": {TLS: TLSConfig{Insecure: true}},
|
||||||
|
"cadata 1": {TLS: TLSConfig{CAData: []byte{1}}},
|
||||||
|
"cadata 2": {TLS: TLSConfig{CAData: []byte{2}}},
|
||||||
|
"cert 1, key 1": {
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CertData: []byte{1},
|
||||||
|
KeyData: []byte{1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"cert 1, key 2": {
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CertData: []byte{1},
|
||||||
|
KeyData: []byte{2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"cert 2, key 1": {
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CertData: []byte{2},
|
||||||
|
KeyData: []byte{1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"cert 2, key 2": {
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CertData: []byte{2},
|
||||||
|
KeyData: []byte{2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"cadata 1, cert 1, key 1": {
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CAData: []byte{1},
|
||||||
|
CertData: []byte{1},
|
||||||
|
KeyData: []byte{1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for nameA, valueA := range uniqueConfigurations {
|
||||||
|
for nameB, valueB := range uniqueConfigurations {
|
||||||
|
// Don't compare to ourselves
|
||||||
|
if nameA == nameB {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keyA, err := tlsConfigKey(valueA)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error for %q: %v", nameA, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyB, err := tlsConfigKey(valueB)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error for %q: %v", nameB, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if keyA == keyB {
|
||||||
|
t.Errorf("Expected unique cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
pkg/client/transport/config.go
Normal file
81
pkg/client/transport/config.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Config holds various options for establishing a transport.
|
||||||
|
type Config struct {
|
||||||
|
// UserAgent is an optional field that specifies the caller of this
|
||||||
|
// request.
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
// The base TLS configuration for this transport.
|
||||||
|
TLS TLSConfig
|
||||||
|
|
||||||
|
// Username and password for basic authentication
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
|
||||||
|
// Bearer token for authentication
|
||||||
|
BearerToken string
|
||||||
|
|
||||||
|
// Transport may be used for custom HTTP behavior. This attribute may
|
||||||
|
// not be specified with the TLS client certificate options. Use
|
||||||
|
// WrapTransport for most client level operations.
|
||||||
|
Transport http.RoundTripper
|
||||||
|
|
||||||
|
// WrapTransport will be invoked for custom HTTP behavior after the
|
||||||
|
// underlying transport is initialized (either the transport created
|
||||||
|
// from TLSClientConfig, Transport, or http.DefaultTransport). The
|
||||||
|
// config may layer other RoundTrippers on top of the returned
|
||||||
|
// RoundTripper.
|
||||||
|
WrapTransport func(rt http.RoundTripper) http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasCA returns whether the configuration has a certificate authority or not.
|
||||||
|
func (c *Config) HasCA() bool {
|
||||||
|
return len(c.TLS.CAData) > 0 || len(c.TLS.CAFile) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasBasicAuth returns whether the configuration has basic authentication or not.
|
||||||
|
func (c *Config) HasBasicAuth() bool {
|
||||||
|
return len(c.Username) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasTokenAuth returns whether the configuration has token authentication or not.
|
||||||
|
func (c *Config) HasTokenAuth() bool {
|
||||||
|
return len(c.BearerToken) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasCertAuth returns whether the configuration has certificate authentication or not.
|
||||||
|
func (c *Config) HasCertAuth() bool {
|
||||||
|
return len(c.TLS.CertData) != 0 || len(c.TLS.CertFile) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig holds the information needed to set up a TLS transport.
|
||||||
|
type TLSConfig struct {
|
||||||
|
CAFile string // Path of the PEM-encoded server trusted root certificates.
|
||||||
|
CertFile string // Path of the PEM-encoded client certificate.
|
||||||
|
KeyFile string // Path of the PEM-encoded client key.
|
||||||
|
|
||||||
|
Insecure bool // Server should be accessed without verifying the certificate. For testing only.
|
||||||
|
|
||||||
|
CAData []byte // Bytes of the PEM-encoded server trusted root certificates. Supercedes CAFile.
|
||||||
|
CertData []byte // Bytes of the PEM-encoded client certificate. Supercedes CertFile.
|
||||||
|
KeyData []byte // Bytes of the PEM-encoded client key. Supercedes KeyFile.
|
||||||
|
}
|
259
pkg/client/transport/round_trippers.go
Normal file
259
pkg/client/transport/round_trippers.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPWrappersForConfig wraps a round tripper with any relevant layered
|
||||||
|
// behavior from the config. Exposed to allow more clients that need HTTP-like
|
||||||
|
// behavior but then must hijack the underlying connection (like WebSocket or
|
||||||
|
// HTTP2 clients). Pure HTTP clients should use the RoundTripper returned from
|
||||||
|
// New.
|
||||||
|
func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
|
||||||
|
if config.WrapTransport != nil {
|
||||||
|
rt = config.WrapTransport(rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
rt = DebugWrappers(rt)
|
||||||
|
|
||||||
|
// Set authentication wrappers
|
||||||
|
switch {
|
||||||
|
case config.HasBasicAuth() && config.HasTokenAuth():
|
||||||
|
return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
|
||||||
|
case config.HasTokenAuth():
|
||||||
|
rt = newBearerAuthRoundTripper(config.BearerToken, rt)
|
||||||
|
case config.HasBasicAuth():
|
||||||
|
rt = newBasicAuthRoundTripper(config.Username, config.Password, rt)
|
||||||
|
}
|
||||||
|
if len(config.UserAgent) > 0 {
|
||||||
|
rt = newUserAgentRoundTripper(config.UserAgent, rt)
|
||||||
|
}
|
||||||
|
return rt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugWrappers wraps a round tripper and logs based on the current log level.
|
||||||
|
func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
|
||||||
|
switch {
|
||||||
|
case bool(glog.V(9)):
|
||||||
|
rt = newDebuggingRoundTripper(rt, debugCurlCommand, debugURLTiming, debugResponseHeaders)
|
||||||
|
case bool(glog.V(8)):
|
||||||
|
rt = newDebuggingRoundTripper(rt, debugJustURL, debugRequestHeaders, debugResponseStatus, debugResponseHeaders)
|
||||||
|
case bool(glog.V(7)):
|
||||||
|
rt = newDebuggingRoundTripper(rt, debugJustURL, debugRequestHeaders, debugResponseStatus)
|
||||||
|
case bool(glog.V(6)):
|
||||||
|
rt = newDebuggingRoundTripper(rt, debugURLTiming)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt
|
||||||
|
}
|
||||||
|
|
||||||
|
type userAgentRoundTripper struct {
|
||||||
|
agent string
|
||||||
|
rt http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
|
||||||
|
return &userAgentRoundTripper{agent, rt}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if len(req.Header.Get("User-Agent")) != 0 {
|
||||||
|
return rt.rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
req = cloneRequest(req)
|
||||||
|
req.Header.Set("User-Agent", rt.agent)
|
||||||
|
return rt.rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicAuthRoundTripper struct {
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
rt http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a
|
||||||
|
// request unless it has already been set.
|
||||||
|
func newBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
|
||||||
|
return &basicAuthRoundTripper{username, password, rt}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if len(req.Header.Get("Authorization")) != 0 {
|
||||||
|
return rt.rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
req = cloneRequest(req)
|
||||||
|
req.SetBasicAuth(rt.username, rt.password)
|
||||||
|
return rt.rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type bearerAuthRoundTripper struct {
|
||||||
|
bearer string
|
||||||
|
rt http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBearerAuthRoundTripper adds the provided bearer token to a request
|
||||||
|
// unless the authorization header has already been set.
|
||||||
|
func newBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
|
||||||
|
return &bearerAuthRoundTripper{bearer, rt}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if len(req.Header.Get("Authorization")) != 0 {
|
||||||
|
return rt.rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
req = cloneRequest(req)
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.bearer))
|
||||||
|
return rt.rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cloneRequest returns a clone of the provided *http.Request.
|
||||||
|
// The clone is a shallow copy of the struct and its Header map.
|
||||||
|
func cloneRequest(r *http.Request) *http.Request {
|
||||||
|
// shallow copy of the struct
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
// deep copy of the Header
|
||||||
|
r2.Header = make(http.Header)
|
||||||
|
for k, s := range r.Header {
|
||||||
|
r2.Header[k] = s
|
||||||
|
}
|
||||||
|
return r2
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestInfo keeps track of information about a request/response combination
|
||||||
|
type requestInfo struct {
|
||||||
|
RequestHeaders http.Header
|
||||||
|
RequestVerb string
|
||||||
|
RequestURL string
|
||||||
|
|
||||||
|
ResponseStatus string
|
||||||
|
ResponseHeaders http.Header
|
||||||
|
ResponseErr error
|
||||||
|
|
||||||
|
Duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRequestInfo creates a new RequestInfo based on an http request
|
||||||
|
func newRequestInfo(req *http.Request) *requestInfo {
|
||||||
|
return &requestInfo{
|
||||||
|
RequestURL: req.URL.String(),
|
||||||
|
RequestVerb: req.Method,
|
||||||
|
RequestHeaders: req.Header,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete adds information about the response to the requestInfo
|
||||||
|
func (r *requestInfo) complete(response *http.Response, err error) {
|
||||||
|
if err != nil {
|
||||||
|
r.ResponseErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.ResponseStatus = response.Status
|
||||||
|
r.ResponseHeaders = response.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
// toCurl returns a string that can be run as a command in a terminal (minus the body)
|
||||||
|
func (r *requestInfo) toCurl() string {
|
||||||
|
headers := ""
|
||||||
|
for key, values := range r.RequestHeaders {
|
||||||
|
for _, value := range values {
|
||||||
|
headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("curl -k -v -X%s %s %s", r.RequestVerb, headers, r.RequestURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// debuggingRoundTripper will display information about the requests passing
|
||||||
|
// through it based on what is configured
|
||||||
|
type debuggingRoundTripper struct {
|
||||||
|
delegatedRoundTripper http.RoundTripper
|
||||||
|
|
||||||
|
levels map[debugLevel]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type debugLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
debugJustURL debugLevel = iota
|
||||||
|
debugURLTiming
|
||||||
|
debugCurlCommand
|
||||||
|
debugRequestHeaders
|
||||||
|
debugResponseStatus
|
||||||
|
debugResponseHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDebuggingRoundTripper(rt http.RoundTripper, levels ...debugLevel) *debuggingRoundTripper {
|
||||||
|
drt := &debuggingRoundTripper{
|
||||||
|
delegatedRoundTripper: rt,
|
||||||
|
levels: make(map[debugLevel]bool, len(levels)),
|
||||||
|
}
|
||||||
|
for _, v := range levels {
|
||||||
|
drt.levels[v] = true
|
||||||
|
}
|
||||||
|
return drt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
reqInfo := newRequestInfo(req)
|
||||||
|
|
||||||
|
if rt.levels[debugJustURL] {
|
||||||
|
glog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL)
|
||||||
|
}
|
||||||
|
if rt.levels[debugCurlCommand] {
|
||||||
|
glog.Infof("%s", reqInfo.toCurl())
|
||||||
|
|
||||||
|
}
|
||||||
|
if rt.levels[debugRequestHeaders] {
|
||||||
|
glog.Infof("Request Headers:")
|
||||||
|
for key, values := range reqInfo.RequestHeaders {
|
||||||
|
for _, value := range values {
|
||||||
|
glog.Infof(" %s: %s", key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
response, err := rt.delegatedRoundTripper.RoundTrip(req)
|
||||||
|
reqInfo.Duration = time.Since(startTime)
|
||||||
|
|
||||||
|
reqInfo.complete(response, err)
|
||||||
|
|
||||||
|
if rt.levels[debugURLTiming] {
|
||||||
|
glog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
|
||||||
|
}
|
||||||
|
if rt.levels[debugResponseStatus] {
|
||||||
|
glog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
|
||||||
|
}
|
||||||
|
if rt.levels[debugResponseHeaders] {
|
||||||
|
glog.Infof("Response Headers:")
|
||||||
|
for key, values := range reqInfo.ResponseHeaders {
|
||||||
|
for _, value := range values {
|
||||||
|
glog.Infof(" %s: %s", key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, err
|
||||||
|
}
|
93
pkg/client/transport/round_trippers_test.go
Normal file
93
pkg/client/transport/round_trippers_test.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testRoundTripper struct {
|
||||||
|
Request *http.Request
|
||||||
|
Response *http.Response
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
rt.Request = req
|
||||||
|
return rt.Response, rt.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBearerAuthRoundTripper(t *testing.T) {
|
||||||
|
rt := &testRoundTripper{}
|
||||||
|
req := &http.Request{}
|
||||||
|
newBearerAuthRoundTripper("test", rt).RoundTrip(req)
|
||||||
|
if rt.Request == nil {
|
||||||
|
t.Fatalf("unexpected nil request: %v", rt)
|
||||||
|
}
|
||||||
|
if rt.Request == req {
|
||||||
|
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
if rt.Request.Header.Get("Authorization") != "Bearer test" {
|
||||||
|
t.Errorf("unexpected authorization header: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicAuthRoundTripper(t *testing.T) {
|
||||||
|
rt := &testRoundTripper{}
|
||||||
|
req := &http.Request{}
|
||||||
|
newBasicAuthRoundTripper("user", "pass", rt).RoundTrip(req)
|
||||||
|
if rt.Request == nil {
|
||||||
|
t.Fatalf("unexpected nil request: %v", rt)
|
||||||
|
}
|
||||||
|
if rt.Request == req {
|
||||||
|
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
if user, pass, found := rt.Request.BasicAuth(); !found || user != "user" || pass != "pass" {
|
||||||
|
t.Errorf("unexpected authorization header: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserAgentRoundTripper(t *testing.T) {
|
||||||
|
rt := &testRoundTripper{}
|
||||||
|
req := &http.Request{
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", "other")
|
||||||
|
newUserAgentRoundTripper("test", rt).RoundTrip(req)
|
||||||
|
if rt.Request == nil {
|
||||||
|
t.Fatalf("unexpected nil request: %v", rt)
|
||||||
|
}
|
||||||
|
if rt.Request != req {
|
||||||
|
t.Fatalf("round tripper should not have copied request object: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
if rt.Request.Header.Get("User-Agent") != "other" {
|
||||||
|
t.Errorf("unexpected user agent header: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
req = &http.Request{}
|
||||||
|
newUserAgentRoundTripper("test", rt).RoundTrip(req)
|
||||||
|
if rt.Request == nil {
|
||||||
|
t.Fatalf("unexpected nil request: %v", rt)
|
||||||
|
}
|
||||||
|
if rt.Request == req {
|
||||||
|
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
if rt.Request.Header.Get("User-Agent") != "test" {
|
||||||
|
t.Errorf("unexpected user agent header: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
}
|
138
pkg/client/transport/transport.go
Normal file
138
pkg/client/transport/transport.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns an http.RoundTripper that will provide the authentication
|
||||||
|
// or transport level security defined by the provided Config.
|
||||||
|
func New(config *Config) (http.RoundTripper, error) {
|
||||||
|
// Set transport level security
|
||||||
|
if config.Transport != nil && (config.HasCA() || config.HasCertAuth() || config.TLS.Insecure) {
|
||||||
|
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
rt http.RoundTripper
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.Transport != nil {
|
||||||
|
rt = config.Transport
|
||||||
|
} else {
|
||||||
|
rt, err = tlsCache.get(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HTTPWrappersForConfig(config, rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfigFor returns a tls.Config that will provide the transport level security defined
|
||||||
|
// by the provided Config. Will return nil if no transport level security is requested.
|
||||||
|
func TLSConfigFor(c *Config) (*tls.Config, error) {
|
||||||
|
if !(c.HasCA() || c.HasCertAuth() || c.TLS.Insecure) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if c.HasCA() && c.TLS.Insecure {
|
||||||
|
return nil, fmt.Errorf("specifying a root certificates file with the insecure flag is not allowed")
|
||||||
|
}
|
||||||
|
if err := loadTLSFiles(c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability)
|
||||||
|
MinVersion: tls.VersionTLS10,
|
||||||
|
InsecureSkipVerify: c.TLS.Insecure,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.HasCA() {
|
||||||
|
tlsConfig.RootCAs = rootCertPool(c.TLS.CAData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.HasCertAuth() {
|
||||||
|
cert, err := tls.X509KeyPair(c.TLS.CertData, c.TLS.KeyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tlsConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData,
|
||||||
|
// KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are
|
||||||
|
// either populated or were empty to start.
|
||||||
|
func loadTLSFiles(c *Config) error {
|
||||||
|
var err error
|
||||||
|
c.TLS.CAData, err = dataFromSliceOrFile(c.TLS.CAData, c.TLS.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.TLS.CertData, err = dataFromSliceOrFile(c.TLS.CertData, c.TLS.CertFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.TLS.KeyData, err = dataFromSliceOrFile(c.TLS.KeyData, c.TLS.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
|
||||||
|
// or an error if an error occurred reading the file
|
||||||
|
func dataFromSliceOrFile(data []byte, file string) ([]byte, error) {
|
||||||
|
if len(data) > 0 {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if len(file) > 0 {
|
||||||
|
fileData, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
return fileData, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rootCertPool returns nil if caData is empty. When passed along, this will mean "use system CAs".
|
||||||
|
// When caData is not empty, it will be the ONLY information used in the CertPool.
|
||||||
|
func rootCertPool(caData []byte) *x509.CertPool {
|
||||||
|
// What we really want is a copy of x509.systemRootsPool, but that isn't exposed. It's difficult to build (see the go
|
||||||
|
// code for a look at the platform specific insanity), so we'll use the fact that RootCAs == nil gives us the system values
|
||||||
|
// It doesn't allow trusting either/or, but hopefully that won't be an issue
|
||||||
|
if len(caData) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have caData, use it
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
certPool.AppendCertsFromPEM(caData)
|
||||||
|
return certPool
|
||||||
|
}
|
204
pkg/client/transport/transport_test.go
Normal file
204
pkg/client/transport/transport_test.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rootCACert = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC4DCCAcqgAwIBAgIBATALBgkqhkiG9w0BAQswIzEhMB8GA1UEAwwYMTAuMTMu
|
||||||
|
MTI5LjEwNkAxNDIxMzU5MDU4MB4XDTE1MDExNTIxNTczN1oXDTE2MDExNTIxNTcz
|
||||||
|
OFowIzEhMB8GA1UEAwwYMTAuMTMuMTI5LjEwNkAxNDIxMzU5MDU4MIIBIjANBgkq
|
||||||
|
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunDRXGwsiYWGFDlWH6kjGun+PshDGeZX
|
||||||
|
xtx9lUnL8pIRWH3wX6f13PO9sktaOWW0T0mlo6k2bMlSLlSZgG9H6og0W6gLS3vq
|
||||||
|
s4VavZ6DbXIwemZG2vbRwsvR+t4G6Nbwelm6F8RFnA1Fwt428pavmNQ/wgYzo+T1
|
||||||
|
1eS+HiN4ACnSoDSx3QRWcgBkB1g6VReofVjx63i0J+w8Q/41L9GUuLqquFxu6ZnH
|
||||||
|
60vTB55lHgFiDLjA1FkEz2dGvGh/wtnFlRvjaPC54JH2K1mPYAUXTreoeJtLJKX0
|
||||||
|
ycoiyB24+zGCniUmgIsmQWRPaOPircexCp1BOeze82BT1LCZNTVaxQIDAQABoyMw
|
||||||
|
ITAOBgNVHQ8BAf8EBAMCAKQwDwYDVR0TAQH/BAUwAwEB/zALBgkqhkiG9w0BAQsD
|
||||||
|
ggEBADMxsUuAFlsYDpF4fRCzXXwrhbtj4oQwcHpbu+rnOPHCZupiafzZpDu+rw4x
|
||||||
|
YGPnCb594bRTQn4pAu3Ac18NbLD5pV3uioAkv8oPkgr8aUhXqiv7KdDiaWm6sbAL
|
||||||
|
EHiXVBBAFvQws10HMqMoKtO8f1XDNAUkWduakR/U6yMgvOPwS7xl0eUTqyRB6zGb
|
||||||
|
K55q2dejiFWaFqB/y78txzvz6UlOZKE44g2JAVoJVM6kGaxh33q8/FmrL4kuN3ut
|
||||||
|
W+MmJCVDvd4eEqPwbp7146ZWTqpIJ8lvA6wuChtqV8lhAPka2hD/LMqY8iXNmfXD
|
||||||
|
uml0obOEy+ON91k+SWTJ3ggmF/U=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
certData = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC6jCCAdSgAwIBAgIBCzALBgkqhkiG9w0BAQswIzEhMB8GA1UEAwwYMTAuMTMu
|
||||||
|
MTI5LjEwNkAxNDIxMzU5MDU4MB4XDTE1MDExNTIyMDEzMVoXDTE2MDExNTIyMDEz
|
||||||
|
MlowGzEZMBcGA1UEAxMQb3BlbnNoaWZ0LWNsaWVudDCCASIwDQYJKoZIhvcNAQEB
|
||||||
|
BQADggEPADCCAQoCggEBAKtdhz0+uCLXw5cSYns9rU/XifFSpb/x24WDdrm72S/v
|
||||||
|
b9BPYsAStiP148buylr1SOuNi8sTAZmlVDDIpIVwMLff+o2rKYDicn9fjbrTxTOj
|
||||||
|
lI4pHJBH+JU3AJ0tbajupioh70jwFS0oYpwtneg2zcnE2Z4l6mhrj2okrc5Q1/X2
|
||||||
|
I2HChtIU4JYTisObtin10QKJX01CLfYXJLa8upWzKZ4/GOcHG+eAV3jXWoXidtjb
|
||||||
|
1Usw70amoTZ6mIVCkiu1QwCoa8+ycojGfZhvqMsAp1536ZcCul+Na+AbCv4zKS7F
|
||||||
|
kQQaImVrXdUiFansIoofGlw/JNuoKK6ssVpS5Ic3pgcCAwEAAaM1MDMwDgYDVR0P
|
||||||
|
AQH/BAQDAgCgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJ
|
||||||
|
KoZIhvcNAQELA4IBAQCKLREH7bXtXtZ+8vI6cjD7W3QikiArGqbl36bAhhWsJLp/
|
||||||
|
p/ndKz39iFNaiZ3GlwIURWOOKx3y3GA0x9m8FR+Llthf0EQ8sUjnwaknWs0Y6DQ3
|
||||||
|
jjPFZOpV3KPCFrdMJ3++E3MgwFC/Ih/N2ebFX9EcV9Vcc6oVWMdwT0fsrhu683rq
|
||||||
|
6GSR/3iVX1G/pmOiuaR0fNUaCyCfYrnI4zHBDgSfnlm3vIvN2lrsR/DQBakNL8DJ
|
||||||
|
HBgKxMGeUPoneBv+c8DMXIL0EhaFXRlBv9QW45/GiAIOuyFJ0i6hCtGZpJjq4OpQ
|
||||||
|
BRjCI+izPzFTjsxD4aORE+WOkyWFCGPWKfNejfw0
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
keyData = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAq12HPT64ItfDlxJiez2tT9eJ8VKlv/HbhYN2ubvZL+9v0E9i
|
||||||
|
wBK2I/Xjxu7KWvVI642LyxMBmaVUMMikhXAwt9/6jaspgOJyf1+NutPFM6OUjikc
|
||||||
|
kEf4lTcAnS1tqO6mKiHvSPAVLShinC2d6DbNycTZniXqaGuPaiStzlDX9fYjYcKG
|
||||||
|
0hTglhOKw5u2KfXRAolfTUIt9hcktry6lbMpnj8Y5wcb54BXeNdaheJ22NvVSzDv
|
||||||
|
RqahNnqYhUKSK7VDAKhrz7JyiMZ9mG+oywCnXnfplwK6X41r4BsK/jMpLsWRBBoi
|
||||||
|
ZWtd1SIVqewiih8aXD8k26gorqyxWlLkhzemBwIDAQABAoIBAD2XYRs3JrGHQUpU
|
||||||
|
FkdbVKZkvrSY0vAZOqBTLuH0zUv4UATb8487anGkWBjRDLQCgxH+jucPTrztekQK
|
||||||
|
aW94clo0S3aNtV4YhbSYIHWs1a0It0UdK6ID7CmdWkAj6s0T8W8lQT7C46mWYVLm
|
||||||
|
5mFnCTHi6aB42jZrqmEpC7sivWwuU0xqj3Ml8kkxQCGmyc9JjmCB4OrFFC8NNt6M
|
||||||
|
ObvQkUI6Z3nO4phTbpxkE1/9dT0MmPIF7GhHVzJMS+EyyRYUDllZ0wvVSOM3qZT0
|
||||||
|
JMUaBerkNwm9foKJ1+dv2nMKZZbJajv7suUDCfU44mVeaEO+4kmTKSGCGjjTBGkr
|
||||||
|
7L1ySDECgYEA5ElIMhpdBzIivCuBIH8LlUeuzd93pqssO1G2Xg0jHtfM4tz7fyeI
|
||||||
|
cr90dc8gpli24dkSxzLeg3Tn3wIj/Bu64m2TpZPZEIlukYvgdgArmRIPQVxerYey
|
||||||
|
OkrfTNkxU1HXsYjLCdGcGXs5lmb+K/kuTcFxaMOs7jZi7La+jEONwf8CgYEAwCs/
|
||||||
|
rUOOA0klDsWWisbivOiNPII79c9McZCNBqncCBfMUoiGe8uWDEO4TFHN60vFuVk9
|
||||||
|
8PkwpCfvaBUX+ajvbafIfHxsnfk1M04WLGCeqQ/ym5Q4sQoQOcC1b1y9qc/xEWfg
|
||||||
|
nIUuia0ukYRpl7qQa3tNg+BNFyjypW8zukUAC/kCgYB1/Kojuxx5q5/oQVPrx73k
|
||||||
|
2bevD+B3c+DYh9MJqSCNwFtUpYIWpggPxoQan4LwdsmO0PKzocb/ilyNFj4i/vII
|
||||||
|
NToqSc/WjDFpaDIKyuu9oWfhECye45NqLWhb/6VOuu4QA/Nsj7luMhIBehnEAHW+
|
||||||
|
GkzTKM8oD1PxpEG3nPKXYQKBgQC6AuMPRt3XBl1NkCrpSBy/uObFlFaP2Enpf39S
|
||||||
|
3OZ0Gv0XQrnSaL1kP8TMcz68rMrGX8DaWYsgytstR4W+jyy7WvZwsUu+GjTJ5aMG
|
||||||
|
77uEcEBpIi9CBzivfn7hPccE8ZgqPf+n4i6q66yxBJflW5xhvafJqDtW2LcPNbW/
|
||||||
|
bvzdmQKBgExALRUXpq+5dbmkdXBHtvXdRDZ6rVmrnjy4nI5bPw+1GqQqk6uAR6B/
|
||||||
|
F6NmLCQOO4PDG/cuatNHIr2FrwTmGdEL6ObLUGWn9Oer9gJhHVqqsY5I4sEPo4XX
|
||||||
|
stR0Yiw0buV6DL/moUO0HIM9Bjh96HJp+LxiIS6UCdIhMPp5HoQa
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
Config *Config
|
||||||
|
Err bool
|
||||||
|
TLS bool
|
||||||
|
Default bool
|
||||||
|
}{
|
||||||
|
"default transport": {
|
||||||
|
Default: true,
|
||||||
|
Config: &Config{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"ca transport": {
|
||||||
|
TLS: true,
|
||||||
|
Config: &Config{
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CAData: []byte(rootCACert),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bad ca file transport": {
|
||||||
|
Err: true,
|
||||||
|
Config: &Config{
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CAFile: "invalid file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ca data overriding bad ca file transport": {
|
||||||
|
TLS: true,
|
||||||
|
Config: &Config{
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CAData: []byte(rootCACert),
|
||||||
|
CAFile: "invalid file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"cert transport": {
|
||||||
|
TLS: true,
|
||||||
|
Config: &Config{
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CAData: []byte(rootCACert),
|
||||||
|
CertData: []byte(certData),
|
||||||
|
KeyData: []byte(keyData),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bad cert data transport": {
|
||||||
|
Err: true,
|
||||||
|
Config: &Config{
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CAData: []byte(rootCACert),
|
||||||
|
CertData: []byte(certData),
|
||||||
|
KeyData: []byte("bad key data"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bad file cert transport": {
|
||||||
|
Err: true,
|
||||||
|
Config: &Config{
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CAData: []byte(rootCACert),
|
||||||
|
CertData: []byte(certData),
|
||||||
|
KeyFile: "invalid file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"key data overriding bad file cert transport": {
|
||||||
|
TLS: true,
|
||||||
|
Config: &Config{
|
||||||
|
TLS: TLSConfig{
|
||||||
|
CAData: []byte(rootCACert),
|
||||||
|
CertData: []byte(certData),
|
||||||
|
KeyData: []byte(keyData),
|
||||||
|
KeyFile: "invalid file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for k, testCase := range testCases {
|
||||||
|
transport, err := New(testCase.Config)
|
||||||
|
switch {
|
||||||
|
case testCase.Err && err == nil:
|
||||||
|
t.Errorf("%s: unexpected non-error", k)
|
||||||
|
continue
|
||||||
|
case !testCase.Err && err != nil:
|
||||||
|
t.Errorf("%s: unexpected error: %v", k, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case testCase.Default && transport != http.DefaultTransport:
|
||||||
|
t.Errorf("%s: expected the default transport, got %#v", k, transport)
|
||||||
|
continue
|
||||||
|
case !testCase.Default && transport == http.DefaultTransport:
|
||||||
|
t.Errorf("%s: expected non-default transport, got %#v", k, transport)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only know how to check TLSConfig on http.Transports
|
||||||
|
if transport, ok := transport.(*http.Transport); ok {
|
||||||
|
switch {
|
||||||
|
case testCase.TLS && transport.TLSClientConfig == nil:
|
||||||
|
t.Errorf("%s: expected TLSClientConfig, got %#v", k, transport)
|
||||||
|
continue
|
||||||
|
case !testCase.TLS && transport.TLSClientConfig != nil:
|
||||||
|
t.Errorf("%s: expected no TLSClientConfig, got %#v", k, transport)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,142 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package unversioned
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/util"
|
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RequestInfo keeps track of information about a request/response combination
|
|
||||||
type RequestInfo struct {
|
|
||||||
RequestHeaders http.Header
|
|
||||||
RequestVerb string
|
|
||||||
RequestURL string
|
|
||||||
|
|
||||||
ResponseStatus string
|
|
||||||
ResponseHeaders http.Header
|
|
||||||
ResponseErr error
|
|
||||||
|
|
||||||
Duration time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRequestInfo creates a new RequestInfo based on an http request
|
|
||||||
func NewRequestInfo(req *http.Request) *RequestInfo {
|
|
||||||
reqInfo := &RequestInfo{}
|
|
||||||
reqInfo.RequestURL = req.URL.String()
|
|
||||||
reqInfo.RequestVerb = req.Method
|
|
||||||
reqInfo.RequestHeaders = req.Header
|
|
||||||
|
|
||||||
return reqInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete adds information about the response to the RequestInfo
|
|
||||||
func (r *RequestInfo) Complete(response *http.Response, err error) {
|
|
||||||
if err != nil {
|
|
||||||
r.ResponseErr = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.ResponseStatus = response.Status
|
|
||||||
r.ResponseHeaders = response.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToCurl returns a string that can be run as a command in a terminal (minus the body)
|
|
||||||
func (r RequestInfo) ToCurl() string {
|
|
||||||
headers := ""
|
|
||||||
for key, values := range map[string][]string(r.RequestHeaders) {
|
|
||||||
for _, value := range values {
|
|
||||||
headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("curl -k -v -X%s %s %s", r.RequestVerb, headers, r.RequestURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebuggingRoundTripper will display information about the requests passing through it based on what is configured
|
|
||||||
type DebuggingRoundTripper struct {
|
|
||||||
delegatedRoundTripper http.RoundTripper
|
|
||||||
|
|
||||||
Levels sets.String
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
JustURL string = "url"
|
|
||||||
URLTiming string = "urltiming"
|
|
||||||
CurlCommand string = "curlcommand"
|
|
||||||
RequestHeaders string = "requestheaders"
|
|
||||||
ResponseStatus string = "responsestatus"
|
|
||||||
ResponseHeaders string = "responseheaders"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewDebuggingRoundTripper(rt http.RoundTripper, levels ...string) *DebuggingRoundTripper {
|
|
||||||
return &DebuggingRoundTripper{rt, sets.NewString(levels...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *DebuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
reqInfo := NewRequestInfo(req)
|
|
||||||
|
|
||||||
if rt.Levels.Has(JustURL) {
|
|
||||||
glog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL)
|
|
||||||
}
|
|
||||||
if rt.Levels.Has(CurlCommand) {
|
|
||||||
glog.Infof("%s", reqInfo.ToCurl())
|
|
||||||
|
|
||||||
}
|
|
||||||
if rt.Levels.Has(RequestHeaders) {
|
|
||||||
glog.Infof("Request Headers:")
|
|
||||||
for key, values := range reqInfo.RequestHeaders {
|
|
||||||
for _, value := range values {
|
|
||||||
glog.Infof(" %s: %s", key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime := time.Now()
|
|
||||||
response, err := rt.delegatedRoundTripper.RoundTrip(req)
|
|
||||||
reqInfo.Duration = time.Since(startTime)
|
|
||||||
|
|
||||||
reqInfo.Complete(response, err)
|
|
||||||
|
|
||||||
if rt.Levels.Has(URLTiming) {
|
|
||||||
glog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
|
|
||||||
}
|
|
||||||
if rt.Levels.Has(ResponseStatus) {
|
|
||||||
glog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
|
|
||||||
}
|
|
||||||
if rt.Levels.Has(ResponseHeaders) {
|
|
||||||
glog.Infof("Response Headers:")
|
|
||||||
for key, values := range reqInfo.ResponseHeaders {
|
|
||||||
for _, value := range values {
|
|
||||||
glog.Infof(" %s: %s", key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = util.RoundTripperWrapper(&DebuggingRoundTripper{})
|
|
||||||
|
|
||||||
func (rt *DebuggingRoundTripper) WrappedRoundTripper() http.RoundTripper {
|
|
||||||
return rt.delegatedRoundTripper
|
|
||||||
}
|
|
@ -28,7 +28,6 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
gruntime "runtime"
|
gruntime "runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
@ -446,124 +445,6 @@ func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
|
|||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
// tlsTransports stores reusable round trippers with custom TLSClientConfig options
|
|
||||||
tlsTransports = map[string]*http.Transport{}
|
|
||||||
|
|
||||||
// tlsTransportLock protects retrieval and storage of round trippers into the tlsTransports map
|
|
||||||
tlsTransportLock sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
// tlsTransportFor returns a http.RoundTripper for the given config, or an error
|
|
||||||
// The same RoundTripper will be returned for configs with identical TLS options
|
|
||||||
// If the config has no custom TLS options, http.DefaultTransport is returned
|
|
||||||
func tlsTransportFor(config *Config) (http.RoundTripper, error) {
|
|
||||||
// Get a unique key for the TLS options in the config
|
|
||||||
key, err := tlsConfigKey(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we only create a single transport for the given TLS options
|
|
||||||
tlsTransportLock.Lock()
|
|
||||||
defer tlsTransportLock.Unlock()
|
|
||||||
|
|
||||||
// See if we already have a custom transport for this config
|
|
||||||
if cachedTransport, ok := tlsTransports[key]; ok {
|
|
||||||
return cachedTransport, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the TLS options for this client config
|
|
||||||
tlsConfig, err := TLSConfigFor(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// The options didn't require a custom TLS config
|
|
||||||
if tlsConfig == nil {
|
|
||||||
return http.DefaultTransport, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache a single transport for these options
|
|
||||||
tlsTransports[key] = util.SetTransportDefaults(&http.Transport{
|
|
||||||
TLSClientConfig: tlsConfig,
|
|
||||||
})
|
|
||||||
return tlsTransports[key], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransportFor returns an http.RoundTripper that will provide the authentication
|
|
||||||
// or transport level security defined by the provided Config. Will return the
|
|
||||||
// default http.DefaultTransport if no special case behavior is needed.
|
|
||||||
func TransportFor(config *Config) (http.RoundTripper, error) {
|
|
||||||
hasCA := len(config.CAFile) > 0 || len(config.CAData) > 0
|
|
||||||
hasCert := len(config.CertFile) > 0 || len(config.CertData) > 0
|
|
||||||
|
|
||||||
// Set transport level security
|
|
||||||
if config.Transport != nil && (hasCA || hasCert || config.Insecure) {
|
|
||||||
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
transport http.RoundTripper
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if config.Transport != nil {
|
|
||||||
transport = config.Transport
|
|
||||||
} else {
|
|
||||||
transport, err = tlsTransportFor(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call wrap prior to adding debugging wrappers
|
|
||||||
if config.WrapTransport != nil {
|
|
||||||
transport = config.WrapTransport(transport)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case bool(glog.V(9)):
|
|
||||||
transport = NewDebuggingRoundTripper(transport, CurlCommand, URLTiming, ResponseHeaders)
|
|
||||||
case bool(glog.V(8)):
|
|
||||||
transport = NewDebuggingRoundTripper(transport, JustURL, RequestHeaders, ResponseStatus, ResponseHeaders)
|
|
||||||
case bool(glog.V(7)):
|
|
||||||
transport = NewDebuggingRoundTripper(transport, JustURL, RequestHeaders, ResponseStatus)
|
|
||||||
case bool(glog.V(6)):
|
|
||||||
transport = NewDebuggingRoundTripper(transport, URLTiming)
|
|
||||||
}
|
|
||||||
|
|
||||||
transport, err = HTTPWrappersForConfig(config, transport)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use the config context to wrap a transport
|
|
||||||
|
|
||||||
return transport, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPWrappersForConfig wraps a round tripper with any relevant layered behavior from the
|
|
||||||
// config. Exposed to allow more clients that need HTTP-like behavior but then must hijack
|
|
||||||
// the underlying connection (like WebSocket or HTTP2 clients). Pure HTTP clients should use
|
|
||||||
// the higher level TransportFor or RESTClientFor methods.
|
|
||||||
func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
|
|
||||||
// Set authentication wrappers
|
|
||||||
hasBasicAuth := config.Username != "" || config.Password != ""
|
|
||||||
if hasBasicAuth && config.BearerToken != "" {
|
|
||||||
return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case config.BearerToken != "":
|
|
||||||
rt = NewBearerAuthRoundTripper(config.BearerToken, rt)
|
|
||||||
case hasBasicAuth:
|
|
||||||
rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
|
|
||||||
}
|
|
||||||
if len(config.UserAgent) > 0 {
|
|
||||||
rt = NewUserAgentRoundTripper(config.UserAgent, rt)
|
|
||||||
}
|
|
||||||
return rt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
|
// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
|
||||||
// to use with a Client at a given API version following the standard conventions for a
|
// to use with a Client at a given API version following the standard conventions for a
|
||||||
// Kubernetes API.
|
// Kubernetes API.
|
||||||
|
@ -28,248 +28,6 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
rootCACert = `-----BEGIN CERTIFICATE-----
|
|
||||||
MIIC4DCCAcqgAwIBAgIBATALBgkqhkiG9w0BAQswIzEhMB8GA1UEAwwYMTAuMTMu
|
|
||||||
MTI5LjEwNkAxNDIxMzU5MDU4MB4XDTE1MDExNTIxNTczN1oXDTE2MDExNTIxNTcz
|
|
||||||
OFowIzEhMB8GA1UEAwwYMTAuMTMuMTI5LjEwNkAxNDIxMzU5MDU4MIIBIjANBgkq
|
|
||||||
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunDRXGwsiYWGFDlWH6kjGun+PshDGeZX
|
|
||||||
xtx9lUnL8pIRWH3wX6f13PO9sktaOWW0T0mlo6k2bMlSLlSZgG9H6og0W6gLS3vq
|
|
||||||
s4VavZ6DbXIwemZG2vbRwsvR+t4G6Nbwelm6F8RFnA1Fwt428pavmNQ/wgYzo+T1
|
|
||||||
1eS+HiN4ACnSoDSx3QRWcgBkB1g6VReofVjx63i0J+w8Q/41L9GUuLqquFxu6ZnH
|
|
||||||
60vTB55lHgFiDLjA1FkEz2dGvGh/wtnFlRvjaPC54JH2K1mPYAUXTreoeJtLJKX0
|
|
||||||
ycoiyB24+zGCniUmgIsmQWRPaOPircexCp1BOeze82BT1LCZNTVaxQIDAQABoyMw
|
|
||||||
ITAOBgNVHQ8BAf8EBAMCAKQwDwYDVR0TAQH/BAUwAwEB/zALBgkqhkiG9w0BAQsD
|
|
||||||
ggEBADMxsUuAFlsYDpF4fRCzXXwrhbtj4oQwcHpbu+rnOPHCZupiafzZpDu+rw4x
|
|
||||||
YGPnCb594bRTQn4pAu3Ac18NbLD5pV3uioAkv8oPkgr8aUhXqiv7KdDiaWm6sbAL
|
|
||||||
EHiXVBBAFvQws10HMqMoKtO8f1XDNAUkWduakR/U6yMgvOPwS7xl0eUTqyRB6zGb
|
|
||||||
K55q2dejiFWaFqB/y78txzvz6UlOZKE44g2JAVoJVM6kGaxh33q8/FmrL4kuN3ut
|
|
||||||
W+MmJCVDvd4eEqPwbp7146ZWTqpIJ8lvA6wuChtqV8lhAPka2hD/LMqY8iXNmfXD
|
|
||||||
uml0obOEy+ON91k+SWTJ3ggmF/U=
|
|
||||||
-----END CERTIFICATE-----`
|
|
||||||
|
|
||||||
certData = `-----BEGIN CERTIFICATE-----
|
|
||||||
MIIC6jCCAdSgAwIBAgIBCzALBgkqhkiG9w0BAQswIzEhMB8GA1UEAwwYMTAuMTMu
|
|
||||||
MTI5LjEwNkAxNDIxMzU5MDU4MB4XDTE1MDExNTIyMDEzMVoXDTE2MDExNTIyMDEz
|
|
||||||
MlowGzEZMBcGA1UEAxMQb3BlbnNoaWZ0LWNsaWVudDCCASIwDQYJKoZIhvcNAQEB
|
|
||||||
BQADggEPADCCAQoCggEBAKtdhz0+uCLXw5cSYns9rU/XifFSpb/x24WDdrm72S/v
|
|
||||||
b9BPYsAStiP148buylr1SOuNi8sTAZmlVDDIpIVwMLff+o2rKYDicn9fjbrTxTOj
|
|
||||||
lI4pHJBH+JU3AJ0tbajupioh70jwFS0oYpwtneg2zcnE2Z4l6mhrj2okrc5Q1/X2
|
|
||||||
I2HChtIU4JYTisObtin10QKJX01CLfYXJLa8upWzKZ4/GOcHG+eAV3jXWoXidtjb
|
|
||||||
1Usw70amoTZ6mIVCkiu1QwCoa8+ycojGfZhvqMsAp1536ZcCul+Na+AbCv4zKS7F
|
|
||||||
kQQaImVrXdUiFansIoofGlw/JNuoKK6ssVpS5Ic3pgcCAwEAAaM1MDMwDgYDVR0P
|
|
||||||
AQH/BAQDAgCgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJ
|
|
||||||
KoZIhvcNAQELA4IBAQCKLREH7bXtXtZ+8vI6cjD7W3QikiArGqbl36bAhhWsJLp/
|
|
||||||
p/ndKz39iFNaiZ3GlwIURWOOKx3y3GA0x9m8FR+Llthf0EQ8sUjnwaknWs0Y6DQ3
|
|
||||||
jjPFZOpV3KPCFrdMJ3++E3MgwFC/Ih/N2ebFX9EcV9Vcc6oVWMdwT0fsrhu683rq
|
|
||||||
6GSR/3iVX1G/pmOiuaR0fNUaCyCfYrnI4zHBDgSfnlm3vIvN2lrsR/DQBakNL8DJ
|
|
||||||
HBgKxMGeUPoneBv+c8DMXIL0EhaFXRlBv9QW45/GiAIOuyFJ0i6hCtGZpJjq4OpQ
|
|
||||||
BRjCI+izPzFTjsxD4aORE+WOkyWFCGPWKfNejfw0
|
|
||||||
-----END CERTIFICATE-----`
|
|
||||||
|
|
||||||
keyData = `-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEowIBAAKCAQEAq12HPT64ItfDlxJiez2tT9eJ8VKlv/HbhYN2ubvZL+9v0E9i
|
|
||||||
wBK2I/Xjxu7KWvVI642LyxMBmaVUMMikhXAwt9/6jaspgOJyf1+NutPFM6OUjikc
|
|
||||||
kEf4lTcAnS1tqO6mKiHvSPAVLShinC2d6DbNycTZniXqaGuPaiStzlDX9fYjYcKG
|
|
||||||
0hTglhOKw5u2KfXRAolfTUIt9hcktry6lbMpnj8Y5wcb54BXeNdaheJ22NvVSzDv
|
|
||||||
RqahNnqYhUKSK7VDAKhrz7JyiMZ9mG+oywCnXnfplwK6X41r4BsK/jMpLsWRBBoi
|
|
||||||
ZWtd1SIVqewiih8aXD8k26gorqyxWlLkhzemBwIDAQABAoIBAD2XYRs3JrGHQUpU
|
|
||||||
FkdbVKZkvrSY0vAZOqBTLuH0zUv4UATb8487anGkWBjRDLQCgxH+jucPTrztekQK
|
|
||||||
aW94clo0S3aNtV4YhbSYIHWs1a0It0UdK6ID7CmdWkAj6s0T8W8lQT7C46mWYVLm
|
|
||||||
5mFnCTHi6aB42jZrqmEpC7sivWwuU0xqj3Ml8kkxQCGmyc9JjmCB4OrFFC8NNt6M
|
|
||||||
ObvQkUI6Z3nO4phTbpxkE1/9dT0MmPIF7GhHVzJMS+EyyRYUDllZ0wvVSOM3qZT0
|
|
||||||
JMUaBerkNwm9foKJ1+dv2nMKZZbJajv7suUDCfU44mVeaEO+4kmTKSGCGjjTBGkr
|
|
||||||
7L1ySDECgYEA5ElIMhpdBzIivCuBIH8LlUeuzd93pqssO1G2Xg0jHtfM4tz7fyeI
|
|
||||||
cr90dc8gpli24dkSxzLeg3Tn3wIj/Bu64m2TpZPZEIlukYvgdgArmRIPQVxerYey
|
|
||||||
OkrfTNkxU1HXsYjLCdGcGXs5lmb+K/kuTcFxaMOs7jZi7La+jEONwf8CgYEAwCs/
|
|
||||||
rUOOA0klDsWWisbivOiNPII79c9McZCNBqncCBfMUoiGe8uWDEO4TFHN60vFuVk9
|
|
||||||
8PkwpCfvaBUX+ajvbafIfHxsnfk1M04WLGCeqQ/ym5Q4sQoQOcC1b1y9qc/xEWfg
|
|
||||||
nIUuia0ukYRpl7qQa3tNg+BNFyjypW8zukUAC/kCgYB1/Kojuxx5q5/oQVPrx73k
|
|
||||||
2bevD+B3c+DYh9MJqSCNwFtUpYIWpggPxoQan4LwdsmO0PKzocb/ilyNFj4i/vII
|
|
||||||
NToqSc/WjDFpaDIKyuu9oWfhECye45NqLWhb/6VOuu4QA/Nsj7luMhIBehnEAHW+
|
|
||||||
GkzTKM8oD1PxpEG3nPKXYQKBgQC6AuMPRt3XBl1NkCrpSBy/uObFlFaP2Enpf39S
|
|
||||||
3OZ0Gv0XQrnSaL1kP8TMcz68rMrGX8DaWYsgytstR4W+jyy7WvZwsUu+GjTJ5aMG
|
|
||||||
77uEcEBpIi9CBzivfn7hPccE8ZgqPf+n4i6q66yxBJflW5xhvafJqDtW2LcPNbW/
|
|
||||||
bvzdmQKBgExALRUXpq+5dbmkdXBHtvXdRDZ6rVmrnjy4nI5bPw+1GqQqk6uAR6B/
|
|
||||||
F6NmLCQOO4PDG/cuatNHIr2FrwTmGdEL6ObLUGWn9Oer9gJhHVqqsY5I4sEPo4XX
|
|
||||||
stR0Yiw0buV6DL/moUO0HIM9Bjh96HJp+LxiIS6UCdIhMPp5HoQa
|
|
||||||
-----END RSA PRIVATE KEY-----`
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTransportFor(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
Config *Config
|
|
||||||
Err bool
|
|
||||||
TLS bool
|
|
||||||
Default bool
|
|
||||||
}{
|
|
||||||
"default transport": {
|
|
||||||
Default: true,
|
|
||||||
Config: &Config{},
|
|
||||||
},
|
|
||||||
|
|
||||||
"ca transport": {
|
|
||||||
TLS: true,
|
|
||||||
Config: &Config{
|
|
||||||
TLSClientConfig: TLSClientConfig{
|
|
||||||
CAData: []byte(rootCACert),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"bad ca file transport": {
|
|
||||||
Err: true,
|
|
||||||
Config: &Config{
|
|
||||||
TLSClientConfig: TLSClientConfig{
|
|
||||||
CAFile: "invalid file",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ca data overriding bad ca file transport": {
|
|
||||||
TLS: true,
|
|
||||||
Config: &Config{
|
|
||||||
TLSClientConfig: TLSClientConfig{
|
|
||||||
CAData: []byte(rootCACert),
|
|
||||||
CAFile: "invalid file",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"cert transport": {
|
|
||||||
TLS: true,
|
|
||||||
Config: &Config{
|
|
||||||
TLSClientConfig: TLSClientConfig{
|
|
||||||
CertData: []byte(certData),
|
|
||||||
KeyData: []byte(keyData),
|
|
||||||
CAData: []byte(rootCACert),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"bad cert data transport": {
|
|
||||||
Err: true,
|
|
||||||
Config: &Config{
|
|
||||||
TLSClientConfig: TLSClientConfig{
|
|
||||||
CertData: []byte(certData),
|
|
||||||
KeyData: []byte("bad key data"),
|
|
||||||
CAData: []byte(rootCACert),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"bad file cert transport": {
|
|
||||||
Err: true,
|
|
||||||
Config: &Config{
|
|
||||||
TLSClientConfig: TLSClientConfig{
|
|
||||||
CertData: []byte(certData),
|
|
||||||
KeyFile: "invalid file",
|
|
||||||
CAData: []byte(rootCACert),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"key data overriding bad file cert transport": {
|
|
||||||
TLS: true,
|
|
||||||
Config: &Config{
|
|
||||||
TLSClientConfig: TLSClientConfig{
|
|
||||||
CertData: []byte(certData),
|
|
||||||
KeyData: []byte(keyData),
|
|
||||||
KeyFile: "invalid file",
|
|
||||||
CAData: []byte(rootCACert),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for k, testCase := range testCases {
|
|
||||||
transport, err := TransportFor(testCase.Config)
|
|
||||||
switch {
|
|
||||||
case testCase.Err && err == nil:
|
|
||||||
t.Errorf("%s: unexpected non-error", k)
|
|
||||||
continue
|
|
||||||
case !testCase.Err && err != nil:
|
|
||||||
t.Errorf("%s: unexpected error: %v", k, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case testCase.Default && transport != http.DefaultTransport:
|
|
||||||
t.Errorf("%s: expected the default transport, got %#v", k, transport)
|
|
||||||
continue
|
|
||||||
case !testCase.Default && transport == http.DefaultTransport:
|
|
||||||
t.Errorf("%s: expected non-default transport, got %#v", k, transport)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only know how to check TLSConfig on http.Transports
|
|
||||||
if transport, ok := transport.(*http.Transport); ok {
|
|
||||||
switch {
|
|
||||||
case testCase.TLS && transport.TLSClientConfig == nil:
|
|
||||||
t.Errorf("%s: expected TLSClientConfig, got %#v", k, transport)
|
|
||||||
continue
|
|
||||||
case !testCase.TLS && transport.TLSClientConfig != nil:
|
|
||||||
t.Errorf("%s: expected no TLSClientConfig, got %#v", k, transport)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLSTransportCache(t *testing.T) {
|
|
||||||
// Empty the cache
|
|
||||||
tlsTransports = map[string]*http.Transport{}
|
|
||||||
// Construct several transports (Insecure=true to force a transport with custom tls settings)
|
|
||||||
identicalConfigurations := map[string]*Config{
|
|
||||||
"empty": {Insecure: true},
|
|
||||||
"host": {Insecure: true, Host: "foo"},
|
|
||||||
"prefix": {Insecure: true, Prefix: "foo"},
|
|
||||||
"version": {Insecure: true, Version: "foo"},
|
|
||||||
"codec": {Insecure: true, Codec: testapi.Default.Codec()},
|
|
||||||
"basic": {Insecure: true, Username: "bob", Password: "password"},
|
|
||||||
"bearer": {Insecure: true, BearerToken: "token"},
|
|
||||||
"user agent": {Insecure: true, UserAgent: "useragent"},
|
|
||||||
"wrap transport": {Insecure: true, WrapTransport: func(http.RoundTripper) http.RoundTripper { return nil }},
|
|
||||||
"qps/burst": {Insecure: true, QPS: 1.0, Burst: 10},
|
|
||||||
}
|
|
||||||
for k, v := range identicalConfigurations {
|
|
||||||
if _, err := TransportFor(v); err != nil {
|
|
||||||
t.Errorf("Unexpected error for %q: %v", k, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(tlsTransports) != 1 {
|
|
||||||
t.Errorf("Expected 1 cached transport, got %d", len(tlsTransports))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty the cache
|
|
||||||
tlsTransports = map[string]*http.Transport{}
|
|
||||||
// Construct several transports with custom TLS settings
|
|
||||||
// (no normalization is performed on ca/cert/key data, so appending a newline lets us test "different" content)
|
|
||||||
uniqueConfigurations := map[string]*Config{
|
|
||||||
"insecure": {Insecure: true},
|
|
||||||
"cadata 1": {TLSClientConfig: TLSClientConfig{CAData: []byte(rootCACert)}},
|
|
||||||
"cadata 2": {TLSClientConfig: TLSClientConfig{CAData: []byte(rootCACert + "\n")}},
|
|
||||||
"cert 1, key 1": {TLSClientConfig: TLSClientConfig{CertData: []byte(certData), KeyData: []byte(keyData)}},
|
|
||||||
"cert 1, key 2": {TLSClientConfig: TLSClientConfig{CertData: []byte(certData), KeyData: []byte(keyData + "\n")}},
|
|
||||||
"cert 2, key 1": {TLSClientConfig: TLSClientConfig{CertData: []byte(certData + "\n"), KeyData: []byte(keyData)}},
|
|
||||||
"cert 2, key 2": {TLSClientConfig: TLSClientConfig{CertData: []byte(certData + "\n"), KeyData: []byte(keyData + "\n")}},
|
|
||||||
"cadata 1, cert 1, key 1": {TLSClientConfig: TLSClientConfig{CAData: []byte(rootCACert), CertData: []byte(certData), KeyData: []byte(keyData)}},
|
|
||||||
}
|
|
||||||
for k, v := range uniqueConfigurations {
|
|
||||||
if _, err := TransportFor(v); err != nil {
|
|
||||||
t.Errorf("Unexpected error for %q: %v", k, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// All custom configs should result in a cache entry
|
|
||||||
if len(tlsTransports) != len(uniqueConfigurations) {
|
|
||||||
t.Errorf("Expected %d cached transports, got %d", len(uniqueConfigurations), len(tlsTransports))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty the cache
|
|
||||||
tlsTransports = map[string]*http.Transport{}
|
|
||||||
if _, err := TransportFor(&Config{}); err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
// A client config with no TLS options should use http.DefaultTransport, not a cached custom transport
|
|
||||||
if len(tlsTransports) != 0 {
|
|
||||||
t.Errorf("Expected no cached transports, got %d", len(tlsTransports))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsConfigTransportTLS(t *testing.T) {
|
func TestIsConfigTransportTLS(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
Config *Config
|
Config *Config
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/client/transport"
|
||||||
"k8s.io/kubernetes/pkg/util"
|
"k8s.io/kubernetes/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,31 +40,20 @@ type HTTPKubeletClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MakeTransport(config *KubeletConfig) (http.RoundTripper, error) {
|
func MakeTransport(config *KubeletConfig) (http.RoundTripper, error) {
|
||||||
cfg := &Config{TLSClientConfig: config.TLSClientConfig}
|
tlsConfig, err := transport.TLSConfigFor(config.transportConfig())
|
||||||
if config.EnableHttps {
|
|
||||||
hasCA := len(config.CAFile) > 0 || len(config.CAData) > 0
|
|
||||||
if !hasCA {
|
|
||||||
cfg.Insecure = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tlsConfig, err := TLSConfigFor(cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := http.DefaultTransport
|
rt := http.DefaultTransport
|
||||||
if config.Dial != nil || tlsConfig != nil {
|
if config.Dial != nil || tlsConfig != nil {
|
||||||
transport = util.SetTransportDefaults(&http.Transport{
|
rt = util.SetTransportDefaults(&http.Transport{
|
||||||
Dial: config.Dial,
|
Dial: config.Dial,
|
||||||
TLSClientConfig: tlsConfig,
|
TLSClientConfig: tlsConfig,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.BearerToken) > 0 {
|
return transport.HTTPWrappersForConfig(config.transportConfig(), rt)
|
||||||
transport = NewBearerAuthRoundTripper(config.BearerToken, transport)
|
|
||||||
}
|
|
||||||
|
|
||||||
return transport, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this structure is questionable, it should be using client.Config and overriding defaults.
|
// TODO: this structure is questionable, it should be using client.Config and overriding defaults.
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/client/transport"
|
||||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||||
"k8s.io/kubernetes/pkg/util/httpstream/spdy"
|
"k8s.io/kubernetes/pkg/util/httpstream/spdy"
|
||||||
@ -99,22 +100,11 @@ func NewStreamExecutor(upgrader httpstream.UpgradeRoundTripper, fn func(http.Rou
|
|||||||
// connection. Upon success, it returns the connection and the protocol
|
// connection. Upon success, it returns the connection and the protocol
|
||||||
// selected by the server.
|
// selected by the server.
|
||||||
func (e *streamExecutor) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
func (e *streamExecutor) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||||
transport := e.transport
|
rt := transport.DebugWrappers(e.transport)
|
||||||
// TODO consider removing this and reusing client.TransportFor above to get this for free
|
|
||||||
switch {
|
|
||||||
case bool(glog.V(9)):
|
|
||||||
transport = client.NewDebuggingRoundTripper(transport, client.CurlCommand, client.URLTiming, client.ResponseHeaders)
|
|
||||||
case bool(glog.V(8)):
|
|
||||||
transport = client.NewDebuggingRoundTripper(transport, client.JustURL, client.RequestHeaders, client.ResponseStatus, client.ResponseHeaders)
|
|
||||||
case bool(glog.V(7)):
|
|
||||||
transport = client.NewDebuggingRoundTripper(transport, client.JustURL, client.RequestHeaders, client.ResponseStatus)
|
|
||||||
case bool(glog.V(6)):
|
|
||||||
transport = client.NewDebuggingRoundTripper(transport, client.URLTiming)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO the client probably shouldn't be created here, as it doesn't allow
|
// TODO the client probably shouldn't be created here, as it doesn't allow
|
||||||
// flexibility to allow callers to configure it.
|
// flexibility to allow callers to configure it.
|
||||||
client := &http.Client{Transport: transport}
|
client := &http.Client{Transport: rt}
|
||||||
|
|
||||||
req, err := http.NewRequest(e.method, e.url.String(), nil)
|
req, err := http.NewRequest(e.method, e.url.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -18,204 +18,68 @@ package unversioned
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/util"
|
"k8s.io/kubernetes/pkg/client/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
type userAgentRoundTripper struct {
|
|
||||||
agent string
|
|
||||||
rt http.RoundTripper
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
|
|
||||||
return &userAgentRoundTripper{agent, rt}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
if len(req.Header.Get("User-Agent")) != 0 {
|
|
||||||
return rt.rt.RoundTrip(req)
|
|
||||||
}
|
|
||||||
req = cloneRequest(req)
|
|
||||||
req.Header.Set("User-Agent", rt.agent)
|
|
||||||
return rt.rt.RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = util.RoundTripperWrapper(&userAgentRoundTripper{})
|
|
||||||
|
|
||||||
func (rt *userAgentRoundTripper) WrappedRoundTripper() http.RoundTripper {
|
|
||||||
return rt.rt
|
|
||||||
}
|
|
||||||
|
|
||||||
type basicAuthRoundTripper struct {
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
rt http.RoundTripper
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a request unless it has
|
|
||||||
// already been set.
|
|
||||||
func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
|
|
||||||
return &basicAuthRoundTripper{username, password, rt}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
if len(req.Header.Get("Authorization")) != 0 {
|
|
||||||
return rt.rt.RoundTrip(req)
|
|
||||||
}
|
|
||||||
req = cloneRequest(req)
|
|
||||||
req.SetBasicAuth(rt.username, rt.password)
|
|
||||||
return rt.rt.RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = util.RoundTripperWrapper(&basicAuthRoundTripper{})
|
|
||||||
|
|
||||||
func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper {
|
|
||||||
return rt.rt
|
|
||||||
}
|
|
||||||
|
|
||||||
type bearerAuthRoundTripper struct {
|
|
||||||
bearer string
|
|
||||||
rt http.RoundTripper
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBearerAuthRoundTripper adds the provided bearer token to a request unless the authorization
|
|
||||||
// header has already been set.
|
|
||||||
func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
|
|
||||||
return &bearerAuthRoundTripper{bearer, rt}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
if len(req.Header.Get("Authorization")) != 0 {
|
|
||||||
return rt.rt.RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
req = cloneRequest(req)
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.bearer))
|
|
||||||
return rt.rt.RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = util.RoundTripperWrapper(&bearerAuthRoundTripper{})
|
|
||||||
|
|
||||||
func (rt *bearerAuthRoundTripper) WrappedRoundTripper() http.RoundTripper {
|
|
||||||
return rt.rt
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSConfigFor returns a tls.Config that will provide the transport level security defined
|
// TLSConfigFor returns a tls.Config that will provide the transport level security defined
|
||||||
// by the provided Config. Will return nil if no transport level security is requested.
|
// by the provided Config. Will return nil if no transport level security is requested.
|
||||||
func TLSConfigFor(config *Config) (*tls.Config, error) {
|
func TLSConfigFor(config *Config) (*tls.Config, error) {
|
||||||
hasCA := len(config.CAFile) > 0 || len(config.CAData) > 0
|
return transport.TLSConfigFor(config.transportConfig())
|
||||||
hasCert := len(config.CertFile) > 0 || len(config.CertData) > 0
|
|
||||||
|
|
||||||
if !hasCA && !hasCert && !config.Insecure {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if hasCA && config.Insecure {
|
|
||||||
return nil, fmt.Errorf("specifying a root certificates file with the insecure flag is not allowed")
|
|
||||||
}
|
|
||||||
if err := LoadTLSFiles(config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability)
|
|
||||||
MinVersion: tls.VersionTLS10,
|
|
||||||
InsecureSkipVerify: config.Insecure,
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasCA {
|
|
||||||
tlsConfig.RootCAs = rootCertPool(config.CAData)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasCert {
|
|
||||||
cert, err := tls.X509KeyPair(config.CertData, config.KeyData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tlsConfigKey returns a unique key for tls.Config objects returned from TLSConfigFor
|
// TransportFor returns an http.RoundTripper that will provide the authentication
|
||||||
func tlsConfigKey(config *Config) (string, error) {
|
// or transport level security defined by the provided Config. Will return the
|
||||||
// Make sure ca/key/cert content is loaded
|
// default http.DefaultTransport if no special case behavior is needed.
|
||||||
if err := LoadTLSFiles(config); err != nil {
|
func TransportFor(config *Config) (http.RoundTripper, error) {
|
||||||
return "", err
|
return transport.New(config.transportConfig())
|
||||||
}
|
|
||||||
// Only include the things that actually affect the tls.Config
|
|
||||||
return fmt.Sprintf("%v/%x/%x/%x", config.Insecure, config.CAData, config.CertData, config.KeyData), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData,
|
// HTTPWrappersForConfig wraps a round tripper with any relevant layered behavior from the
|
||||||
// KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are
|
// config. Exposed to allow more clients that need HTTP-like behavior but then must hijack
|
||||||
// either populated or were empty to start.
|
// the underlying connection (like WebSocket or HTTP2 clients). Pure HTTP clients should use
|
||||||
func LoadTLSFiles(config *Config) error {
|
// the higher level TransportFor or RESTClientFor methods.
|
||||||
certData, err := dataFromSliceOrFile(config.CertData, config.CertFile)
|
func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
|
||||||
if err != nil {
|
return transport.HTTPWrappersForConfig(config.transportConfig(), rt)
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.CertData = certData
|
|
||||||
keyData, err := dataFromSliceOrFile(config.KeyData, config.KeyFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.KeyData = keyData
|
|
||||||
caData, err := dataFromSliceOrFile(config.CAData, config.CAFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.CAData = caData
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
|
// transportConfig converts a client config to an appropriate transport config.
|
||||||
// or an error if an error occurred reading the file
|
func (c *Config) transportConfig() *transport.Config {
|
||||||
func dataFromSliceOrFile(data []byte, file string) ([]byte, error) {
|
return &transport.Config{
|
||||||
if len(data) > 0 {
|
UserAgent: c.UserAgent,
|
||||||
return data, nil
|
Transport: c.Transport,
|
||||||
|
WrapTransport: c.WrapTransport,
|
||||||
|
TLS: transport.TLSConfig{
|
||||||
|
CAFile: c.CAFile,
|
||||||
|
CAData: c.CAData,
|
||||||
|
CertFile: c.CertFile,
|
||||||
|
CertData: c.CertData,
|
||||||
|
KeyFile: c.KeyFile,
|
||||||
|
KeyData: c.KeyData,
|
||||||
|
Insecure: c.Insecure,
|
||||||
|
},
|
||||||
|
Username: c.Username,
|
||||||
|
Password: c.Password,
|
||||||
|
BearerToken: c.BearerToken,
|
||||||
}
|
}
|
||||||
if len(file) > 0 {
|
|
||||||
fileData, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, err
|
|
||||||
}
|
|
||||||
return fileData, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rootCertPool returns nil if caData is empty. When passed along, this will mean "use system CAs".
|
// transportConfig converts a client config to an appropriate transport config.
|
||||||
// When caData is not empty, it will be the ONLY information used in the CertPool.
|
func (c *KubeletConfig) transportConfig() *transport.Config {
|
||||||
func rootCertPool(caData []byte) *x509.CertPool {
|
cfg := &transport.Config{
|
||||||
// What we really want is a copy of x509.systemRootsPool, but that isn't exposed. It's difficult to build (see the go
|
TLS: transport.TLSConfig{
|
||||||
// code for a look at the platform specific insanity), so we'll use the fact that RootCAs == nil gives us the system values
|
CAFile: c.CAFile,
|
||||||
// It doesn't allow trusting either/or, but hopefully that won't be an issue
|
CAData: c.CAData,
|
||||||
if len(caData) == 0 {
|
CertFile: c.CertFile,
|
||||||
return nil
|
CertData: c.CertData,
|
||||||
|
KeyFile: c.KeyFile,
|
||||||
|
KeyData: c.KeyData,
|
||||||
|
},
|
||||||
|
BearerToken: c.BearerToken,
|
||||||
}
|
}
|
||||||
|
if c.EnableHttps && !cfg.HasCA() {
|
||||||
// if we have caData, use it
|
cfg.TLS.Insecure = true
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
certPool.AppendCertsFromPEM(caData)
|
|
||||||
return certPool
|
|
||||||
}
|
|
||||||
|
|
||||||
// cloneRequest returns a clone of the provided *http.Request.
|
|
||||||
// The clone is a shallow copy of the struct and its Header map.
|
|
||||||
func cloneRequest(r *http.Request) *http.Request {
|
|
||||||
// shallow copy of the struct
|
|
||||||
r2 := new(http.Request)
|
|
||||||
*r2 = *r
|
|
||||||
// deep copy of the Header
|
|
||||||
r2.Header = make(http.Header)
|
|
||||||
for k, s := range r.Header {
|
|
||||||
r2.Header[k] = s
|
|
||||||
}
|
}
|
||||||
return r2
|
return cfg
|
||||||
}
|
}
|
||||||
|
@ -1,166 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package unversioned
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api/testapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testRoundTripper struct {
|
|
||||||
Request *http.Request
|
|
||||||
Response *http.Response
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
rt.Request = req
|
|
||||||
return rt.Response, rt.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBearerAuthRoundTripper(t *testing.T) {
|
|
||||||
rt := &testRoundTripper{}
|
|
||||||
req := &http.Request{}
|
|
||||||
NewBearerAuthRoundTripper("test", rt).RoundTrip(req)
|
|
||||||
if rt.Request == nil {
|
|
||||||
t.Fatalf("unexpected nil request: %v", rt)
|
|
||||||
}
|
|
||||||
if rt.Request == req {
|
|
||||||
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
|
||||||
}
|
|
||||||
if rt.Request.Header.Get("Authorization") != "Bearer test" {
|
|
||||||
t.Errorf("unexpected authorization header: %#v", rt.Request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicAuthRoundTripper(t *testing.T) {
|
|
||||||
rt := &testRoundTripper{}
|
|
||||||
req := &http.Request{}
|
|
||||||
NewBasicAuthRoundTripper("user", "pass", rt).RoundTrip(req)
|
|
||||||
if rt.Request == nil {
|
|
||||||
t.Fatalf("unexpected nil request: %v", rt)
|
|
||||||
}
|
|
||||||
if rt.Request == req {
|
|
||||||
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
|
||||||
}
|
|
||||||
if user, pass, found := rt.Request.BasicAuth(); !found || user != "user" || pass != "pass" {
|
|
||||||
t.Errorf("unexpected authorization header: %#v", rt.Request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserAgentRoundTripper(t *testing.T) {
|
|
||||||
rt := &testRoundTripper{}
|
|
||||||
req := &http.Request{
|
|
||||||
Header: make(http.Header),
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", "other")
|
|
||||||
NewUserAgentRoundTripper("test", rt).RoundTrip(req)
|
|
||||||
if rt.Request == nil {
|
|
||||||
t.Fatalf("unexpected nil request: %v", rt)
|
|
||||||
}
|
|
||||||
if rt.Request != req {
|
|
||||||
t.Fatalf("round tripper should not have copied request object: %#v", rt.Request)
|
|
||||||
}
|
|
||||||
if rt.Request.Header.Get("User-Agent") != "other" {
|
|
||||||
t.Errorf("unexpected user agent header: %#v", rt.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
req = &http.Request{}
|
|
||||||
NewUserAgentRoundTripper("test", rt).RoundTrip(req)
|
|
||||||
if rt.Request == nil {
|
|
||||||
t.Fatalf("unexpected nil request: %v", rt)
|
|
||||||
}
|
|
||||||
if rt.Request == req {
|
|
||||||
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
|
||||||
}
|
|
||||||
if rt.Request.Header.Get("User-Agent") != "test" {
|
|
||||||
t.Errorf("unexpected user agent header: %#v", rt.Request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLSConfigKey(t *testing.T) {
|
|
||||||
// Make sure config fields that don't affect the tls config don't affect the cache key
|
|
||||||
identicalConfigurations := map[string]*Config{
|
|
||||||
"empty": {},
|
|
||||||
"host": {Host: "foo"},
|
|
||||||
"prefix": {Prefix: "foo"},
|
|
||||||
"version": {Version: "foo"},
|
|
||||||
"codec": {Codec: testapi.Default.Codec()},
|
|
||||||
"basic": {Username: "bob", Password: "password"},
|
|
||||||
"bearer": {BearerToken: "token"},
|
|
||||||
"user agent": {UserAgent: "useragent"},
|
|
||||||
"transport": {Transport: http.DefaultTransport},
|
|
||||||
"wrap transport": {WrapTransport: func(http.RoundTripper) http.RoundTripper { return nil }},
|
|
||||||
"qps/burst": {QPS: 1.0, Burst: 10},
|
|
||||||
}
|
|
||||||
for nameA, valueA := range identicalConfigurations {
|
|
||||||
for nameB, valueB := range identicalConfigurations {
|
|
||||||
keyA, err := tlsConfigKey(valueA)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error for %q: %v", nameA, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keyB, err := tlsConfigKey(valueB)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error for %q: %v", nameB, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if keyA != keyB {
|
|
||||||
t.Errorf("Expected identical cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure config fields that affect the tls config affect the cache key
|
|
||||||
uniqueConfigurations := map[string]*Config{
|
|
||||||
"no tls": {},
|
|
||||||
"insecure": {Insecure: true},
|
|
||||||
"cadata 1": {TLSClientConfig: TLSClientConfig{CAData: []byte{1}}},
|
|
||||||
"cadata 2": {TLSClientConfig: TLSClientConfig{CAData: []byte{2}}},
|
|
||||||
"cert 1, key 1": {TLSClientConfig: TLSClientConfig{CertData: []byte{1}, KeyData: []byte{1}}},
|
|
||||||
"cert 1, key 2": {TLSClientConfig: TLSClientConfig{CertData: []byte{1}, KeyData: []byte{2}}},
|
|
||||||
"cert 2, key 1": {TLSClientConfig: TLSClientConfig{CertData: []byte{2}, KeyData: []byte{1}}},
|
|
||||||
"cert 2, key 2": {TLSClientConfig: TLSClientConfig{CertData: []byte{2}, KeyData: []byte{2}}},
|
|
||||||
"cadata 1, cert 1, key 1": {TLSClientConfig: TLSClientConfig{CAData: []byte{1}, CertData: []byte{1}, KeyData: []byte{1}}},
|
|
||||||
}
|
|
||||||
for nameA, valueA := range uniqueConfigurations {
|
|
||||||
for nameB, valueB := range uniqueConfigurations {
|
|
||||||
// Don't compare to ourselves
|
|
||||||
if nameA == nameB {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
keyA, err := tlsConfigKey(valueA)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error for %q: %v", nameA, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keyB, err := tlsConfigKey(valueB)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error for %q: %v", nameB, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if keyA == keyB {
|
|
||||||
t.Errorf("Expected unique cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user