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