1
16
17
18
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
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
49
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
61 func NewInformerManager(bundles certinformersv1alpha1.ClusterTrustBundleInformer, cacheSize int, cacheTTL time.Duration) (*InformerManager, error) {
62
63
64 m := &InformerManager{
65 ctbInformer: bundles.Informer(),
66 ctbLister: bundles.Lister(),
67 normalizationCache: lrucache.NewLRUExpireCache(cacheSize),
68 cacheTTL: cacheTTL,
69 }
70
71
72
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
126
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
157
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
164
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
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
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
249 type NoopManager struct{}
250
251 var _ Manager = (*NoopManager)(nil)
252
253
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
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