1
16
17 package transport
18
19 import (
20 "crypto/tls"
21 "fmt"
22 "net/http"
23 "net/http/httptrace"
24 "strings"
25 "sync"
26 "time"
27
28 "golang.org/x/oauth2"
29
30 utilnet "k8s.io/apimachinery/pkg/util/net"
31 "k8s.io/klog/v2"
32 )
33
34
35
36
37
38
39 func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
40 if config.WrapTransport != nil {
41 rt = config.WrapTransport(rt)
42 }
43
44 rt = DebugWrappers(rt)
45
46
47 switch {
48 case config.HasBasicAuth() && config.HasTokenAuth():
49 return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
50 case config.HasTokenAuth():
51 var err error
52 rt, err = NewBearerAuthWithRefreshRoundTripper(config.BearerToken, config.BearerTokenFile, rt)
53 if err != nil {
54 return nil, err
55 }
56 case config.HasBasicAuth():
57 rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
58 }
59 if len(config.UserAgent) > 0 {
60 rt = NewUserAgentRoundTripper(config.UserAgent, rt)
61 }
62 if len(config.Impersonate.UserName) > 0 ||
63 len(config.Impersonate.UID) > 0 ||
64 len(config.Impersonate.Groups) > 0 ||
65 len(config.Impersonate.Extra) > 0 {
66 rt = NewImpersonatingRoundTripper(config.Impersonate, rt)
67 }
68 return rt, nil
69 }
70
71
72 func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
73 switch {
74 case bool(klog.V(9).Enabled()):
75 rt = NewDebuggingRoundTripper(rt, DebugCurlCommand, DebugURLTiming, DebugDetailedTiming, DebugResponseHeaders)
76 case bool(klog.V(8).Enabled()):
77 rt = NewDebuggingRoundTripper(rt, DebugJustURL, DebugRequestHeaders, DebugResponseStatus, DebugResponseHeaders)
78 case bool(klog.V(7).Enabled()):
79 rt = NewDebuggingRoundTripper(rt, DebugJustURL, DebugRequestHeaders, DebugResponseStatus)
80 case bool(klog.V(6).Enabled()):
81 rt = NewDebuggingRoundTripper(rt, DebugURLTiming)
82 }
83
84 return rt
85 }
86
87 type authProxyRoundTripper struct {
88 username string
89 groups []string
90 extra map[string][]string
91
92 rt http.RoundTripper
93 }
94
95 var _ utilnet.RoundTripperWrapper = &authProxyRoundTripper{}
96
97
98
99
100
101
102
103
104
105
106
107 func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {
108 return &authProxyRoundTripper{
109 username: username,
110 groups: groups,
111 extra: extra,
112 rt: rt,
113 }
114 }
115
116 func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
117 req = utilnet.CloneRequest(req)
118 SetAuthProxyHeaders(req, rt.username, rt.groups, rt.extra)
119
120 return rt.rt.RoundTrip(req)
121 }
122
123
124 func SetAuthProxyHeaders(req *http.Request, username string, groups []string, extra map[string][]string) {
125 req.Header.Del("X-Remote-User")
126 req.Header.Del("X-Remote-Group")
127 for key := range req.Header {
128 if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {
129 req.Header.Del(key)
130 }
131 }
132
133 req.Header.Set("X-Remote-User", username)
134 for _, group := range groups {
135 req.Header.Add("X-Remote-Group", group)
136 }
137 for key, values := range extra {
138 for _, value := range values {
139 req.Header.Add("X-Remote-Extra-"+headerKeyEscape(key), value)
140 }
141 }
142 }
143
144 func (rt *authProxyRoundTripper) CancelRequest(req *http.Request) {
145 tryCancelRequest(rt.WrappedRoundTripper(), req)
146 }
147
148 func (rt *authProxyRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
149
150 type userAgentRoundTripper struct {
151 agent string
152 rt http.RoundTripper
153 }
154
155 var _ utilnet.RoundTripperWrapper = &userAgentRoundTripper{}
156
157
158 func NewUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
159 return &userAgentRoundTripper{agent, rt}
160 }
161
162 func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
163 if len(req.Header.Get("User-Agent")) != 0 {
164 return rt.rt.RoundTrip(req)
165 }
166 req = utilnet.CloneRequest(req)
167 req.Header.Set("User-Agent", rt.agent)
168 return rt.rt.RoundTrip(req)
169 }
170
171 func (rt *userAgentRoundTripper) CancelRequest(req *http.Request) {
172 tryCancelRequest(rt.WrappedRoundTripper(), req)
173 }
174
175 func (rt *userAgentRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
176
177 type basicAuthRoundTripper struct {
178 username string
179 password string `datapolicy:"password"`
180 rt http.RoundTripper
181 }
182
183 var _ utilnet.RoundTripperWrapper = &basicAuthRoundTripper{}
184
185
186
187 func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
188 return &basicAuthRoundTripper{username, password, rt}
189 }
190
191 func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
192 if len(req.Header.Get("Authorization")) != 0 {
193 return rt.rt.RoundTrip(req)
194 }
195 req = utilnet.CloneRequest(req)
196 req.SetBasicAuth(rt.username, rt.password)
197 return rt.rt.RoundTrip(req)
198 }
199
200 func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) {
201 tryCancelRequest(rt.WrappedRoundTripper(), req)
202 }
203
204 func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
205
206
207
208 const (
209
210 ImpersonateUserHeader = "Impersonate-User"
211
212
213 ImpersonateUIDHeader = "Impersonate-Uid"
214
215
216
217 ImpersonateGroupHeader = "Impersonate-Group"
218
219
220
221
222
223
224
225
226 ImpersonateUserExtraHeaderPrefix = "Impersonate-Extra-"
227 )
228
229 type impersonatingRoundTripper struct {
230 impersonate ImpersonationConfig
231 delegate http.RoundTripper
232 }
233
234 var _ utilnet.RoundTripperWrapper = &impersonatingRoundTripper{}
235
236
237 func NewImpersonatingRoundTripper(impersonate ImpersonationConfig, delegate http.RoundTripper) http.RoundTripper {
238 return &impersonatingRoundTripper{impersonate, delegate}
239 }
240
241 func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
242
243 if len(req.Header.Get(ImpersonateUserHeader)) != 0 {
244 return rt.delegate.RoundTrip(req)
245 }
246 req = utilnet.CloneRequest(req)
247 req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName)
248 if rt.impersonate.UID != "" {
249 req.Header.Set(ImpersonateUIDHeader, rt.impersonate.UID)
250 }
251 for _, group := range rt.impersonate.Groups {
252 req.Header.Add(ImpersonateGroupHeader, group)
253 }
254 for k, vv := range rt.impersonate.Extra {
255 for _, v := range vv {
256 req.Header.Add(ImpersonateUserExtraHeaderPrefix+headerKeyEscape(k), v)
257 }
258 }
259
260 return rt.delegate.RoundTrip(req)
261 }
262
263 func (rt *impersonatingRoundTripper) CancelRequest(req *http.Request) {
264 tryCancelRequest(rt.WrappedRoundTripper(), req)
265 }
266
267 func (rt *impersonatingRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.delegate }
268
269 type bearerAuthRoundTripper struct {
270 bearer string
271 source oauth2.TokenSource
272 rt http.RoundTripper
273 }
274
275 var _ utilnet.RoundTripperWrapper = &bearerAuthRoundTripper{}
276
277
278
279 func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
280 return &bearerAuthRoundTripper{bearer, nil, rt}
281 }
282
283
284
285
286
287
288
289 func NewBearerAuthWithRefreshRoundTripper(bearer string, tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) {
290 if len(tokenFile) == 0 {
291 return &bearerAuthRoundTripper{bearer, nil, rt}, nil
292 }
293 source := NewCachedFileTokenSource(tokenFile)
294 if len(bearer) == 0 {
295 token, err := source.Token()
296 if err != nil {
297 return nil, err
298 }
299 bearer = token.AccessToken
300 }
301 return &bearerAuthRoundTripper{bearer, source, rt}, nil
302 }
303
304 func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
305 if len(req.Header.Get("Authorization")) != 0 {
306 return rt.rt.RoundTrip(req)
307 }
308
309 req = utilnet.CloneRequest(req)
310 token := rt.bearer
311 if rt.source != nil {
312 if refreshedToken, err := rt.source.Token(); err == nil {
313 token = refreshedToken.AccessToken
314 }
315 }
316 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
317 return rt.rt.RoundTrip(req)
318 }
319
320 func (rt *bearerAuthRoundTripper) CancelRequest(req *http.Request) {
321 tryCancelRequest(rt.WrappedRoundTripper(), req)
322 }
323
324 func (rt *bearerAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
325
326
327 type requestInfo struct {
328 RequestHeaders http.Header `datapolicy:"token"`
329 RequestVerb string
330 RequestURL string
331
332 ResponseStatus string
333 ResponseHeaders http.Header
334 ResponseErr error
335
336 muTrace sync.Mutex
337 DNSLookup time.Duration
338 Dialing time.Duration
339 GetConnection time.Duration
340 TLSHandshake time.Duration
341 ServerProcessing time.Duration
342 ConnectionReused bool
343
344 Duration time.Duration
345 }
346
347
348 func newRequestInfo(req *http.Request) *requestInfo {
349 return &requestInfo{
350 RequestURL: req.URL.String(),
351 RequestVerb: req.Method,
352 RequestHeaders: req.Header,
353 }
354 }
355
356
357 func (r *requestInfo) complete(response *http.Response, err error) {
358 if err != nil {
359 r.ResponseErr = err
360 return
361 }
362 r.ResponseStatus = response.Status
363 r.ResponseHeaders = response.Header
364 }
365
366
367 func (r *requestInfo) toCurl() string {
368 headers := ""
369 for key, values := range r.RequestHeaders {
370 for _, value := range values {
371 value = maskValue(key, value)
372 headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, value))
373 }
374 }
375
376 return fmt.Sprintf("curl -v -X%s %s '%s'", r.RequestVerb, headers, r.RequestURL)
377 }
378
379
380
381 type debuggingRoundTripper struct {
382 delegatedRoundTripper http.RoundTripper
383 levels map[DebugLevel]bool
384 }
385
386 var _ utilnet.RoundTripperWrapper = &debuggingRoundTripper{}
387
388
389
390 type DebugLevel int
391
392 const (
393
394 DebugJustURL DebugLevel = iota
395
396 DebugURLTiming
397
398
399 DebugCurlCommand
400
401 DebugRequestHeaders
402
403 DebugResponseStatus
404
405 DebugResponseHeaders
406
407 DebugDetailedTiming
408 )
409
410
411
412 func NewDebuggingRoundTripper(rt http.RoundTripper, levels ...DebugLevel) http.RoundTripper {
413 drt := &debuggingRoundTripper{
414 delegatedRoundTripper: rt,
415 levels: make(map[DebugLevel]bool, len(levels)),
416 }
417 for _, v := range levels {
418 drt.levels[v] = true
419 }
420 return drt
421 }
422
423 func (rt *debuggingRoundTripper) CancelRequest(req *http.Request) {
424 tryCancelRequest(rt.WrappedRoundTripper(), req)
425 }
426
427 var knownAuthTypes = map[string]bool{
428 "bearer": true,
429 "basic": true,
430 "negotiate": true,
431 }
432
433
434
435 func maskValue(key string, value string) string {
436 if !strings.EqualFold(key, "Authorization") {
437 return value
438 }
439 if len(value) == 0 {
440 return ""
441 }
442 var authType string
443 if i := strings.Index(value, " "); i > 0 {
444 authType = value[0:i]
445 } else {
446 authType = value
447 }
448 if !knownAuthTypes[strings.ToLower(authType)] {
449 return "<masked>"
450 }
451 if len(value) > len(authType)+1 {
452 value = authType + " <masked>"
453 } else {
454 value = authType
455 }
456 return value
457 }
458
459 func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
460 reqInfo := newRequestInfo(req)
461
462 if rt.levels[DebugJustURL] {
463 klog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL)
464 }
465 if rt.levels[DebugCurlCommand] {
466 klog.Infof("%s", reqInfo.toCurl())
467 }
468 if rt.levels[DebugRequestHeaders] {
469 klog.Info("Request Headers:")
470 for key, values := range reqInfo.RequestHeaders {
471 for _, value := range values {
472 value = maskValue(key, value)
473 klog.Infof(" %s: %s", key, value)
474 }
475 }
476 }
477
478 startTime := time.Now()
479
480 if rt.levels[DebugDetailedTiming] {
481 var getConn, dnsStart, dialStart, tlsStart, serverStart time.Time
482 var host string
483 trace := &httptrace.ClientTrace{
484
485 DNSStart: func(info httptrace.DNSStartInfo) {
486 reqInfo.muTrace.Lock()
487 defer reqInfo.muTrace.Unlock()
488 dnsStart = time.Now()
489 host = info.Host
490 },
491 DNSDone: func(info httptrace.DNSDoneInfo) {
492 reqInfo.muTrace.Lock()
493 defer reqInfo.muTrace.Unlock()
494 reqInfo.DNSLookup = time.Since(dnsStart)
495 klog.Infof("HTTP Trace: DNS Lookup for %s resolved to %v", host, info.Addrs)
496 },
497
498 ConnectStart: func(network, addr string) {
499 reqInfo.muTrace.Lock()
500 defer reqInfo.muTrace.Unlock()
501 dialStart = time.Now()
502 },
503 ConnectDone: func(network, addr string, err error) {
504 reqInfo.muTrace.Lock()
505 defer reqInfo.muTrace.Unlock()
506 reqInfo.Dialing = time.Since(dialStart)
507 if err != nil {
508 klog.Infof("HTTP Trace: Dial to %s:%s failed: %v", network, addr, err)
509 } else {
510 klog.Infof("HTTP Trace: Dial to %s:%s succeed", network, addr)
511 }
512 },
513
514 TLSHandshakeStart: func() {
515 tlsStart = time.Now()
516 },
517 TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
518 reqInfo.muTrace.Lock()
519 defer reqInfo.muTrace.Unlock()
520 reqInfo.TLSHandshake = time.Since(tlsStart)
521 },
522
523 GetConn: func(hostPort string) {
524 getConn = time.Now()
525 },
526 GotConn: func(info httptrace.GotConnInfo) {
527 reqInfo.muTrace.Lock()
528 defer reqInfo.muTrace.Unlock()
529 reqInfo.GetConnection = time.Since(getConn)
530 reqInfo.ConnectionReused = info.Reused
531 },
532
533 WroteRequest: func(info httptrace.WroteRequestInfo) {
534 reqInfo.muTrace.Lock()
535 defer reqInfo.muTrace.Unlock()
536 serverStart = time.Now()
537 },
538 GotFirstResponseByte: func() {
539 reqInfo.muTrace.Lock()
540 defer reqInfo.muTrace.Unlock()
541 reqInfo.ServerProcessing = time.Since(serverStart)
542 },
543 }
544 req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
545 }
546
547 response, err := rt.delegatedRoundTripper.RoundTrip(req)
548 reqInfo.Duration = time.Since(startTime)
549
550 reqInfo.complete(response, err)
551
552 if rt.levels[DebugURLTiming] {
553 klog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
554 }
555 if rt.levels[DebugDetailedTiming] {
556 stats := ""
557 if !reqInfo.ConnectionReused {
558 stats += fmt.Sprintf(`DNSLookup %d ms Dial %d ms TLSHandshake %d ms`,
559 reqInfo.DNSLookup.Nanoseconds()/int64(time.Millisecond),
560 reqInfo.Dialing.Nanoseconds()/int64(time.Millisecond),
561 reqInfo.TLSHandshake.Nanoseconds()/int64(time.Millisecond),
562 )
563 } else {
564 stats += fmt.Sprintf(`GetConnection %d ms`, reqInfo.GetConnection.Nanoseconds()/int64(time.Millisecond))
565 }
566 if reqInfo.ServerProcessing != 0 {
567 stats += fmt.Sprintf(` ServerProcessing %d ms`, reqInfo.ServerProcessing.Nanoseconds()/int64(time.Millisecond))
568 }
569 stats += fmt.Sprintf(` Duration %d ms`, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
570 klog.Infof("HTTP Statistics: %s", stats)
571 }
572
573 if rt.levels[DebugResponseStatus] {
574 klog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
575 }
576 if rt.levels[DebugResponseHeaders] {
577 klog.Info("Response Headers:")
578 for key, values := range reqInfo.ResponseHeaders {
579 for _, value := range values {
580 klog.Infof(" %s: %s", key, value)
581 }
582 }
583 }
584
585 return response, err
586 }
587
588 func (rt *debuggingRoundTripper) WrappedRoundTripper() http.RoundTripper {
589 return rt.delegatedRoundTripper
590 }
591
592 func legalHeaderByte(b byte) bool {
593 return int(b) < len(legalHeaderKeyBytes) && legalHeaderKeyBytes[b]
594 }
595
596 func shouldEscape(b byte) bool {
597
598
599 return !legalHeaderByte(b) || b == '%'
600 }
601
602 func headerKeyEscape(key string) string {
603 buf := strings.Builder{}
604 for i := 0; i < len(key); i++ {
605 b := key[i]
606 if shouldEscape(b) {
607
608
609 fmt.Fprintf(&buf, "%%%02X", b)
610 continue
611 }
612 buf.WriteByte(b)
613 }
614 return buf.String()
615 }
616
617
618
619 var legalHeaderKeyBytes = [127]bool{
620 '%': true,
621 '!': true,
622 '#': true,
623 '$': true,
624 '&': true,
625 '\'': true,
626 '*': true,
627 '+': true,
628 '-': true,
629 '.': true,
630 '0': true,
631 '1': true,
632 '2': true,
633 '3': true,
634 '4': true,
635 '5': true,
636 '6': true,
637 '7': true,
638 '8': true,
639 '9': true,
640 'A': true,
641 'B': true,
642 'C': true,
643 'D': true,
644 'E': true,
645 'F': true,
646 'G': true,
647 'H': true,
648 'I': true,
649 'J': true,
650 'K': true,
651 'L': true,
652 'M': true,
653 'N': true,
654 'O': true,
655 'P': true,
656 'Q': true,
657 'R': true,
658 'S': true,
659 'T': true,
660 'U': true,
661 'W': true,
662 'V': true,
663 'X': true,
664 'Y': true,
665 'Z': true,
666 '^': true,
667 '_': true,
668 '`': true,
669 'a': true,
670 'b': true,
671 'c': true,
672 'd': true,
673 'e': true,
674 'f': true,
675 'g': true,
676 'h': true,
677 'i': true,
678 'j': true,
679 'k': true,
680 'l': true,
681 'm': true,
682 'n': true,
683 'o': true,
684 'p': true,
685 'q': true,
686 'r': true,
687 's': true,
688 't': true,
689 'u': true,
690 'v': true,
691 'w': true,
692 'x': true,
693 'y': true,
694 'z': true,
695 '|': true,
696 '~': true,
697 }
698
View as plain text