Merge pull request #115554 from yt2985/cleanSA

LegacyServiceAccountTokenCleanUp alpha
This commit is contained in:
Kubernetes Prow Robot 2023-05-26 08:54:53 -07:00 committed by GitHub
commit c35a2775b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1134 additions and 16 deletions

View File

@ -518,6 +518,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,K
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,HPAController
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,JobController
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,KubeCloudShared
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,LegacySATokenCleaner
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,NamespaceController
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,NodeIPAMController
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,NodeLifecycleController
@ -530,6 +531,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,K
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ServiceController
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,StatefulSetController
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,TTLAfterFinishedController
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,LegacySATokenCleanerConfiguration,CleanUpPeriod
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,ConcurrentNamespaceSyncs
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,NamespaceSyncPeriod
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NodeIPAMControllerConfiguration,NodeCIDRMaskSize

View File

@ -483,6 +483,9 @@ func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.DynamicResourceAllocation) {
register("resource-claim-controller", startResourceClaimController)
}
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.LegacyServiceAccountTokenCleanUp) {
register("legacy-service-account-token-cleaner", startLegacySATokenCleaner)
}
return controllers
}

View File

@ -68,6 +68,7 @@ import (
"k8s.io/kubernetes/pkg/controller/volume/pvprotection"
quotainstall "k8s.io/kubernetes/pkg/quota/v1/install"
"k8s.io/kubernetes/pkg/volume/csimigration"
"k8s.io/utils/clock"
netutils "k8s.io/utils/net"
)
@ -581,6 +582,25 @@ func startTTLAfterFinishedController(ctx context.Context, controllerContext Cont
return nil, true, nil
}
func startLegacySATokenCleaner(ctx context.Context, controllerContext ControllerContext) (controller.Interface, bool, error) {
cleanUpPeriod := controllerContext.ComponentConfig.LegacySATokenCleaner.CleanUpPeriod.Duration
legacySATokenCleaner, err := serviceaccountcontroller.NewLegacySATokenCleaner(
controllerContext.InformerFactory.Core().V1().ServiceAccounts(),
controllerContext.InformerFactory.Core().V1().Secrets(),
controllerContext.InformerFactory.Core().V1().Pods(),
controllerContext.ClientBuilder.ClientOrDie("legacy-service-account-token-cleaner"),
clock.RealClock{},
serviceaccountcontroller.LegacySATokenCleanerOptions{
CleanUpPeriod: cleanUpPeriod,
SyncInterval: serviceaccountcontroller.DefaultCleanerSyncInterval,
})
if err != nil {
return nil, true, fmt.Errorf("failed to start the legacy service account token cleaner: %v", err)
}
go legacySATokenCleaner.Run(ctx)
return nil, true, nil
}
// processCIDRs is a helper function that works on a comma separated cidrs and returns
// a list of typed cidrs
// error if failed to parse any of the cidrs or invalid length of cidrs

View File

