/* * * 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 authorizerfactory import ( "bytes" "fmt" "k8s.io/apiserver/pkg/authentication/serviceaccount" iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer" "kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/utils/sliceutil" "k8s.io/klog" rbacv1 "k8s.io/api/rbac/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/authentication/user" rbacv1helpers "kubesphere.io/kubesphere/pkg/apis/rbac/v1" ) type RBACAuthorizer struct { am am.AccessManagementInterface } // authorizingVisitor short-circuits once allowed, and collects any resolution errors encountered type authorizingVisitor struct { requestAttributes authorizer.Attributes allowed bool reason string errors []error } func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool { if rule != nil && ruleAllows(v.requestAttributes, rule) { v.allowed = true v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String()) return false } if err != nil { v.errors = append(v.errors, err) } return true } type ruleAccumulator struct { rules []rbacv1.PolicyRule errors []error } func (r *ruleAccumulator) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool { if rule != nil { r.rules = append(r.rules, *rule) } if err != nil { r.errors = append(r.errors, err) } return true } func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) { ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes} r.visitRulesFor(requestAttributes, ruleCheckingVisitor.visit) if ruleCheckingVisitor.allowed { return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil } // Build a detailed log of the denial. // Make the whole block conditional so we don't do a lot of string-building we won't use. if klog.V(4) { var operation string if requestAttributes.IsResourceRequest() { b := &bytes.Buffer{} b.WriteString(`"`) b.WriteString(requestAttributes.GetVerb()) b.WriteString(`" resource "`) b.WriteString(requestAttributes.GetResource()) if len(requestAttributes.GetAPIGroup()) > 0 { b.WriteString(`.`) b.WriteString(requestAttributes.GetAPIGroup()) } if len(requestAttributes.GetSubresource()) > 0 { b.WriteString(`/`) b.WriteString(requestAttributes.GetSubresource()) } b.WriteString(`"`) if len(requestAttributes.GetName()) > 0 { b.WriteString(` named "`) b.WriteString(requestAttributes.GetName()) b.WriteString(`"`) } operation = b.String() } else { operation = fmt.Sprintf("%q nonResourceURL %q", requestAttributes.GetVerb(), requestAttributes.GetPath()) } var scope string if ns := requestAttributes.GetNamespace(); len(ns) > 0 { scope = fmt.Sprintf("in namespace %q", ns) } else if ws := requestAttributes.GetWorkspace(); len(ws) > 0 { scope = fmt.Sprintf("in workspace %q", ws) } else if cluster := requestAttributes.GetWorkspace(); len(cluster) > 0 { scope = fmt.Sprintf("in cluster %q", cluster) } else { scope = "global-wide" } klog.Infof("RBAC: no rules authorize user %q with groups %q to %s %s", requestAttributes.GetUser().GetName(), requestAttributes.GetUser().GetGroups(), operation, scope) } reason := "" if len(ruleCheckingVisitor.errors) > 0 { reason = fmt.Sprintf("RBAC: %v", utilerrors.NewAggregate(ruleCheckingVisitor.errors)) } return authorizer.DecisionNoOpinion, reason, nil } func NewRBACAuthorizer(am am.AccessManagementInterface) *RBACAuthorizer { return &RBACAuthorizer{am: am} } func ruleAllows(requestAttributes authorizer.Attributes, rule *rbacv1.PolicyRule) bool { if requestAttributes.IsResourceRequest() { combinedResource := requestAttributes.GetResource() if len(requestAttributes.GetSubresource()) > 0 { combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource() } return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) && rbacv1helpers.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) && rbacv1helpers.ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) && rbacv1helpers.ResourceNameMatches(rule, requestAttributes.GetName()) } return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) && rbacv1helpers.NonResourceURLMatches(rule, requestAttributes.GetPath()) } func (r *RBACAuthorizer) rulesFor(requestAttributes authorizer.Attributes) ([]rbacv1.PolicyRule, error) { visitor := &ruleAccumulator{} r.visitRulesFor(requestAttributes, visitor.visit) return visitor.rules, utilerrors.NewAggregate(visitor.errors) } func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) { if globalRoleBindings, err := r.am.ListGlobalRoleBindings(""); err != nil { if !visitor(nil, nil, err) { return } } else { sourceDescriber := &globalRoleBindingDescriber{} for _, globalRoleBinding := range globalRoleBindings { subjectIndex, applies := appliesTo(requestAttributes.GetUser(), globalRoleBinding.Subjects, "") if !applies { continue } rules, err := r.am.GetRoleReferenceRules(globalRoleBinding.RoleRef, "") if err != nil { if !visitor(nil, nil, err) { return } continue } sourceDescriber.binding = globalRoleBinding sourceDescriber.subject = &globalRoleBinding.Subjects[subjectIndex] for i := range rules { if !visitor(sourceDescriber, &rules[i], nil) { return } } } } if requestAttributes.GetResourceScope() == iamv1alpha2.WorkspaceScope { if workspaceRoleBindings, err := r.am.ListWorkspaceRoleBindings("", requestAttributes.GetWorkspace()); err != nil { if !visitor(nil, nil, err) { return } } else { sourceDescriber := &workspaceRoleBindingDescriber{} for _, workspaceRoleBinding := range workspaceRoleBindings { subjectIndex, applies := appliesTo(requestAttributes.GetUser(), workspaceRoleBinding.Subjects, "") if !applies { continue } rules, err := r.am.GetRoleReferenceRules(workspaceRoleBinding.RoleRef, "") if err != nil { if !visitor(nil, nil, err) { return } continue } sourceDescriber.binding = workspaceRoleBinding sourceDescriber.subject = &workspaceRoleBinding.Subjects[subjectIndex] for i := range rules { if !visitor(sourceDescriber, &rules[i], nil) { return } } } } } if requestAttributes.GetResourceScope() == iamv1alpha2.NamespaceScope { if roleBindings, err := r.am.ListRoleBindings("", requestAttributes.GetNamespace()); err != nil { if !visitor(nil, nil, err) { return } } else { sourceDescriber := &roleBindingDescriber{} for _, roleBinding := range roleBindings { subjectIndex, applies := appliesTo(requestAttributes.GetUser(), roleBinding.Subjects, requestAttributes.GetNamespace()) if !applies { continue } rules, err := r.am.GetRoleReferenceRules(roleBinding.RoleRef, requestAttributes.GetNamespace()) if err != nil { if !visitor(nil, nil, err) { return } continue } sourceDescriber.binding = roleBinding sourceDescriber.subject = &roleBinding.Subjects[subjectIndex] for i := range rules { if !visitor(sourceDescriber, &rules[i], nil) { return } } } } } 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 } rules, err := r.am.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") if err != nil { if !visitor(nil, nil, err) { return } continue } sourceDescriber.binding = clusterRoleBinding sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex] for i := range rules { if !visitor(sourceDescriber, &rules[i], nil) { return } } } } } // appliesTo returns whether any of the bindingSubjects applies to the specified subject, // and if true, the index of the first subject that applies func appliesTo(user user.Info, bindingSubjects []rbacv1.Subject, namespace string) (int, bool) { for i, bindingSubject := range bindingSubjects { if appliesToUser(user, bindingSubject, namespace) { return i, true } } return 0, false } func appliesToUser(user user.Info, subject rbacv1.Subject, namespace string) bool { switch subject.Kind { case rbacv1.UserKind: return user.GetName() == subject.Name case rbacv1.GroupKind: return sliceutil.HasString(user.GetGroups(), subject.Name) case rbacv1.ServiceAccountKind: // default the namespace to namespace we're working in if its available. This allows rolebindings that reference // SAs in th local namespace to avoid having to qualify them. saNamespace := namespace if len(subject.Namespace) > 0 { saNamespace = subject.Namespace } if len(saNamespace) == 0 { return false } // use a more efficient comparison for RBAC checking return serviceaccount.MatchesUsername(saNamespace, subject.Name, user.GetName()) default: return false } } type globalRoleBindingDescriber struct { binding *iamv1alpha2.GlobalRoleBinding subject *rbacv1.Subject } func (d *globalRoleBindingDescriber) String() string { return fmt.Sprintf("GlobalRoleBinding %q of %s %q to %s", d.binding.Name, d.binding.RoleRef.Kind, d.binding.RoleRef.Name, describeSubject(d.subject, ""), ) } type clusterRoleBindingDescriber struct { binding *rbacv1.ClusterRoleBinding subject *rbacv1.Subject } func (d *clusterRoleBindingDescriber) String() string { return fmt.Sprintf("ClusterRoleBinding %q of %s %q to %s", d.binding.Name, d.binding.RoleRef.Kind, d.binding.RoleRef.Name, describeSubject(d.subject, ""), ) } type workspaceRoleBindingDescriber struct { binding *iamv1alpha2.WorkspaceRoleBinding subject *rbacv1.Subject } func (d *workspaceRoleBindingDescriber) String() string { return fmt.Sprintf("GlobalRoleBinding %q of %s %q to %s", d.binding.Name, d.binding.RoleRef.Kind, d.binding.RoleRef.Name, describeSubject(d.subject, ""), ) } type roleBindingDescriber struct { binding *rbacv1.RoleBinding subject *rbacv1.Subject } func (d *roleBindingDescriber) String() string { return fmt.Sprintf("RoleBinding %q of %s %q to %s", d.binding.Name+"/"+d.binding.Namespace, d.binding.RoleRef.Kind, d.binding.RoleRef.Name, describeSubject(d.subject, d.binding.Namespace), ) } func describeSubject(s *rbacv1.Subject, bindingNamespace string) string { switch s.Kind { case rbacv1.ServiceAccountKind: if len(s.Namespace) > 0 { return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+s.Namespace) } return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+bindingNamespace) default: return fmt.Sprintf("%s %q", s.Kind, s.Name) } }