...

Source file src/k8s.io/kubectl/pkg/proxy/proxy_server.go

Documentation: k8s.io/kubectl/pkg/proxy

     1  /*
     2  Copyright 2014 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 proxy
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"net/http"
    23  	"net/url"
    24  	"os"
    25  	"regexp"
    26  	"strings"
    27  	"time"
    28  
    29  	utilnet "k8s.io/apimachinery/pkg/util/net"
    30  	"k8s.io/apimachinery/pkg/util/proxy"
    31  	"k8s.io/client-go/rest"
    32  	"k8s.io/client-go/transport"
    33  	"k8s.io/klog/v2"
    34  	"k8s.io/kubectl/pkg/util"
    35  )
    36  
    37  const (
    38  	// DefaultHostAcceptRE is the default value for which hosts to accept.
    39  	DefaultHostAcceptRE = "^localhost$,^127\\.0\\.0\\.1$,^\\[::1\\]$"
    40  	// DefaultPathAcceptRE is the default path to accept.
    41  	DefaultPathAcceptRE = "^.*"
    42  	// DefaultPathRejectRE is the default set of paths to reject.
    43  	DefaultPathRejectRE = "^/api/.*/pods/.*/exec,^/api/.*/pods/.*/attach"
    44  	// DefaultMethodRejectRE is the set of HTTP methods to reject by default.
    45  	DefaultMethodRejectRE = "^$"
    46  )
    47  
    48  // FilterServer rejects requests which don't match one of the specified regular expressions
    49  type FilterServer struct {
    50  	// Only paths that match this regexp will be accepted
    51  	AcceptPaths []*regexp.Regexp
    52  	// Paths that match this regexp will be rejected, even if they match the above
    53  	RejectPaths []*regexp.Regexp
    54  	// Hosts are required to match this list of regexp
    55  	AcceptHosts []*regexp.Regexp
    56  	// Methods that match this regexp are rejected
    57  	RejectMethods []*regexp.Regexp
    58  	// The delegate to call to handle accepted requests.
    59  	delegate http.Handler
    60  }
    61  
    62  // MakeRegexpArray splits a comma separated list of regexps into an array of Regexp objects.
    63  func MakeRegexpArray(str string) ([]*regexp.Regexp, error) {
    64  	if str == "" {
    65  		return []*regexp.Regexp{}, nil
    66  	}
    67  	parts := strings.Split(str, ",")
    68  	result := make([]*regexp.Regexp, len(parts))
    69  	for ix := range parts {
    70  		re, err := regexp.Compile(parts[ix])
    71  		if err != nil {
    72  			return nil, err
    73  		}
    74  		result[ix] = re
    75  	}
    76  	return result, nil
    77  }
    78  
    79  // MakeRegexpArrayOrDie creates an array of regular expression objects from a string or exits.
    80  func MakeRegexpArrayOrDie(str string) []*regexp.Regexp {
    81  	result, err := MakeRegexpArray(str)
    82  	if err != nil {
    83  		klog.Fatalf("Error compiling re: %v", err)
    84  	}
    85  	return result
    86  }
    87  
    88  func matchesRegexp(str string, regexps []*regexp.Regexp) bool {
    89  	for _, re := range regexps {
    90  		if re.MatchString(str) {
    91  			klog.V(6).Infof("%v matched %s", str, re)
    92  			return true
    93  		}
    94  	}
    95  	return false
    96  }
    97  
    98  func (f *FilterServer) accept(method, path, host string) bool {
    99  	if matchesRegexp(path, f.RejectPaths) {
   100  		return false
   101  	}
   102  	if matchesRegexp(method, f.RejectMethods) {
   103  		return false
   104  	}
   105  	if matchesRegexp(path, f.AcceptPaths) && matchesRegexp(host, f.AcceptHosts) {
   106  		return true
   107  	}
   108  	return false
   109  }
   110  
   111  // HandlerFor makes a shallow copy of f which passes its requests along to the
   112  // new delegate.
   113  func (f *FilterServer) HandlerFor(delegate http.Handler) *FilterServer {
   114  	f2 := *f
   115  	f2.delegate = delegate
   116  	return &f2
   117  }
   118  
   119  // Get host from a host header value like "localhost" or "localhost:8080"
   120  func extractHost(header string) (host string) {
   121  	host, _, err := net.SplitHostPort(header)
   122  	if err != nil {
   123  		host = header
   124  	}
   125  	return host
   126  }
   127  
   128  func (f *FilterServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
   129  	host := extractHost(req.Host)
   130  	if f.accept(req.Method, req.URL.Path, host) {
   131  		klog.V(3).Infof("Filter accepting %v %v %v", req.Method, req.URL.Path, host)
   132  		f.delegate.ServeHTTP(rw, req)
   133  		return
   134  	}
   135  	klog.V(3).Infof("Filter rejecting %v %v %v", req.Method, req.URL.Path, host)
   136  	http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)
   137  }
   138  
   139  // Server is a http.Handler which proxies Kubernetes APIs to remote API server.
   140  type Server struct {
   141  	handler http.Handler
   142  }
   143  
   144  type responder struct{}
   145  
   146  func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) {
   147  	klog.Errorf("Error while proxying request: %v", err)
   148  	http.Error(w, err.Error(), http.StatusInternalServerError)
   149  }
   150  
   151  // makeUpgradeTransport creates a transport that explicitly bypasses HTTP2 support
   152  // for proxy connections that must upgrade.
   153  func makeUpgradeTransport(config *rest.Config, keepalive time.Duration) (proxy.UpgradeRequestRoundTripper, error) {
   154  	transportConfig, err := config.TransportConfig()
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	tlsConfig, err := transport.TLSConfigFor(transportConfig)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	rt := utilnet.SetOldTransportDefaults(&http.Transport{
   163  		TLSClientConfig: tlsConfig,
   164  		DialContext: (&net.Dialer{
   165  			Timeout:   30 * time.Second,
   166  			KeepAlive: keepalive,
   167  		}).DialContext,
   168  	})
   169  
   170  	upgrader, err := transport.HTTPWrappersForConfig(transportConfig, proxy.MirrorRequest)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	return proxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil
   175  }
   176  
   177  // NewServer creates and installs a new Server.
   178  // 'filter', if non-nil, protects requests to the api only.
   179  func NewServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *rest.Config, keepalive time.Duration, appendLocationPath bool) (*Server, error) {
   180  	proxyHandler, err := NewProxyHandler(apiProxyPrefix, filter, cfg, keepalive, appendLocationPath)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	mux := http.NewServeMux()
   185  	mux.Handle(apiProxyPrefix, proxyHandler)
   186  	if filebase != "" {
   187  		// Require user to explicitly request this behavior rather than
   188  		// serving their working directory by default.
   189  		mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase))
   190  	}
   191  	return &Server{handler: mux}, nil
   192  }
   193  
   194  // NewProxyHandler creates an api proxy handler for the cluster
   195  func NewProxyHandler(apiProxyPrefix string, filter *FilterServer, cfg *rest.Config, keepalive time.Duration, appendLocationPath bool) (http.Handler, error) {
   196  	host := cfg.Host
   197  	if !strings.HasSuffix(host, "/") {
   198  		host = host + "/"
   199  	}
   200  	target, err := url.Parse(host)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	responder := &responder{}
   206  	transport, err := rest.TransportFor(cfg)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	upgradeTransport, err := makeUpgradeTransport(cfg, keepalive)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	proxy := proxy.NewUpgradeAwareHandler(target, transport, false, false, responder)
   215  	proxy.UpgradeTransport = upgradeTransport
   216  	proxy.UseRequestLocation = true
   217  	proxy.UseLocationHost = true
   218  	proxy.AppendLocationPath = appendLocationPath
   219  
   220  	proxyServer := http.Handler(proxy)
   221  	if filter != nil {
   222  		proxyServer = filter.HandlerFor(proxyServer)
   223  	}
   224  
   225  	if !strings.HasPrefix(apiProxyPrefix, "/api") {
   226  		proxyServer = stripLeaveSlash(apiProxyPrefix, proxyServer)
   227  	}
   228  	return proxyServer, nil
   229  }
   230  
   231  // Listen is a simple wrapper around net.Listen.
   232  func (s *Server) Listen(address string, port int) (net.Listener, error) {
   233  	return net.Listen("tcp", fmt.Sprintf("%s:%d", address, port))
   234  }
   235  
   236  // ListenUnix does net.Listen for a unix socket
   237  func (s *Server) ListenUnix(path string) (net.Listener, error) {
   238  	// Remove any socket, stale or not, but fall through for other files
   239  	fi, err := os.Stat(path)
   240  	if err == nil && (fi.Mode()&os.ModeSocket) != 0 {
   241  		os.Remove(path)
   242  	}
   243  	// Default to only user accessible socket, caller can open up later if desired
   244  	oldmask, _ := util.Umask(0077)
   245  	l, err := net.Listen("unix", path)
   246  	util.Umask(oldmask)
   247  	return l, err
   248  }
   249  
   250  // ServeOnListener starts the server using given listener, loops forever.
   251  func (s *Server) ServeOnListener(l net.Listener) error {
   252  	server := http.Server{
   253  		Handler: s.handler,
   254  	}
   255  	return server.Serve(l)
   256  }
   257  
   258  func newFileHandler(prefix, base string) http.Handler {
   259  	return http.StripPrefix(prefix, http.FileServer(http.Dir(base)))
   260  }
   261  
   262  // like http.StripPrefix, but always leaves an initial slash. (so that our
   263  // regexps will work.)
   264  func stripLeaveSlash(prefix string, h http.Handler) http.Handler {
   265  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   266  		p := strings.TrimPrefix(req.URL.Path, prefix)
   267  		if len(p) >= len(req.URL.Path) {
   268  			http.NotFound(w, req)
   269  			return
   270  		}
   271  		if len(p) > 0 && p[:1] != "/" {
   272  			p = "/" + p
   273  		}
   274  		req.URL.Path = p
   275  		h.ServeHTTP(w, req)
   276  	})
   277  }
   278  

View as plain text