k8s.io/kubernetes@v1.29.3/pkg/controller/serviceaccount/legacy_serviceaccount_token_cleaner.go (about) 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 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 // TokenCleanerOptions contains options for the LegacySATokenCleaner 51 type LegacySATokenCleanerOptions struct { 52 // CleanUpPeriod is the period of time since the last usage of an legacy token before it can be deleted. 53 CleanUpPeriod time.Duration 54 SyncInterval time.Duration 55 } 56 57 // LegacySATokenCleaner is a controller that deletes legacy serviceaccount tokens that are not in use for a specified period of time. 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 // NewLegacySATokenCleaner returns a new *NewLegacySATokenCleaner. 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 // we haven't been tracking long enough 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 // if LastUsedLabelKey does not exist, we think the secret has not been used 153 // since the legacy token starts to track. 154 lastUsed, ok := secret.Labels[serviceaccount.LastUsedLabelKey] 155 if ok { 156 _, err := time.Parse(dateFormat, lastUsed) 157 if err != nil { 158 // the lastUsed value is not well-formed thus we cannot determine it 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 // can't determine if this is an auto-generated token 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 // still used by pods 185 continue 186 } 187 188 invalidSince := secret.Labels[serviceaccount.InvalidSinceLabelKey] 189 // If the secret has not been labeled with invalid since date or the label value has invalid format, update the invalidSince label with the current date value. 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 // Ensure UID matches if given 257 if len(saUID) == 0 || saUID == sa.UID { 258 return sa, nil 259 } 260 261 return nil, nil 262 } 263 264 // get the latest possible TrackedSince time information from the configMap label. 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 // make sure the time to be 00:00 on the day just after the date starts to track 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 }