262 lines
8.3 KiB
Go
262 lines
8.3 KiB
Go
/*
|
|
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")
|
|
}
|