2 Copyright 2015 The Kubernetes Authors.
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
28 utilnet "k8s.io/apimachinery/pkg/util/net"
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
36 func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
37 if config.WrapTransport != nil {
38 rt = config.WrapTransport(rt)
41 rt = DebugWrappers(rt)
43 // Set authentication wrappers
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():
49 rt, err = NewBearerAuthWithRefreshRoundTripper(config.BearerToken, config.BearerTokenFile, rt)
53 case config.HasBasicAuth():
54 rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
56 if len(config.UserAgent) > 0 {
57 rt = NewUserAgentRoundTripper(config.UserAgent, rt)
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)
67 // DebugWrappers wraps a round tripper and logs based on the current log level.
68 func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
71 rt = newDebuggingRoundTripper(rt, debugCurlCommand, debugURLTiming, debugResponseHeaders)
73 rt = newDebuggingRoundTripper(rt, debugJustURL, debugRequestHeaders, debugResponseStatus, debugResponseHeaders)
75 rt = newDebuggingRoundTripper(rt, debugJustURL, debugRequestHeaders, debugResponseStatus)
77 rt = newDebuggingRoundTripper(rt, debugURLTiming)
83 type requestCanceler interface {
84 CancelRequest(*http.Request)
87 type authProxyRoundTripper struct {
90 extra map[string][]string
95 // NewAuthProxyRoundTripper provides a roundtripper which will add auth proxy fields to requests for
96 // authentication terminating proxy cases
97 // assuming you pull the user from the context:
98 // username is the user.Info.GetName() of the user
99 // groups is the user.Info.GetGroups() of the user
100 // extra is the user.Info.GetExtra() of the user
101 // extra can contain any additional information that the authenticator
102 // thought was interesting, for example authorization scopes.
103 // In order to faithfully round-trip through an impersonation flow, these keys
104 // MUST be lowercase.
105 func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {
106 return &authProxyRoundTripper{
114 func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
115 req = utilnet.CloneRequest(req)
116 SetAuthProxyHeaders(req, rt.username, rt.groups, rt.extra)
118 return rt.rt.RoundTrip(req)
121 // SetAuthProxyHeaders stomps the auth proxy header fields. It mutates its argument.
122 func SetAuthProxyHeaders(req *http.Request, username string, groups []string, extra map[string][]string) {
123 req.Header.Del("X-Remote-User")
124 req.Header.Del("X-Remote-Group")
125 for key := range req.Header {
126 if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {
131 req.Header.Set("X-Remote-User", username)
132 for _, group := range groups {
133 req.Header.Add("X-Remote-Group", group)
135 for key, values := range extra {
136 for _, value := range values {
137 req.Header.Add("X-Remote-Extra-"+headerKeyEscape(key), value)
142 func (rt *authProxyRoundTripper) CancelRequest(req *http.Request) {
143 if canceler, ok := rt.rt.(requestCanceler); ok {
144 canceler.CancelRequest(req)
146 klog.Errorf("CancelRequest not implemented by %T", rt.rt)
150 func (rt *authProxyRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
152 type userAgentRoundTripper struct {
157 func NewUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
158 return &userAgentRoundTripper{agent, rt}
161 func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
162 if len(req.Header.Get("User-Agent")) != 0 {
163 return rt.rt.RoundTrip(req)
165 req = utilnet.CloneRequest(req)
166 req.Header.Set("User-Agent", rt.agent)
167 return rt.rt.RoundTrip(req)
170 func (rt *userAgentRoundTripper) CancelRequest(req *http.Request) {
171 if canceler, ok := rt.rt.(requestCanceler); ok {
172 canceler.CancelRequest(req)
174 klog.Errorf("CancelRequest not implemented by %T", rt.rt)
178 func (rt *userAgentRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
180 type basicAuthRoundTripper struct {
186 // NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a
187 // request unless it has already been set.
188 func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
189 return &basicAuthRoundTripper{username, password, rt}
192 func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
193 if len(req.Header.Get("Authorization")) != 0 {
194 return rt.rt.RoundTrip(req)
196 req = utilnet.CloneRequest(req)
197 req.SetBasicAuth(rt.username, rt.password)
198 return rt.rt.RoundTrip(req)
201 func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) {
202 if canceler, ok := rt.rt.(requestCanceler); ok {
203 canceler.CancelRequest(req)
205 klog.Errorf("CancelRequest not implemented by %T", rt.rt)
209 func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
211 // These correspond to the headers used in pkg/apis/authentication. We don't want the package dependency,
212 // but you must not change the values.
214 // ImpersonateUserHeader is used to impersonate a particular user during an API server request
215 ImpersonateUserHeader = "Impersonate-User"
217 // ImpersonateGroupHeader is used to impersonate a particular group during an API server request.
218 // It can be repeated multiplied times for multiple groups.
219 ImpersonateGroupHeader = "Impersonate-Group"
221 // ImpersonateUserExtraHeaderPrefix is a prefix for a header used to impersonate an entry in the
222 // extra map[string][]string for user.Info. The key for the `extra` map is suffix.
223 // The same key can be repeated multiple times to have multiple elements in the slice under a single key.
225 // Impersonate-Extra-Foo: one
226 // Impersonate-Extra-Foo: two
227 // results in extra["Foo"] = []string{"one", "two"}
228 ImpersonateUserExtraHeaderPrefix = "Impersonate-Extra-"
231 type impersonatingRoundTripper struct {
232 impersonate ImpersonationConfig
233 delegate http.RoundTripper
236 // NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set.
237 func NewImpersonatingRoundTripper(impersonate ImpersonationConfig, delegate http.RoundTripper) http.RoundTripper {
238 return &impersonatingRoundTripper{impersonate, delegate}
241 func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
242 // use the user header as marker for the rest.
243 if len(req.Header.Get(ImpersonateUserHeader)) != 0 {
244 return rt.delegate.RoundTrip(req)
246 req = utilnet.CloneRequest(req)
247 req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName)
249 for _, group := range rt.impersonate.Groups {
250 req.Header.Add(ImpersonateGroupHeader, group)
252 for k, vv := range rt.impersonate.Extra {
253 for _, v := range vv {
254 req.Header.Add(ImpersonateUserExtraHeaderPrefix+headerKeyEscape(k), v)
258 return rt.delegate.RoundTrip(req)
261 func (rt *impersonatingRoundTripper) CancelRequest(req *http.Request) {
262 if canceler, ok := rt.delegate.(requestCanceler); ok {
263 canceler.CancelRequest(req)
265 klog.Errorf("CancelRequest not implemented by %T", rt.delegate)
269 func (rt *impersonatingRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.delegate }
271 type bearerAuthRoundTripper struct {
273 source oauth2.TokenSource
277 // NewBearerAuthRoundTripper adds the provided bearer token to a request
278 // unless the authorization header has already been set.
279 func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
280 return &bearerAuthRoundTripper{bearer, nil, rt}
283 // NewBearerAuthRoundTripper adds the provided bearer token to a request
284 // unless the authorization header has already been set.
285 // If tokenFile is non-empty, it is periodically read,
286 // and the last successfully read content is used as the bearer token.
287 // If tokenFile is non-empty and bearer is empty, the tokenFile is read
288 // immediately to populate the initial bearer token.
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
293 source := NewCachedFileTokenSource(tokenFile)
294 if len(bearer) == 0 {
295 token, err := source.Token()
299 bearer = token.AccessToken
301 return &bearerAuthRoundTripper{bearer, source, rt}, nil
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)
309 req = utilnet.CloneRequest(req)
311 if rt.source != nil {
312 if refreshedToken, err := rt.source.Token(); err == nil {
313 token = refreshedToken.AccessToken
316 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
317 return rt.rt.RoundTrip(req)
320 func (rt *bearerAuthRoundTripper) CancelRequest(req *http.Request) {
321 if canceler, ok := rt.rt.(requestCanceler); ok {
322 canceler.CancelRequest(req)
324 klog.Errorf("CancelRequest not implemented by %T", rt.rt)
328 func (rt *bearerAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
330 // requestInfo keeps track of information about a request/response combination
331 type requestInfo struct {
332 RequestHeaders http.Header
336 ResponseStatus string
337 ResponseHeaders http.Header
340 Duration time.Duration
343 // newRequestInfo creates a new RequestInfo based on an http request
344 func newRequestInfo(req *http.Request) *requestInfo {
346 RequestURL: req.URL.String(),
347 RequestVerb: req.Method,
348 RequestHeaders: req.Header,
352 // complete adds information about the response to the requestInfo
353 func (r *requestInfo) complete(response *http.Response, err error) {
358 r.ResponseStatus = response.Status
359 r.ResponseHeaders = response.Header
362 // toCurl returns a string that can be run as a command in a terminal (minus the body)
363 func (r *requestInfo) toCurl() string {
365 for key, values := range r.RequestHeaders {
366 for _, value := range values {
367 headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, value))
371 return fmt.Sprintf("curl -k -v -X%s %s '%s'", r.RequestVerb, headers, r.RequestURL)
374 // debuggingRoundTripper will display information about the requests passing
375 // through it based on what is configured
376 type debuggingRoundTripper struct {
377 delegatedRoundTripper http.RoundTripper
379 levels map[debugLevel]bool
385 debugJustURL debugLevel = iota
393 func newDebuggingRoundTripper(rt http.RoundTripper, levels ...debugLevel) *debuggingRoundTripper {
394 drt := &debuggingRoundTripper{
395 delegatedRoundTripper: rt,
396 levels: make(map[debugLevel]bool, len(levels)),
398 for _, v := range levels {
404 func (rt *debuggingRoundTripper) CancelRequest(req *http.Request) {
405 if canceler, ok := rt.delegatedRoundTripper.(requestCanceler); ok {
406 canceler.CancelRequest(req)
408 klog.Errorf("CancelRequest not implemented by %T", rt.delegatedRoundTripper)
412 func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
413 reqInfo := newRequestInfo(req)
415 if rt.levels[debugJustURL] {
416 klog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL)
418 if rt.levels[debugCurlCommand] {
419 klog.Infof("%s", reqInfo.toCurl())
422 if rt.levels[debugRequestHeaders] {
423 klog.Infof("Request Headers:")
424 for key, values := range reqInfo.RequestHeaders {
425 for _, value := range values {
426 klog.Infof(" %s: %s", key, value)
431 startTime := time.Now()
432 response, err := rt.delegatedRoundTripper.RoundTrip(req)
433 reqInfo.Duration = time.Since(startTime)
435 reqInfo.complete(response, err)
437 if rt.levels[debugURLTiming] {
438 klog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
440 if rt.levels[debugResponseStatus] {
441 klog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
443 if rt.levels[debugResponseHeaders] {
444 klog.Infof("Response Headers:")
445 for key, values := range reqInfo.ResponseHeaders {
446 for _, value := range values {
447 klog.Infof(" %s: %s", key, value)
455 func (rt *debuggingRoundTripper) WrappedRoundTripper() http.RoundTripper {
456 return rt.delegatedRoundTripper
459 func legalHeaderByte(b byte) bool {
460 return int(b) < len(legalHeaderKeyBytes) && legalHeaderKeyBytes[b]
463 func shouldEscape(b byte) bool {
464 // url.PathUnescape() returns an error if any '%' is not followed by two
465 // hexadecimal digits, so we'll intentionally encode it.
466 return !legalHeaderByte(b) || b == '%'
469 func headerKeyEscape(key string) string {
470 buf := strings.Builder{}
471 for i := 0; i < len(key); i++ {
474 // %-encode bytes that should be escaped:
475 // https://tools.ietf.org/html/rfc3986#section-2.1
476 fmt.Fprintf(&buf, "%%%02X", b)
484 // legalHeaderKeyBytes was copied from net/http/lex.go's isTokenTable.
485 // See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
486 var legalHeaderKeyBytes = [127]bool{