diff --git a/pkg/api/types.go b/pkg/api/types.go index b9c9024fe6d8b92f7c26939bea7b5cb059fe3b18..8b1266f32529f4bd661bce17d3e4008292b992b8 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -155,4 +155,7 @@ const ( ResourceKindeS2iRun = "s2iruns" ResourceKindS2iBuilder = "s2ibuilders" ResourceKindApplication = "applications" + + WorkspaceNone = "" + ClusterNone = "" ) diff --git a/pkg/apis/tower/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/tower/v1alpha1/zz_generated.deepcopy.go index 2cfab57f9f7d0cd30b5c080363bc4f4d177c5ca2..87a6e5cd3c9150f1b0ae81f32210d9e9fb8df12b 100644 --- a/pkg/apis/tower/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/tower/v1alpha1/zz_generated.deepcopy.go @@ -1,6 +1,7 @@ // +build !ignore_autogenerated /* +Copyright 2019 The KubeSphere authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -124,6 +125,11 @@ func (in *AgentStatus) DeepCopyInto(out *AgentStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.KubeConfig != nil { + in, out := &in.KubeConfig, &out.KubeConfig + *out = make([]byte, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentStatus. diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index c0b77784821490b4771b031c66b9b009a862bc62..ff72bee01727ef4cb8637ac730b9b7eb3898620c 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -27,6 +27,7 @@ import ( ksruntime "kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/informers" devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2" + configv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/config/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2" loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2" monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2" @@ -35,7 +36,6 @@ import ( operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2" resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha3" - "kubesphere.io/kubesphere/pkg/kapis/serverconfig/v1alpha2" servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2" terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2" "kubesphere.io/kubesphere/pkg/models/iam/am" @@ -134,7 +134,7 @@ func (s *APIServer) PrepareRun() error { } func (s *APIServer) installKubeSphereAPIs() { - urlruntime.Must(v1alpha2.AddToContainer(s.container, s.Config)) + urlruntime.Must(configv1alpha2.AddToContainer(s.container, s.Config)) urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory)) urlruntime.Must(loggingv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.LoggingClient)) urlruntime.Must(monitoringv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.MonitoringClient)) @@ -184,20 +184,20 @@ func (s *APIServer) buildHandlerChain() { } handler := s.Server.Handler - handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{}) handler = filters.WithMultipleClusterDispatcher(handler, dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Tower().V1alpha1().Agents().Lister())) excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*"} pathAuthorizer, _ := path.NewAuthorizer(excludedPaths) - authorizer := unionauthorizer.New(pathAuthorizer, - authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator())) - handler = filters.WithAuthorization(handler, authorizer) + // union authorizers are ordered, don't change the order here + authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator())) + handler = filters.WithAuthorization(handler, authorizers) + + // authenticators are unordered authn := unionauth.New(anonymous.NewAuthenticator(), basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())), - bearertoken.New(jwttoken.NewTokenAuthenticator( - token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient)))) + bearertoken.New(jwttoken.NewTokenAuthenticator(token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient)))) handler = filters.WithAuthentication(handler, authn) handler = filters.WithRequestInfo(handler, requestInfoResolver) s.Server.Handler = handler diff --git a/pkg/apiserver/authorization/path/path.go b/pkg/apiserver/authorization/path/path.go index 4df9c41a5f98f7bf2a649f6f882284dba1cda48d..435cca19823aea445c3bfb9ca62a8ed7f6c97ffa 100644 --- a/pkg/apiserver/authorization/path/path.go +++ b/pkg/apiserver/authorization/path/path.go @@ -47,10 +47,6 @@ func NewAuthorizer(alwaysAllowPaths []string) (authorizer.Authorizer, error) { } return authorizer.AuthorizerFunc(func(a authorizer.Attributes) (authorizer.Decision, string, error) { - if a.IsResourceRequest() { - return authorizer.DecisionNoOpinion, "", nil - } - pth := strings.TrimPrefix(a.GetPath(), "/") if paths.Has(pth) { return authorizer.DecisionAllow, "", nil diff --git a/pkg/apiserver/dispatch/dispatch.go b/pkg/apiserver/dispatch/dispatch.go index da9628b38f4f69c67cd3c56e89c22c433f4dad49..42385c55cb14975a840c4897dbdcb9f6d5aa4893 100644 --- a/pkg/apiserver/dispatch/dispatch.go +++ b/pkg/apiserver/dispatch/dispatch.go @@ -54,7 +54,7 @@ func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request, han } u := *req.URL - u.Host = agent.Spec.Proxy + u.Host = fmt.Sprintf("%s:%d", agent.Spec.Proxy, agent.Spec.KubeSphereAPIServerPort) u.Path = strings.Replace(u.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1) httpProxy := proxy.NewUpgradeAwareHandler(&u, http.DefaultTransport, true, false, c) diff --git a/pkg/apiserver/filters/authorization.go b/pkg/apiserver/filters/authorization.go index fb63f97ba7e9812ad28b220dc2821cd7d594c354..44c9ba4dbd9ae2581569594321f78cba7bf34dc5 100644 --- a/pkg/apiserver/filters/authorization.go +++ b/pkg/apiserver/filters/authorization.go @@ -13,23 +13,23 @@ import ( ) // WithAuthorization passes all authorized requests on to handler, and returns forbidden error otherwise. -func WithAuthorization(handler http.Handler, a authorizer.Authorizer) http.Handler { - if a == nil { +func WithAuthorization(handler http.Handler, authorizers authorizer.Authorizer) http.Handler { + if authorizers == nil { klog.Warningf("Authorization is disabled") return handler } - serializer := serializer.NewCodecFactory(runtime.NewScheme()).WithoutConversion() + defaultSerializer := serializer.NewCodecFactory(runtime.NewScheme()).WithoutConversion() return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() - attributes, err := GetAuthorizerAttributes(ctx) + attributes, err := getAuthorizerAttributes(ctx) if err != nil { responsewriters.InternalError(w, req, err) } - authorized, reason, err := a.Authorize(attributes) + authorized, reason, err := authorizers.Authorize(attributes) if authorized == authorizer.DecisionAllow { handler.ServeHTTP(w, req) return @@ -41,11 +41,11 @@ func WithAuthorization(handler http.Handler, a authorizer.Authorizer) http.Handl } klog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason) - responsewriters.Forbidden(ctx, attributes, w, req, reason, serializer) + responsewriters.Forbidden(ctx, attributes, w, req, reason, defaultSerializer) }) } -func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) { +func getAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) { attribs := authorizer.AttributesRecord{} user, ok := request.UserFrom(ctx) diff --git a/pkg/apiserver/request/requestinfo.go b/pkg/apiserver/request/requestinfo.go index 7a39a97861352928c29bec808d0a5f393695424f..1645be19ab5e7b063f6977b8bf29614ed0bf440e 100644 --- a/pkg/apiserver/request/requestinfo.go +++ b/pkg/apiserver/request/requestinfo.go @@ -8,6 +8,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog" + "kubesphere.io/kubesphere/pkg/api" "net/http" "strings" @@ -88,6 +89,8 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er Path: req.URL.Path, Verb: req.Method, }, + Workspace: api.WorkspaceNone, + Cluster: api.ClusterNone, } defer func() { @@ -123,16 +126,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er requestInfo.APIVersion = currentParts[0] currentParts = currentParts[1:] - if currentParts[0] == "clusters" { - requestInfo.Cluster = currentParts[1] - currentParts = currentParts[2:] - } - - if currentParts[0] == "workspaces" { - requestInfo.Workspace = currentParts[1] - currentParts = currentParts[2:] - } - if specialVerbs.Has(currentParts[0]) { if len(currentParts) < 2 { return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url: %v", req.URL) @@ -157,6 +150,26 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er } } + // URL forms: /clusters/{cluster}/* + if currentParts[0] == "clusters" { + if len(currentParts) > 1 { + requestInfo.Cluster = currentParts[1] + } + if len(currentParts) > 2 { + currentParts = currentParts[2:] + } + } + + // URL forms: /workspaces/{workspace}/* + if currentParts[0] == "workspaces" { + if len(currentParts) > 1 { + requestInfo.Workspace = currentParts[1] + } + if len(currentParts) > 2 { + currentParts = currentParts[2:] + } + } + // URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind if currentParts[0] == "namespaces" { if len(currentParts) > 1 { diff --git a/pkg/apiserver/request/requestinfo_test.go b/pkg/apiserver/request/requestinfo_test.go index 4348184cc5b3098ac1a3807a36dad6caac037136..8e87a116ab9b14f68837c2a8b462ed093ad4134d 100644 --- a/pkg/apiserver/request/requestinfo_test.go +++ b/pkg/apiserver/request/requestinfo_test.go @@ -162,6 +162,19 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) { expectedCluster: "", expectedKubernetesRequest: false, }, + { + name: "", + url: "/kapis/tenant.kubesphere.io/v1alpha2/workspaces", + method: http.MethodGet, + expectedErr: nil, + expectedVerb: "list", + expectedNamespace: "", + expectedCluster: "", + expectedWorkspace: "", + expectedKubernetesRequest: false, + expectedIsResourceRequest: true, + expectedResource: "workspaces", + }, { name: "kubesphere api without clusters", url: "/kapis/foo/bar/", @@ -180,39 +193,42 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) { requestInfoResolver := newTestRequestInfoResolver() for _, test := range tests { - req, err := http.NewRequest(test.method, test.url, nil) - if err != nil { - t.Fatal(err) - } - requestInfo, err := requestInfoResolver.NewRequestInfo(req) - - if err != nil { - if test.expectedErr != err { - t.Errorf("%s: expected error %v, actual %v", test.name, test.expectedErr, err) - } - } else { - if test.expectedVerb != requestInfo.Verb { - t.Errorf("%s: expected verb %v, actual %+v", test.name, test.expectedVerb, requestInfo.Verb) - } - if test.expectedResource != requestInfo.Resource { - t.Errorf("%s: expected resource %v, actual %+v", test.name, test.expectedResource, requestInfo.Resource) - } - if test.expectedIsResourceRequest != requestInfo.IsResourceRequest { - t.Errorf("%s: expected is resource request %v, actual %+v", test.name, test.expectedIsResourceRequest, requestInfo.IsResourceRequest) - } - if test.expectedCluster != requestInfo.Cluster { - t.Errorf("%s: expected cluster %v, actual %+v", test.name, test.expectedCluster, requestInfo.Cluster) - } - if test.expectedWorkspace != requestInfo.Workspace { - t.Errorf("%s: expected workspace %v, actual %+v", test.name, test.expectedWorkspace, requestInfo.Workspace) - } - if test.expectedNamespace != requestInfo.Namespace { - t.Errorf("%s: expected namespace %v, actual %+v", test.name, test.expectedNamespace, requestInfo.Namespace) + t.Run(test.url, func(t *testing.T) { + req, err := http.NewRequest(test.method, test.url, nil) + if err != nil { + t.Fatal(err) } + requestInfo, err := requestInfoResolver.NewRequestInfo(req) - if test.expectedKubernetesRequest != requestInfo.IsKubernetesRequest { - t.Errorf("%s: expected kubernetes request %v, actual %+v", test.name, test.expectedKubernetesRequest, requestInfo.IsKubernetesRequest) + if err != nil { + if test.expectedErr != err { + t.Errorf("%s: expected error %v, actual %v", test.name, test.expectedErr, err) + } + } else { + if test.expectedVerb != requestInfo.Verb { + t.Errorf("%s: expected verb %v, actual %+v", test.name, test.expectedVerb, requestInfo.Verb) + } + if test.expectedResource != requestInfo.Resource { + t.Errorf("%s: expected resource %v, actual %+v", test.name, test.expectedResource, requestInfo.Resource) + } + if test.expectedIsResourceRequest != requestInfo.IsResourceRequest { + t.Errorf("%s: expected is resource request %v, actual %+v", test.name, test.expectedIsResourceRequest, requestInfo.IsResourceRequest) + } + if test.expectedCluster != requestInfo.Cluster { + t.Errorf("%s: expected cluster %v, actual %+v", test.name, test.expectedCluster, requestInfo.Cluster) + } + if test.expectedWorkspace != requestInfo.Workspace { + t.Errorf("%s: expected workspace %v, actual %+v", test.name, test.expectedWorkspace, requestInfo.Workspace) + } + if test.expectedNamespace != requestInfo.Namespace { + t.Errorf("%s: expected namespace %v, actual %+v", test.name, test.expectedNamespace, requestInfo.Namespace) + } + + if test.expectedKubernetesRequest != requestInfo.IsKubernetesRequest { + t.Errorf("%s: expected kubernetes request %v, actual %+v", test.name, test.expectedKubernetesRequest, requestInfo.IsKubernetesRequest) + } } - } + }) + } } diff --git a/pkg/kapis/serverconfig/v1alpha2/register.go b/pkg/kapis/config/v1alpha2/register.go similarity index 100% rename from pkg/kapis/serverconfig/v1alpha2/register.go rename to pkg/kapis/config/v1alpha2/register.go