mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #97350 from FabianKramm/master
kubectl proxy: append context host path to request path
This commit is contained in:
commit
341d312066
@ -60,6 +60,8 @@ type UpgradeAwareHandler struct {
|
|||||||
// Location is the location of the upstream proxy. It is used as the location to Dial on the upstream server
|
// Location is the location of the upstream proxy. It is used as the location to Dial on the upstream server
|
||||||
// for upgrade requests unless UseRequestLocationOnUpgrade is true.
|
// for upgrade requests unless UseRequestLocationOnUpgrade is true.
|
||||||
Location *url.URL
|
Location *url.URL
|
||||||
|
// AppendLocationPath determines if the original path of the Location should be appended to the upstream proxy request path
|
||||||
|
AppendLocationPath bool
|
||||||
// Transport provides an optional round tripper to use to proxy. If nil, the default proxy transport is used
|
// Transport provides an optional round tripper to use to proxy. If nil, the default proxy transport is used
|
||||||
Transport http.RoundTripper
|
Transport http.RoundTripper
|
||||||
// UpgradeTransport, if specified, will be used as the backend transport when upgrade requests are provided.
|
// UpgradeTransport, if specified, will be used as the backend transport when upgrade requests are provided.
|
||||||
@ -239,7 +241,13 @@ func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request
|
|||||||
newReq.Host = h.Location.Host
|
newReq.Host = h.Location.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host})
|
// create the target location to use for the reverse proxy
|
||||||
|
reverseProxyLocation := &url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host}
|
||||||
|
if h.AppendLocationPath {
|
||||||
|
reverseProxyLocation.Path = h.Location.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(reverseProxyLocation)
|
||||||
proxy.Transport = h.Transport
|
proxy.Transport = h.Transport
|
||||||
proxy.FlushInterval = h.FlushInterval
|
proxy.FlushInterval = h.FlushInterval
|
||||||
proxy.ErrorLog = log.New(noSuppressPanicError{}, "", log.LstdFlags)
|
proxy.ErrorLog = log.New(noSuppressPanicError{}, "", log.LstdFlags)
|
||||||
@ -282,6 +290,9 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques
|
|||||||
location = *req.URL
|
location = *req.URL
|
||||||
location.Scheme = h.Location.Scheme
|
location.Scheme = h.Location.Scheme
|
||||||
location.Host = h.Location.Host
|
location.Host = h.Location.Host
|
||||||
|
if h.AppendLocationPath {
|
||||||
|
location.Path = singleJoiningSlash(h.Location.Path, location.Path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clone := utilnet.CloneRequest(req)
|
clone := utilnet.CloneRequest(req)
|
||||||
@ -414,6 +425,20 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Taken from net/http/httputil/reverseproxy.go as singleJoiningSlash is not exported to be re-used.
|
||||||
|
// See-also: https://github.com/golang/go/issues/44290
|
||||||
|
func singleJoiningSlash(a, b string) string {
|
||||||
|
aslash := strings.HasSuffix(a, "/")
|
||||||
|
bslash := strings.HasPrefix(b, "/")
|
||||||
|
switch {
|
||||||
|
case aslash && bslash:
|
||||||
|
return a + b[1:]
|
||||||
|
case !aslash && !bslash:
|
||||||
|
return a + "/" + b
|
||||||
|
}
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
func (h *UpgradeAwareHandler) DialForUpgrade(req *http.Request) (net.Conn, error) {
|
func (h *UpgradeAwareHandler) DialForUpgrade(req *http.Request) (net.Conn, error) {
|
||||||
if h.UpgradeTransport == nil {
|
if h.UpgradeTransport == nil {
|
||||||
return dial(req, h.Transport)
|
return dial(req, h.Transport)
|
||||||
|
@ -163,6 +163,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
expectedRespHeader map[string]string
|
expectedRespHeader map[string]string
|
||||||
notExpectedRespHeader []string
|
notExpectedRespHeader []string
|
||||||
upgradeRequired bool
|
upgradeRequired bool
|
||||||
|
appendLocationPath bool
|
||||||
expectError func(err error) bool
|
expectError func(err error) bool
|
||||||
useLocationHost bool
|
useLocationHost bool
|
||||||
}{
|
}{
|
||||||
@ -246,6 +247,27 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
expectedPath: "/some/path",
|
expectedPath: "/some/path",
|
||||||
useLocationHost: true,
|
useLocationHost: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "append server path to request path",
|
||||||
|
method: "GET",
|
||||||
|
requestPath: "/base",
|
||||||
|
expectedPath: "/base/base",
|
||||||
|
appendLocationPath: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "append server path to request path with ending slash",
|
||||||
|
method: "GET",
|
||||||
|
requestPath: "/base/",
|
||||||
|
expectedPath: "/base/base/",
|
||||||
|
appendLocationPath: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "don't append server path to request path",
|
||||||
|
method: "GET",
|
||||||
|
requestPath: "/base",
|
||||||
|
expectedPath: "/base",
|
||||||
|
appendLocationPath: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
@ -269,6 +291,7 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
backendURL.Path = test.requestPath
|
backendURL.Path = test.requestPath
|
||||||
proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, test.upgradeRequired, responder)
|
proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, test.upgradeRequired, responder)
|
||||||
proxyHandler.UseLocationHost = test.useLocationHost
|
proxyHandler.UseLocationHost = test.useLocationHost
|
||||||
|
proxyHandler.AppendLocationPath = test.appendLocationPath
|
||||||
proxyServer := httptest.NewServer(proxyHandler)
|
proxyServer := httptest.NewServer(proxyHandler)
|
||||||
defer proxyServer.Close()
|
defer proxyServer.Close()
|
||||||
proxyURL, _ := url.Parse(proxyServer.URL)
|
proxyURL, _ := url.Parse(proxyServer.URL)
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -50,6 +51,8 @@ type ProxyOptions struct {
|
|||||||
unixSocket string
|
unixSocket string
|
||||||
keepalive time.Duration
|
keepalive time.Duration
|
||||||
|
|
||||||
|
appendServerPath bool
|
||||||
|
|
||||||
clientConfig *rest.Config
|
clientConfig *rest.Config
|
||||||
filter *proxy.FilterServer
|
filter *proxy.FilterServer
|
||||||
|
|
||||||
@ -138,6 +141,7 @@ func NewCmdProxy(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobr
|
|||||||
cmd.Flags().BoolVar(&o.disableFilter, "disable-filter", o.disableFilter, "If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks, when used with an accessible port.")
|
cmd.Flags().BoolVar(&o.disableFilter, "disable-filter", o.disableFilter, "If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks, when used with an accessible port.")
|
||||||
cmd.Flags().StringVarP(&o.unixSocket, "unix-socket", "u", o.unixSocket, "Unix socket on which to run the proxy.")
|
cmd.Flags().StringVarP(&o.unixSocket, "unix-socket", "u", o.unixSocket, "Unix socket on which to run the proxy.")
|
||||||
cmd.Flags().DurationVar(&o.keepalive, "keepalive", o.keepalive, "keepalive specifies the keep-alive period for an active network connection. Set to 0 to disable keepalive.")
|
cmd.Flags().DurationVar(&o.keepalive, "keepalive", o.keepalive, "keepalive specifies the keep-alive period for an active network connection. Set to 0 to disable keepalive.")
|
||||||
|
cmd.Flags().BoolVar(&o.appendServerPath, "append-server-path", o.appendServerPath, "If true, enables automatic path appending of the kube context server path to each request.")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +161,15 @@ func (o *ProxyOptions) Complete(f cmdutil.Factory) error {
|
|||||||
o.apiPrefix += "/"
|
o.apiPrefix += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.appendServerPath == false {
|
||||||
|
target, err := url.Parse(clientConfig.Host)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if target.Path != "" && target.Path != "/" {
|
||||||
|
klog.Warning("Your kube context contains a server path " + target.Path + ", use --append-server-path to automatically append the path to each request")
|
||||||
|
}
|
||||||
|
}
|
||||||
if o.disableFilter {
|
if o.disableFilter {
|
||||||
if o.unixSocket == "" {
|
if o.unixSocket == "" {
|
||||||
klog.Warning("Request filter disabled, your proxy is vulnerable to XSRF attacks, please be cautious")
|
klog.Warning("Request filter disabled, your proxy is vulnerable to XSRF attacks, please be cautious")
|
||||||
@ -193,7 +206,7 @@ func (o ProxyOptions) Validate() error {
|
|||||||
|
|
||||||
// RunProxy checks given arguments and executes command
|
// RunProxy checks given arguments and executes command
|
||||||
func (o ProxyOptions) RunProxy() error {
|
func (o ProxyOptions) RunProxy() error {
|
||||||
server, err := proxy.NewServer(o.staticDir, o.apiPrefix, o.staticPrefix, o.filter, o.clientConfig, o.keepalive)
|
server, err := proxy.NewServer(o.staticDir, o.apiPrefix, o.staticPrefix, o.filter, o.clientConfig, o.keepalive, o.appendServerPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -176,8 +176,8 @@ func makeUpgradeTransport(config *rest.Config, keepalive time.Duration) (proxy.U
|
|||||||
|
|
||||||
// NewServer creates and installs a new Server.
|
// NewServer creates and installs a new Server.
|
||||||
// 'filter', if non-nil, protects requests to the api only.
|
// 'filter', if non-nil, protects requests to the api only.
|
||||||
func NewServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *rest.Config, keepalive time.Duration) (*Server, error) {
|
func NewServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *rest.Config, keepalive time.Duration, appendLocationPath bool) (*Server, error) {
|
||||||
proxyHandler, err := NewProxyHandler(apiProxyPrefix, filter, cfg, keepalive)
|
proxyHandler, err := NewProxyHandler(apiProxyPrefix, filter, cfg, keepalive, appendLocationPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -192,7 +192,7 @@ func NewServer(filebase string, apiProxyPrefix string, staticPrefix string, filt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewProxyHandler creates an api proxy handler for the cluster
|
// NewProxyHandler creates an api proxy handler for the cluster
|
||||||
func NewProxyHandler(apiProxyPrefix string, filter *FilterServer, cfg *rest.Config, keepalive time.Duration) (http.Handler, error) {
|
func NewProxyHandler(apiProxyPrefix string, filter *FilterServer, cfg *rest.Config, keepalive time.Duration, appendLocationPath bool) (http.Handler, error) {
|
||||||
host := cfg.Host
|
host := cfg.Host
|
||||||
if !strings.HasSuffix(host, "/") {
|
if !strings.HasSuffix(host, "/") {
|
||||||
host = host + "/"
|
host = host + "/"
|
||||||
@ -215,6 +215,7 @@ func NewProxyHandler(apiProxyPrefix string, filter *FilterServer, cfg *rest.Conf
|
|||||||
proxy.UpgradeTransport = upgradeTransport
|
proxy.UpgradeTransport = upgradeTransport
|
||||||
proxy.UseRequestLocation = true
|
proxy.UseRequestLocation = true
|
||||||
proxy.UseLocationHost = true
|
proxy.UseLocationHost = true
|
||||||
|
proxy.AppendLocationPath = appendLocationPath
|
||||||
|
|
||||||
proxyServer := http.Handler(proxy)
|
proxyServer := http.Handler(proxy)
|
||||||
if filter != nil {
|
if filter != nil {
|
||||||
|
@ -455,17 +455,19 @@ func TestPathHandling(t *testing.T) {
|
|||||||
prefix string
|
prefix string
|
||||||
reqPath string
|
reqPath string
|
||||||
expectPath string
|
expectPath string
|
||||||
|
appendPath bool
|
||||||
}{
|
}{
|
||||||
{"test1", "/api/", "/metrics", "404 page not found\n"},
|
{"test1", "/api/", "/metrics", "404 page not found\n", false},
|
||||||
{"test2", "/api/", "/api/metrics", "/api/metrics"},
|
{"test2", "/api/", "/api/metrics", "/api/metrics", false},
|
||||||
{"test3", "/api/", "/api/v1/pods/", "/api/v1/pods/"},
|
{"test3", "/api/", "/api/v1/pods/", "/api/v1/pods/", false},
|
||||||
{"test4", "/", "/metrics", "/metrics"},
|
{"test4", "/", "/metrics", "/metrics", false},
|
||||||
{"test5", "/", "/api/v1/pods/", "/api/v1/pods/"},
|
{"test5", "/", "/api/v1/pods/", "/api/v1/pods/", false},
|
||||||
{"test6", "/custom/", "/metrics", "404 page not found\n"},
|
{"test6", "/custom/", "/metrics", "404 page not found\n", false},
|
||||||
{"test7", "/custom/", "/api/metrics", "404 page not found\n"},
|
{"test7", "/custom/", "/api/metrics", "404 page not found\n", false},
|
||||||
{"test8", "/custom/", "/api/v1/pods/", "404 page not found\n"},
|
{"test8", "/custom/", "/api/v1/pods/", "404 page not found\n", false},
|
||||||
{"test9", "/custom/", "/custom/api/metrics", "/api/metrics"},
|
{"test9", "/custom/", "/custom/api/metrics", "/api/metrics", false},
|
||||||
{"test10", "/custom/", "/custom/api/v1/pods/", "/api/v1/pods/"},
|
{"test10", "/custom/", "/custom/api/v1/pods/", "/api/v1/pods/", false},
|
||||||
|
{"test11", "/custom/", "/custom/api/v1/services/", "/api/v1/services/", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
cc := &rest.Config{
|
cc := &rest.Config{
|
||||||
@ -474,7 +476,7 @@ func TestPathHandling(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
p, err := NewServer("", tt.prefix, "/not/used/for/this/test", nil, cc, 0)
|
p, err := NewServer("", tt.prefix, "/not/used/for/this/test", nil, cc, 0, tt.appendPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%#v: %v", tt, err)
|
t.Fatalf("%#v: %v", tt, err)
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ func TestWatchClientTimeout(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("kubectl proxy", func(t *testing.T) {
|
t.Run("kubectl proxy", func(t *testing.T) {
|
||||||
kubectlProxyServer, err := kubectlproxy.NewServer("", "/", "/static/", nil, &restclient.Config{Host: s.URL, Timeout: 2 * time.Second}, 0)
|
kubectlProxyServer, err := kubectlproxy.NewServer("", "/", "/static/", nil, &restclient.Config{Host: s.URL, Timeout: 2 * time.Second}, 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user