From 689a3ee7a2675b3290c5d98e4d7191d6ac6b99f3 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Mon, 22 Jun 2015 16:54:37 -0700 Subject: [PATCH] Make UI work through kubectl proxy (again) Fixes #10115. --- docs/kubectl_proxy.md | 4 +- docs/man/man1/kubectl-proxy.1 | 2 +- pkg/kubectl/proxy_server.go | 72 ++++++++++++++++++++++++----------- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/docs/kubectl_proxy.md b/docs/kubectl_proxy.md index a54d1ff52c1..06a1397114b 100644 --- a/docs/kubectl_proxy.md +++ b/docs/kubectl_proxy.md @@ -41,7 +41,7 @@ $ kubectl proxy --api-prefix=/k8s-api ``` --accept-hosts="^localhost$,^127\\.0\\.0\\.1$,^\\[::1\\]$": Regular expression for hosts that the proxy should accept. - --accept-paths="^/api/.*": Regular expression for paths that the proxy should accept. + --accept-paths="^/.*": Regular expression for paths that the proxy should accept. --api-prefix="/api/": Prefix to serve the proxied API under. --disable-filter=false: If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks. Use with caution. -h, --help=false: help for proxy @@ -84,6 +84,6 @@ $ kubectl proxy --api-prefix=/k8s-api ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-06-11 03:49:29.837564354 +0000 UTC +###### Auto generated by spf13/cobra at 2015-06-23 19:00:28.69764897 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl_proxy.md?pixel)]() diff --git a/docs/man/man1/kubectl-proxy.1 b/docs/man/man1/kubectl-proxy.1 index 6057ae73fbe..81a8bae67d4 100644 --- a/docs/man/man1/kubectl-proxy.1 +++ b/docs/man/man1/kubectl-proxy.1 @@ -43,7 +43,7 @@ The above lets you 'curl localhost:8001/custom/api/v1/pods' Regular expression for hosts that the proxy should accept. .PP -\fB\-\-accept\-paths\fP="^/api/.*" +\fB\-\-accept\-paths\fP="^/.*" Regular expression for paths that the proxy should accept. .PP diff --git a/pkg/kubectl/proxy_server.go b/pkg/kubectl/proxy_server.go index 6755fdd8281..70620f201af 100644 --- a/pkg/kubectl/proxy_server.go +++ b/pkg/kubectl/proxy_server.go @@ -31,7 +31,7 @@ import ( const ( DefaultHostAcceptRE = "^localhost$,^127\\.0\\.0\\.1$,^\\[::1\\]$" - DefaultPathAcceptRE = "^/api/.*" + DefaultPathAcceptRE = "^/.*" DefaultPathRejectRE = "^/api/.*/exec,^/api/.*/run" DefaultMethodRejectRE = "POST,PUT,PATCH" ) @@ -75,6 +75,7 @@ func MakeRegexpArrayOrDie(str string) []*regexp.Regexp { func matchesRegexp(str string, regexps []*regexp.Regexp) bool { for _, re := range regexps { if re.MatchString(str) { + glog.V(6).Infof("%v matched %s", str, re) return true } } @@ -83,17 +84,28 @@ func matchesRegexp(str string, regexps []*regexp.Regexp) bool { func (f *FilterServer) accept(method, path, host string) bool { if matchesRegexp(path, f.RejectPaths) { + glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host) return false } if matchesRegexp(method, f.RejectMethods) { + glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host) return false } if matchesRegexp(path, f.AcceptPaths) && matchesRegexp(host, f.AcceptHosts) { + glog.V(3).Infof("Filter accepting %v %v %v", method, path, host) return true } + glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host) return false } +// Make a copy of f which passes requests along to the new delegate. +func (f *FilterServer) HandlerFor(delegate http.Handler) *FilterServer { + f2 := *f + f2.delegate = delegate + return &f2 +} + func (f *FilterServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { host, _, _ := net.SplitHostPort(req.Host) if f.accept(req.Method, req.URL.Path, host) { @@ -106,12 +118,12 @@ func (f *FilterServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // ProxyServer is a http.Handler which proxies Kubernetes APIs to remote API server. type ProxyServer struct { - mux *http.ServeMux - httputil.ReverseProxy + handler http.Handler } // NewProxyServer creates and installs a new ProxyServer. // It automatically registers the created ProxyServer to http.DefaultServeMux. +// 'filter', if non-nil, protects requests to the api only. func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *client.Config) (*ProxyServer, error) { host := cfg.Host if !strings.HasSuffix(host, "/") { @@ -121,46 +133,45 @@ func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, if err != nil { return nil, err } - proxy := newProxyServer(target) + proxy := newProxy(target) if proxy.Transport, err = client.TransportFor(cfg); err != nil { return nil, err } - - var server http.Handler - if strings.HasPrefix(apiProxyPrefix, "/api") { - server = proxy - } else { - server = http.StripPrefix(apiProxyPrefix, proxy) - } + proxyServer := http.Handler(proxy) if filter != nil { - filter.delegate = server - server = filter + proxyServer = filter.HandlerFor(proxyServer) } - proxy.mux.Handle(apiProxyPrefix, server) - proxy.mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase)) - return proxy, nil + if !strings.HasPrefix(apiProxyPrefix, "/api") { + proxyServer = stripLeaveSlash(apiProxyPrefix, proxyServer) + } + + mux := http.NewServeMux() + mux.Handle(apiProxyPrefix, proxyServer) + if filebase != "" { + // Require user to explicitly request this behavior rather than + // serving their working directory by default. + mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase)) + } + return &ProxyServer{handler: mux}, nil } // Serve starts the server (http.DefaultServeMux) on given port, loops forever. func (s *ProxyServer) Serve(port int) error { server := http.Server{ Addr: fmt.Sprintf(":%d", port), - Handler: s.mux, + Handler: s.handler, } return server.ListenAndServe() } -func newProxyServer(target *url.URL) *ProxyServer { +func newProxy(target *url.URL) *httputil.ReverseProxy { director := func(req *http.Request) { req.URL.Scheme = target.Scheme req.URL.Host = target.Host req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) } - return &ProxyServer{ - ReverseProxy: httputil.ReverseProxy{Director: director}, - mux: http.NewServeMux(), - } + return &httputil.ReverseProxy{Director: director} } func newFileHandler(prefix, base string) http.Handler { @@ -178,3 +189,20 @@ func singleJoiningSlash(a, b string) string { } return a + b } + +// like http.StripPrefix, but always leaves an initial slash. (so that our +// regexps will work.) +func stripLeaveSlash(prefix string, h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + p := strings.TrimPrefix(req.URL.Path, prefix) + if len(p) >= len(req.URL.Path) { + http.NotFound(w, req) + return + } + if len(p) > 0 && p[:1] != "/" { + p = "/" + p + } + req.URL.Path = p + h.ServeHTTP(w, req) + }) +}