Files
kubesphere/pkg/apiserver/authorization/authorizerfactory/rbac.go
hongming 7db2ba662c migrate legacy API
Signed-off-by: hongming <talonwan@yunify.com>
2020-04-28 00:45:12 +08:00

385 lines
12 KiB
Go

/*
*
* 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)
}
}