123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- /*
- Copyright 2015 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package transport
- import (
- "fmt"
- "net/http"
- "strings"
- "time"
- "k8s.io/klog"
- utilnet "k8s.io/apimachinery/pkg/util/net"
- )
- // HTTPWrappersForConfig wraps a round tripper with any relevant layered
- // behavior from the config. Exposed to allow more clients that need HTTP-like
- // behavior but then must hijack the underlying connection (like WebSocket or
- // HTTP2 clients). Pure HTTP clients should use the RoundTripper returned from
- // New.
- func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
- if config.WrapTransport != nil {
- rt = config.WrapTransport(rt)
- }
- rt = DebugWrappers(rt)
- // Set authentication wrappers
- switch {
- case config.HasBasicAuth() && config.HasTokenAuth():
- return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
- case config.HasTokenAuth():
- rt = NewBearerAuthRoundTripper(config.BearerToken, rt)
- case config.HasBasicAuth():
- rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
- }
- if len(config.UserAgent) > 0 {
- rt = NewUserAgentRoundTripper(config.UserAgent, rt)
- }
- if len(config.Impersonate.UserName) > 0 ||
- len(config.Impersonate.Groups) > 0 ||
- len(config.Impersonate.Extra) > 0 {
- rt = NewImpersonatingRoundTripper(config.Impersonate, rt)
- }
- return rt, nil
- }
- // DebugWrappers wraps a round tripper and logs based on the current log level.
- func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
- switch {
- case bool(klog.V(9)):
- rt = newDebuggingRoundTripper(rt, debugCurlCommand, debugURLTiming, debugResponseHeaders)
- case bool(klog.V(8)):
- rt = newDebuggingRoundTripper(rt, debugJustURL, debugRequestHeaders, debugResponseStatus, debugResponseHeaders)
- case bool(klog.V(7)):
- rt = newDebuggingRoundTripper(rt, debugJustURL, debugRequestHeaders, debugResponseStatus)
- case bool(klog.V(6)):
- rt = newDebuggingRoundTripper(rt, debugURLTiming)
- }
- return rt
- }
- type requestCanceler interface {
- CancelRequest(*http.Request)
- }
- type authProxyRoundTripper struct {
- username string
- groups []string
- extra map[string][]string
- rt http.RoundTripper
- }
- // NewAuthProxyRoundTripper provides a roundtripper which will add auth proxy fields to requests for
- // authentication terminating proxy cases
- // assuming you pull the user from the context:
- // username is the user.Info.GetName() of the user
- // groups is the user.Info.GetGroups() of the user
- // extra is the user.Info.GetExtra() of the user
- // extra can contain any additional information that the authenticator
- // thought was interesting, for example authorization scopes.
- // In order to faithfully round-trip through an impersonation flow, these keys
- // MUST be lowercase.
- func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {
- return &authProxyRoundTripper{
- username: username,
- groups: groups,
- extra: extra,
- rt: rt,
- }
- }
- func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- req = utilnet.CloneRequest(req)
- SetAuthProxyHeaders(req, rt.username, rt.groups, rt.extra)
- return rt.rt.RoundTrip(req)
- }
- // SetAuthProxyHeaders stomps the auth proxy header fields. It mutates its argument.
- func SetAuthProxyHeaders(req *http.Request, username string, groups []string, extra map[string][]string) {
- req.Header.Del("X-Remote-User")
- req.Header.Del("X-Remote-Group")
- for key := range req.Header {
- if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {
- req.Header.Del(key)
- }
- }
- req.Header.Set("X-Remote-User", username)
- for _, group := range groups {
- req.Header.Add("X-Remote-Group", group)
- }
- for key, values := range extra {
- for _, value := range values {
- req.Header.Add("X-Remote-Extra-"+headerKeyEscape(key), value)
- }
- }
- }
- func (rt *authProxyRoundTripper) CancelRequest(req *http.Request) {
- if canceler, ok := rt.rt.(requestCanceler); ok {
- canceler.CancelRequest(req)
- } else {
- klog.Errorf("CancelRequest not implemented by %T", rt.rt)
- }
- }
- func (rt *authProxyRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
- type userAgentRoundTripper struct {
- agent string
- rt http.RoundTripper
- }
- func NewUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
- return &userAgentRoundTripper{agent, rt}
- }
- func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- if len(req.Header.Get("User-Agent")) != 0 {
- return rt.rt.RoundTrip(req)
- }
- req = utilnet.CloneRequest(req)
- req.Header.Set("User-Agent", rt.agent)
- return rt.rt.RoundTrip(req)
- }
- func (rt *userAgentRoundTripper) CancelRequest(req *http.Request) {
- if canceler, ok := rt.rt.(requestCanceler); ok {
- canceler.CancelRequest(req)
- } else {
- klog.Errorf("CancelRequest not implemented by %T", rt.rt)
- }
- }
- func (rt *userAgentRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
- type basicAuthRoundTripper struct {
- username string
- password string
- rt http.RoundTripper
- }
- // NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a
- // request unless it has already been set.
- func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
- return &basicAuthRoundTripper{username, password, rt}
- }
- func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- if len(req.Header.Get("Authorization")) != 0 {
- return rt.rt.RoundTrip(req)
- }
- req = utilnet.CloneRequest(req)
- req.SetBasicAuth(rt.username, rt.password)
- return rt.rt.RoundTrip(req)
- }
- func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) {
- if canceler, ok := rt.rt.(requestCanceler); ok {
- canceler.CancelRequest(req)
- } else {
- klog.Errorf("CancelRequest not implemented by %T", rt.rt)
- }
- }
- func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
- // These correspond to the headers used in pkg/apis/authentication. We don't want the package dependency,
- // but you must not change the values.
- const (
- // ImpersonateUserHeader is used to impersonate a particular user during an API server request
- ImpersonateUserHeader = "Impersonate-User"
- // ImpersonateGroupHeader is used to impersonate a particular group during an API server request.
- // It can be repeated multiplied times for multiple groups.
- ImpersonateGroupHeader = "Impersonate-Group"
- // ImpersonateUserExtraHeaderPrefix is a prefix for a header used to impersonate an entry in the
- // extra map[string][]string for user.Info. The key for the `extra` map is suffix.
- // The same key can be repeated multiple times to have multiple elements in the slice under a single key.
- // For instance:
- // Impersonate-Extra-Foo: one
- // Impersonate-Extra-Foo: two
- // results in extra["Foo"] = []string{"one", "two"}
- ImpersonateUserExtraHeaderPrefix = "Impersonate-Extra-"
- )
- type impersonatingRoundTripper struct {
- impersonate ImpersonationConfig
- delegate http.RoundTripper
- }
- // NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set.
- func NewImpersonatingRoundTripper(impersonate ImpersonationConfig, delegate http.RoundTripper) http.RoundTripper {
- return &impersonatingRoundTripper{impersonate, delegate}
- }
- func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- // use the user header as marker for the rest.
- if len(req.Header.Get(ImpersonateUserHeader)) != 0 {
- return rt.delegate.RoundTrip(req)
- }
- req = utilnet.CloneRequest(req)
- req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName)
- for _, group := range rt.impersonate.Groups {
- req.Header.Add(ImpersonateGroupHeader, group)
- }
- for k, vv := range rt.impersonate.Extra {
- for _, v := range vv {
- req.Header.Add(ImpersonateUserExtraHeaderPrefix+headerKeyEscape(k), v)
- }
- }
- return rt.delegate.RoundTrip(req)
- }
- func (rt *impersonatingRoundTripper) CancelRequest(req *http.Request) {
- if canceler, ok := rt.delegate.(requestCanceler); ok {
- canceler.CancelRequest(req)
- } else {
- klog.Errorf("CancelRequest not implemented by %T", rt.delegate)
- }
- }
- func (rt *impersonatingRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.delegate }
- type bearerAuthRoundTripper struct {
- bearer string
- rt http.RoundTripper
- }
- // NewBearerAuthRoundTripper adds the provided bearer token to a request
- // unless the authorization header has already been set.
- func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
- return &bearerAuthRoundTripper{bearer, rt}
- }
- func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- if len(req.Header.Get("Authorization")) != 0 {
- return rt.rt.RoundTrip(req)
- }
- req = utilnet.CloneRequest(req)
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.bearer))
- return rt.rt.RoundTrip(req)
- }
- func (rt *bearerAuthRoundTripper) CancelRequest(req *http.Request) {
- if canceler, ok := rt.rt.(requestCanceler); ok {
- canceler.CancelRequest(req)
- } else {
- klog.Errorf("CancelRequest not implemented by %T", rt.rt)
- }
- }
- func (rt *bearerAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
- // requestInfo keeps track of information about a request/response combination
- type requestInfo struct {
- RequestHeaders http.Header
- RequestVerb string
- RequestURL string
- ResponseStatus string
- ResponseHeaders http.Header
- ResponseErr error
- Duration time.Duration
- }
- // newRequestInfo creates a new RequestInfo based on an http request
- func newRequestInfo(req *http.Request) *requestInfo {
- return &requestInfo{
- RequestURL: req.URL.String(),
- RequestVerb: req.Method,
- RequestHeaders: req.Header,
- }
- }
- // complete adds information about the response to the requestInfo
- func (r *requestInfo) complete(response *http.Response, err error) {
- if err != nil {
- r.ResponseErr = err
- return
- }
- r.ResponseStatus = response.Status
- r.ResponseHeaders = response.Header
- }
- // toCurl returns a string that can be run as a command in a terminal (minus the body)
- func (r *requestInfo) toCurl() string {
- headers := ""
- for key, values := range r.RequestHeaders {
- for _, value := range values {
- headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, value))
- }
- }
- return fmt.Sprintf("curl -k -v -X%s %s '%s'", r.RequestVerb, headers, r.RequestURL)
- }
- // debuggingRoundTripper will display information about the requests passing
- // through it based on what is configured
- type debuggingRoundTripper struct {
- delegatedRoundTripper http.RoundTripper
- levels map[debugLevel]bool
- }
- type debugLevel int
- const (
- debugJustURL debugLevel = iota
- debugURLTiming
- debugCurlCommand
- debugRequestHeaders
- debugResponseStatus
- debugResponseHeaders
- )
- func newDebuggingRoundTripper(rt http.RoundTripper, levels ...debugLevel) *debuggingRoundTripper {
- drt := &debuggingRoundTripper{
- delegatedRoundTripper: rt,
- levels: make(map[debugLevel]bool, len(levels)),
- }
- for _, v := range levels {
- drt.levels[v] = true
- }
- return drt
- }
- func (rt *debuggingRoundTripper) CancelRequest(req *http.Request) {
- if canceler, ok := rt.delegatedRoundTripper.(requestCanceler); ok {
- canceler.CancelRequest(req)
- } else {
- klog.Errorf("CancelRequest not implemented by %T", rt.delegatedRoundTripper)
- }
- }
- func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
- reqInfo := newRequestInfo(req)
- if rt.levels[debugJustURL] {
- klog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL)
- }
- if rt.levels[debugCurlCommand] {
- klog.Infof("%s", reqInfo.toCurl())
- }
- if rt.levels[debugRequestHeaders] {
- klog.Infof("Request Headers:")
- for key, values := range reqInfo.RequestHeaders {
- for _, value := range values {
- klog.Infof(" %s: %s", key, value)
- }
- }
- }
- startTime := time.Now()
- response, err := rt.delegatedRoundTripper.RoundTrip(req)
- reqInfo.Duration = time.Since(startTime)
- reqInfo.complete(response, err)
- if rt.levels[debugURLTiming] {
- klog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
- }
- if rt.levels[debugResponseStatus] {
- klog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
- }
- if rt.levels[debugResponseHeaders] {
- klog.Infof("Response Headers:")
- for key, values := range reqInfo.ResponseHeaders {
- for _, value := range values {
- klog.Infof(" %s: %s", key, value)
- }
- }
- }
- return response, err
- }
- func (rt *debuggingRoundTripper) WrappedRoundTripper() http.RoundTripper {
- return rt.delegatedRoundTripper
- }
- func legalHeaderByte(b byte) bool {
- return int(b) < len(legalHeaderKeyBytes) && legalHeaderKeyBytes[b]
- }
- func shouldEscape(b byte) bool {
- // url.PathUnescape() returns an error if any '%' is not followed by two
- // hexadecimal digits, so we'll intentionally encode it.
- return !legalHeaderByte(b) || b == '%'
- }
- func headerKeyEscape(key string) string {
- buf := strings.Builder{}
- for i := 0; i < len(key); i++ {
- b := key[i]
- if shouldEscape(b) {
- // %-encode bytes that should be escaped:
- // https://tools.ietf.org/html/rfc3986#section-2.1
- fmt.Fprintf(&buf, "%%%02X", b)
- continue
- }
- buf.WriteByte(b)
- }
- return buf.String()
- }
- // legalHeaderKeyBytes was copied from net/http/lex.go's isTokenTable.
- // See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
- var legalHeaderKeyBytes = [127]bool{
- '%': true,
- '!': true,
- '#': true,
- '$': true,
- '&': true,
- '\'': true,
- '*': true,
- '+': true,
- '-': true,
- '.': true,
- '0': true,
- '1': true,
- '2': true,
- '3': true,
- '4': true,
- '5': true,
- '6': true,
- '7': true,
- '8': true,
- '9': true,
- 'A': true,
- 'B': true,
- 'C': true,
- 'D': true,
- 'E': true,
- 'F': true,
- 'G': true,
- 'H': true,
- 'I': true,
- 'J': true,
- 'K': true,
- 'L': true,
- 'M': true,
- 'N': true,
- 'O': true,
- 'P': true,
- 'Q': true,
- 'R': true,
- 'S': true,
- 'T': true,
- 'U': true,
- 'W': true,
- 'V': true,
- 'X': true,
- 'Y': true,
- 'Z': true,
- '^': true,
- '_': true,
- '`': true,
- 'a': true,
- 'b': true,
- 'c': true,
- 'd': true,
- 'e': true,
- 'f': true,
- 'g': true,
- 'h': true,
- 'i': true,
- 'j': true,
- 'k': true,
- 'l': true,
- 'm': true,
- 'n': true,
- 'o': true,
- 'p': true,
- 'q': true,
- 'r': true,
- 's': true,
- 't': true,
- 'u': true,
- 'v': true,
- 'w': true,
- 'x': true,
- 'y': true,
- 'z': true,
- '|': true,
- '~': true,
- }
|