@ -0,0 +1,58 @@
/*
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 options
import (
"github.com/spf13/pflag"
serviceaccountconfig "k8s.io/kubernetes/pkg/controller/serviceaccount/config"
)
// LegacySATokenCleanerOptions holds the LegacySATokenCleaner options.
type LegacySATokenCleanerOptions struct {
*serviceaccountconfig.LegacySATokenCleanerConfiguration
}
// AddFlags adds flags related to LegacySATokenCleaner for controller manager to the specified FlagSet
func (o *LegacySATokenCleanerOptions) AddFlags(fs *pflag.FlagSet) {
if o == nil {
return
}
fs.DurationVar(&o.CleanUpPeriod.Duration, "legacy-service-account-token-clean-up-period", o.CleanUpPeriod.Duration, "The period of time since the last usage of an legacy service account token before it can be deleted.")
}
// ApplyTo fills up LegacySATokenCleaner config with options.
func (o *LegacySATokenCleanerOptions) ApplyTo(cfg *serviceaccountconfig.LegacySATokenCleanerConfiguration) error {
if o == nil {
return nil
}
cfg.CleanUpPeriod = o.CleanUpPeriod
return nil
}
// Validate checks validation of LegacySATokenCleanerOptions.
func (o *LegacySATokenCleanerOptions) Validate() []error {
if o == nil {
return nil
}
errs := []error{}
return errs
}

View File

@ -74,6 +74,7 @@ type KubeControllerManagerOptions struct {
HPAController *HPAControllerOptions
JobController *JobControllerOptions
CronJobController *CronJobControllerOptions
LegacySATokenCleaner *LegacySATokenCleanerOptions
NamespaceController *NamespaceControllerOptions
NodeIPAMController *NodeIPAMControllerOptions
NodeLifecycleController *NodeLifecycleControllerOptions
@ -150,6 +151,9 @@ func NewKubeControllerManagerOptions() (*KubeControllerManagerOptions, error) {
CronJobController: &CronJobControllerOptions{
&componentConfig.CronJobController,
},
LegacySATokenCleaner: &LegacySATokenCleanerOptions{
&componentConfig.LegacySATokenCleaner,
},
NamespaceController: &NamespaceControllerOptions{
&componentConfig.NamespaceController,
},
@ -244,6 +248,7 @@ func (s *KubeControllerManagerOptions) Flags(allControllers []string, disabledBy
s.HPAController.AddFlags(fss.FlagSet("horizontalpodautoscaling controller"))
s.JobController.AddFlags(fss.FlagSet("job controller"))
s.CronJobController.AddFlags(fss.FlagSet("cronjob controller"))
s.LegacySATokenCleaner.AddFlags(fss.FlagSet("legacy service account token cleaner"))
s.NamespaceController.AddFlags(fss.FlagSet("namespace controller"))
s.NodeIPAMController.AddFlags(fss.FlagSet("nodeipam controller"))
s.NodeLifecycleController.AddFlags(fss.FlagSet("nodelifecycle controller"))
@ -315,6 +320,9 @@ func (s *KubeControllerManagerOptions) ApplyTo(c *kubecontrollerconfig.Config) e
if err := s.CronJobController.ApplyTo(&c.ComponentConfig.CronJobController); err != nil {
return err
}
if err := s.LegacySATokenCleaner.ApplyTo(&c.ComponentConfig.LegacySATokenCleaner); err != nil {
return err
}
if err := s.NamespaceController.ApplyTo(&c.ComponentConfig.NamespaceController); err != nil {
return err
}
@ -382,6 +390,7 @@ func (s *KubeControllerManagerOptions) Validate(allControllers []string, disable
errs = append(errs, s.HPAController.Validate()...)
errs = append(errs, s.JobController.Validate()...)
errs = append(errs, s.CronJobController.Validate()...)
errs = append(errs, s.LegacySATokenCleaner.Validate()...)
errs = append(errs, s.NamespaceController.Validate()...)
errs = append(errs, s.NodeIPAMController.Validate()...)
errs = append(errs, s.NodeLifecycleController.Validate()...)

View File

@ -129,6 +129,7 @@ var args = []string{
"--leader-elect-renew-deadline=15s",
"--leader-elect-resource-lock=configmap",
"--leader-elect-retry-period=5s",
"--legacy-service-account-token-clean-up-period=8760h",
"--master=192.168.4.20",
"--max-endpoints-per-slice=200",
"--min-resync-period=8h",
@ -397,6 +398,11 @@ func TestAddFlags(t *testing.T) {
ConcurrentSATokenSyncs: 10,
},
},
LegacySATokenCleaner: &LegacySATokenCleanerOptions{
&serviceaccountconfig.LegacySATokenCleanerConfiguration{
CleanUpPeriod: metav1.Duration{Duration: 365 * 24 * time.Hour},
},
},
TTLAfterFinishedController: &TTLAfterFinishedControllerOptions{
&ttlafterfinishedconfig.TTLAfterFinishedControllerConfiguration{
ConcurrentTTLSyncs: 8,
@ -627,6 +633,9 @@ func TestApplyTo(t *testing.T) {
ServiceAccountKeyFile: "/service-account-private-key",
ConcurrentSATokenSyncs: 10,
},
LegacySATokenCleaner: serviceaccountconfig.LegacySATokenCleanerConfiguration{
CleanUpPeriod: metav1.Duration{Duration: 365 * 24 * time.Hour},
},
TTLAfterFinishedController: ttlafterfinishedconfig.TTLAfterFinishedControllerConfiguration{
ConcurrentTTLSyncs: 8,
},
@ -1225,6 +1234,15 @@ func TestValidateControllersOptions(t *testing.T) {
},
}).Validate,
},
{
name: "LegacySATokenCleanerOptions",
expectErrors: false,
validate: (&LegacySATokenCleanerOptions{
&serviceaccountconfig.LegacySATokenCleanerConfiguration{
CleanUpPeriod: metav1.Duration{Duration: 24 * 365 * time.Hour},
},
}).Validate,
},
{
name: "TTLAfterFinishedControllerOptions",
expectErrors: false,

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)
}
}
})
}
}

View File

@ -467,6 +467,13 @@ const (
// Enables tracking of secret-based service account tokens usage.
LegacyServiceAccountTokenTracking featuregate.Feature = "LegacyServiceAccountTokenTracking"
// owner: @yt2985
// kep: http://kep.k8s.io/2800
// alpha: v1.28
//
// Enables cleaning up of secret-based service account tokens.
LegacyServiceAccountTokenCleanUp featuregate.Feature = "LegacyServiceAccountTokenCleanUp"
// owner: @RobertKrawitz
// alpha: v1.15
//
@ -963,6 +970,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
LegacyServiceAccountTokenTracking: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.30
LegacyServiceAccountTokenCleanUp: {Default: false, PreRelease: featuregate.Alpha},
LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha},
LogarithmicScaleDown: {Default: true, PreRelease: featuregate.Beta},

View File

@ -1064,6 +1064,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"k8s.io/kube-controller-manager/config/v1alpha1.HPAControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_HPAControllerConfiguration(ref),
"k8s.io/kube-controller-manager/config/v1alpha1.JobControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_JobControllerConfiguration(ref),
"k8s.io/kube-controller-manager/config/v1alpha1.KubeControllerManagerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_KubeControllerManagerConfiguration(ref),
"k8s.io/kube-controller-manager/config/v1alpha1.LegacySATokenCleanerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_LegacySATokenCleanerConfiguration(ref),
"k8s.io/kube-controller-manager/config/v1alpha1.NamespaceControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_NamespaceControllerConfiguration(ref),
"k8s.io/kube-controller-manager/config/v1alpha1.NodeIPAMControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_NodeIPAMControllerConfiguration(ref),
"k8s.io/kube-controller-manager/config/v1alpha1.NodeLifecycleControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_NodeLifecycleControllerConfiguration(ref),
@ -53044,9 +53045,16 @@ func schema_k8sio_kube_controller_manager_config_v1alpha1_KubeControllerManagerC
Ref: ref("k8s.io/kube-controller-manager/config/v1alpha1.CronJobControllerConfiguration"),
},
},
"LegacySATokenCleaner": {
SchemaProps: spec.SchemaProps{
Description: "LegacySATokenCleanerConfiguration holds configuration for LegacySATokenCleaner related features.",
Default: map[string]interface{}{},
Ref: ref("k8s.io/kube-controller-manager/config/v1alpha1.LegacySATokenCleanerConfiguration"),
},
},
"NamespaceController": {
SchemaProps: spec.SchemaProps{
Description: "NamespaceControllerConfiguration holds configuration for NamespaceController related features. NamespaceControllerConfiguration holds configuration for NamespaceController related features.",
Description: "NamespaceControllerConfiguration holds configuration for NamespaceController related features.",
Default: map[string]interface{}{},
Ref: ref("k8s.io/kube-controller-manager/config/v1alpha1.NamespaceControllerConfiguration"),
},
@ -53122,11 +53130,34 @@ func schema_k8sio_kube_controller_manager_config_v1alpha1_KubeControllerManagerC
},
},
},
Required: []string{"Generic", "KubeCloudShared", "AttachDetachController", "CSRSigningController", "DaemonSetController", "DeploymentController", "StatefulSetController", "DeprecatedController", "EndpointController", "EndpointSliceController", "EndpointSliceMirroringController", "EphemeralVolumeController", "GarbageCollectorController", "HPAController", "JobController", "CronJobController", "NamespaceController", "NodeIPAMController", "NodeLifecycleController", "PersistentVolumeBinderController", "PodGCController", "ReplicaSetController", "ReplicationController", "ResourceQuotaController", "SAController", "ServiceController", "TTLAfterFinishedController"},
Required: []string{"Generic", "KubeCloudShared", "AttachDetachController", "CSRSigningController", "DaemonSetController", "DeploymentController", "StatefulSetController", "DeprecatedController", "EndpointController", "EndpointSliceController", "EndpointSliceMirroringController", "EphemeralVolumeController", "GarbageCollectorController", "HPAController", "JobController", "CronJobController", "LegacySATokenCleaner", "NamespaceController", "NodeIPAMController", "NodeLifecycleController", "PersistentVolumeBinderController", "PodGCController", "ReplicaSetController", "ReplicationController", "ResourceQuotaController", "SAController", "ServiceController", "TTLAfterFinishedController"},
},
},
Dependencies: []string{
"k8s.io/cloud-provider/config/v1alpha1.KubeCloudSharedConfiguration", "k8s.io/cloud-provider/controllers/service/config/v1alpha1.ServiceControllerConfiguration", "k8s.io/controller-manager/config/v1alpha1.GenericControllerManagerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.AttachDetachControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.CSRSigningControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.CronJobControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DaemonSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DeploymentControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DeprecatedControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointSliceControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointSliceMirroringControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EphemeralVolumeControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.GarbageCollectorControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.HPAControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.JobControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NamespaceControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NodeIPAMControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NodeLifecycleControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.PersistentVolumeBinderControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.PodGCControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ReplicaSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ReplicationControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ResourceQuotaControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.SAControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.StatefulSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.TTLAfterFinishedControllerConfiguration"},
"k8s.io/cloud-provider/config/v1alpha1.KubeCloudSharedConfiguration", "k8s.io/cloud-provider/controllers/service/config/v1alpha1.ServiceControllerConfiguration", "k8s.io/controller-manager/config/v1alpha1.GenericControllerManagerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.AttachDetachControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.CSRSigningControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.CronJobControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DaemonSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DeploymentControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DeprecatedControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointSliceControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointSliceMirroringControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EphemeralVolumeControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.GarbageCollectorControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.HPAControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.JobControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.LegacySATokenCleanerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NamespaceControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NodeIPAMControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NodeLifecycleControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.PersistentVolumeBinderControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.PodGCControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ReplicaSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ReplicationControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ResourceQuotaControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.SAControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.StatefulSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.TTLAfterFinishedControllerConfiguration"},
}
}
func schema_k8sio_kube_controller_manager_config_v1alpha1_LegacySATokenCleanerConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "LegacySATokenCleanerConfiguration contains elements describing LegacySATokenCleaner",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"CleanUpPeriod": {
SchemaProps: spec.SchemaProps{
Description: "CleanUpPeriod is the period of time since the last usage of an auto-generated service account token before it can be deleted.",
Default: 0,
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
},
},
},
Required: []string{"CleanUpPeriod"},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.Duration"},
}
}

View File

@ -27,6 +27,7 @@ import (
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
"k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
"k8s.io/kubernetes/pkg/features"
)
@ -452,6 +453,15 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
},
})
}
if utilfeature.DefaultFeatureGate.Enabled(features.LegacyServiceAccountTokenCleanUp) {
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "legacy-service-account-token-cleaner"},
Rules: []rbacv1.PolicyRule{
rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("configmaps").Names(legacytokentracking.ConfigMapName).RuleOrDie(),
rbacv1helpers.NewRule("delete").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
},
})
}
return controllerRoles, controllerRoleBindings
}

View File

@ -131,8 +131,8 @@ type KubeControllerManagerConfiguration struct {
JobController JobControllerConfiguration
// CronJobControllerConfiguration holds configuration for CronJobController related features.
CronJobController CronJobControllerConfiguration
// NamespaceControllerConfiguration holds configuration for NamespaceController
// related features.
// LegacySATokenCleanerConfiguration holds configuration for LegacySATokenCleaner related features.
LegacySATokenCleaner LegacySATokenCleanerConfiguration
// NamespaceControllerConfiguration holds configuration for NamespaceController
// related features.
NamespaceController NamespaceControllerConfiguration
@ -357,6 +357,13 @@ type CronJobControllerConfiguration struct {
ConcurrentCronJobSyncs int32
}
// LegacySATokenCleanerConfiguration contains elements describing LegacySATokenCleaner
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
}
// NamespaceControllerConfiguration contains elements describing NamespaceController.
type NamespaceControllerConfiguration struct {
// namespaceSyncPeriod is the period for syncing namespace life-cycle

View File

@ -310,6 +310,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
@ -342,6 +343,23 @@ func (in *KubeControllerManagerConfiguration) DeepCopyObject() runtime.Object {
return nil
}
// 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 *NamespaceControllerConfiguration) DeepCopyInto(out *NamespaceControllerConfiguration) {
*out = *in

View File

@ -0,0 +1,251 @@
/*
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
// This file tests the legacy service account token cleaning-up.
import (
"context"
"fmt"
"testing"
"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/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientinformers "k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
listersv1 "k8s.io/client-go/listers/core/v1"
featuregatetesting "k8s.io/component-base/featuregate/testing"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
kubefeatures "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/utils/clock"
testingclock "k8s.io/utils/clock/testing"
)
const (
dateFormat = "2006-01-02"
cleanUpPeriod = 24 * time.Hour
syncInterval = 1 * time.Second
)
func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.LegacyServiceAccountTokenCleanUp, true)()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fakeClock := testingclock.NewFakeClock(time.Now().UTC())
c, config, stopFunc, informers, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
defer stopFunc()
if err != nil {
t.Fatalf("failed to setup ServiceAccounts server: %v", err)
}
// start legacy service account token cleaner
startLegacyServiceAccountTokenCleaner(ctx, c, fakeClock, informers)
// wait configmap to label with tracking date
if err := wait.PollImmediate(time.Second, 10*time.Second, func() (bool, error) {
configMap, err := c.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, legacytokentracking.ConfigMapName, metav1.GetOptions{})
if err != nil {
return false, err
}
_, exist := configMap.Data[legacytokentracking.ConfigMapDataKey]
if !exist {
return false, fmt.Errorf("configMap does not have since label")
}
return true, nil
}); err != nil {
t.Fatalf("failed to wait configmap starts to track: %v", err)
}
// create service account
myns := "clean-ns"
_, err = c.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: myns}}, metav1.CreateOptions{})
if err != nil && !apierrors.IsAlreadyExists(err) {
t.Fatalf("could not create namespace: %v", err)
}
mysa, err := c.CoreV1().ServiceAccounts(myns).Create(context.TODO(), &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: readOnlyServiceAccountName}}, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Service Account not created: %v", err)
}
tests := []struct {
name string
secretName string
secretTokenData string
expectCleanedUp bool
lastUsedLabel bool
isPodMounted bool
isManual bool
}{
{
name: "auto created legacy token without pod binding",
secretName: "auto-token-without-pod-mounting-a",
lastUsedLabel: true,
isManual: false,
isPodMounted: false,
expectCleanedUp: true,
},
{
name: "manually created legacy token",
secretName: "manual-token",
lastUsedLabel: true,
isManual: true,
isPodMounted: false,
expectCleanedUp: false,
},
{
name: "auto created legacy token with pod binding",
secretName: "auto-token-with-pod-mounting",
lastUsedLabel: true,
isManual: false,
isPodMounted: true,
expectCleanedUp: false,
},
{
name: "auto created legacy token without pod binding, secret has not been used after tracking",
secretName: "auto-token-without-pod-mounting-b",
lastUsedLabel: false,
isManual: false,
isPodMounted: false,
expectCleanedUp: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// create secret
secret, err := createServiceAccountToken(c, mysa, myns, test.secretName)
if err != nil {
t.Fatalf("Secret not created: %v", err)
}
if !test.isManual {
if err := addReferencedServiceAccountToken(c, myns, readOnlyServiceAccountName, secret); err != nil {
t.Fatal(err)
}
}
podLister := informers.Core().V1().Pods().Lister()
if test.isPodMounted {
_, err = createAutotokenMountedPod(c, myns, test.secretName, podLister)
if err != nil {
t.Fatalf("Pod not created: %v", err)
}
}
myConfig := *config
wh := &warningHandler{}
myConfig.WarningHandler = wh
myConfig.BearerToken = string(string(secret.Data[v1.ServiceAccountTokenKey]))
roClient := clientset.NewForConfigOrDie(&myConfig)
// the secret should not be labeled with LastUsedLabelKey.
liveSecret, err := c.CoreV1().Secrets(myns).Get(context.TODO(), test.secretName, metav1.GetOptions{})
if err != nil {
t.Fatalf("Could not get secret: %v", err)
}
_, ok := liveSecret.GetLabels()[serviceaccount.LastUsedLabelKey]
if ok {
t.Fatalf("Secret %s should not have the lastUsed label", test.secretName)
}
// authenticate legacy tokens
if test.lastUsedLabel {
doServiceAccountAPIRequests(t, roClient, myns, true, true, false)
// all service account tokens should be labeled with LastUsedLabelKey.
liveSecret, err = c.CoreV1().Secrets(myns).Get(context.TODO(), test.secretName, metav1.GetOptions{})
if err != nil {
t.Fatalf("Could not get secret: %v", err)
}
lastUsed, ok := liveSecret.GetLabels()[serviceaccount.LastUsedLabelKey]
if !ok {
t.Fatalf("The secret %s should be labeled lastUsed time: %s", test.secretName, lastUsed)
} else {
t.Logf("The secret %s has been labeled with %s", test.secretName, lastUsed)
}
}
_, err = c.CoreV1().Secrets(myns).Get(context.TODO(), test.secretName, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
t.Fatalf("The secret %s should not be cleaned up, err: %v", test.secretName, err)
} else {
t.Fatalf("Failed to get secret %s, err: %v", test.secretName, err)
}
}
fakeClock.Step(cleanUpPeriod + 24*time.Hour)
time.Sleep(2 * syncInterval)
liveSecret, err = c.CoreV1().Secrets(myns).Get(context.TODO(), test.secretName, metav1.GetOptions{})
if test.expectCleanedUp && err == nil {
t.Fatalf("The secret %s should be cleaned up. time: %v; creationTime: %v", test.secretName, fakeClock.Now().UTC(), liveSecret.CreationTimestamp)
} else if !test.expectCleanedUp && err != nil {
if apierrors.IsNotFound(err) {
t.Fatalf("The secret %s should not be cleaned up, err: %v", test.secretName, err)
} else {
t.Fatalf("Failed to get secret %s, err: %v", test.secretName, err)
}
}
})
}
}
func startLegacyServiceAccountTokenCleaner(ctx context.Context, client clientset.Interface, fakeClock clock.Clock, informers clientinformers.SharedInformerFactory) {
legacySATokenCleaner, _ := serviceaccountcontroller.NewLegacySATokenCleaner(
informers.Core().V1().ServiceAccounts(),
informers.Core().V1().Secrets(),
informers.Core().V1().Pods(),
client,
fakeClock,
serviceaccountcontroller.LegacySATokenCleanerOptions{
SyncInterval: syncInterval,
CleanUpPeriod: cleanUpPeriod,
})
go legacySATokenCleaner.Run(ctx)
informers.Start(ctx.Done())
}
func createAutotokenMountedPod(c clientset.Interface, ns, secretName string, podLister listersv1.PodLister) (*v1.Pod, error) {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "token-bound-pod",
Namespace: ns,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "name", Image: "image"},
},
Volumes: []v1.Volume{{Name: "foo", VolumeSource: v1.VolumeSource{Secret: &v1.SecretVolumeSource{SecretName: secretName}}}},
},
}
pod, err := c.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf("failed to create pod with token (%s:%s) bound, err: %v", ns, secretName, err)
}
err = wait.PollImmediate(time.Second, 10*time.Second, func() (bool, error) {
pod, err = podLister.Pods(ns).Get("token-bound-pod")
if err != nil {
return false, fmt.Errorf("failed to get pod with token (%s:%s) bound, err: %v", ns, secretName, err)
}
return true, nil
})
return pod, nil
}

View File

@ -52,8 +52,6 @@ import (
const (
readOnlyServiceAccountName = "ro"
readWriteServiceAccountName = "rw"
dateFormat = "2006-01-02"
)
func TestServiceAccountAutoCreate(t *testing.T) {
@ -61,7 +59,7 @@ func TestServiceAccountAutoCreate(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
c, _, stopFunc, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
c, _, stopFunc, _, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
defer stopFunc()
if err != nil {
t.Fatalf("failed to setup ServiceAccounts server: %v", err)
@ -102,7 +100,7 @@ func TestServiceAccountTokenAutoMount(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
c, _, stopFunc, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
c, _, stopFunc, _, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
defer stopFunc()
if err != nil {
t.Fatalf("failed to setup ServiceAccounts server: %v", err)
@ -148,7 +146,7 @@ func TestServiceAccountTokenAuthentication(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
c, config, stopFunc, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
c, config, stopFunc, _, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
defer stopFunc()
if err != nil {
t.Fatalf("failed to setup ServiceAccounts server: %v", err)
@ -229,7 +227,7 @@ func TestLegacyServiceAccountTokenTracking(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
c, config, stopFunc, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
c, config, stopFunc, _, err := startServiceAccountTestServerAndWaitForCaches(ctx, t)
defer stopFunc()
if err != nil {
t.Fatalf("failed to setup ServiceAccounts server: %v", err)
@ -347,7 +345,7 @@ func TestLegacyServiceAccountTokenTracking(t *testing.T) {
// startServiceAccountTestServerAndWaitForCaches returns a started server
// It is the responsibility of the caller to ensure the returned stopFunc is called
func startServiceAccountTestServerAndWaitForCaches(ctx context.Context, t *testing.T) (clientset.Interface, *restclient.Config, func(), error) {
func startServiceAccountTestServerAndWaitForCaches(ctx context.Context, t *testing.T) (clientset.Interface, *restclient.Config, func(), clientinformers.SharedInformerFactory, error) {
var serviceAccountKey interface{}
ctx, cancel := context.WithCancel(ctx)
@ -404,7 +402,7 @@ func startServiceAccountTestServerAndWaitForCaches(ctx context.Context, t *testi
// Start the service account and service account token controllers
tokenGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, serviceAccountKey)
if err != nil {
return rootClientset, clientConfig, stop, err
return rootClientset, clientConfig, stop, informers, err
}
tokenController, err := serviceaccountcontroller.NewTokensController(
informers.Core().V1().ServiceAccounts(),
@ -415,7 +413,7 @@ func startServiceAccountTestServerAndWaitForCaches(ctx context.Context, t *testi
},
)
if err != nil {
return rootClientset, clientConfig, stop, err
return rootClientset, clientConfig, stop, informers, err
}
go tokenController.Run(ctx, 1)
@ -426,7 +424,7 @@ func startServiceAccountTestServerAndWaitForCaches(ctx context.Context, t *testi
serviceaccountcontroller.DefaultServiceAccountsControllerOptions(),
)
if err != nil {
return rootClientset, clientConfig, stop, err
return rootClientset, clientConfig, stop, informers, err
}
informers.Start(ctx.Done())
go serviceAccountController.Run(ctx, 5)
@ -437,7 +435,7 @@ func startServiceAccountTestServerAndWaitForCaches(ctx context.Context, t *testi
// thus we wait until caches have synced
informers.WaitForCacheSync(ctx.Done())
return rootClientset, clientConfig, stop, nil
return rootClientset, clientConfig, stop, informers, nil
}
func getServiceAccount(c clientset.Interface, ns string, name string, shouldWait bool) (*v1.ServiceAccount, error) {