implement LegacyServiceAccountTokenCleanUp alpha

This commit is contained in:
tinatingyu
2022-12-27 17:23:05 +00:00
parent 61ca72b541
commit 133eff3df4
24 changed files with 1134 additions and 16 deletions

View File

@@ -98,6 +98,8 @@ type KubeControllerManagerConfiguration struct {
// CronJobControllerConfiguration holds configuration for CronJobController
// related features.
CronJobController cronjobconfig.CronJobControllerConfiguration
// LegacySATokenCleanerConfiguration holds configuration for LegacySATokenCleaner related features.
LegacySATokenCleaner serviceaccountconfig.LegacySATokenCleanerConfiguration
// NamespaceControllerConfiguration holds configuration for NamespaceController
// related features.
NamespaceController namespaceconfig.NamespaceControllerConfiguration

View File

@@ -104,6 +104,8 @@ func SetDefaults_KubeControllerManagerConfiguration(obj *kubectrlmgrconfigv1alph
resourcequotaconfigv1alpha1.RecommendedDefaultResourceQuotaControllerConfiguration(&obj.ResourceQuotaController)
// Use the default RecommendedDefaultGenericControllerManagerConfiguration options
serviceconfigv1alpha1.RecommendedDefaultServiceControllerConfiguration(&obj.ServiceController)
// Use the default RecommendedDefaultLegacySATokenCleanerConfiguration options
serviceaccountconfigv1alpha1.RecommendedDefaultLegacySATokenCleanerConfiguration(&obj.LegacySATokenCleaner)
// Use the default RecommendedDefaultSAControllerConfiguration options
serviceaccountconfigv1alpha1.RecommendedDefaultSAControllerConfiguration(&obj.SAController)
// Use the default RecommendedDefaultTTLAfterFinishedControllerConfiguration options

View File

@@ -184,6 +184,9 @@ func autoConvert_v1alpha1_KubeControllerManagerConfiguration_To_config_KubeContr
if err := cronjobconfigv1alpha1.Convert_v1alpha1_CronJobControllerConfiguration_To_config_CronJobControllerConfiguration(&in.CronJobController, &out.CronJobController, s); err != nil {
return err
}
if err := serviceaccountconfigv1alpha1.Convert_v1alpha1_LegacySATokenCleanerConfiguration_To_config_LegacySATokenCleanerConfiguration(&in.LegacySATokenCleaner, &out.LegacySATokenCleaner, s); err != nil {
return err
}
if err := namespaceconfigv1alpha1.Convert_v1alpha1_NamespaceControllerConfiguration_To_config_NamespaceControllerConfiguration(&in.NamespaceController, &out.NamespaceController, s); err != nil {
return err
}
@@ -274,6 +277,9 @@ func autoConvert_config_KubeControllerManagerConfiguration_To_v1alpha1_KubeContr
if err := cronjobconfigv1alpha1.Convert_config_CronJobControllerConfiguration_To_v1alpha1_CronJobControllerConfiguration(&in.CronJobController, &out.CronJobController, s); err != nil {
return err
}
if err := serviceaccountconfigv1alpha1.Convert_config_LegacySATokenCleanerConfiguration_To_v1alpha1_LegacySATokenCleanerConfiguration(&in.LegacySATokenCleaner, &out.LegacySATokenCleaner, s); err != nil {
return err
}
if err := namespaceconfigv1alpha1.Convert_config_NamespaceControllerConfiguration_To_v1alpha1_NamespaceControllerConfiguration(&in.NamespaceController, &out.NamespaceController, s); err != nil {
return err
}

View File

@@ -61,6 +61,7 @@ func (in *KubeControllerManagerConfiguration) DeepCopyInto(out *KubeControllerMa
out.HPAController = in.HPAController
out.JobController = in.JobController
out.CronJobController = in.CronJobController
out.LegacySATokenCleaner = in.LegacySATokenCleaner
out.NamespaceController = in.NamespaceController
out.NodeIPAMController = in.NodeIPAMController
out.NodeLifecycleController = in.NodeLifecycleController

View File

@@ -16,6 +16,10 @@ limitations under the License.
package config
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// SAControllerConfiguration contains elements describing ServiceAccountController.
type SAControllerConfiguration struct {
// serviceAccountKeyFile is the filename containing a PEM-encoded private RSA key
@@ -28,3 +32,9 @@ type SAControllerConfiguration struct {
// account's token secret. This must be a valid PEM-encoded CA bundle.
RootCAFile string
}
type LegacySATokenCleanerConfiguration struct {
// CleanUpPeriod is the period of time since the last usage of an
// auto-generated service account token before it can be deleted.
CleanUpPeriod metav1.Duration
}

View File

@@ -38,3 +38,13 @@ func Convert_v1alpha1_SAControllerConfiguration_To_config_SAControllerConfigurat
func Convert_config_SAControllerConfiguration_To_v1alpha1_SAControllerConfiguration(in *config.SAControllerConfiguration, out *v1alpha1.SAControllerConfiguration, s conversion.Scope) error {
return autoConvert_config_SAControllerConfiguration_To_v1alpha1_SAControllerConfiguration(in, out, s)
}
// Convert_v1alpha1_LegacySATokenCleanerConfiguration_To_config_LegacySATokenCleanerConfiguration is an autogenerated conversion function.
func Convert_v1alpha1_LegacySATokenCleanerConfiguration_To_config_LegacySATokenCleanerConfiguration(in *v1alpha1.LegacySATokenCleanerConfiguration, out *config.LegacySATokenCleanerConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_LegacySATokenCleanerConfiguration_To_config_LegacySATokenCleanerConfiguration(in, out, s)
}
// Convert_config_LegacySATokenCleanerConfiguration_To_v1alpha1_LegacySATokenCleanerConfiguration is an autogenerated conversion function.
func Convert_config_LegacySATokenCleanerConfiguration_To_v1alpha1_LegacySATokenCleanerConfiguration(in *config.LegacySATokenCleanerConfiguration, out *v1alpha1.LegacySATokenCleanerConfiguration, s conversion.Scope) error {
return autoConvert_config_LegacySATokenCleanerConfiguration_To_v1alpha1_LegacySATokenCleanerConfiguration(in, out, s)
}

View File

@@ -17,6 +17,9 @@ limitations under the License.
package v1alpha1
import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubectrlmgrconfigv1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1"
)
@@ -34,3 +37,10 @@ func RecommendedDefaultSAControllerConfiguration(obj *kubectrlmgrconfigv1alpha1.
obj.ConcurrentSATokenSyncs = 5
}
}
func RecommendedDefaultLegacySATokenCleanerConfiguration(obj *kubectrlmgrconfigv1alpha1.LegacySATokenCleanerConfiguration) {
zero := metav1.Duration{}
if obj.CleanUpPeriod == zero {
obj.CleanUpPeriod = metav1.Duration{Duration: 365 * 24 * time.Hour}
}
}

View File

@@ -46,11 +46,21 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.LegacySATokenCleanerConfiguration)(nil), (*v1alpha1.LegacySATokenCleanerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_LegacySATokenCleanerConfiguration_To_v1alpha1_LegacySATokenCleanerConfiguration(a.(*config.LegacySATokenCleanerConfiguration), b.(*v1alpha1.LegacySATokenCleanerConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.SAControllerConfiguration)(nil), (*v1alpha1.SAControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_SAControllerConfiguration_To_v1alpha1_SAControllerConfiguration(a.(*config.SAControllerConfiguration), b.(*v1alpha1.SAControllerConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*v1alpha1.LegacySATokenCleanerConfiguration)(nil), (*config.LegacySATokenCleanerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_LegacySATokenCleanerConfiguration_To_config_LegacySATokenCleanerConfiguration(a.(*v1alpha1.LegacySATokenCleanerConfiguration), b.(*config.LegacySATokenCleanerConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*v1alpha1.SAControllerConfiguration)(nil), (*config.SAControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_SAControllerConfiguration_To_config_SAControllerConfiguration(a.(*v1alpha1.SAControllerConfiguration), b.(*config.SAControllerConfiguration), scope)
}); err != nil {
@@ -81,6 +91,16 @@ func Convert_v1_GroupResource_To_v1alpha1_GroupResource(in *v1.GroupResource, ou
return autoConvert_v1_GroupResource_To_v1alpha1_GroupResource(in, out, s)
}
func autoConvert_v1alpha1_LegacySATokenCleanerConfiguration_To_config_LegacySATokenCleanerConfiguration(in *v1alpha1.LegacySATokenCleanerConfiguration, out *config.LegacySATokenCleanerConfiguration, s conversion.Scope) error {
out.CleanUpPeriod = in.CleanUpPeriod
return nil
}
func autoConvert_config_LegacySATokenCleanerConfiguration_To_v1alpha1_LegacySATokenCleanerConfiguration(in *config.LegacySATokenCleanerConfiguration, out *v1alpha1.LegacySATokenCleanerConfiguration, s conversion.Scope) error {
out.CleanUpPeriod = in.CleanUpPeriod
return nil
}
func autoConvert_v1alpha1_SAControllerConfiguration_To_config_SAControllerConfiguration(in *v1alpha1.SAControllerConfiguration, out *config.SAControllerConfiguration, s conversion.Scope) error {
out.ServiceAccountKeyFile = in.ServiceAccountKeyFile
out.ConcurrentSATokenSyncs = in.ConcurrentSATokenSyncs

View File

@@ -21,6 +21,23 @@ limitations under the License.
package config
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LegacySATokenCleanerConfiguration) DeepCopyInto(out *LegacySATokenCleanerConfiguration) {
*out = *in
out.CleanUpPeriod = in.CleanUpPeriod
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LegacySATokenCleanerConfiguration.
func (in *LegacySATokenCleanerConfiguration) DeepCopy() *LegacySATokenCleanerConfiguration {
if in == nil {
return nil
}
out := new(LegacySATokenCleanerConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SAControllerConfiguration) DeepCopyInto(out *SAControllerConfiguration) {
*out = *in

View File

@@ -0,0 +1,266 @@
/*
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 serviceaccount
import (
"context"
"fmt"
"time"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
coreinformers "k8s.io/client-go/informers/core/v1"
clientset "k8s.io/client-go/kubernetes"
listersv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
"k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/utils/clock"
)
const (
dateFormat = "2006-01-02"
DefaultCleanerSyncInterval = 24 * time.Hour
)
// TokenCleanerOptions contains options for the LegacySATokenCleaner
type LegacySATokenCleanerOptions struct {
// CleanUpPeriod is the period of time since the last usage of an legacy token before it can be deleted.
CleanUpPeriod time.Duration
SyncInterval time.Duration
}
// LegacySATokenCleaner is a controller that deletes legacy serviceaccount tokens that are not in use for a specified period of time.
type LegacySATokenCleaner struct {
client clientset.Interface
clock clock.Clock
saLister listersv1.ServiceAccountLister
saInformerSynced cache.InformerSynced
secretLister listersv1.SecretLister
secretInformerSynced cache.InformerSynced
podLister listersv1.PodLister
podInformerSynced cache.InformerSynced
syncInterval time.Duration
minimumSinceLastUsed time.Duration
}
// NewLegacySATokenCleaner returns a new *NewLegacySATokenCleaner.
func NewLegacySATokenCleaner(saInformer coreinformers.ServiceAccountInformer, secretInformer coreinformers.SecretInformer, podInformer coreinformers.PodInformer, client clientset.Interface, cl clock.Clock, options LegacySATokenCleanerOptions) (*LegacySATokenCleaner, error) {
if !(options.CleanUpPeriod > 0) {
return nil, fmt.Errorf("invalid CleanUpPeriod: %v", options.CleanUpPeriod)
}
if !(options.SyncInterval > 0) {
return nil, fmt.Errorf("invalid SyncInterval: %v", options.SyncInterval)
}
tc := &LegacySATokenCleaner{
client: client,
clock: cl,
saLister: saInformer.Lister(),
saInformerSynced: saInformer.Informer().HasSynced,
secretLister: secretInformer.Lister(),
secretInformerSynced: secretInformer.Informer().HasSynced,
podLister: podInformer.Lister(),
podInformerSynced: podInformer.Informer().HasSynced,
minimumSinceLastUsed: options.CleanUpPeriod,
syncInterval: options.SyncInterval,
}
return tc, nil
}
func (tc *LegacySATokenCleaner) Run(ctx context.Context) {
defer utilruntime.HandleCrash()
logger := klog.FromContext(ctx)
logger.Info("Starting legacy service account token cleaner controller")
defer logger.Info("Shutting down legacy service account token cleaner controller")
if !cache.WaitForNamedCacheSync("legacy-service-account-token-cleaner", ctx.Done(), tc.saInformerSynced, tc.secretInformerSynced, tc.podInformerSynced) {
return
}
go wait.UntilWithContext(ctx, tc.evaluateSATokens, tc.syncInterval)
<-ctx.Done()
}
func (tc *LegacySATokenCleaner) evaluateSATokens(ctx context.Context) {
logger := klog.FromContext(ctx)
now := tc.clock.Now().UTC()
trackedSince, err := tc.latestPossibleTrackedSinceTime(ctx)
if err != nil {
logger.Error(err, "Getting lastest possible tracked_since time")
return
}
if now.Before(trackedSince.Add(tc.minimumSinceLastUsed)) {
// we haven't been tracking long enough
return
}
preserveCreatedOnOrAfter := now.Add(-tc.minimumSinceLastUsed)
preserveUsedOnOrAfter := now.Add(-tc.minimumSinceLastUsed).Format(dateFormat)
secretList, err := tc.secretLister.Secrets(metav1.NamespaceAll).List(labels.Everything())
if err != nil {
logger.Error(err, "Getting cached secret list")
return
}
namespaceToUsedSecretNames := make(map[string]sets.String)
for _, secret := range secretList {
if secret.Type != v1.SecretTypeServiceAccountToken {
continue
}
if !secret.CreationTimestamp.Time.Before(preserveCreatedOnOrAfter) {
continue
}
if secret.DeletionTimestamp != nil {
continue
}
// if LastUsedLabelKey does not exist, we think the secret has not been used
// since the legacy token starts to track.
lastUsed, ok := secret.Labels[serviceaccount.LastUsedLabelKey]
if ok {
_, err := time.Parse(dateFormat, lastUsed)
if err != nil {
// the lastUsed value is not well-formed thus we cannot determine it
logger.Error(err, "Parsing lastUsed time", "secret", klog.KRef(secret.Namespace, secret.Name))
continue
}
if lastUsed >= preserveUsedOnOrAfter {
continue
}
}
sa, saErr := tc.getServiceAccount(secret)
if saErr != nil {
logger.Error(saErr, "Getting service account", "secret", klog.KRef(secret.Namespace, secret.Name))
continue
}
if sa == nil || !hasSecretReference(sa, secret.Name) {
// can't determine if this is an auto-generated token
continue
}
mountedSecretNames, err := tc.getMountedSecretNames(secret.Namespace, namespaceToUsedSecretNames)
if err != nil {
logger.Error(err, "Resolving mounted secrets", "secret", klog.KRef(secret.Namespace, secret.Name))
continue
}
if mountedSecretNames.Has(secret.Name) {
// still used by pods
continue
}
logger.Info("Delete auto-generated service account token", "secret", klog.KRef(secret.Namespace, secret.Name), "creationTime", secret.CreationTimestamp, "lastUsed", lastUsed)
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) {
logger.Error(err, "Deleting legacy service account token", "secret", klog.KRef(secret.Namespace, secret.Name), "serviceaccount", sa.Name)
}
}
}
func (tc *LegacySATokenCleaner) getMountedSecretNames(secretNamespace string, namespaceToUsedSecretNames map[string]sets.String) (sets.String, error) {
if secrets, ok := namespaceToUsedSecretNames[secretNamespace]; ok {
return secrets, nil
}
podList, err := tc.podLister.Pods(secretNamespace).List(labels.Everything())
if err != nil {
return nil, fmt.Errorf("failed to get pod list from pod cache: %v", err)
}
var secrets sets.String
for _, pod := range podList {
podutil.VisitPodSecretNames(pod, func(secretName string) bool {
if secrets == nil {
secrets = sets.NewString()
}
secrets.Insert(secretName)
return true
})
}
if secrets != nil {
namespaceToUsedSecretNames[secretNamespace] = secrets
}
return secrets, nil
}
func (tc *LegacySATokenCleaner) getServiceAccount(secret *v1.Secret) (*v1.ServiceAccount, error) {
saName := secret.Annotations[v1.ServiceAccountNameKey]
if len(saName) == 0 {
return nil, nil
}
saUID := types.UID(secret.Annotations[v1.ServiceAccountUIDKey])
sa, err := tc.saLister.ServiceAccounts(secret.Namespace).Get(saName)
if apierrors.IsNotFound(err) {
return nil, nil
}
if err != nil {
return nil, err
}
// Ensure UID matches if given
if len(saUID) == 0 || saUID == sa.UID {
return sa, nil
}
return nil, nil
}
// get the latest possible TrackedSince time information from the configMap label.
func (tc *LegacySATokenCleaner) latestPossibleTrackedSinceTime(ctx context.Context) (time.Time, error) {
configMap, err := tc.client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, legacytokentracking.ConfigMapName, metav1.GetOptions{})
if err != nil {
return time.Time{}, err
}
trackedSince, exist := configMap.Data[legacytokentracking.ConfigMapDataKey]
if !exist {
return time.Time{}, fmt.Errorf("configMap does not have since label")
}
trackedSinceTime, err := time.Parse(dateFormat, trackedSince)
if err != nil {
return time.Time{}, fmt.Errorf("error parsing trackedSince time: %v", err)
}
// make sure the time to be 00:00 on the day just after the date starts to track
return trackedSinceTime.AddDate(0, 0, 1), nil
}
func hasSecretReference(serviceAccount *v1.ServiceAccount, secretName string) bool {
for _, secret := range serviceAccount.Secrets {
if secret.Name == secretName {
return true
}
}
return false
}

View File

@@ -0,0 +1,340 @@
/*
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 serviceaccount
import (
"context"
"reflect"
"testing"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
"k8s.io/kubernetes/pkg/serviceaccount"
testingclock "k8s.io/utils/clock/testing"
)
func configuredConfigMap(label string) *v1.ConfigMap {
if label == "" {
return &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: legacytokentracking.ConfigMapName},
}
}
return &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: legacytokentracking.ConfigMapName},
Data: map[string]string{legacytokentracking.ConfigMapDataKey: label},
}
}
func configuredServiceAccountTokenSecret(label, creationTimeString, serviceAccountName, serviceAccountUID, deletionTimeString string) *v1.Secret {
var deletionTime *metav1.Time
if deletionTimeString == "" {
deletionTime = nil
} else {
deletionTime = &metav1.Time{Time: time.Now().UTC()}
}
creationTime, _ := time.Parse(dateFormat, creationTimeString)
labels := map[string]string{}
if label != "" {
labels[serviceaccount.LastUsedLabelKey] = label
}
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "token-secret-1",
Namespace: "default",
UID: "23456",
ResourceVersion: "1",
Labels: labels,
CreationTimestamp: metav1.NewTime(creationTime),
DeletionTimestamp: deletionTime,
Annotations: map[string]string{
v1.ServiceAccountNameKey: serviceAccountName,
v1.ServiceAccountUIDKey: serviceAccountUID,
},
},
Type: v1.SecretTypeServiceAccountToken,
Data: map[string][]byte{
"token": []byte("ABC"),
"ca.crt": []byte("CA Data"),
"namespace": []byte("default"),
},
}
}
func configuredLegacyTokenCleanUpPeriod(start string) time.Duration {
current := time.Now().UTC()
startTime, _ := time.Parse(dateFormat, start)
return current.Sub(startTime)
}
func configuredPod(withSecretMount bool) *v1.Pod {
if !withSecretMount {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: "default",
},
}
}
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: "default",
},
Spec: v1.PodSpec{
Volumes: []v1.Volume{{Name: "foo", VolumeSource: v1.VolumeSource{Secret: &v1.SecretVolumeSource{SecretName: "token-secret-1"}}}},
},
}
}
func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
testcases := map[string]struct {
LegacyTokenCleanUpPeriod time.Duration
ExistingServiceAccount *v1.ServiceAccount
ExistingSecret *v1.Secret
ExistingPod *v1.Pod
ClientObjects []runtime.Object
ExpectedActions []core.Action
}{
"configmap does not exist": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-28"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"configmap exists, but the configmap does not have tracked-since label": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-28"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"configmap exists, the time period since 'tracked-since' is smaller than the CleanUpPeriod": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-29")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"configmap exists, the 'tracked-since' cannot be parsed": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27-1")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"secret is not SecretTypeServiceAccountToken type": {
ExistingSecret: opaqueSecret(),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"secret is not referenced by serviceaccount": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(emptySecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"auto-generated secret has a late creation time": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-30", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"auto-generated secret has a deletion time": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", "deleted"),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"auto-generated secret has a late last-used time": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-30", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"auto-generated secret has a last-used label, but it can not be parsed": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27-1", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"secret-referenced service account does not exist": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"secret-referenced service account uid does not match": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "123456", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"secret-referenced service account name is empty": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"auto-generated secret does not have 'last-used' label": {
ExistingSecret: configuredServiceAccountTokenSecret("", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
core.NewDeleteActionWithOptions(
schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
metav1.NamespaceDefault, "token-secret-1",
metav1.DeleteOptions{
Preconditions: &metav1.Preconditions{ResourceVersion: &configuredServiceAccountTokenSecret("", "2022-12-27", "default", "12345", "").ResourceVersion},
}),
},
},
"auto-generated secret is mounted by the pod": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(true),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
},
},
"auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
core.NewDeleteActionWithOptions(
schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
metav1.NamespaceDefault, "token-secret-1",
metav1.DeleteOptions{Preconditions: &metav1.Preconditions{
ResourceVersion: &configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", "").ResourceVersion,
},
}),
},
},
}
for k, tc := range testcases {
t.Run(k, func(t *testing.T) {
tc.ClientObjects = append(tc.ClientObjects, tc.ExistingSecret)
client := fake.NewSimpleClientset(tc.ClientObjects...)
informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
secretInformer := informers.Core().V1().Secrets()
saInformer := informers.Core().V1().ServiceAccounts()
podInformer := informers.Core().V1().Pods()
secrets := secretInformer.Informer().GetStore()
serviceAccounts := saInformer.Informer().GetStore()
pods := podInformer.Informer().GetStore()
options := LegacySATokenCleanerOptions{
SyncInterval: 30 * time.Second,
CleanUpPeriod: tc.LegacyTokenCleanUpPeriod,
}
cleaner, _ := NewLegacySATokenCleaner(saInformer, secretInformer, podInformer, client, testingclock.NewFakeClock(time.Now().UTC()), options)
if tc.ExistingServiceAccount != nil {
serviceAccounts.Add(tc.ExistingServiceAccount)
}
if tc.ExistingPod != nil {
pods.Add(tc.ExistingPod)
}
secrets.Add(tc.ExistingSecret)
ctx := context.TODO()
cleaner.evaluateSATokens(ctx)
actions := client.Actions()
if len(actions) != len(tc.ExpectedActions) {
t.Fatalf("got %d actions, wanted %d actions", len(actions), len(tc.ExpectedActions))
}
for i, action := range actions {
if len(tc.ExpectedActions) < i+1 {
t.Errorf("%s: %d unexpected actions: %+v", k, len(actions)-len(tc.ExpectedActions), actions[i:])
break
}
expectedAction := tc.ExpectedActions[i]
if !reflect.DeepEqual(expectedAction, action) {
t.Errorf("got action %#v, wanted %v", action, expectedAction)
}
}
})
}
}