| /* |
| Copyright 2023 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| // Package clustertrustbundle abstracts access to ClusterTrustBundles so that |
| // projected volumes can use them. |
| package clustertrustbundle |
| |
| import ( |
| "encoding/pem" |
| "fmt" |
| "math/rand" |
| "time" |
| |
| certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1" |
| k8serrors "k8s.io/apimachinery/pkg/api/errors" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| lrucache "k8s.io/apimachinery/pkg/util/cache" |
| "k8s.io/apimachinery/pkg/util/sets" |
| certinformersv1alpha1 "k8s.io/client-go/informers/certificates/v1alpha1" |
| certlistersv1alpha1 "k8s.io/client-go/listers/certificates/v1alpha1" |
| "k8s.io/client-go/tools/cache" |
| "k8s.io/klog/v2" |
| ) |
| |
| const ( |
| maxLabelSelectorLength = 100 * 1024 |
| ) |
| |
| // Manager abstracts over the ability to get trust anchors. |
| type Manager interface { |
| GetTrustAnchorsByName(name string, allowMissing bool) ([]byte, error) |
| GetTrustAnchorsBySigner(signerName string, labelSelector *metav1.LabelSelector, allowMissing bool) ([]byte, error) |
| } |
| |
| // InformerManager is the "real" manager. It uses informers to track |
| // ClusterTrustBundle objects. |
| type InformerManager struct { |
| ctbInformer cache.SharedIndexInformer |
| ctbLister certlistersv1alpha1.ClusterTrustBundleLister |
| |
| normalizationCache *lrucache.LRUExpireCache |
| cacheTTL time.Duration |
| } |
| |
| var _ Manager = (*InformerManager)(nil) |
| |
| // NewInformerManager returns an initialized InformerManager. |
| func NewInformerManager(bundles certinformersv1alpha1.ClusterTrustBundleInformer, cacheSize int, cacheTTL time.Duration) (*InformerManager, error) { |
| // We need to call Informer() before calling start on the shared informer |
| // factory, or the informer won't be registered to be started. |
| m := &InformerManager{ |
| ctbInformer: bundles.Informer(), |
| ctbLister: bundles.Lister(), |
| normalizationCache: lrucache.NewLRUExpireCache(cacheSize), |
| cacheTTL: cacheTTL, |
| } |
| |
| // Have the informer bust cache entries when it sees updates that could |
| // apply to them. |
| _, err := m.ctbInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ |
| AddFunc: func(obj any) { |
| ctb, ok := obj.(*certificatesv1alpha1.ClusterTrustBundle) |
| if !ok { |
| return |
| } |
| klog.InfoS("Dropping all cache entries for signer", "signerName", ctb.Spec.SignerName) |
| m.dropCacheFor(ctb) |
| }, |
| UpdateFunc: func(old, new any) { |
| ctb, ok := new.(*certificatesv1alpha1.ClusterTrustBundle) |
| if !ok { |
| return |
| } |
| klog.InfoS("Dropping cache for ClusterTrustBundle", "signerName", ctb.Spec.SignerName) |
| m.dropCacheFor(new.(*certificatesv1alpha1.ClusterTrustBundle)) |
| }, |
| DeleteFunc: func(obj any) { |
| ctb, ok := obj.(*certificatesv1alpha1.ClusterTrustBundle) |
| if !ok { |
| tombstone, ok := obj.(cache.DeletedFinalStateUnknown) |
| if !ok { |
| return |
| } |
| ctb, ok = tombstone.Obj.(*certificatesv1alpha1.ClusterTrustBundle) |
| if !ok { |
| return |
| } |
| } |
| klog.InfoS("Dropping cache for ClusterTrustBundle", "signerName", ctb.Spec.SignerName) |
| m.dropCacheFor(ctb) |
| }, |
| }) |
| if err != nil { |
| return nil, fmt.Errorf("while registering event handler on informer: %w", err) |
| } |
| |
| return m, nil |
| } |
| |
| func (m *InformerManager) dropCacheFor(ctb *certificatesv1alpha1.ClusterTrustBundle) { |
| if ctb.Spec.SignerName != "" { |
| m.normalizationCache.RemoveAll(func(key any) bool { |
| return key.(cacheKeyType).signerName == ctb.Spec.SignerName |
| }) |
| } else { |
| m.normalizationCache.RemoveAll(func(key any) bool { |
| return key.(cacheKeyType).ctbName == ctb.ObjectMeta.Name |
| }) |
| } |
| } |
| |
| // GetTrustAnchorsByName returns normalized and deduplicated trust anchors from |
| // a single named ClusterTrustBundle. |
| func (m *InformerManager) GetTrustAnchorsByName(name string, allowMissing bool) ([]byte, error) { |
| if !m.ctbInformer.HasSynced() { |
| return nil, fmt.Errorf("ClusterTrustBundle informer has not yet synced") |
| } |
| |
| cacheKey := cacheKeyType{ctbName: name} |
| |
| if cachedAnchors, ok := m.normalizationCache.Get(cacheKey); ok { |
| return cachedAnchors.([]byte), nil |
| } |
| |
| ctb, err := m.ctbLister.Get(name) |
| if k8serrors.IsNotFound(err) && allowMissing { |
| return []byte{}, nil |
| } |
| if err != nil { |
| return nil, fmt.Errorf("while getting ClusterTrustBundle: %w", err) |
| } |
| |
| pemTrustAnchors, err := m.normalizeTrustAnchors([]*certificatesv1alpha1.ClusterTrustBundle{ctb}) |
| if err != nil { |
| return nil, fmt.Errorf("while normalizing trust anchors: %w", err) |
| } |
| |
| m.normalizationCache.Add(cacheKey, pemTrustAnchors, m.cacheTTL) |
| |
| return pemTrustAnchors, nil |
| } |
| |
| // GetTrustAnchorsBySigner returns normalized and deduplicated trust anchors |
| // from a set of selected ClusterTrustBundles. |
| func (m *InformerManager) GetTrustAnchorsBySigner(signerName string, labelSelector *metav1.LabelSelector, allowMissing bool) ([]byte, error) { |
| if !m.ctbInformer.HasSynced() { |
| return nil, fmt.Errorf("ClusterTrustBundle informer has not yet synced") |
| } |
| |
| // Note that this function treats nil as "match nothing", and non-nil but |
| // empty as "match everything". |
| selector, err := metav1.LabelSelectorAsSelector(labelSelector) |
| if err != nil { |
| return nil, fmt.Errorf("while parsing label selector: %w", err) |
| } |
| |
| cacheKey := cacheKeyType{signerName: signerName, labelSelector: selector.String()} |
| |
| if lsLen := len(cacheKey.labelSelector); lsLen > maxLabelSelectorLength { |
| return nil, fmt.Errorf("label selector length (%d) is larger than %d", lsLen, maxLabelSelectorLength) |
| } |
| |
| if cachedAnchors, ok := m.normalizationCache.Get(cacheKey); ok { |
| return cachedAnchors.([]byte), nil |
| } |
| |
| rawCTBList, err := m.ctbLister.List(selector) |
| if err != nil { |
| return nil, fmt.Errorf("while listing ClusterTrustBundles matching label selector %v: %w", labelSelector, err) |
| } |
| |
| ctbList := []*certificatesv1alpha1.ClusterTrustBundle{} |
| for _, ctb := range rawCTBList { |
| if ctb.Spec.SignerName == signerName { |
| ctbList = append(ctbList, ctb) |
| } |
| } |
| |
| if len(ctbList) == 0 { |
| if allowMissing { |
| return []byte{}, nil |
| } |
| return nil, fmt.Errorf("combination of signerName and labelSelector matched zero ClusterTrustBundles") |
| } |
| |
| pemTrustAnchors, err := m.normalizeTrustAnchors(ctbList) |
| if err != nil { |
| return nil, fmt.Errorf("while normalizing trust anchors: %w", err) |
| } |
| |
| m.normalizationCache.Add(cacheKey, pemTrustAnchors, m.cacheTTL) |
| |
| return pemTrustAnchors, nil |
| } |
| |
| func (m *InformerManager) normalizeTrustAnchors(ctbList []*certificatesv1alpha1.ClusterTrustBundle) ([]byte, error) { |
| // Deduplicate trust anchors from all ClusterTrustBundles. |
| trustAnchorSet := sets.Set[string]{} |
| for _, ctb := range ctbList { |
| rest := []byte(ctb.Spec.TrustBundle) |
| var b *pem.Block |
| for { |
| b, rest = pem.Decode(rest) |
| if b == nil { |
| break |
| } |
| trustAnchorSet = trustAnchorSet.Insert(string(b.Bytes)) |
| } |
| } |
| |
| // Give the list a stable ordering that changes each time Kubelet restarts. |
| trustAnchorList := sets.List(trustAnchorSet) |
| rand.Shuffle(len(trustAnchorList), func(i, j int) { |
| trustAnchorList[i], trustAnchorList[j] = trustAnchorList[j], trustAnchorList[i] |
| }) |
| |
| pemTrustAnchors := []byte{} |
| for _, ta := range trustAnchorList { |
| b := &pem.Block{ |
| Type: "CERTIFICATE", |
| Bytes: []byte(ta), |
| } |
| pemTrustAnchors = append(pemTrustAnchors, pem.EncodeToMemory(b)...) |
| } |
| |
| return pemTrustAnchors, nil |
| } |
| |
| type cacheKeyType struct { |
| ctbName string |
| signerName string |
| labelSelector string |
| } |
| |
| // NoopManager always returns an error, for use in static kubelet mode. |
| type NoopManager struct{} |
| |
| var _ Manager = (*NoopManager)(nil) |
| |
| // GetTrustAnchorsByName implements Manager. |
| func (m *NoopManager) GetTrustAnchorsByName(name string, allowMissing bool) ([]byte, error) { |
| return nil, fmt.Errorf("ClusterTrustBundle projection is not supported in static kubelet mode") |
| } |
| |
| // GetTrustAnchorsBySigner implements Manager. |
| func (m *NoopManager) GetTrustAnchorsBySigner(signerName string, labelSelector *metav1.LabelSelector, allowMissing bool) ([]byte, error) { |
| return nil, fmt.Errorf("ClusterTrustBundle projection is not supported in static kubelet mode") |
| } |