1
16
17 package serviceaccount
18
19 import (
20 "context"
21 "encoding/json"
22 "reflect"
23 "testing"
24 "time"
25
26 v1 "k8s.io/api/core/v1"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/apimachinery/pkg/types"
31 applyv1 "k8s.io/client-go/applyconfigurations/core/v1"
32 "k8s.io/client-go/informers"
33 "k8s.io/client-go/kubernetes/fake"
34 core "k8s.io/client-go/testing"
35 "k8s.io/kubernetes/pkg/controller"
36 "k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
37 "k8s.io/kubernetes/pkg/serviceaccount"
38 testingclock "k8s.io/utils/clock/testing"
39 )
40
41 func configuredConfigMap(label string) *v1.ConfigMap {
42 if label == "" {
43 return &v1.ConfigMap{
44 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: legacytokentracking.ConfigMapName},
45 }
46 }
47 return &v1.ConfigMap{
48 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: legacytokentracking.ConfigMapName},
49 Data: map[string]string{legacytokentracking.ConfigMapDataKey: label},
50 }
51 }
52
53 func configuredServiceAccountTokenSecret(lastUsedLabel, invalidSinceLabel, creationTimeString, serviceAccountName, serviceAccountUID, deletionTimeString string) *v1.Secret {
54 var deletionTime *metav1.Time
55 if deletionTimeString == "" {
56 deletionTime = nil
57 } else {
58 deletionTime = &metav1.Time{Time: time.Now().UTC()}
59 }
60 creationTime, _ := time.Parse(dateFormat, creationTimeString)
61 labels := map[string]string{}
62 if lastUsedLabel != "" {
63 labels[serviceaccount.LastUsedLabelKey] = lastUsedLabel
64 }
65 if invalidSinceLabel != "" {
66 labels[serviceaccount.InvalidSinceLabelKey] = invalidSinceLabel
67 }
68 return &v1.Secret{
69 ObjectMeta: metav1.ObjectMeta{
70 Name: "token-secret-1",
71 Namespace: "default",
72 UID: "23456",
73 ResourceVersion: "1",
74 Labels: labels,
75 CreationTimestamp: metav1.NewTime(creationTime),
76 DeletionTimestamp: deletionTime,
77 Annotations: map[string]string{
78 v1.ServiceAccountNameKey: serviceAccountName,
79 v1.ServiceAccountUIDKey: serviceAccountUID,
80 },
81 },
82 Type: v1.SecretTypeServiceAccountToken,
83 Data: map[string][]byte{
84 "token": []byte("ABC"),
85 "ca.crt": []byte("CA Data"),
86 "namespace": []byte("default"),
87 },
88 }
89 }
90
91 func configuredLegacyTokenCleanUpPeriod(start string) time.Duration {
92 current := time.Now().UTC()
93 startTime, _ := time.Parse(dateFormat, start)
94 return current.Sub(startTime)
95 }
96
97 func configuredPod(withSecretMount bool) *v1.Pod {
98 if !withSecretMount {
99 return &v1.Pod{
100 ObjectMeta: metav1.ObjectMeta{
101 Name: "pod-1",
102 Namespace: "default",
103 },
104 }
105 }
106 return &v1.Pod{
107 ObjectMeta: metav1.ObjectMeta{
108 Name: "pod-1",
109 Namespace: "default",
110 },
111 Spec: v1.PodSpec{
112 Volumes: []v1.Volume{{Name: "foo", VolumeSource: v1.VolumeSource{Secret: &v1.SecretVolumeSource{SecretName: "token-secret-1"}}}},
113 },
114 }
115 }
116
117 func patchContent(namespace, name, invalidSince string, uID types.UID) []byte {
118 patch, _ := json.Marshal(applyv1.Secret(name, namespace).WithUID(uID).WithLabels(map[string]string{serviceaccount.InvalidSinceLabelKey: invalidSince}))
119 return patch
120 }
121
122 func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
123 testcases := map[string]struct {
124 LegacyTokenCleanUpPeriod time.Duration
125
126 ExistingServiceAccount *v1.ServiceAccount
127 ExistingSecret *v1.Secret
128 ExistingPod *v1.Pod
129 ClientObjects []runtime.Object
130
131 ExpectedActions []core.Action
132 }{
133 "configmap does not exist": {
134 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
135 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
136 ExistingPod: configuredPod(false),
137 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-28"),
138 ExpectedActions: []core.Action{
139 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
140 },
141 },
142 "configmap exists, but the configmap does not have tracked-since label": {
143 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
144 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
145 ExistingPod: configuredPod(false),
146 ClientObjects: []runtime.Object{configuredConfigMap("")},
147 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-28"),
148 ExpectedActions: []core.Action{
149 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
150 },
151 },
152 "configmap exists, the time period since 'tracked-since' is smaller than the CleanUpPeriod": {
153 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
154 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
155 ExistingPod: configuredPod(false),
156 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-29")},
157 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
158 ExpectedActions: []core.Action{
159 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
160 },
161 },
162 "configmap exists, the 'tracked-since' cannot be parsed": {
163 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
164 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
165 ExistingPod: configuredPod(false),
166 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27-1")},
167 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
168 ExpectedActions: []core.Action{
169 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
170 },
171 },
172 "secret is not SecretTypeServiceAccountToken type": {
173 ExistingSecret: opaqueSecret(),
174 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
175 ExistingPod: configuredPod(false),
176 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
177 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
178 ExpectedActions: []core.Action{
179 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
180 },
181 },
182 "secret is not referenced by serviceaccount": {
183 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
184 ExistingServiceAccount: serviceAccount(emptySecretReferences()),
185 ExistingPod: configuredPod(false),
186 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
187 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
188 ExpectedActions: []core.Action{
189 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
190 },
191 },
192 "auto-generated secret has a late creation time": {
193 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-30", "default", "12345", ""),
194 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
195 ExistingPod: configuredPod(false),
196 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
197 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
198 ExpectedActions: []core.Action{
199 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
200 },
201 },
202 "auto-generated secret has a deletion time": {
203 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", "deleted"),
204 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
205 ExistingPod: configuredPod(false),
206 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
207 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
208 ExpectedActions: []core.Action{
209 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
210 },
211 },
212 "auto-generated secret has a late last-used time": {
213 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-30", "", "2022-12-27", "default", "12345", ""),
214 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
215 ExistingPod: configuredPod(false),
216 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
217 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
218 ExpectedActions: []core.Action{
219 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
220 },
221 },
222 "auto-generated secret has a last-used label, but it can not be parsed": {
223 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27-1", "", "2022-12-27", "default", "12345", ""),
224 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
225 ExistingPod: configuredPod(false),
226 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
227 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
228 ExpectedActions: []core.Action{
229 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
230 },
231 },
232 "secret-referenced service account does not exist": {
233 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
234 ExistingPod: configuredPod(false),
235 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
236 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
237 ExpectedActions: []core.Action{
238 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
239 },
240 },
241 "secret-referenced service account uid does not match": {
242 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "123456", ""),
243 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
244 ExistingPod: configuredPod(false),
245 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
246 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
247 ExpectedActions: []core.Action{
248 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
249 },
250 },
251 "secret-referenced service account name is empty": {
252 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "", "12345", ""),
253 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
254 ExistingPod: configuredPod(false),
255 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
256 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
257 ExpectedActions: []core.Action{
258 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
259 },
260 },
261 "auto-generated secret does not have 'last-used' label, has not been marked as invalid": {
262 ExistingSecret: configuredServiceAccountTokenSecret("", "", "2022-12-27", "default", "12345", ""),
263 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
264 ExistingPod: configuredPod(false),
265 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
266 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
267 ExpectedActions: []core.Action{
268 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
269 core.NewPatchAction(
270 schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
271 metav1.NamespaceDefault, "token-secret-1",
272 types.MergePatchType,
273 patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")),
274 ),
275 },
276 },
277 "auto-generated secret does not have 'last-used' label, has been marked as invalid, invalid_since label can not be parsed": {
278 ExistingSecret: configuredServiceAccountTokenSecret("", "2022-12-29-1", "2022-12-27", "default", "12345", ""),
279 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
280 ExistingPod: configuredPod(false),
281 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
282 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
283 ExpectedActions: []core.Action{
284 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
285 core.NewPatchAction(
286 schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
287 metav1.NamespaceDefault, "token-secret-1",
288 types.MergePatchType,
289 patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")),
290 ),
291 },
292 },
293 "auto-generated secret does not have 'last-used' label, has been marked as invalid, time period since invalid is less than CleanUpPeriod": {
294 ExistingSecret: configuredServiceAccountTokenSecret("", "2023-01-01", "2022-12-27", "default", "12345", ""),
295 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
296 ExistingPod: configuredPod(false),
297 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
298 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
299 ExpectedActions: []core.Action{
300 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
301 },
302 },
303 "auto-generated secret does not have 'last-used' label, has been marked as invalid, time period since invalid is larger than CleanUpPeriod": {
304 ExistingSecret: configuredServiceAccountTokenSecret("", "2022-12-29", "2022-12-27", "default", "12345", ""),
305 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
306 ExistingPod: configuredPod(false),
307 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
308 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2023-01-01"),
309 ExpectedActions: []core.Action{
310 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
311 core.NewDeleteActionWithOptions(
312 schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
313 metav1.NamespaceDefault, "token-secret-1",
314 metav1.DeleteOptions{
315 Preconditions: &metav1.Preconditions{ResourceVersion: &configuredServiceAccountTokenSecret("", "2022-12-29", "2022-12-27", "default", "12345", "").ResourceVersion},
316 }),
317 },
318 },
319 "auto-generated secret is mounted by the pod": {
320 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
321 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
322 ExistingPod: configuredPod(true),
323 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
324 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
325 ExpectedActions: []core.Action{
326 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
327 },
328 },
329 "auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has not been marked as invalid": {
330 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
331 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
332 ExistingPod: configuredPod(false),
333 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
334 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
335 ExpectedActions: []core.Action{
336 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
337 core.NewPatchAction(
338 schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
339 metav1.NamespaceDefault, "token-secret-1",
340 types.MergePatchType,
341 patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")),
342 ),
343 },
344 },
345 "auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has been marked as invalid, time peroid since invalid is less than CleanUpPeriod": {
346 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2023-05-01", "2022-12-27", "default", "12345", ""),
347 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
348 ExistingPod: configuredPod(false),
349 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
350 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
351 ExpectedActions: []core.Action{
352 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
353 },
354 },
355 "auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has been marked as invalid, time peroid since invalid is larger than CleanUpPeriod": {
356 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2023-01-05", "2022-12-27", "default", "12345", ""),
357 ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
358 ExistingPod: configuredPod(false),
359 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
360 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2023-05-01"),
361 ExpectedActions: []core.Action{
362 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
363 core.NewDeleteActionWithOptions(
364 schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
365 metav1.NamespaceDefault, "token-secret-1",
366 metav1.DeleteOptions{
367 Preconditions: &metav1.Preconditions{ResourceVersion: &configuredServiceAccountTokenSecret("", "2023-01-05", "2022-12-27", "default", "12345", "").ResourceVersion},
368 }),
369 },
370 },
371 }
372
373 for k, tc := range testcases {
374 t.Run(k, func(t *testing.T) {
375 tc.ClientObjects = append(tc.ClientObjects, tc.ExistingSecret)
376 client := fake.NewSimpleClientset(tc.ClientObjects...)
377
378 informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
379 secretInformer := informers.Core().V1().Secrets()
380 saInformer := informers.Core().V1().ServiceAccounts()
381 podInformer := informers.Core().V1().Pods()
382 secrets := secretInformer.Informer().GetStore()
383 serviceAccounts := saInformer.Informer().GetStore()
384 pods := podInformer.Informer().GetStore()
385 options := LegacySATokenCleanerOptions{
386 SyncInterval: 30 * time.Second,
387 CleanUpPeriod: tc.LegacyTokenCleanUpPeriod,
388 }
389 cleaner, _ := NewLegacySATokenCleaner(saInformer, secretInformer, podInformer, client, testingclock.NewFakeClock(time.Now().UTC()), options)
390
391 if tc.ExistingServiceAccount != nil {
392 serviceAccounts.Add(tc.ExistingServiceAccount)
393 }
394 if tc.ExistingPod != nil {
395 pods.Add(tc.ExistingPod)
396 }
397 secrets.Add(tc.ExistingSecret)
398
399 ctx := context.TODO()
400 cleaner.evaluateSATokens(ctx)
401
402 actions := client.Actions()
403 if len(actions) != len(tc.ExpectedActions) {
404 t.Fatalf("got %d actions, wanted %d actions", len(actions), len(tc.ExpectedActions))
405 }
406 for i, action := range actions {
407 if len(tc.ExpectedActions) < i+1 {
408 t.Errorf("%s: %d unexpected actions: %+v", k, len(actions)-len(tc.ExpectedActions), actions[i:])
409 break
410 }
411 expectedAction := tc.ExpectedActions[i]
412 if !reflect.DeepEqual(expectedAction, action) {
413 t.Errorf("got action %#v, wanted %v", action, expectedAction)
414 }
415 }
416 })
417 }
418 }
419
View as plain text