diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index c75ddeb2b718566396aaf47e9fd98bdd8436a191..a46518494fe7de68835b1385761cba5c384c1f89 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -38,6 +38,7 @@ import ( "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory" authorizationoptions "kubesphere.io/kubesphere/pkg/apiserver/authorization/options" "kubesphere.io/kubesphere/pkg/apiserver/authorization/path" + "kubesphere.io/kubesphere/pkg/apiserver/authorization/proxy" unionauthorizer "kubesphere.io/kubesphere/pkg/apiserver/authorization/union" apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config" "kubesphere.io/kubesphere/pkg/apiserver/dispatch" @@ -267,7 +268,7 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) { excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*", "/kapis/version"} pathAuthorizer, _ := path.NewAuthorizer(excludedPaths) amOperator := am.NewReadOnlyOperator(s.InformerFactory) - authorizers = unionauthorizer.New(pathAuthorizer, authorizerfactory.NewRBACAuthorizer(amOperator)) + authorizers = unionauthorizer.New(pathAuthorizer, proxy.NewAuthorizer(s.Config.MultiClusterOptions.Enable), authorizerfactory.NewRBACAuthorizer(amOperator)) } handler = filters.WithAuthorization(handler, authorizers) diff --git a/pkg/apiserver/authorization/authorizerfactory/rbac.go b/pkg/apiserver/authorization/authorizerfactory/rbac.go index a84d158754221945711bd1588523c4ef759e3162..7bad41792b2099ac9f7436e1afd9776deae68e8a 100644 --- a/pkg/apiserver/authorization/authorizerfactory/rbac.go +++ b/pkg/apiserver/authorization/authorizerfactory/rbac.go @@ -230,37 +230,6 @@ func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes, } } - if requestAttributes.GetResourceScope() == request.ClusterScope || requestAttributes.GetResourceScope() == request.NamespaceScope { - if clusterRoleBindings, err := r.am.ListClusterRoleBindings(""); err != nil { - if !visitor(nil, "", nil, err) { - return - } - } else { - sourceDescriber := &clusterRoleBindingDescriber{} - for _, clusterRoleBinding := range clusterRoleBindings { - subjectIndex, applies := appliesTo(requestAttributes.GetUser(), clusterRoleBinding.Subjects, "") - if !applies { - continue - } - regoPolicy, rules, err := r.am.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") - if err != nil { - visitor(nil, "", nil, err) - continue - } - sourceDescriber.binding = clusterRoleBinding - sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex] - if !visitor(sourceDescriber, regoPolicy, nil, nil) { - return - } - for i := range rules { - if !visitor(sourceDescriber, "", &rules[i], nil) { - return - } - } - } - } - } - if requestAttributes.GetResourceScope() == request.WorkspaceScope || requestAttributes.GetResourceScope() == request.NamespaceScope { var workspace string @@ -338,6 +307,35 @@ func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes, } } } + + if clusterRoleBindings, err := r.am.ListClusterRoleBindings(""); err != nil { + if !visitor(nil, "", nil, err) { + return + } + } else { + sourceDescriber := &clusterRoleBindingDescriber{} + for _, clusterRoleBinding := range clusterRoleBindings { + subjectIndex, applies := appliesTo(requestAttributes.GetUser(), clusterRoleBinding.Subjects, "") + if !applies { + continue + } + regoPolicy, rules, err := r.am.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") + if err != nil { + visitor(nil, "", nil, err) + continue + } + sourceDescriber.binding = clusterRoleBinding + sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex] + if !visitor(sourceDescriber, regoPolicy, nil, nil) { + return + } + for i := range rules { + if !visitor(sourceDescriber, "", &rules[i], nil) { + return + } + } + } + } } // appliesTo returns whether any of the bindingSubjects applies to the specified subject, diff --git a/pkg/apiserver/authorization/proxy/doc.go b/pkg/apiserver/authorization/proxy/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..adc9a30a53ffcb0d1f78e263f4c1134748076252 --- /dev/null +++ b/pkg/apiserver/authorization/proxy/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2020 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy diff --git a/pkg/apiserver/authorization/proxy/proxy.go b/pkg/apiserver/authorization/proxy/proxy.go new file mode 100644 index 0000000000000000000000000000000000000000..aae1c17e24413805b2e257076ff0be6effbfe182 --- /dev/null +++ b/pkg/apiserver/authorization/proxy/proxy.go @@ -0,0 +1,33 @@ +/* +Copyright 2020 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy + +import ( + "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer" +) + +// NewAuthorizer returns an authorizer which accepts cluster proxy request. +// If multi-cluster mode is enabled, request should authorize by target apiserver. +func NewAuthorizer(multiClusterEnabled bool) authorizer.Authorizer { + return authorizer.AuthorizerFunc(func(a authorizer.Attributes) (authorizer.Decision, string, error) { + // in multi cluster mode, the request will be dispatch. + if multiClusterEnabled && a.GetCluster() != "" { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionNoOpinion, "", nil + }) +} diff --git a/pkg/apiserver/authorization/proxy/proxy_test.go b/pkg/apiserver/authorization/proxy/proxy_test.go new file mode 100644 index 0000000000000000000000000000000000000000..51f45e8f2cd9b1502d228f1adbe49d42e56e5496 --- /dev/null +++ b/pkg/apiserver/authorization/proxy/proxy_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxy + +import ( + "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer" + "testing" +) + +func TestNewAuthorizer(t *testing.T) { + tests := []struct { + multiClusterEnabled bool + request authorizer.AttributesRecord + expectResult authorizer.Decision + }{ + { + multiClusterEnabled: false, + request: authorizer.AttributesRecord{ + Workspace: "ws", + Namespace: "ns", + KubernetesRequest: false, + ResourceRequest: false, + }, + expectResult: authorizer.DecisionNoOpinion, + }, + { + multiClusterEnabled: false, + request: authorizer.AttributesRecord{ + Cluster: "cluster1", + Workspace: "ws", + Namespace: "ns", + KubernetesRequest: false, + ResourceRequest: false, + }, + expectResult: authorizer.DecisionNoOpinion, + }, + { + multiClusterEnabled: true, + request: authorizer.AttributesRecord{ + Cluster: "cluster1", + Workspace: "ws", + Namespace: "ns", + KubernetesRequest: false, + ResourceRequest: false, + }, + expectResult: authorizer.DecisionAllow, + }, + { + multiClusterEnabled: true, + request: authorizer.AttributesRecord{ + Workspace: "ws", + Namespace: "ns", + KubernetesRequest: false, + ResourceRequest: false, + }, + expectResult: authorizer.DecisionNoOpinion, + }, + } + for i, test := range tests { + a := NewAuthorizer(test.multiClusterEnabled) + result, _, _ := a.Authorize(test.request) + if result != test.expectResult { + t.Errorf("case %d, got %#v, expected %#v", i, result, test.expectResult) + } + } +} diff --git a/pkg/apiserver/request/requestinfo.go b/pkg/apiserver/request/requestinfo.go index cae39643df821a973dd16541d8a73a87d41679af..7e0da44978adcaa7abdb4f006a36b2901b866be7 100644 --- a/pkg/apiserver/request/requestinfo.go +++ b/pkg/apiserver/request/requestinfo.go @@ -299,10 +299,6 @@ const ( func (r *RequestInfoFactory) resolveResourceScope(request RequestInfo) string { - if request.Cluster != "" { - return ClusterScope - } - if request.Namespace != "" { return NamespaceScope } @@ -311,5 +307,5 @@ func (r *RequestInfoFactory) resolveResourceScope(request RequestInfo) string { return WorkspaceScope } - return GlobalScope + return ClusterScope } diff --git a/pkg/controller/workspacetemplate/workspacetemplate_controller.go b/pkg/controller/workspacetemplate/workspacetemplate_controller.go index 10461e3569468bd84b4e7d5ff38b938e919f0615..50838022676f7088b8a9933f908ebe6a38e372cb 100644 --- a/pkg/controller/workspacetemplate/workspacetemplate_controller.go +++ b/pkg/controller/workspacetemplate/workspacetemplate_controller.go @@ -447,6 +447,7 @@ func (r *Controller) initRoles(workspace *tenantv1alpha2.WorkspaceTemplate) erro } // make sure workspace label always exist role.Labels[tenantv1alpha1.WorkspaceLabel] = workspace.Name + role.Name = roleName old, err := r.workspaceRoleLister.Get(roleName) if err != nil { if errors.IsNotFound(err) {