k8s.io/client-go@v0.22.2/transport/round_trippers.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package transport
    18  
    19  import (
    20  	"fmt"
    21  	"net/http"
    22  	"strings"
    23  	"time"
    24  
    25  	"golang.org/x/oauth2"
    26  
    27  	utilnet "k8s.io/apimachinery/pkg/util/net"
    28  	"k8s.io/klog/v2"
    29  )
    30  
    31  // HTTPWrappersForConfig wraps a round tripper with any relevant layered
    32  // behavior from the config. Exposed to allow more clients that need HTTP-like
    33  // behavior but then must hijack the underlying connection (like WebSocket or
    34  // HTTP2 clients). Pure HTTP clients should use the RoundTripper returned from
    35  // New.
    36  func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
    37  	if config.WrapTransport != nil {
    38  		rt = config.WrapTransport(rt)
    39  	}
    40  
    41  	rt = DebugWrappers(rt)
    42  
    43  	// Set authentication wrappers
    44  	switch {
    45  	case config.HasBasicAuth() && config.HasTokenAuth():
    46  		return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
    47  	case config.HasTokenAuth():
    48  		var err error
    49  		rt, err = NewBearerAuthWithRefreshRoundTripper(config.BearerToken, config.BearerTokenFile, rt)
    50  		if err != nil {
    51  			return nil, err
    52  		}
    53  	case config.HasBasicAuth():
    54  		rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
    55  	}
    56  	if len(config.UserAgent) > 0 {
    57  		rt = NewUserAgentRoundTripper(config.UserAgent, rt)
    58  	}
    59  	if len(config.Impersonate.UserName) > 0 ||
    60  		len(config.Impersonate.Groups) > 0 ||
    61  		len(config.Impersonate.Extra) > 0 {
    62  		rt = NewImpersonatingRoundTripper(config.Impersonate, rt)
    63  	}
    64  	return rt, nil
    65  }
    66  
    67  // DebugWrappers wraps a round tripper and logs based on the current log level.
    68  func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
    69  	switch {
    70  	case bool(klog.V(9).Enabled()):
    71  		rt = NewDebuggingRoundTripper(rt, DebugCurlCommand, DebugURLTiming, DebugResponseHeaders)
    72  	case bool(klog.V(8).Enabled()):
    73  		rt = NewDebuggingRoundTripper(rt, DebugJustURL, DebugRequestHeaders, DebugResponseStatus, DebugResponseHeaders)
    74  	case bool(klog.V(7).Enabled()):
    75  		rt = NewDebuggingRoundTripper(rt, DebugJustURL, DebugRequestHeaders, DebugResponseStatus)
    76  	case bool(klog.V(6).Enabled()):
    77  		rt = NewDebuggingRoundTripper(rt, DebugURLTiming)
    78  	}
    79  
    80  	return rt
    81  }
    82  
    83  type authProxyRoundTripper struct {
    84  	username string
    85  	groups   []string
    86  	extra    map[string][]string
    87  
    88  	rt http.RoundTripper
    89  }
    90  
    91  // NewAuthProxyRoundTripper provides a roundtripper which will add auth proxy fields to requests for
    92  // authentication terminating proxy cases
    93  // assuming you pull the user from the context:
    94  // username is the user.Info.GetName() of the user
    95  // groups is the user.Info.GetGroups() of the user
    96  // extra is the user.Info.GetExtra() of the user
    97  // extra can contain any additional information that the authenticator
    98  // thought was interesting, for example authorization scopes.
    99  // In order to faithfully round-trip through an impersonation flow, these keys
   100  // MUST be lowercase.
   101  func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {
   102  	return &authProxyRoundTripper{
   103  		username: username,
   104  		groups:   groups,
   105  		extra:    extra,
   106  		rt:       rt,
   107  	}
   108  }
   109  
   110  func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   111  	req = utilnet.CloneRequest(req)
   112  	SetAuthProxyHeaders(req, rt.username, rt.groups, rt.extra)
   113  
   114  	return rt.rt.RoundTrip(req)
   115  }
   116  
   117  // SetAuthProxyHeaders stomps the auth proxy header fields.  It mutates its argument.
   118  func SetAuthProxyHeaders(req *http.Request, username string, groups []string, extra map[string][]string) {
   119  	req.Header.Del("X-Remote-User")
   120  	req.Header.Del("X-Remote-Group")
   121  	for key := range req.Header {
   122  		if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {
   123  			req.Header.Del(key)
   124  		}
   125  	}
   126  
   127  	req.Header.Set("X-Remote-User", username)
   128  	for _, group := range groups {
   129  		req.Header.Add("X-Remote-Group", group)
   130  	}
   131  	for key, values := range extra {
   132  		for _, value := range values {
   133  			req.Header.Add("X-Remote-Extra-"+headerKeyEscape(key), value)
   134  		}
   135  	}
   136  }
   137  
   138  func (rt *authProxyRoundTripper) CancelRequest(req *http.Request) {
   139  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   140  }
   141  
   142  func (rt *authProxyRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
   143  
   144  type userAgentRoundTripper struct {
   145  	agent string
   146  	rt    http.RoundTripper
   147  }
   148  
   149  // NewUserAgentRoundTripper will add User-Agent header to a request unless it has already been set.
   150  func NewUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
   151  	return &userAgentRoundTripper{agent, rt}
   152  }
   153  
   154  func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   155  	if len(req.Header.Get("User-Agent")) != 0 {
   156  		return rt.rt.RoundTrip(req)
   157  	}
   158  	req = utilnet.CloneRequest(req)
   159  	req.Header.Set("User-Agent", rt.agent)
   160  	return rt.rt.RoundTrip(req)
   161  }
   162  
   163  func (rt *userAgentRoundTripper) CancelRequest(req *http.Request) {
   164  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   165  }
   166  
   167  func (rt *userAgentRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
   168  
   169  type basicAuthRoundTripper struct {
   170  	username string
   171  	password string `datapolicy:"password"`
   172  	rt       http.RoundTripper
   173  }
   174  
   175  // NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a
   176  // request unless it has already been set.
   177  func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
   178  	return &basicAuthRoundTripper{username, password, rt}
   179  }
   180  
   181  func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   182  	if len(req.Header.Get("Authorization")) != 0 {
   183  		return rt.rt.RoundTrip(req)
   184  	}
   185  	req = utilnet.CloneRequest(req)
   186  	req.SetBasicAuth(rt.username, rt.password)
   187  	return rt.rt.RoundTrip(req)
   188  }
   189  
   190  func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) {
   191  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   192  }
   193  
   194  func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
   195  
   196  // These correspond to the headers used in pkg/apis/authentication.  We don't want the package dependency,
   197  // but you must not change the values.
   198  const (
   199  	// ImpersonateUserHeader is used to impersonate a particular user during an API server request
   200  	ImpersonateUserHeader = "Impersonate-User"
   201  
   202  	// ImpersonateGroupHeader is used to impersonate a particular group during an API server request.
   203  	// It can be repeated multiplied times for multiple groups.
   204  	ImpersonateGroupHeader = "Impersonate-Group"
   205  
   206  	// ImpersonateUserExtraHeaderPrefix is a prefix for a header used to impersonate an entry in the
   207  	// extra map[string][]string for user.Info.  The key for the `extra` map is suffix.
   208  	// The same key can be repeated multiple times to have multiple elements in the slice under a single key.
   209  	// For instance:
   210  	// Impersonate-Extra-Foo: one
   211  	// Impersonate-Extra-Foo: two
   212  	// results in extra["Foo"] = []string{"one", "two"}
   213  	ImpersonateUserExtraHeaderPrefix = "Impersonate-Extra-"
   214  )
   215  
   216  type impersonatingRoundTripper struct {
   217  	impersonate ImpersonationConfig
   218  	delegate    http.RoundTripper
   219  }
   220  
   221  // NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set.
   222  func NewImpersonatingRoundTripper(impersonate ImpersonationConfig, delegate http.RoundTripper) http.RoundTripper {
   223  	return &impersonatingRoundTripper{impersonate, delegate}
   224  }
   225  
   226  func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   227  	// use the user header as marker for the rest.
   228  	if len(req.Header.Get(ImpersonateUserHeader)) != 0 {
   229  		return rt.delegate.RoundTrip(req)
   230  	}
   231  	req = utilnet.CloneRequest(req)
   232  	req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName)
   233  
   234  	for _, group := range rt.impersonate.Groups {
   235  		req.Header.Add(ImpersonateGroupHeader, group)
   236  	}
   237  	for k, vv := range rt.impersonate.Extra {
   238  		for _, v := range vv {
   239  			req.Header.Add(ImpersonateUserExtraHeaderPrefix+headerKeyEscape(k), v)
   240  		}
   241  	}
   242  
   243  	return rt.delegate.RoundTrip(req)
   244  }
   245  
   246  func (rt *impersonatingRoundTripper) CancelRequest(req *http.Request) {
   247  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   248  }
   249  
   250  func (rt *impersonatingRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.delegate }
   251  
   252  type bearerAuthRoundTripper struct {
   253  	bearer string
   254  	source oauth2.TokenSource
   255  	rt     http.RoundTripper
   256  }
   257  
   258  // NewBearerAuthRoundTripper adds the provided bearer token to a request
   259  // unless the authorization header has already been set.
   260  func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
   261  	return &bearerAuthRoundTripper{bearer, nil, rt}
   262  }
   263  
   264  // NewBearerAuthWithRefreshRoundTripper adds the provided bearer token to a request
   265  // unless the authorization header has already been set.
   266  // If tokenFile is non-empty, it is periodically read,
   267  // and the last successfully read content is used as the bearer token.
   268  // If tokenFile is non-empty and bearer is empty, the tokenFile is read
   269  // immediately to populate the initial bearer token.
   270  func NewBearerAuthWithRefreshRoundTripper(bearer string, tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) {
   271  	if len(tokenFile) == 0 {
   272  		return &bearerAuthRoundTripper{bearer, nil, rt}, nil
   273  	}
   274  	source := NewCachedFileTokenSource(tokenFile)
   275  	if len(bearer) == 0 {
   276  		token, err := source.Token()
   277  		if err != nil {
   278  			return nil, err
   279  		}
   280  		bearer = token.AccessToken
   281  	}
   282  	return &bearerAuthRoundTripper{bearer, source, rt}, nil
   283  }
   284  
   285  func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   286  	if len(req.Header.Get("Authorization")) != 0 {
   287  		return rt.rt.RoundTrip(req)
   288  	}
   289  
   290  	req = utilnet.CloneRequest(req)
   291  	token := rt.bearer
   292  	if rt.source != nil {
   293  		if refreshedToken, err := rt.source.Token(); err == nil {
   294  			token = refreshedToken.AccessToken
   295  		}
   296  	}
   297  	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   298  	return rt.rt.RoundTrip(req)
   299  }
   300  
   301  func (rt *bearerAuthRoundTripper) CancelRequest(req *http.Request) {
   302  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   303  }
   304  
   305  func (rt *bearerAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
   306  
   307  // requestInfo keeps track of information about a request/response combination
   308  type requestInfo struct {
   309  	RequestHeaders http.Header `datapolicy:"token"`
   310  	RequestVerb    string
   311  	RequestURL     string
   312  
   313  	ResponseStatus  string
   314  	ResponseHeaders http.Header
   315  	ResponseErr     error
   316  
   317  	Duration time.Duration
   318  }
   319  
   320  // newRequestInfo creates a new RequestInfo based on an http request
   321  func newRequestInfo(req *http.Request) *requestInfo {
   322  	return &requestInfo{
   323  		RequestURL:     req.URL.String(),
   324  		RequestVerb:    req.Method,
   325  		RequestHeaders: req.Header,
   326  	}
   327  }
   328  
   329  // complete adds information about the response to the requestInfo
   330  func (r *requestInfo) complete(response *http.Response, err error) {
   331  	if err != nil {
   332  		r.ResponseErr = err
   333  		return
   334  	}
   335  	r.ResponseStatus = response.Status
   336  	r.ResponseHeaders = response.Header
   337  }
   338  
   339  // toCurl returns a string that can be run as a command in a terminal (minus the body)
   340  func (r *requestInfo) toCurl() string {
   341  	headers := ""
   342  	for key, values := range r.RequestHeaders {
   343  		for _, value := range values {
   344  			value = maskValue(key, value)
   345  			headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, value))
   346  		}
   347  	}
   348  
   349  	return fmt.Sprintf("curl -v -X%s %s '%s'", r.RequestVerb, headers, r.RequestURL)
   350  }
   351  
   352  // debuggingRoundTripper will display information about the requests passing
   353  // through it based on what is configured
   354  type debuggingRoundTripper struct {
   355  	delegatedRoundTripper http.RoundTripper
   356  	levels                map[DebugLevel]bool
   357  }
   358  
   359  // DebugLevel is used to enable debugging of certain
   360  // HTTP requests and responses fields via the debuggingRoundTripper.
   361  type DebugLevel int
   362  
   363  const (
   364  	// DebugJustURL will add to the debug output HTTP requests method and url.
   365  	DebugJustURL DebugLevel = iota
   366  	// DebugURLTiming will add to the debug output the duration of HTTP requests.
   367  	DebugURLTiming
   368  	// DebugCurlCommand will add to the debug output the curl command equivalent to the
   369  	// HTTP request.
   370  	DebugCurlCommand
   371  	// DebugRequestHeaders will add to the debug output the HTTP requests headers.
   372  	DebugRequestHeaders
   373  	// DebugResponseStatus will add to the debug output the HTTP response status.
   374  	DebugResponseStatus
   375  	// DebugResponseHeaders will add to the debug output the HTTP response headers.
   376  	DebugResponseHeaders
   377  )
   378  
   379  // NewDebuggingRoundTripper allows to display in the logs output debug information
   380  // on the API requests performed by the client.
   381  func NewDebuggingRoundTripper(rt http.RoundTripper, levels ...DebugLevel) http.RoundTripper {
   382  	drt := &debuggingRoundTripper{
   383  		delegatedRoundTripper: rt,
   384  		levels:                make(map[DebugLevel]bool, len(levels)),
   385  	}
   386  	for _, v := range levels {
   387  		drt.levels[v] = true
   388  	}
   389  	return drt
   390  }
   391  
   392  func (rt *debuggingRoundTripper) CancelRequest(req *http.Request) {
   393  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   394  }
   395  
   396  var knownAuthTypes = map[string]bool{
   397  	"bearer":    true,
   398  	"basic":     true,
   399  	"negotiate": true,
   400  }
   401  
   402  // maskValue masks credential content from authorization headers
   403  // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
   404  func maskValue(key string, value string) string {
   405  	if !strings.EqualFold(key, "Authorization") {
   406  		return value
   407  	}
   408  	if len(value) == 0 {
   409  		return ""
   410  	}
   411  	var authType string
   412  	if i := strings.Index(value, " "); i > 0 {
   413  		authType = value[0:i]
   414  	} else {
   415  		authType = value
   416  	}
   417  	if !knownAuthTypes[strings.ToLower(authType)] {
   418  		return "<masked>"
   419  	}
   420  	if len(value) > len(authType)+1 {
   421  		value = authType + " <masked>"
   422  	} else {
   423  		value = authType
   424  	}
   425  	return value
   426  }
   427  
   428  func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   429  	reqInfo := newRequestInfo(req)
   430  
   431  	if rt.levels[DebugJustURL] {
   432  		klog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL)
   433  	}
   434  	if rt.levels[DebugCurlCommand] {
   435  		klog.Infof("%s", reqInfo.toCurl())
   436  	}
   437  	if rt.levels[DebugRequestHeaders] {
   438  		klog.Info("Request Headers:")
   439  		for key, values := range reqInfo.RequestHeaders {
   440  			for _, value := range values {
   441  				value = maskValue(key, value)
   442  				klog.Infof("    %s: %s", key, value)
   443  			}
   444  		}
   445  	}
   446  
   447  	startTime := time.Now()
   448  	response, err := rt.delegatedRoundTripper.RoundTrip(req)
   449  	reqInfo.Duration = time.Since(startTime)
   450  
   451  	reqInfo.complete(response, err)
   452  
   453  	if rt.levels[DebugURLTiming] {
   454  		klog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
   455  	}
   456  	if rt.levels[DebugResponseStatus] {
   457  		klog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
   458  	}
   459  	if rt.levels[DebugResponseHeaders] {
   460  		klog.Info("Response Headers:")
   461  		for key, values := range reqInfo.ResponseHeaders {
   462  			for _, value := range values {
   463  				klog.Infof("    %s: %s", key, value)
   464  			}
   465  		}
   466  	}
   467  
   468  	return response, err
   469  }
   470  
   471  func (rt *debuggingRoundTripper) WrappedRoundTripper() http.RoundTripper {
   472  	return rt.delegatedRoundTripper
   473  }
   474  
   475  func legalHeaderByte(b byte) bool {
   476  	return int(b) < len(legalHeaderKeyBytes) && legalHeaderKeyBytes[b]
   477  }
   478  
   479  func shouldEscape(b byte) bool {
   480  	// url.PathUnescape() returns an error if any '%' is not followed by two
   481  	// hexadecimal digits, so we'll intentionally encode it.
   482  	return !legalHeaderByte(b) || b == '%'
   483  }
   484  
   485  func headerKeyEscape(key string) string {
   486  	buf := strings.Builder{}
   487  	for i := 0; i < len(key); i++ {
   488  		b := key[i]
   489  		if shouldEscape(b) {
   490  			// %-encode bytes that should be escaped:
   491  			// https://tools.ietf.org/html/rfc3986#section-2.1
   492  			fmt.Fprintf(&buf, "%%%02X", b)
   493  			continue
   494  		}
   495  		buf.WriteByte(b)
   496  	}
   497  	return buf.String()
   498  }
   499  
   500  // legalHeaderKeyBytes was copied from net/http/lex.go's isTokenTable.
   501  // See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
   502  var legalHeaderKeyBytes = [127]bool{
   503  	'%':  true,
   504  	'!':  true,
   505  	'#':  true,
   506  	'$':  true,
   507  	'&':  true,
   508  	'\'': true,
   509  	'*':  true,
   510  	'+':  true,
   511  	'-':  true,
   512  	'.':  true,
   513  	'0':  true,
   514  	'1':  true,
   515  	'2':  true,
   516  	'3':  true,
   517  	'4':  true,
   518  	'5':  true,
   519  	'6':  true,
   520  	'7':  true,
   521  	'8':  true,
   522  	'9':  true,
   523  	'A':  true,
   524  	'B':  true,
   525  	'C':  true,
   526  	'D':  true,
   527  	'E':  true,
   528  	'F':  true,
   529  	'G':  true,
   530  	'H':  true,
   531  	'I':  true,
   532  	'J':  true,
   533  	'K':  true,
   534  	'L':  true,
   535  	'M':  true,
   536  	'N':  true,
   537  	'O':  true,
   538  	'P':  true,
   539  	'Q':  true,
   540  	'R':  true,
   541  	'S':  true,
   542  	'T':  true,
   543  	'U':  true,
   544  	'W':  true,
   545  	'V':  true,
   546  	'X':  true,
   547  	'Y':  true,
   548  	'Z':  true,
   549  	'^':  true,
   550  	'_':  true,
   551  	'`':  true,
   552  	'a':  true,
   553  	'b':  true,
   554  	'c':  true,
   555  	'd':  true,
   556  	'e':  true,
   557  	'f':  true,
   558  	'g':  true,
   559  	'h':  true,
   560  	'i':  true,
   561  	'j':  true,
   562  	'k':  true,
   563  	'l':  true,
   564  	'm':  true,
   565  	'n':  true,
   566  	'o':  true,
   567  	'p':  true,
   568  	'q':  true,
   569  	'r':  true,
   570  	's':  true,
   571  	't':  true,
   572  	'u':  true,
   573  	'v':  true,
   574  	'w':  true,
   575  	'x':  true,
   576  	'y':  true,
   577  	'z':  true,
   578  	'|':  true,
   579  	'~':  true,
   580  }