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  }