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