From 845887a32513a664a16d4affb825df9b5dc4e403 Mon Sep 17 00:00:00 2001 From: zryfish Date: Tue, 2 Jun 2020 14:26:31 +0800 Subject: [PATCH] fix proxy bug (#2146) --- pkg/apiserver/dispatch/dispatch.go | 43 +++++++++++----------- pkg/kapis/cluster/v1alpha1/handler.go | 34 +++++++++++++---- pkg/kapis/cluster/v1alpha1/handler_test.go | 2 +- 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/pkg/apiserver/dispatch/dispatch.go b/pkg/apiserver/dispatch/dispatch.go index fc8c87444..32165dc2e 100644 --- a/pkg/apiserver/dispatch/dispatch.go +++ b/pkg/apiserver/dispatch/dispatch.go @@ -39,6 +39,8 @@ import ( const proxyURLFormat = "/api/v1/namespaces/kubesphere-system/services/:ks-apiserver:/proxy%s" // Dispatcher defines how to forward request to designated cluster based on cluster name +// This should only be used in host cluster when multicluster mode enabled, use in any other cases may cause +// unexpected behavior type Dispatcher interface { Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler) } @@ -127,35 +129,32 @@ func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request, han transport := http.DefaultTransport + // change request host to actually cluster hosts u := *req.URL u.Path = strings.Replace(u.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1) - // change request host to actually cluster hosts - if info.IsKubernetesRequest { - u.Host = innCluster.kubernetesURL.Host + // if cluster connection is direct and kubesphere apiserver endpoint is empty + // we use kube-apiserver proxy way + if cluster.Spec.Connection.Type == clusterv1alpha1.ConnectionTypeDirect && + len(cluster.Spec.Connection.KubeSphereAPIEndpoint) == 0 { + u.Scheme = innCluster.kubernetesURL.Scheme + u.Host = innCluster.kubernetesURL.Host + u.Path = fmt.Sprintf(proxyURLFormat, u.Path) + transport = innCluster.transport + + // The reason we need this is kube-apiserver doesn't behave like a standard proxy, it will strip + // authorization header of proxy requests. Use custom header to avoid stripping by kube-apiserver. + // https://github.com/kubernetes/kubernetes/issues/38775#issuecomment-277915961 + // We first copy req.Header['Authorization'] to req.Header['X-KubeSphere-Authorization'] before sending + // designated cluster kube-apiserver, then copy req.Header['X-KubeSphere-Authorization'] to + // req.Header['Authorization'] before authentication. + req.Header.Set("X-KubeSphere-Authorization", req.Header.Get("Authorization")) } else { + // everything else goes to ks-apiserver, since our ks-apiserver has the ability to proxy kube-apiserver requests + u.Host = innCluster.kubesphereURL.Host u.Scheme = innCluster.kubesphereURL.Scheme - - // if cluster connection is direct and kubesphere apiserver endpoint is empty - // we use kube-apiserver proxy way - if cluster.Spec.Connection.Type == clusterv1alpha1.ConnectionTypeDirect && - len(cluster.Spec.Connection.KubeSphereAPIEndpoint) == 0 { - - u.Scheme = innCluster.kubernetesURL.Scheme - u.Host = innCluster.kubernetesURL.Host - u.Path = fmt.Sprintf(proxyURLFormat, u.Path) - transport = innCluster.transport - - // The reason we need this is kube-apiserver doesn't behave like a standard proxy, it will strip - // authorization header of proxy requests. Use custom header to avoid stripping by kube-apiserver. - // https://github.com/kubernetes/kubernetes/issues/38775#issuecomment-277915961 - // We first copy req.Header['Authorization'] to req.Header['X-KubeSphere-Authorization'] before sending - // designated cluster kube-apiserver, then copy req.Header['X-KubeSphere-Authorization'] to - // req.Header['Authorization'] before authentication. - req.Header.Set("X-KubeSphere-Authorization", req.Header.Get("Authorization")) - } } httpProxy := proxy.NewUpgradeAwareHandler(&u, transport, false, false, c) diff --git a/pkg/kapis/cluster/v1alpha1/handler.go b/pkg/kapis/cluster/v1alpha1/handler.go index 41a945a4b..e6f654597 100644 --- a/pkg/kapis/cluster/v1alpha1/handler.go +++ b/pkg/kapis/cluster/v1alpha1/handler.go @@ -243,7 +243,7 @@ func (h *handler) ValidateCluster(request *restful.Request, response *restful.Re return } - _, err = validateKubeSphereAPIServer(cluster.Spec.Connection.KubeSphereAPIEndpoint) + _, err = validateKubeSphereAPIServer(cluster.Spec.Connection.KubeSphereAPIEndpoint, cluster.Spec.Connection.KubeConfig) if err != nil { api.HandleBadRequest(response, request, fmt.Errorf("unable validate kubesphere endpoint, %v", err)) return @@ -286,16 +286,36 @@ func loadKubeConfigFromBytes(kubeconfig []byte) (*rest.Config, error) { } // validateKubeSphereAPIServer uses version api to check the accessibility -func validateKubeSphereAPIServer(ksEndpoint string) (*version.Info, error) { - _, err := url.Parse(ksEndpoint) - if err != nil { - return nil, err +// If ksEndpoint is empty, use +func validateKubeSphereAPIServer(ksEndpoint string, kubeconfig []byte) (*version.Info, error) { + if len(ksEndpoint) == 0 && len(kubeconfig) == 0 { + return nil, fmt.Errorf("neither kubesphere api endpoint nor kubeconfig was provided") + } + + client := http.Client{ + Timeout: defaultTimeout, } path := fmt.Sprintf("%s/kapis/version", ksEndpoint) - client := http.Client{ - Timeout: defaultTimeout, + if len(ksEndpoint) != 0 { + _, err := url.Parse(ksEndpoint) + if err != nil { + return nil, err + } + } else { + config, err := loadKubeConfigFromBytes(kubeconfig) + if err != nil { + return nil, err + } + + transport, err := rest.TransportFor(config) + if err != nil { + return nil, err + } + + client.Transport = transport + path = fmt.Sprintf("%s/api/v1/namespaces/kubesphere-system/services/:ks-apiserver:/proxy/kapis/version", config.Host) } response, err := client.Get(path) diff --git a/pkg/kapis/cluster/v1alpha1/handler_test.go b/pkg/kapis/cluster/v1alpha1/handler_test.go index 4da3ec6cc..81545fd18 100644 --- a/pkg/kapis/cluster/v1alpha1/handler_test.go +++ b/pkg/kapis/cluster/v1alpha1/handler_test.go @@ -263,7 +263,7 @@ func TestValidateKubeSphereEndpoint(t *testing.T) { svr := httptest.NewServer(http.HandlerFunc(endpoint)) defer svr.Close() - got, err := validateKubeSphereAPIServer(svr.URL) + got, err := validateKubeSphereAPIServer(svr.URL, nil) if err != nil { t.Fatal(err) }