k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/matching/matching.go (about) 1 /* 2 Copyright 2022 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 matching 18 19 import ( 20 "fmt" 21 22 v1 "k8s.io/api/admissionregistration/v1" 23 corev1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 "k8s.io/apiserver/pkg/admission" 26 "k8s.io/client-go/kubernetes" 27 listersv1 "k8s.io/client-go/listers/core/v1" 28 29 "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace" 30 "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object" 31 "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules" 32 ) 33 34 type MatchCriteria interface { 35 namespace.NamespaceSelectorProvider 36 object.ObjectSelectorProvider 37 38 GetMatchResources() v1.MatchResources 39 } 40 41 // Matcher decides if a request matches against matchCriteria 42 type Matcher struct { 43 namespaceMatcher *namespace.Matcher 44 objectMatcher *object.Matcher 45 } 46 47 func (m *Matcher) GetNamespace(name string) (*corev1.Namespace, error) { 48 return m.namespaceMatcher.GetNamespace(name) 49 } 50 51 // NewMatcher initialize the matcher with dependencies requires 52 func NewMatcher( 53 namespaceLister listersv1.NamespaceLister, 54 client kubernetes.Interface, 55 ) *Matcher { 56 return &Matcher{ 57 namespaceMatcher: &namespace.Matcher{ 58 NamespaceLister: namespaceLister, 59 Client: client, 60 }, 61 objectMatcher: &object.Matcher{}, 62 } 63 } 64 65 // ValidateInitialization verify if the matcher is ready before use 66 func (m *Matcher) ValidateInitialization() error { 67 if err := m.namespaceMatcher.Validate(); err != nil { 68 return fmt.Errorf("namespaceMatcher is not properly setup: %v", err) 69 } 70 return nil 71 } 72 73 func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) { 74 matches, matchNsErr := m.namespaceMatcher.MatchNamespaceSelector(criteria, attr) 75 // Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario. 76 if !matches && matchNsErr == nil { 77 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil 78 } 79 80 matches, matchObjErr := m.objectMatcher.MatchObjectSelector(criteria, attr) 81 // Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario. 82 if !matches && matchObjErr == nil { 83 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil 84 } 85 86 matchResources := criteria.GetMatchResources() 87 matchPolicy := matchResources.MatchPolicy 88 if isExcluded, _, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil { 89 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, err 90 } 91 92 var ( 93 isMatch bool 94 matchResource schema.GroupVersionResource 95 matchKind schema.GroupVersionKind 96 matchErr error 97 ) 98 if len(matchResources.ResourceRules) == 0 { 99 isMatch = true 100 matchKind = attr.GetKind() 101 matchResource = attr.GetResource() 102 } else { 103 isMatch, matchResource, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o) 104 } 105 if matchErr != nil { 106 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchErr 107 } 108 if !isMatch { 109 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil 110 } 111 112 // now that we know this applies to this request otherwise, if there were selector errors, return them 113 if matchNsErr != nil { 114 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchNsErr 115 } 116 if matchObjErr != nil { 117 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchObjErr 118 } 119 120 return true, matchResource, matchKind, nil 121 } 122 123 func matchesResourceRules(namedRules []v1.NamedRuleWithOperations, matchPolicy *v1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) { 124 matchKind := attr.GetKind() 125 matchResource := attr.GetResource() 126 127 for _, namedRule := range namedRules { 128 rule := v1.RuleWithOperations(namedRule.RuleWithOperations) 129 ruleMatcher := rules.Matcher{ 130 Rule: rule, 131 Attr: attr, 132 } 133 if !ruleMatcher.Matches() { 134 continue 135 } 136 // an empty name list always matches 137 if len(namedRule.ResourceNames) == 0 { 138 return true, matchResource, matchKind, nil 139 } 140 // TODO: GetName() can return an empty string if the user is relying on 141 // the API server to generate the name... figure out what to do for this edge case 142 name := attr.GetName() 143 for _, matchedName := range namedRule.ResourceNames { 144 if name == matchedName { 145 return true, matchResource, matchKind, nil 146 } 147 } 148 } 149 150 // if match policy is undefined or exact, don't perform fuzzy matching 151 // note that defaulting to fuzzy matching is set by the API 152 if matchPolicy == nil || *matchPolicy == v1.Exact { 153 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil 154 } 155 156 attrWithOverride := &attrWithResourceOverride{Attributes: attr} 157 equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource()) 158 for _, namedRule := range namedRules { 159 for _, equivalent := range equivalents { 160 if equivalent == attr.GetResource() { 161 // we have already checked the original resource 162 continue 163 } 164 attrWithOverride.resource = equivalent 165 rule := v1.RuleWithOperations(namedRule.RuleWithOperations) 166 m := rules.Matcher{ 167 Rule: rule, 168 Attr: attrWithOverride, 169 } 170 if !m.Matches() { 171 continue 172 } 173 matchKind = o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource()) 174 if matchKind.Empty() { 175 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent) 176 } 177 // an empty name list always matches 178 if len(namedRule.ResourceNames) == 0 { 179 return true, equivalent, matchKind, nil 180 } 181 182 // TODO: GetName() can return an empty string if the user is relying on 183 // the API server to generate the name... figure out what to do for this edge case 184 name := attr.GetName() 185 for _, matchedName := range namedRule.ResourceNames { 186 if name == matchedName { 187 return true, equivalent, matchKind, nil 188 } 189 } 190 } 191 } 192 return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil 193 } 194 195 type attrWithResourceOverride struct { 196 admission.Attributes 197 resource schema.GroupVersionResource 198 } 199 200 func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { return a.resource }