mirror of https://github.com/k3s-io/k3s
Preserve leading and trailing slashes on proxy subpaths
parent
158f6b78da
commit
04eede9b2a
|
@ -14,6 +14,7 @@ go_library(
|
|||
"//pkg/kubelet/client:go_default_library",
|
||||
"//pkg/registry/core/node:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/proxy:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
|
||||
|
|
|
@ -20,9 +20,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
|
@ -70,7 +70,7 @@ func (r *ProxyREST) Connect(ctx genericapirequest.Context, id string, opts runti
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
location.Path = path.Join("/", location.Path, proxyOpts.Path)
|
||||
location.Path = net.JoinPreservingTrailingSlash(location.Path, proxyOpts.Path)
|
||||
// Return a proxy handler that uses the desired transport, wrapped with additional proxy handling (to get URL rewriting, X-Forwarded-* headers, etc)
|
||||
return newThrottledUpgradeAwareProxyHandler(location, transport, true, false, responder), nil
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ go_library(
|
|||
"//pkg/registry/core/pod:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/proxy:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
|
||||
|
|
|
@ -20,9 +20,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
|
@ -71,7 +71,7 @@ func (r *ProxyREST) Connect(ctx genericapirequest.Context, id string, opts runti
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
location.Path = path.Join("/", location.Path, proxyOpts.Path)
|
||||
location.Path = net.JoinPreservingTrailingSlash(location.Path, proxyOpts.Path)
|
||||
// Return a proxy handler that uses the desired transport, wrapped with additional proxy handling (to get URL rewriting, X-Forwarded-* headers, etc)
|
||||
return newThrottledUpgradeAwareProxyHandler(location, transport, true, false, false, responder), nil
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
@ -66,7 +66,7 @@ func (r *ProxyREST) Connect(ctx genericapirequest.Context, id string, opts runti
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
location.Path = path.Join("/", location.Path, proxyOpts.Path)
|
||||
location.Path = net.JoinPreservingTrailingSlash(location.Path, proxyOpts.Path)
|
||||
// Return a proxy handler that uses the desired transport, wrapped with additional proxy handling (to get URL rewriting, X-Forwarded-* headers, etc)
|
||||
return newThrottledUpgradeAwareProxyHandler(location, transport, true, false, responder), nil
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -33,6 +34,26 @@ import (
|
|||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// JoinPreservingTrailingSlash does a path.Join of the specified elements,
|
||||
// preserving any trailing slash on the last non-empty segment
|
||||
func JoinPreservingTrailingSlash(elem ...string) string {
|
||||
// do the basic path join
|
||||
result := path.Join(elem...)
|
||||
|
||||
// find the last non-empty segment
|
||||
for i := len(elem) - 1; i >= 0; i-- {
|
||||
if len(elem[i]) > 0 {
|
||||
// if the last segment ended in a slash, ensure our result does as well
|
||||
if strings.HasSuffix(elem[i], "/") && !strings.HasSuffix(result, "/") {
|
||||
result += "/"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// IsProbableEOF returns true if the given error resembles a connection termination
|
||||
// scenario that would justify assuming that the watch is empty.
|
||||
// These errors are what the Go http stack returns back to us which are general
|
||||
|
|
|
@ -20,6 +20,7 @@ package net
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -218,3 +219,40 @@ func TestTLSClientConfigHolder(t *testing.T) {
|
|||
t.Errorf("didn't find tls config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinPreservingTrailingSlash(t *testing.T) {
|
||||
tests := []struct {
|
||||
a string
|
||||
b string
|
||||
want string
|
||||
}{
|
||||
// All empty
|
||||
{"", "", ""},
|
||||
|
||||
// Empty a
|
||||
{"", "/", "/"},
|
||||
{"", "foo", "foo"},
|
||||
{"", "/foo", "/foo"},
|
||||
{"", "/foo/", "/foo/"},
|
||||
|
||||
// Empty b
|
||||
{"/", "", "/"},
|
||||
{"foo", "", "foo"},
|
||||
{"/foo", "", "/foo"},
|
||||
{"/foo/", "", "/foo/"},
|
||||
|
||||
// Both populated
|
||||
{"/", "/", "/"},
|
||||
{"foo", "foo", "foo/foo"},
|
||||
{"/foo", "/foo", "/foo/foo"},
|
||||
{"/foo/", "/foo/", "/foo/foo/"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
name := fmt.Sprintf("%q+%q=%q", tt.a, tt.b, tt.want)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if got := JoinPreservingTrailingSlash(tt.a, tt.b); got != tt.want {
|
||||
t.Errorf("JoinPreservingTrailingSlash() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2135,15 +2135,25 @@ func TestGetWithOptions(t *testing.T) {
|
|||
requestURL: "/namespaces/default/simple/id?param1=test1¶m2=test2",
|
||||
expectedPath: "",
|
||||
},
|
||||
{
|
||||
name: "with root slash",
|
||||
requestURL: "/namespaces/default/simple/id/?param1=test1¶m2=test2",
|
||||
expectedPath: "/",
|
||||
},
|
||||
{
|
||||
name: "with path",
|
||||
requestURL: "/namespaces/default/simple/id/a/different/path?param1=test1¶m2=test2",
|
||||
expectedPath: "a/different/path",
|
||||
expectedPath: "/a/different/path",
|
||||
},
|
||||
{
|
||||
name: "with path with trailing slash",
|
||||
requestURL: "/namespaces/default/simple/id/a/different/path/?param1=test1¶m2=test2",
|
||||
expectedPath: "/a/different/path/",
|
||||
},
|
||||
{
|
||||
name: "as subresource",
|
||||
requestURL: "/namespaces/default/simple/id/subresource/another/different/path?param1=test1¶m2=test2",
|
||||
expectedPath: "another/different/path",
|
||||
expectedPath: "/another/different/path",
|
||||
},
|
||||
{
|
||||
name: "cluster-scoped basic",
|
||||
|
@ -2155,13 +2165,13 @@ func TestGetWithOptions(t *testing.T) {
|
|||
name: "cluster-scoped basic with path",
|
||||
rootScoped: true,
|
||||
requestURL: "/simple/id/a/cluster/path?param1=test1¶m2=test2",
|
||||
expectedPath: "a/cluster/path",
|
||||
expectedPath: "/a/cluster/path",
|
||||
},
|
||||
{
|
||||
name: "cluster-scoped basic as subresource",
|
||||
rootScoped: true,
|
||||
requestURL: "/simple/id/subresource/another/cluster/path?param1=test1¶m2=test2",
|
||||
expectedPath: "another/cluster/path",
|
||||
expectedPath: "/another/cluster/path",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2556,7 +2566,7 @@ func TestConnectWithOptions(t *testing.T) {
|
|||
func TestConnectWithOptionsAndPath(t *testing.T) {
|
||||
responseText := "Hello World"
|
||||
itemID := "theID"
|
||||
testPath := "a/b/c/def"
|
||||
testPath := "/a/b/c/def"
|
||||
connectStorage := &ConnecterRESTStorage{
|
||||
connectHandler: &OutputConnect{
|
||||
response: responseText,
|
||||
|
@ -2572,7 +2582,7 @@ func TestConnectWithOptionsAndPath(t *testing.T) {
|
|||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect/" + testPath + "?param1=value1¶m2=value2")
|
||||
resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect" + testPath + "?param1=value1¶m2=value2")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
|
|
|
@ -212,7 +212,20 @@ func getRequestOptions(req *http.Request, scope RequestScope, into runtime.Objec
|
|||
if isSubresource {
|
||||
startingIndex = 3
|
||||
}
|
||||
newQuery[subpathKey] = []string{strings.Join(requestInfo.Parts[startingIndex:], "/")}
|
||||
|
||||
p := strings.Join(requestInfo.Parts[startingIndex:], "/")
|
||||
|
||||
// ensure non-empty subpaths correctly reflect a leading slash
|
||||
if len(p) > 0 && !strings.HasPrefix(p, "/") {
|
||||
p = "/" + p
|
||||
}
|
||||
|
||||
// ensure subpaths correctly reflect the presence of a trailing slash on the original request
|
||||
if strings.HasSuffix(requestInfo.Path, "/") && !strings.HasSuffix(p, "/") {
|
||||
p += "/"
|
||||
}
|
||||
|
||||
newQuery[subpathKey] = []string{p}
|
||||
query = newQuery
|
||||
}
|
||||
return scope.ParameterCodec.DecodeParameters(query, scope.Kind.GroupVersion(), into)
|
||||
|
|
Loading…
Reference in New Issue