k8s.io/apiserver@v0.31.1/pkg/util/flowcontrol/rule.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package flowcontrol
    18  
    19  import (
    20  	"strings"
    21  
    22  	flowcontrol "k8s.io/api/flowcontrol/v1"
    23  	"k8s.io/apiserver/pkg/authentication/serviceaccount"
    24  	"k8s.io/apiserver/pkg/authentication/user"
    25  	"k8s.io/apiserver/pkg/endpoints/request"
    26  )
    27  
    28  // Tests whether a given request and FlowSchema match.  Nobody mutates
    29  // either input.
    30  func matchesFlowSchema(digest RequestDigest, flowSchema *flowcontrol.FlowSchema) bool {
    31  	for _, policyRule := range flowSchema.Spec.Rules {
    32  		if matchesPolicyRule(digest, &policyRule) {
    33  			return true
    34  		}
    35  	}
    36  	return false
    37  }
    38  
    39  func matchesPolicyRule(digest RequestDigest, policyRule *flowcontrol.PolicyRulesWithSubjects) bool {
    40  	if !matchesASubject(digest.User, policyRule.Subjects) {
    41  		return false
    42  	}
    43  	if digest.RequestInfo.IsResourceRequest {
    44  		return matchesAResourceRule(digest.RequestInfo, policyRule.ResourceRules)
    45  	}
    46  	return matchesANonResourceRule(digest.RequestInfo, policyRule.NonResourceRules)
    47  }
    48  
    49  func matchesASubject(user user.Info, subjects []flowcontrol.Subject) bool {
    50  	for _, subject := range subjects {
    51  		if matchesSubject(user, subject) {
    52  			return true
    53  		}
    54  	}
    55  	return false
    56  }
    57  
    58  func matchesSubject(user user.Info, subject flowcontrol.Subject) bool {
    59  	switch subject.Kind {
    60  	case flowcontrol.SubjectKindUser:
    61  		return subject.User != nil && (subject.User.Name == flowcontrol.NameAll || subject.User.Name == user.GetName())
    62  	case flowcontrol.SubjectKindGroup:
    63  		if subject.Group == nil {
    64  			return false
    65  		}
    66  		seek := subject.Group.Name
    67  		if seek == "*" {
    68  			return true
    69  		}
    70  		for _, userGroup := range user.GetGroups() {
    71  			if userGroup == seek {
    72  				return true
    73  			}
    74  		}
    75  		return false
    76  	case flowcontrol.SubjectKindServiceAccount:
    77  		if subject.ServiceAccount == nil {
    78  			return false
    79  		}
    80  		if subject.ServiceAccount.Name == flowcontrol.NameAll {
    81  			return serviceAccountMatchesNamespace(subject.ServiceAccount.Namespace, user.GetName())
    82  		}
    83  		return serviceaccount.MatchesUsername(subject.ServiceAccount.Namespace, subject.ServiceAccount.Name, user.GetName())
    84  	default:
    85  		return false
    86  	}
    87  }
    88  
    89  // serviceAccountMatchesNamespace checks whether the provided service account username matches the namespace, without
    90  // allocating. Use this when checking a service account namespace against a known string.
    91  // This is copied from `k8s.io/apiserver/pkg/authentication/serviceaccount::MatchesUsername` and simplified to not check the name part.
    92  func serviceAccountMatchesNamespace(namespace string, username string) bool {
    93  	const (
    94  		ServiceAccountUsernamePrefix    = "system:serviceaccount:"
    95  		ServiceAccountUsernameSeparator = ":"
    96  	)
    97  	if !strings.HasPrefix(username, ServiceAccountUsernamePrefix) {
    98  		return false
    99  	}
   100  	username = username[len(ServiceAccountUsernamePrefix):]
   101  
   102  	if !strings.HasPrefix(username, namespace) {
   103  		return false
   104  	}
   105  	username = username[len(namespace):]
   106  
   107  	return strings.HasPrefix(username, ServiceAccountUsernameSeparator)
   108  }
   109  
   110  func matchesAResourceRule(ri *request.RequestInfo, rules []flowcontrol.ResourcePolicyRule) bool {
   111  	for _, rr := range rules {
   112  		if matchesResourcePolicyRule(ri, rr) {
   113  			return true
   114  		}
   115  	}
   116  	return false
   117  }
   118  
   119  func matchesResourcePolicyRule(ri *request.RequestInfo, policyRule flowcontrol.ResourcePolicyRule) bool {
   120  	if !matchPolicyRuleVerb(policyRule.Verbs, ri.Verb) {
   121  		return false
   122  	}
   123  	if !matchPolicyRuleResource(policyRule.Resources, ri.Resource, ri.Subresource) {
   124  		return false
   125  	}
   126  	if !matchPolicyRuleAPIGroup(policyRule.APIGroups, ri.APIGroup) {
   127  		return false
   128  	}
   129  	if len(ri.Namespace) == 0 {
   130  		return policyRule.ClusterScope
   131  	}
   132  	return containsString(ri.Namespace, policyRule.Namespaces, flowcontrol.NamespaceEvery)
   133  }
   134  
   135  func matchesANonResourceRule(ri *request.RequestInfo, rules []flowcontrol.NonResourcePolicyRule) bool {
   136  	for _, rr := range rules {
   137  		if matchesNonResourcePolicyRule(ri, rr) {
   138  			return true
   139  		}
   140  	}
   141  	return false
   142  }
   143  
   144  func matchesNonResourcePolicyRule(ri *request.RequestInfo, policyRule flowcontrol.NonResourcePolicyRule) bool {
   145  	if !matchPolicyRuleVerb(policyRule.Verbs, ri.Verb) {
   146  		return false
   147  	}
   148  	return matchPolicyRuleNonResourceURL(policyRule.NonResourceURLs, ri.Path)
   149  }
   150  
   151  func matchPolicyRuleVerb(policyRuleVerbs []string, requestVerb string) bool {
   152  	return containsString(requestVerb, policyRuleVerbs, flowcontrol.VerbAll)
   153  }
   154  
   155  func matchPolicyRuleNonResourceURL(policyRuleRequestURLs []string, requestPath string) bool {
   156  	for _, rulePath := range policyRuleRequestURLs {
   157  		if rulePath == flowcontrol.NonResourceAll || rulePath == requestPath {
   158  			return true
   159  		}
   160  		rulePrefix := strings.TrimSuffix(rulePath, "*")
   161  		if !strings.HasSuffix(rulePrefix, "/") {
   162  			rulePrefix = rulePrefix + "/"
   163  		}
   164  		if strings.HasPrefix(requestPath, rulePrefix) {
   165  			return true
   166  		}
   167  	}
   168  	return false
   169  }
   170  
   171  func matchPolicyRuleAPIGroup(policyRuleAPIGroups []string, requestAPIGroup string) bool {
   172  	return containsString(requestAPIGroup, policyRuleAPIGroups, flowcontrol.APIGroupAll)
   173  }
   174  
   175  func rsJoin(requestResource, requestSubresource string) string {
   176  	seekString := requestResource
   177  	if requestSubresource != "" {
   178  		seekString = requestResource + "/" + requestSubresource
   179  	}
   180  	return seekString
   181  }
   182  
   183  func matchPolicyRuleResource(policyRuleRequestResources []string, requestResource, requestSubresource string) bool {
   184  	return containsString(rsJoin(requestResource, requestSubresource), policyRuleRequestResources, flowcontrol.ResourceAll)
   185  }
   186  
   187  // containsString returns true if either `x` or `wildcard` is in
   188  // `list`.  The wildcard is not a pattern to match against `x`; rather
   189  // the presence of the wildcard in the list is the caller's way of
   190  // saying that all values of `x` should match the list.  This function
   191  // assumes that if `wildcard` is in `list` then it is the only member
   192  // of the list, which is enforced by validation.
   193  func containsString(x string, list []string, wildcard string) bool {
   194  	if len(list) == 1 && list[0] == wildcard {
   195  		return true
   196  	}
   197  	for _, y := range list {
   198  		if x == y {
   199  			return true
   200  		}
   201  	}
   202  	return false
   203  }