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