...

Source file src/k8s.io/kubernetes/pkg/kubelet/clustertrustbundle/clustertrustbundle_manager.go

Documentation: k8s.io/kubernetes/pkg/kubelet/clustertrustbundle

     1  /*
     2  Copyright 2023 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 clustertrustbundle abstracts access to ClusterTrustBundles so that
    18  // projected volumes can use them.
    19  package clustertrustbundle
    20  
    21  import (
    22  	"encoding/pem"
    23  	"fmt"
    24  	"math/rand"
    25  	"time"
    26  
    27  	certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
    28  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	lrucache "k8s.io/apimachinery/pkg/util/cache"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	certinformersv1alpha1 "k8s.io/client-go/informers/certificates/v1alpha1"
    33  	certlistersv1alpha1 "k8s.io/client-go/listers/certificates/v1alpha1"
    34  	"k8s.io/client-go/tools/cache"
    35  	"k8s.io/klog/v2"
    36  )
    37  
    38  const (
    39  	maxLabelSelectorLength = 100 * 1024
    40  )
    41  
    42  // Manager abstracts over the ability to get trust anchors.
    43  type Manager interface {
    44  	GetTrustAnchorsByName(name string, allowMissing bool) ([]byte, error)
    45  	GetTrustAnchorsBySigner(signerName string, labelSelector *metav1.LabelSelector, allowMissing bool) ([]byte, error)
    46  }
    47  
    48  // InformerManager is the "real" manager.  It uses informers to track
    49  // ClusterTrustBundle objects.
    50  type InformerManager struct {
    51  	ctbInformer cache.SharedIndexInformer
    52  	ctbLister   certlistersv1alpha1.ClusterTrustBundleLister
    53  
    54  	normalizationCache *lrucache.LRUExpireCache
    55  	cacheTTL           time.Duration
    56  }
    57  
    58  var _ Manager = (*InformerManager)(nil)
    59  
    60  // NewInformerManager returns an initialized InformerManager.
    61  func NewInformerManager(bundles certinformersv1alpha1.ClusterTrustBundleInformer, cacheSize int, cacheTTL time.Duration) (*InformerManager, error) {
    62  	// We need to call Informer() before calling start on the shared informer
    63  	// factory, or the informer won't be registered to be started.
    64  	m := &InformerManager{
    65  		ctbInformer:        bundles.Informer(),
    66  		ctbLister:          bundles.Lister(),
    67  		normalizationCache: lrucache.NewLRUExpireCache(cacheSize),
    68  		cacheTTL:           cacheTTL,
    69  	}
    70  
    71  	// Have the informer bust cache entries when it sees updates that could
    72  	// apply to them.
    73  	_, err := m.ctbInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    74  		AddFunc: func(obj any) {
    75  			ctb, ok := obj.(*certificatesv1alpha1.ClusterTrustBundle)
    76  			if !ok {
    77  				return
    78  			}
    79  			klog.InfoS("Dropping all cache entries for signer", "signerName", ctb.Spec.SignerName)
    80  			m.dropCacheFor(ctb)
    81  		},
    82  		UpdateFunc: func(old, new any) {
    83  			ctb, ok := new.(*certificatesv1alpha1.ClusterTrustBundle)
    84  			if !ok {
    85  				return
    86  			}
    87  			klog.InfoS("Dropping cache for ClusterTrustBundle", "signerName", ctb.Spec.SignerName)
    88  			m.dropCacheFor(new.(*certificatesv1alpha1.ClusterTrustBundle))
    89  		},
    90  		DeleteFunc: func(obj any) {
    91  			ctb, ok := obj.(*certificatesv1alpha1.ClusterTrustBundle)
    92  			if !ok {
    93  				tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
    94  				if !ok {
    95  					return
    96  				}
    97  				ctb, ok = tombstone.Obj.(*certificatesv1alpha1.ClusterTrustBundle)
    98  				if !ok {
    99  					return
   100  				}
   101  			}
   102  			klog.InfoS("Dropping cache for ClusterTrustBundle", "signerName", ctb.Spec.SignerName)
   103  			m.dropCacheFor(ctb)
   104  		},
   105  	})
   106  	if err != nil {
   107  		return nil, fmt.Errorf("while registering event handler on informer: %w", err)
   108  	}
   109  
   110  	return m, nil
   111  }
   112  
   113  func (m *InformerManager) dropCacheFor(ctb *certificatesv1alpha1.ClusterTrustBundle) {
   114  	if ctb.Spec.SignerName != "" {
   115  		m.normalizationCache.RemoveAll(func(key any) bool {
   116  			return key.(cacheKeyType).signerName == ctb.Spec.SignerName
   117  		})
   118  	} else {
   119  		m.normalizationCache.RemoveAll(func(key any) bool {
   120  			return key.(cacheKeyType).ctbName == ctb.ObjectMeta.Name
   121  		})
   122  	}
   123  }
   124  
   125  // GetTrustAnchorsByName returns normalized and deduplicated trust anchors from
   126  // a single named ClusterTrustBundle.
   127  func (m *InformerManager) GetTrustAnchorsByName(name string, allowMissing bool) ([]byte, error) {
   128  	if !m.ctbInformer.HasSynced() {
   129  		return nil, fmt.Errorf("ClusterTrustBundle informer has not yet synced")
   130  	}
   131  
   132  	cacheKey := cacheKeyType{ctbName: name}
   133  
   134  	if cachedAnchors, ok := m.normalizationCache.Get(cacheKey); ok {
   135  		return cachedAnchors.([]byte), nil
   136  	}
   137  
   138  	ctb, err := m.ctbLister.Get(name)
   139  	if k8serrors.IsNotFound(err) && allowMissing {
   140  		return []byte{}, nil
   141  	}
   142  	if err != nil {
   143  		return nil, fmt.Errorf("while getting ClusterTrustBundle: %w", err)
   144  	}
   145  
   146  	pemTrustAnchors, err := m.normalizeTrustAnchors([]*certificatesv1alpha1.ClusterTrustBundle{ctb})
   147  	if err != nil {
   148  		return nil, fmt.Errorf("while normalizing trust anchors: %w", err)
   149  	}
   150  
   151  	m.normalizationCache.Add(cacheKey, pemTrustAnchors, m.cacheTTL)
   152  
   153  	return pemTrustAnchors, nil
   154  }
   155  
   156  // GetTrustAnchorsBySigner returns normalized and deduplicated trust anchors
   157  // from a set of selected ClusterTrustBundles.
   158  func (m *InformerManager) GetTrustAnchorsBySigner(signerName string, labelSelector *metav1.LabelSelector, allowMissing bool) ([]byte, error) {
   159  	if !m.ctbInformer.HasSynced() {
   160  		return nil, fmt.Errorf("ClusterTrustBundle informer has not yet synced")
   161  	}
   162  
   163  	// Note that this function treats nil as "match nothing", and non-nil but
   164  	// empty as "match everything".
   165  	selector, err := metav1.LabelSelectorAsSelector(labelSelector)
   166  	if err != nil {
   167  		return nil, fmt.Errorf("while parsing label selector: %w", err)
   168  	}
   169  
   170  	cacheKey := cacheKeyType{signerName: signerName, labelSelector: selector.String()}
   171  
   172  	if lsLen := len(cacheKey.labelSelector); lsLen > maxLabelSelectorLength {
   173  		return nil, fmt.Errorf("label selector length (%d) is larger than %d", lsLen, maxLabelSelectorLength)
   174  	}
   175  
   176  	if cachedAnchors, ok := m.normalizationCache.Get(cacheKey); ok {
   177  		return cachedAnchors.([]byte), nil
   178  	}
   179  
   180  	rawCTBList, err := m.ctbLister.List(selector)
   181  	if err != nil {
   182  		return nil, fmt.Errorf("while listing ClusterTrustBundles matching label selector %v: %w", labelSelector, err)
   183  	}
   184  
   185  	ctbList := []*certificatesv1alpha1.ClusterTrustBundle{}
   186  	for _, ctb := range rawCTBList {
   187  		if ctb.Spec.SignerName == signerName {
   188  			ctbList = append(ctbList, ctb)
   189  		}
   190  	}
   191  
   192  	if len(ctbList) == 0 {
   193  		if allowMissing {
   194  			return []byte{}, nil
   195  		}
   196  		return nil, fmt.Errorf("combination of signerName and labelSelector matched zero ClusterTrustBundles")
   197  	}
   198  
   199  	pemTrustAnchors, err := m.normalizeTrustAnchors(ctbList)
   200  	if err != nil {
   201  		return nil, fmt.Errorf("while normalizing trust anchors: %w", err)
   202  	}
   203  
   204  	m.normalizationCache.Add(cacheKey, pemTrustAnchors, m.cacheTTL)
   205  
   206  	return pemTrustAnchors, nil
   207  }
   208  
   209  func (m *InformerManager) normalizeTrustAnchors(ctbList []*certificatesv1alpha1.ClusterTrustBundle) ([]byte, error) {
   210  	// Deduplicate trust anchors from all ClusterTrustBundles.
   211  	trustAnchorSet := sets.Set[string]{}
   212  	for _, ctb := range ctbList {
   213  		rest := []byte(ctb.Spec.TrustBundle)
   214  		var b *pem.Block
   215  		for {
   216  			b, rest = pem.Decode(rest)
   217  			if b == nil {
   218  				break
   219  			}
   220  			trustAnchorSet = trustAnchorSet.Insert(string(b.Bytes))
   221  		}
   222  	}
   223  
   224  	// Give the list a stable ordering that changes each time Kubelet restarts.
   225  	trustAnchorList := sets.List(trustAnchorSet)
   226  	rand.Shuffle(len(trustAnchorList), func(i, j int) {
   227  		trustAnchorList[i], trustAnchorList[j] = trustAnchorList[j], trustAnchorList[i]
   228  	})
   229  
   230  	pemTrustAnchors := []byte{}
   231  	for _, ta := range trustAnchorList {
   232  		b := &pem.Block{
   233  			Type:  "CERTIFICATE",
   234  			Bytes: []byte(ta),
   235  		}
   236  		pemTrustAnchors = append(pemTrustAnchors, pem.EncodeToMemory(b)...)
   237  	}
   238  
   239  	return pemTrustAnchors, nil
   240  }
   241  
   242  type cacheKeyType struct {
   243  	ctbName       string
   244  	signerName    string
   245  	labelSelector string
   246  }
   247  
   248  // NoopManager always returns an error, for use in static kubelet mode.
   249  type NoopManager struct{}
   250  
   251  var _ Manager = (*NoopManager)(nil)
   252  
   253  // GetTrustAnchorsByName implements Manager.
   254  func (m *NoopManager) GetTrustAnchorsByName(name string, allowMissing bool) ([]byte, error) {
   255  	return nil, fmt.Errorf("ClusterTrustBundle projection is not supported in static kubelet mode")
   256  }
   257  
   258  // GetTrustAnchorsBySigner implements Manager.
   259  func (m *NoopManager) GetTrustAnchorsBySigner(signerName string, labelSelector *metav1.LabelSelector, allowMissing bool) ([]byte, error) {
   260  	return nil, fmt.Errorf("ClusterTrustBundle projection is not supported in static kubelet mode")
   261  }
   262  

View as plain text