k8s.io/apiserver@v0.31.1/pkg/admission/plugin/webhook/predicates/namespace/matcher.go (about) 1 /* 2 Copyright 2017 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 namespace 18 19 import ( 20 "context" 21 "fmt" 22 23 v1 "k8s.io/api/core/v1" 24 25 apierrors "k8s.io/apimachinery/pkg/api/errors" 26 "k8s.io/apimachinery/pkg/api/meta" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/labels" 29 utilerrors "k8s.io/apimachinery/pkg/util/errors" 30 "k8s.io/apiserver/pkg/admission" 31 clientset "k8s.io/client-go/kubernetes" 32 corelisters "k8s.io/client-go/listers/core/v1" 33 ) 34 35 type NamespaceSelectorProvider interface { 36 // GetNamespaceSelector gets the webhook NamespaceSelector field. 37 GetParsedNamespaceSelector() (labels.Selector, error) 38 } 39 40 // Matcher decides if a request is exempted by the NamespaceSelector of a 41 // webhook configuration. 42 type Matcher struct { 43 NamespaceLister corelisters.NamespaceLister 44 Client clientset.Interface 45 } 46 47 func (m *Matcher) GetNamespace(name string) (*v1.Namespace, error) { 48 return m.NamespaceLister.Get(name) 49 } 50 51 // Validate checks if the Matcher has a NamespaceLister and Client. 52 func (m *Matcher) Validate() error { 53 var errs []error 54 if m.NamespaceLister == nil { 55 errs = append(errs, fmt.Errorf("the namespace matcher requires a namespaceLister")) 56 } 57 if m.Client == nil { 58 errs = append(errs, fmt.Errorf("the namespace matcher requires a client")) 59 } 60 return utilerrors.NewAggregate(errs) 61 } 62 63 // GetNamespaceLabels gets the labels of the namespace related to the attr. 64 func (m *Matcher) GetNamespaceLabels(attr admission.Attributes) (map[string]string, error) { 65 // If the request itself is creating or updating a namespace, then get the 66 // labels from attr.Object, because namespaceLister doesn't have the latest 67 // namespace yet. 68 // 69 // However, if the request is deleting a namespace, then get the label from 70 // the namespace in the namespaceLister, because a delete request is not 71 // going to change the object, and attr.Object will be a DeleteOptions 72 // rather than a namespace object. 73 if attr.GetResource().Resource == "namespaces" && 74 len(attr.GetSubresource()) == 0 && 75 (attr.GetOperation() == admission.Create || attr.GetOperation() == admission.Update) { 76 accessor, err := meta.Accessor(attr.GetObject()) 77 if err != nil { 78 return nil, err 79 } 80 return accessor.GetLabels(), nil 81 } 82 83 namespaceName := attr.GetNamespace() 84 namespace, err := m.NamespaceLister.Get(namespaceName) 85 if err != nil && !apierrors.IsNotFound(err) { 86 return nil, err 87 } 88 if apierrors.IsNotFound(err) { 89 // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not 90 namespace, err = m.Client.CoreV1().Namespaces().Get(context.TODO(), namespaceName, metav1.GetOptions{}) 91 if err != nil { 92 return nil, err 93 } 94 } 95 return namespace.Labels, nil 96 } 97 98 // MatchNamespaceSelector decideds whether the request matches the 99 // namespaceSelctor of the webhook. Only when they match, the webhook is called. 100 func (m *Matcher) MatchNamespaceSelector(p NamespaceSelectorProvider, attr admission.Attributes) (bool, *apierrors.StatusError) { 101 namespaceName := attr.GetNamespace() 102 if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" { 103 // If the request is about a cluster scoped resource, and it is not a 104 // namespace, it is never exempted. 105 // TODO: figure out a way selective exempt cluster scoped resources. 106 // Also update the comment in types.go 107 return true, nil 108 } 109 selector, err := p.GetParsedNamespaceSelector() 110 if err != nil { 111 return false, apierrors.NewInternalError(err) 112 } 113 if selector.Empty() { 114 return true, nil 115 } 116 117 namespaceLabels, err := m.GetNamespaceLabels(attr) 118 // this means the namespace is not found, for backwards compatibility, 119 // return a 404 120 if apierrors.IsNotFound(err) { 121 status, ok := err.(apierrors.APIStatus) 122 if !ok { 123 return false, apierrors.NewInternalError(err) 124 } 125 return false, &apierrors.StatusError{ErrStatus: status.Status()} 126 } 127 if err != nil { 128 return false, apierrors.NewInternalError(err) 129 } 130 return selector.Matches(labels.Set(namespaceLabels)), nil 131 }