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 }