1
16
17 package serviceaccount
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "time"
24
25 v1 "k8s.io/api/core/v1"
26 apierrors "k8s.io/apimachinery/pkg/api/errors"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/labels"
29 "k8s.io/apimachinery/pkg/types"
30 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
31 "k8s.io/apimachinery/pkg/util/sets"
32 "k8s.io/apimachinery/pkg/util/wait"
33 applyv1 "k8s.io/client-go/applyconfigurations/core/v1"
34 coreinformers "k8s.io/client-go/informers/core/v1"
35 clientset "k8s.io/client-go/kubernetes"
36 listersv1 "k8s.io/client-go/listers/core/v1"
37 "k8s.io/client-go/tools/cache"
38 "k8s.io/klog/v2"
39 podutil "k8s.io/kubernetes/pkg/api/v1/pod"
40 "k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
41 "k8s.io/kubernetes/pkg/serviceaccount"
42 "k8s.io/utils/clock"
43 )
44
45 const (
46 dateFormat = "2006-01-02"
47 DefaultCleanerSyncInterval = 24 * time.Hour
48 )
49
50
51 type LegacySATokenCleanerOptions struct {
52
53 CleanUpPeriod time.Duration
54 SyncInterval time.Duration
55 }
56
57
58 type LegacySATokenCleaner struct {
59 client clientset.Interface
60 clock clock.Clock
61 saLister listersv1.ServiceAccountLister
62 saInformerSynced cache.InformerSynced
63
64 secretLister listersv1.SecretLister
65 secretInformerSynced cache.InformerSynced
66
67 podLister listersv1.PodLister
68 podInformerSynced cache.InformerSynced
69
70 syncInterval time.Duration
71 minimumSinceLastUsed time.Duration
72 }
73
74
75 func NewLegacySATokenCleaner(saInformer coreinformers.ServiceAccountInformer, secretInformer coreinformers.SecretInformer, podInformer coreinformers.PodInformer, client clientset.Interface, cl clock.Clock, options LegacySATokenCleanerOptions) (*LegacySATokenCleaner, error) {
76 if !(options.CleanUpPeriod > 0) {
77 return nil, fmt.Errorf("invalid CleanUpPeriod: %v", options.CleanUpPeriod)
78 }
79 if !(options.SyncInterval > 0) {
80 return nil, fmt.Errorf("invalid SyncInterval: %v", options.SyncInterval)
81 }
82
83 tc := &LegacySATokenCleaner{
84 client: client,
85 clock: cl,
86 saLister: saInformer.Lister(),
87 saInformerSynced: saInformer.Informer().HasSynced,
88 secretLister: secretInformer.Lister(),
89 secretInformerSynced: secretInformer.Informer().HasSynced,
90 podLister: podInformer.Lister(),
91 podInformerSynced: podInformer.Informer().HasSynced,
92 minimumSinceLastUsed: options.CleanUpPeriod,
93 syncInterval: options.SyncInterval,
94 }
95
96 return tc, nil
97 }
98
99 func (tc *LegacySATokenCleaner) Run(ctx context.Context) {
100 defer utilruntime.HandleCrash()
101
102 logger := klog.FromContext(ctx)
103 logger.Info("Starting legacy service account token cleaner controller")
104 defer logger.Info("Shutting down legacy service account token cleaner controller")
105
106 if !cache.WaitForNamedCacheSync("legacy-service-account-token-cleaner", ctx.Done(), tc.saInformerSynced, tc.secretInformerSynced, tc.podInformerSynced) {
107 return
108 }
109
110 go wait.UntilWithContext(ctx, tc.evaluateSATokens, tc.syncInterval)
111
112 <-ctx.Done()
113 }
114
115 func (tc *LegacySATokenCleaner) evaluateSATokens(ctx context.Context) {
116 logger := klog.FromContext(ctx)
117
118 now := tc.clock.Now().UTC()
119 trackedSince, err := tc.latestPossibleTrackedSinceTime(ctx)
120 if err != nil {
121 logger.Error(err, "Getting lastest possible tracked_since time")
122 return
123 }
124
125 if now.Before(trackedSince.Add(tc.minimumSinceLastUsed)) {
126
127 return
128 }
129
130 preserveCreatedOnOrAfter := now.Add(-tc.minimumSinceLastUsed)
131 preserveUsedOnOrAfter := now.Add(-tc.minimumSinceLastUsed).Format(dateFormat)
132
133 secretList, err := tc.secretLister.Secrets(metav1.NamespaceAll).List(labels.Everything())
134 if err != nil {
135 logger.Error(err, "Getting cached secret list")
136 return
137 }
138
139 namespaceToUsedSecretNames := make(map[string]sets.String)
140 for _, secret := range secretList {
141 if secret.Type != v1.SecretTypeServiceAccountToken {
142 continue
143 }
144 if !secret.CreationTimestamp.Time.Before(preserveCreatedOnOrAfter) {
145 continue
146 }
147
148 if secret.DeletionTimestamp != nil {
149 continue
150 }
151
152
153
154 lastUsed, ok := secret.Labels[serviceaccount.LastUsedLabelKey]
155 if ok {
156 _, err := time.Parse(dateFormat, lastUsed)
157 if err != nil {
158
159 logger.Error(err, "Parsing lastUsed time", "secret", klog.KRef(secret.Namespace, secret.Name))
160 continue
161 }
162 if lastUsed >= preserveUsedOnOrAfter {
163 continue
164 }
165 }
166
167 sa, saErr := tc.getServiceAccount(secret)
168
169 if saErr != nil {
170 logger.Error(saErr, "Getting service account", "secret", klog.KRef(secret.Namespace, secret.Name))
171 continue
172 }
173 if sa == nil || !hasSecretReference(sa, secret.Name) {
174
175 continue
176 }
177
178 mountedSecretNames, err := tc.getMountedSecretNames(secret.Namespace, namespaceToUsedSecretNames)
179 if err != nil {
180 logger.Error(err, "Resolving mounted secrets", "secret", klog.KRef(secret.Namespace, secret.Name))
181 continue
182 }
183 if mountedSecretNames.Has(secret.Name) {
184
185 continue
186 }
187
188 invalidSince := secret.Labels[serviceaccount.InvalidSinceLabelKey]
189
190 _, err = time.Parse(dateFormat, invalidSince)
191 if err != nil {
192 invalidSince = now.Format(dateFormat)
193 logger.Info("Mark the auto-generated service account token as invalid", "invalidSince", invalidSince, "secret", klog.KRef(secret.Namespace, secret.Name))
194 patchContent, err := json.Marshal(applyv1.Secret(secret.Name, secret.Namespace).WithUID(secret.UID).WithLabels(map[string]string{serviceaccount.InvalidSinceLabelKey: invalidSince}))
195 if err != nil {
196 logger.Error(err, "Failed to marshal invalid since label")
197 } else {
198 if _, err := tc.client.CoreV1().Secrets(secret.Namespace).Patch(ctx, secret.Name, types.MergePatchType, patchContent, metav1.PatchOptions{}); err != nil {
199 logger.Error(err, "Failed to label legacy service account token secret with invalid since date")
200 }
201 }
202 continue
203 }
204
205 if invalidSince >= preserveUsedOnOrAfter {
206 continue
207 }
208
209 logger.Info("Delete auto-generated service account token", "secret", klog.KRef(secret.Namespace, secret.Name), "creationTime", secret.CreationTimestamp, "lastUsed", lastUsed, "invalidSince", invalidSince)
210 if err := tc.client.CoreV1().Secrets(secret.Namespace).Delete(ctx, secret.Name, metav1.DeleteOptions{Preconditions: &metav1.Preconditions{ResourceVersion: &secret.ResourceVersion}}); err != nil && !apierrors.IsConflict(err) && !apierrors.IsNotFound(err) {
211 logger.Error(err, "Deleting legacy service account token", "secret", klog.KRef(secret.Namespace, secret.Name), "serviceaccount", sa.Name)
212 }
213 }
214 }
215
216 func (tc *LegacySATokenCleaner) getMountedSecretNames(secretNamespace string, namespaceToUsedSecretNames map[string]sets.String) (sets.String, error) {
217 if secrets, ok := namespaceToUsedSecretNames[secretNamespace]; ok {
218 return secrets, nil
219 }
220
221 podList, err := tc.podLister.Pods(secretNamespace).List(labels.Everything())
222 if err != nil {
223 return nil, fmt.Errorf("failed to get pod list from pod cache: %v", err)
224 }
225
226 var secrets sets.String
227 for _, pod := range podList {
228 podutil.VisitPodSecretNames(pod, func(secretName string) bool {
229 if secrets == nil {
230 secrets = sets.NewString()
231 }
232 secrets.Insert(secretName)
233 return true
234 })
235 }
236 if secrets != nil {
237 namespaceToUsedSecretNames[secretNamespace] = secrets
238 }
239 return secrets, nil
240 }
241
242 func (tc *LegacySATokenCleaner) getServiceAccount(secret *v1.Secret) (*v1.ServiceAccount, error) {
243 saName := secret.Annotations[v1.ServiceAccountNameKey]
244 if len(saName) == 0 {
245 return nil, nil
246 }
247 saUID := types.UID(secret.Annotations[v1.ServiceAccountUIDKey])
248 sa, err := tc.saLister.ServiceAccounts(secret.Namespace).Get(saName)
249 if apierrors.IsNotFound(err) {
250 return nil, nil
251 }
252 if err != nil {
253 return nil, err
254 }
255
256
257 if len(saUID) == 0 || saUID == sa.UID {
258 return sa, nil
259 }
260
261 return nil, nil
262 }
263
264
265 func (tc *LegacySATokenCleaner) latestPossibleTrackedSinceTime(ctx context.Context) (time.Time, error) {
266 configMap, err := tc.client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, legacytokentracking.ConfigMapName, metav1.GetOptions{})
267 if err != nil {
268 return time.Time{}, err
269 }
270 trackedSince, exist := configMap.Data[legacytokentracking.ConfigMapDataKey]
271 if !exist {
272 return time.Time{}, fmt.Errorf("configMap does not have since label")
273 }
274 trackedSinceTime, err := time.Parse(dateFormat, trackedSince)
275 if err != nil {
276 return time.Time{}, fmt.Errorf("error parsing trackedSince time: %v", err)
277 }
278
279 return trackedSinceTime.AddDate(0, 0, 1), nil
280 }
281
282 func hasSecretReference(serviceAccount *v1.ServiceAccount, secretName string) bool {
283 for _, secret := range serviceAccount.Secrets {
284 if secret.Name == secretName {
285 return true
286 }
287 }
288 return false
289 }
290
View as plain text