Merge pull request #120682 from yt2985/cleanSA

LegacyServiceAccountTokenCleanUp beta
This commit is contained in:
Kubernetes Prow Robot 2023-10-27 19:08:05 +02:00 committed by GitHub
commit fe21e4d749
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 498 additions and 136 deletions

View File

@ -18,6 +18,7 @@ package serviceaccount
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"time" "time"
@ -29,6 +30,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
applyv1 "k8s.io/client-go/applyconfigurations/core/v1"
coreinformers "k8s.io/client-go/informers/core/v1" coreinformers "k8s.io/client-go/informers/core/v1"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
listersv1 "k8s.io/client-go/listers/core/v1" listersv1 "k8s.io/client-go/listers/core/v1"
@ -183,7 +185,28 @@ func (tc *LegacySATokenCleaner) evaluateSATokens(ctx context.Context) {
continue continue
} }
logger.Info("Delete auto-generated service account token", "secret", klog.KRef(secret.Namespace, secret.Name), "creationTime", secret.CreationTimestamp, "lastUsed", lastUsed) invalidSince := secret.Labels[serviceaccount.InvalidSinceLabelKey]
// If the secret has not been labeled with invalid since date or the label value has invalid format, update the invalidSince label with the current date value.
_, err = time.Parse(dateFormat, invalidSince)
if err != nil {
invalidSince = now.Format(dateFormat)
logger.Info("Mark the auto-generated service account token as invalid", "invalidSince", invalidSince, "secret", klog.KRef(secret.Namespace, secret.Name))
patchContent, err := json.Marshal(applyv1.Secret(secret.Name, secret.Namespace).WithUID(secret.UID).WithLabels(map[string]string{serviceaccount.InvalidSinceLabelKey: invalidSince}))
if err != nil {
logger.Error(err, "Failed to marshal invalid since label")
} else {
if _, err := tc.client.CoreV1().Secrets(secret.Namespace).Patch(ctx, secret.Name, types.MergePatchType, patchContent, metav1.PatchOptions{}); err != nil {
logger.Error(err, "Failed to label legacy service account token secret with invalid since date")
}
}
continue
}
if invalidSince >= preserveUsedOnOrAfter {
continue
}
logger.Info("Delete auto-generated service account token", "secret", klog.KRef(secret.Namespace, secret.Name), "creationTime", secret.CreationTimestamp, "lastUsed", lastUsed, "invalidSince", invalidSince)
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) { 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) logger.Error(err, "Deleting legacy service account token", "secret", klog.KRef(secret.Namespace, secret.Name), "serviceaccount", sa.Name)
} }

View File

@ -18,6 +18,7 @@ package serviceaccount
import ( import (
"context" "context"
"encoding/json"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -26,6 +27,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
applyv1 "k8s.io/client-go/applyconfigurations/core/v1"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing" core "k8s.io/client-go/testing"
@ -47,7 +50,7 @@ func configuredConfigMap(label string) *v1.ConfigMap {
} }
} }
func configuredServiceAccountTokenSecret(label, creationTimeString, serviceAccountName, serviceAccountUID, deletionTimeString string) *v1.Secret { func configuredServiceAccountTokenSecret(lastUsedLabel, invalidSinceLabel, creationTimeString, serviceAccountName, serviceAccountUID, deletionTimeString string) *v1.Secret {
var deletionTime *metav1.Time var deletionTime *metav1.Time
if deletionTimeString == "" { if deletionTimeString == "" {
deletionTime = nil deletionTime = nil
@ -56,8 +59,11 @@ func configuredServiceAccountTokenSecret(label, creationTimeString, serviceAccou
} }
creationTime, _ := time.Parse(dateFormat, creationTimeString) creationTime, _ := time.Parse(dateFormat, creationTimeString)
labels := map[string]string{} labels := map[string]string{}
if label != "" { if lastUsedLabel != "" {
labels[serviceaccount.LastUsedLabelKey] = label labels[serviceaccount.LastUsedLabelKey] = lastUsedLabel
}
if invalidSinceLabel != "" {
labels[serviceaccount.InvalidSinceLabelKey] = invalidSinceLabel
} }
return &v1.Secret{ return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -108,6 +114,11 @@ func configuredPod(withSecretMount bool) *v1.Pod {
} }
} }
func patchContent(namespace, name, invalidSince string, uID types.UID) []byte {
patch, _ := json.Marshal(applyv1.Secret(name, namespace).WithUID(uID).WithLabels(map[string]string{serviceaccount.InvalidSinceLabelKey: invalidSince}))
return patch
}
func TestLegacyServiceAccountTokenCleanUp(t *testing.T) { func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
testcases := map[string]struct { testcases := map[string]struct {
LegacyTokenCleanUpPeriod time.Duration LegacyTokenCleanUpPeriod time.Duration
@ -120,7 +131,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
ExpectedActions []core.Action ExpectedActions []core.Action
}{ }{
"configmap does not exist": { "configmap does not exist": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-28"), LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-28"),
@ -129,7 +140,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"configmap exists, but the configmap does not have tracked-since label": { "configmap exists, but the configmap does not have tracked-since label": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("")}, ClientObjects: []runtime.Object{configuredConfigMap("")},
@ -139,7 +150,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"configmap exists, the time period since 'tracked-since' is smaller than the CleanUpPeriod": { "configmap exists, the time period since 'tracked-since' is smaller than the CleanUpPeriod": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-29")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-29")},
@ -149,7 +160,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"configmap exists, the 'tracked-since' cannot be parsed": { "configmap exists, the 'tracked-since' cannot be parsed": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27-1")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27-1")},
@ -169,7 +180,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"secret is not referenced by serviceaccount": { "secret is not referenced by serviceaccount": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(emptySecretReferences()), ExistingServiceAccount: serviceAccount(emptySecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
@ -179,7 +190,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"auto-generated secret has a late creation time": { "auto-generated secret has a late creation time": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-30", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-30", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
@ -189,7 +200,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"auto-generated secret has a deletion time": { "auto-generated secret has a deletion time": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", "deleted"), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", "deleted"),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
@ -199,7 +210,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"auto-generated secret has a late last-used time": { "auto-generated secret has a late last-used time": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-30", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-30", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
@ -209,7 +220,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"auto-generated secret has a last-used label, but it can not be parsed": { "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", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27-1", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
@ -219,7 +230,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"secret-referenced service account does not exist": { "secret-referenced service account does not exist": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"),
@ -228,7 +239,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"secret-referenced service account uid does not match": { "secret-referenced service account uid does not match": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "123456", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "123456", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
@ -238,7 +249,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
}, },
}, },
"secret-referenced service account name is empty": { "secret-referenced service account name is empty": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")},
@ -247,24 +258,66 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
}, },
}, },
"auto-generated secret does not have 'last-used' label": { "auto-generated secret does not have 'last-used' label, has not been marked as invalid": {
ExistingSecret: configuredServiceAccountTokenSecret("", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"), LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
core.NewPatchAction(
schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
metav1.NamespaceDefault, "token-secret-1",
types.MergePatchType,
patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")),
),
},
},
"auto-generated secret does not have 'last-used' label, has been marked as invalid, invalid_since label can not be parsed": {
ExistingSecret: configuredServiceAccountTokenSecret("", "2022-12-29-1", "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.NewPatchAction(
schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
metav1.NamespaceDefault, "token-secret-1",
types.MergePatchType,
patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")),
),
},
},
"auto-generated secret does not have 'last-used' label, has been marked as invalid, time period since invalid is less than CleanUpPeriod": {
ExistingSecret: configuredServiceAccountTokenSecret("", "2023-01-01", "2022-12-27", "default", "12345", ""),
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),
},
},
"auto-generated secret does not have 'last-used' label, has been marked as invalid, time period since invalid is larger than CleanUpPeriod": {
ExistingSecret: configuredServiceAccountTokenSecret("", "2022-12-29", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2023-01-01"),
ExpectedActions: []core.Action{ ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
core.NewDeleteActionWithOptions( core.NewDeleteActionWithOptions(
schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
metav1.NamespaceDefault, "token-secret-1", metav1.NamespaceDefault, "token-secret-1",
metav1.DeleteOptions{ metav1.DeleteOptions{
Preconditions: &metav1.Preconditions{ResourceVersion: &configuredServiceAccountTokenSecret("", "2022-12-27", "default", "12345", "").ResourceVersion}, Preconditions: &metav1.Preconditions{ResourceVersion: &configuredServiceAccountTokenSecret("", "2022-12-29", "2022-12-27", "default", "12345", "").ResourceVersion},
}), }),
}, },
}, },
"auto-generated secret is mounted by the pod": { "auto-generated secret is mounted by the pod": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(true), ExistingPod: configuredPod(true),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
@ -273,20 +326,45 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 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": { "auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has not been marked as invalid": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", ""), ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()), ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false), ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"), LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"),
ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
core.NewPatchAction(
schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
metav1.NamespaceDefault, "token-secret-1",
types.MergePatchType,
patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")),
),
},
},
"auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has been marked as invalid, time peroid since invalid is less than CleanUpPeriod": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2023-05-01", "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),
},
},
"auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has been marked as invalid, time peroid since invalid is larger than CleanUpPeriod": {
ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2023-01-05", "2022-12-27", "default", "12345", ""),
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
ExistingPod: configuredPod(false),
ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")},
LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2023-05-01"),
ExpectedActions: []core.Action{ ExpectedActions: []core.Action{
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName),
core.NewDeleteActionWithOptions( core.NewDeleteActionWithOptions(
schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
metav1.NamespaceDefault, "token-secret-1", metav1.NamespaceDefault, "token-secret-1",
metav1.DeleteOptions{Preconditions: &metav1.Preconditions{ metav1.DeleteOptions{
ResourceVersion: &configuredServiceAccountTokenSecret("2022-12-27", "2022-12-27", "default", "12345", "").ResourceVersion, Preconditions: &metav1.Preconditions{ResourceVersion: &configuredServiceAccountTokenSecret("", "2023-01-05", "2022-12-27", "default", "12345", "").ResourceVersion},
},
}), }),
}, },
}, },

View File

@ -475,6 +475,7 @@ const (
// owner: @yt2985 // owner: @yt2985
// kep: http://kep.k8s.io/2800 // kep: http://kep.k8s.io/2800
// alpha: v1.28 // alpha: v1.28
// beta: v1.29
// //
// Enables cleaning up of secret-based service account tokens. // Enables cleaning up of secret-based service account tokens.
LegacyServiceAccountTokenCleanUp featuregate.Feature = "LegacyServiceAccountTokenCleanUp" LegacyServiceAccountTokenCleanUp featuregate.Feature = "LegacyServiceAccountTokenCleanUp"
@ -1014,7 +1015,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
LegacyServiceAccountTokenTracking: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.30 LegacyServiceAccountTokenTracking: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.30
LegacyServiceAccountTokenCleanUp: {Default: false, PreRelease: featuregate.Alpha}, LegacyServiceAccountTokenCleanUp: {Default: true, PreRelease: featuregate.Beta},
LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha}, LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha},

View File

@ -151,6 +151,15 @@ func TestTokenGenerateAndValidate(t *testing.T) {
Namespace: "test", Namespace: "test",
}, },
} }
invalidAutoSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "my-rsa-secret",
Namespace: "test",
Labels: map[string]string{
"kubernetes.io/legacy-token-invalid-since": "2022-12-20",
},
},
}
ecdsaSecret := &v1.Secret{ ecdsaSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "my-ecdsa-secret", Name: "my-ecdsa-secret",
@ -176,6 +185,20 @@ func TestTokenGenerateAndValidate(t *testing.T) {
checkJSONWebSignatureHasKeyID(t, rsaToken, rsaKeyID) checkJSONWebSignatureHasKeyID(t, rsaToken, rsaKeyID)
// Generate RSA token with invalidAutoSecret
invalidAutoSecretToken, err := rsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *invalidAutoSecret))
if err != nil {
t.Fatalf("error generating token: %v", err)
}
if len(invalidAutoSecretToken) == 0 {
t.Fatalf("no token generated")
}
invalidAutoSecret.Data = map[string][]byte{
"token": []byte(invalidAutoSecretToken),
}
checkJSONWebSignatureHasKeyID(t, invalidAutoSecretToken, rsaKeyID)
// Generate the ECDSA token // Generate the ECDSA token
ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey)) ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey))
if err != nil { if err != nil {
@ -327,6 +350,12 @@ func TestTokenGenerateAndValidate(t *testing.T) {
ExpectedErr: true, ExpectedErr: true,
ExpectedOK: false, ExpectedOK: false,
}, },
"secret is marked as invalid": {
Token: invalidAutoSecretToken,
Client: fake.NewSimpleClientset(serviceAccount, invalidAutoSecret),
Keys: []interface{}{getPublicKey(rsaPublicKey)},
ExpectedErr: true,
},
} }
for k, tc := range testCases { for k, tc := range testCases {

View File

@ -36,6 +36,8 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
const InvalidSinceLabelKey = "kubernetes.io/legacy-token-invalid-since"
func LegacyClaims(serviceAccount v1.ServiceAccount, secret v1.Secret) (*jwt.Claims, interface{}) { func LegacyClaims(serviceAccount v1.ServiceAccount, secret v1.Secret) (*jwt.Claims, interface{}) {
return &jwt.Claims{ return &jwt.Claims{
Subject: apiserverserviceaccount.MakeUsername(serviceAccount.Namespace, serviceAccount.Name), Subject: apiserverserviceaccount.MakeUsername(serviceAccount.Namespace, serviceAccount.Name),
@ -148,6 +150,14 @@ func (v *legacyValidator) Validate(ctx context.Context, tokenData string, public
// Track secret-based long-lived service account tokens and add audit annotations and metrics. // Track secret-based long-lived service account tokens and add audit annotations and metrics.
autoGenerated := false autoGenerated := false
// Check if the secret has been marked as invalid
if invalidSince := secret.Labels[InvalidSinceLabelKey]; invalidSince != "" {
audit.AddAuditAnnotation(ctx, "authentication.k8s.io/legacy-token-invalidated", secret.Name+"/"+secret.Namespace)
invalidatedAutoTokensTotal.WithContext(ctx).Inc()
v.patchSecretWithLastUsedDate(ctx, secret)
return nil, fmt.Errorf("the token in secret %s/%s for service account %s/%s has been marked invalid. Use tokens from the TokenRequest API or manually created secret-based tokens, or remove the '%s' label from the secret to temporarily allow use of this token", namespace, secretName, namespace, serviceAccountName, InvalidSinceLabelKey)
}
// Check if it is an auto-generated secret-based token // Check if it is an auto-generated secret-based token
for _, ref := range serviceAccount.Secrets { for _, ref := range serviceAccount.Secrets {
if ref.Name == secret.Name { if ref.Name == secret.Name {
@ -165,20 +175,7 @@ func (v *legacyValidator) Validate(ctx context.Context, tokenData string, public
manuallyCreatedTokensTotal.WithContext(ctx).Inc() manuallyCreatedTokensTotal.WithContext(ctx).Inc()
} }
now := time.Now().UTC() v.patchSecretWithLastUsedDate(ctx, secret)
today := now.Format("2006-01-02")
tomorrow := now.AddDate(0, 0, 1).Format("2006-01-02")
lastUsed := secret.Labels[LastUsedLabelKey]
if lastUsed != today && lastUsed != tomorrow {
patchContent, err := json.Marshal(applyv1.Secret(secret.Name, secret.Namespace).WithLabels(map[string]string{LastUsedLabelKey: today}))
if err != nil {
klog.Errorf("Failed to marshal legacy service account token tracking labels, err: %v", err)
} else {
if _, err := v.secretsWriter.Secrets(namespace).Patch(ctx, secret.Name, types.MergePatchType, patchContent, metav1.PatchOptions{}); err != nil {
klog.Errorf("Failed to label legacy service account token secret with last-used, err: %v", err)
}
}
}
} }
return &apiserverserviceaccount.ServiceAccountInfo{ return &apiserverserviceaccount.ServiceAccountInfo{
@ -188,6 +185,23 @@ func (v *legacyValidator) Validate(ctx context.Context, tokenData string, public
}, nil }, nil
} }
func (v *legacyValidator) patchSecretWithLastUsedDate(ctx context.Context, secret *v1.Secret) {
now := time.Now().UTC()
today := now.Format("2006-01-02")
tomorrow := now.AddDate(0, 0, 1).Format("2006-01-02")
lastUsed := secret.Labels[LastUsedLabelKey]
if lastUsed != today && lastUsed != tomorrow {
patchContent, err := json.Marshal(applyv1.Secret(secret.Name, secret.Namespace).WithUID(secret.UID).WithLabels(map[string]string{LastUsedLabelKey: today}))
if err != nil {
klog.Errorf("Failed to marshal legacy service account token %s/%s tracking labels, err: %w", secret.Name, secret.Namespace, err)
} else {
if _, err := v.secretsWriter.Secrets(secret.Namespace).Patch(ctx, secret.Name, types.MergePatchType, patchContent, metav1.PatchOptions{}); err != nil {
klog.Errorf("Failed to label legacy service account token %s/%s with last-used date, err: %w", secret.Name, secret.Namespace, err)
}
}
}
}
func (v *legacyValidator) NewPrivateClaims() interface{} { func (v *legacyValidator) NewPrivateClaims() interface{} {
return &legacyPrivateClaims{} return &legacyPrivateClaims{}
} }

View File

@ -67,6 +67,16 @@ var (
}, },
) )
// invalidatedAutoTokensTotal is the number of uses of automatically generated secret-based long lived tokens that have been marked as invalid.
invalidatedAutoTokensTotal = metrics.NewCounter(
&metrics.CounterOpts{
Subsystem: kubeServiceAccountSubsystem,
Name: "invalid_legacy_auto_token_uses_total",
Help: "Cumulative invalid auto-generated legacy tokens used",
StabilityLevel: metrics.ALPHA,
},
)
// ValidTokensTotal is the number of valid projected tokens used. // ValidTokensTotal is the number of valid projected tokens used.
validTokensTotal = metrics.NewCounter( validTokensTotal = metrics.NewCounter(
&metrics.CounterOpts{ &metrics.CounterOpts{
@ -86,6 +96,7 @@ func RegisterMetrics() {
legacyregistry.MustRegister(staleTokensTotal) legacyregistry.MustRegister(staleTokensTotal)
legacyregistry.MustRegister(manuallyCreatedTokensTotal) legacyregistry.MustRegister(manuallyCreatedTokensTotal)
legacyregistry.MustRegister(autoGeneratedTokensTotal) legacyregistry.MustRegister(autoGeneratedTokensTotal)
legacyregistry.MustRegister(invalidatedAutoTokensTotal)
legacyregistry.MustRegister(validTokensTotal) legacyregistry.MustRegister(validTokensTotal)
}) })
} }

View File

@ -472,7 +472,7 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "legacy-service-account-token-cleaner"}, ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "legacy-service-account-token-cleaner"},
Rules: []rbacv1.PolicyRule{ Rules: []rbacv1.PolicyRule{
rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("configmaps").Names(legacytokentracking.ConfigMapName).RuleOrDie(), rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("configmaps").Names(legacytokentracking.ConfigMapName).RuleOrDie(),
rbacv1helpers.NewRule("delete").Groups(legacyGroup).Resources("secrets").RuleOrDie(), rbacv1helpers.NewRule("patch", "delete").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
}, },
}) })
} }

View File

@ -255,6 +255,23 @@ items:
- kind: ServiceAccount - kind: ServiceAccount
name: job-controller name: job-controller
namespace: kube-system namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:legacy-service-account-token-cleaner
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:controller:legacy-service-account-token-cleaner
subjects:
- kind: ServiceAccount
name: legacy-service-account-token-cleaner
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1 - apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding kind: ClusterRoleBinding
metadata: metadata:

View File

@ -830,6 +830,31 @@ items:
- create - create
- patch - patch
- update - update
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:controller:legacy-service-account-token-cleaner
rules:
- apiGroups:
- ""
resourceNames:
- kube-apiserver-legacy-service-account-token-tracking
resources:
- configmaps
verbs:
- get
- apiGroups:
- ""
resources:
- secrets
verbs:
- delete
- patch
- apiVersion: rbac.authorization.k8s.io/v1 - apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:

View File

@ -20,6 +20,7 @@ package serviceaccount
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"testing" "testing"
"time" "time"
@ -27,8 +28,10 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
applyv1 "k8s.io/client-go/applyconfigurations/core/v1"
clientinformers "k8s.io/client-go/informers" clientinformers "k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
listersv1 "k8s.io/client-go/listers/core/v1" listersv1 "k8s.io/client-go/listers/core/v1"
@ -44,7 +47,9 @@ import (
const ( const (
dateFormat = "2006-01-02" dateFormat = "2006-01-02"
cleanUpPeriod = 24 * time.Hour cleanUpPeriod = 24 * time.Hour
syncInterval = 1 * time.Second syncInterval = 5 * time.Second
pollTimeout = 15 * time.Second
pollInterval = time.Second
) )
func TestLegacyServiceAccountTokenCleanUp(t *testing.T) { func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
@ -57,20 +62,8 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
t.Fatalf("failed to setup ServiceAccounts server: %v", err) t.Fatalf("failed to setup ServiceAccounts server: %v", err)
} }
// wait configmap to label with tracking date // wait configmap to be labeled with tracking date
if err := wait.PollImmediate(time.Second, 10*time.Second, func() (bool, error) { waitConfigmapToBeLabeled(ctx, t, c)
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)
}
tests := []struct { tests := []struct {
name string name string
@ -78,6 +71,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
secretTokenData string secretTokenData string
namespace string namespace string
expectCleanedUp bool expectCleanedUp bool
expectInvalidLabel bool
lastUsedLabel bool lastUsedLabel bool
isPodMounted bool isPodMounted bool
isManual bool isManual bool
@ -90,6 +84,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
isManual: false, isManual: false,
isPodMounted: false, isPodMounted: false,
expectCleanedUp: true, expectCleanedUp: true,
expectInvalidLabel: true,
}, },
{ {
name: "manually created legacy token", name: "manually created legacy token",
@ -99,6 +94,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
isManual: true, isManual: true,
isPodMounted: false, isPodMounted: false,
expectCleanedUp: false, expectCleanedUp: false,
expectInvalidLabel: false,
}, },
{ {
name: "auto created legacy token with pod binding", name: "auto created legacy token with pod binding",
@ -108,6 +104,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
isManual: false, isManual: false,
isPodMounted: true, isPodMounted: true,
expectCleanedUp: false, expectCleanedUp: false,
expectInvalidLabel: false,
}, },
{ {
name: "auto created legacy token without pod binding, secret has not been used after tracking", name: "auto created legacy token without pod binding, secret has not been used after tracking",
@ -117,6 +114,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
isManual: false, isManual: false,
isPodMounted: false, isPodMounted: false,
expectCleanedUp: true, expectCleanedUp: true,
expectInvalidLabel: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -152,10 +150,7 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
} }
podLister := informers.Core().V1().Pods().Lister() podLister := informers.Core().V1().Pods().Lister()
if test.isPodMounted { if test.isPodMounted {
_, err = createAutotokenMountedPod(c, test.namespace, test.secretName, podLister) createAutotokenMountedPod(ctx, t, c, test.namespace, test.secretName, podLister)
if err != nil {
t.Fatalf("Pod not created: %v", err)
}
} }
myConfig := *config myConfig := *config
@ -165,51 +160,191 @@ func TestLegacyServiceAccountTokenCleanUp(t *testing.T) {
roClient := clientset.NewForConfigOrDie(&myConfig) roClient := clientset.NewForConfigOrDie(&myConfig)
// the secret should not be labeled with LastUsedLabelKey. // the secret should not be labeled with LastUsedLabelKey.
liveSecret, err := c.CoreV1().Secrets(test.namespace).Get(context.TODO(), test.secretName, metav1.GetOptions{}) checkLastUsedLabel(ctx, t, c, secret, false)
if err != nil {
t.Fatalf("Could not get secret: %v", err) if test.lastUsedLabel {
} doServiceAccountAPIReadRequest(ctx, t, roClient, test.namespace, true)
_, ok := liveSecret.GetLabels()[serviceaccount.LastUsedLabelKey]
if ok { // all service account tokens should be labeled with LastUsedLabelKey.
t.Fatalf("Secret %s should not have the lastUsed label", test.secretName) checkLastUsedLabel(ctx, t, c, secret, true)
} }
// authenticate legacy tokens // Test invalid labels
if test.lastUsedLabel { fakeClock.Step(cleanUpPeriod + 24*time.Hour)
doServiceAccountAPIRequests(t, roClient, test.namespace, true, true, false) checkInvalidSinceLabel(ctx, t, c, secret, fakeClock, test.expectInvalidLabel)
// all service account tokens should be labeled with LastUsedLabelKey.
liveSecret, err = c.CoreV1().Secrets(test.namespace).Get(context.TODO(), test.secretName, metav1.GetOptions{}) // Test invalid secret cannot be used
if err != nil { if test.expectInvalidLabel {
t.Fatalf("Could not get secret: %v", err) t.Logf("Check the invalid token cannot authenticate request.")
} doServiceAccountAPIReadRequest(ctx, t, roClient, test.namespace, false)
lastUsed, ok := liveSecret.GetLabels()[serviceaccount.LastUsedLabelKey]
if !ok { // Check the secret has been labelded with the LastUsedLabelKey.
t.Fatalf("The secret %s should be labeled lastUsed time: %s", test.secretName, lastUsed) if !test.lastUsedLabel {
} else { checkLastUsedLabel(ctx, t, c, secret, true)
t.Logf("The secret %s has been labeled with %s", test.secretName, lastUsed)
} }
// Update secret by removing the invalid since label
removeInvalidLabel(ctx, c, t, secret)
t.Logf("Check the token can authenticate request after patching the secret by removing the invalid label.")
doServiceAccountAPIReadRequest(ctx, t, roClient, test.namespace, true)
// Update the lastUsed label date to the fakeClock date (as the validation function uses the real time to label the lastUsed date)
patchSecret(ctx, c, t, fakeClock.Now().UTC().Format(dateFormat), secret)
// The secret will be marked as invalid again after time period duration cleanUpPeriod + 24*time.Hour
fakeClock.Step(cleanUpPeriod + 24*time.Hour)
checkInvalidSinceLabel(ctx, t, c, secret, fakeClock, true)
} }
fakeClock.Step(cleanUpPeriod + 24*time.Hour) fakeClock.Step(cleanUpPeriod + 24*time.Hour)
time.Sleep(2 * syncInterval) checkSecretCleanUp(ctx, t, c, secret, test.expectCleanedUp)
liveSecret, err = c.CoreV1().Secrets(test.namespace).Get(context.TODO(), test.secretName, metav1.GetOptions{})
if test.expectCleanedUp {
if err == nil {
t.Fatalf("The secret %s should be cleaned up. time: %v; creationTime: %v", test.secretName, fakeClock.Now().UTC(), liveSecret.CreationTimestamp)
} else if !apierrors.IsNotFound(err) {
t.Fatalf("Failed to get secret %s, err: %v", test.secretName, err)
}
} else 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)
}
}
}) })
} }
} }
func waitConfigmapToBeLabeled(ctx context.Context, t *testing.T, c clientset.Interface) {
if err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, func(ctx context.Context) (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)
}
}
func checkSecretCleanUp(ctx context.Context, t *testing.T, c clientset.Interface, secret *v1.Secret, shouldCleanUp bool) {
err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, func(ctx context.Context) (bool, error) {
_, err := c.CoreV1().Secrets(secret.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{})
if shouldCleanUp {
if err == nil {
return false, nil
} else if !apierrors.IsNotFound(err) {
t.Fatalf("Failed to get secret %s, err: %v", secret.Name, err)
}
return true, nil
}
if err != nil {
if apierrors.IsNotFound(err) {
t.Fatalf("The secret %s should not be cleaned up, err: %v", secret.Name, err)
} else {
t.Fatalf("Failed to get secret %s, err: %v", secret.Name, err)
}
}
return true, nil
})
if err != nil {
t.Fatalf("Failed to check the existence for secret: %s, shouldCleanUp: %v, error: %v", secret.Name, shouldCleanUp, err)
}
}
func checkInvalidSinceLabel(ctx context.Context, t *testing.T, c clientset.Interface, secret *v1.Secret, fakeClock *testingclock.FakeClock, shouldLabel bool) {
err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, func(ctx context.Context) (bool, error) {
liveSecret, err := c.CoreV1().Secrets(secret.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed to get secret: %s, err: %v", secret.Name, err)
}
invalidSince, ok := liveSecret.GetLabels()[serviceaccount.InvalidSinceLabelKey]
if shouldLabel {
if !ok || invalidSince != fakeClock.Now().UTC().Format(dateFormat) {
return false, nil
}
return true, nil
}
if invalidSince != "" {
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("Failed to check secret invalid since label for secret: %s, shouldLabel: %v, error: %v", secret.Name, shouldLabel, err)
}
}
func checkLastUsedLabel(ctx context.Context, t *testing.T, c clientset.Interface, secret *v1.Secret, shouldLabel bool) {
err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, func(ctx context.Context) (bool, error) {
liveSecret, err := c.CoreV1().Secrets(secret.Namespace).Get(ctx, secret.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed to get secret: %s, err: %v", secret.Name, err)
}
lastUsed, ok := liveSecret.GetLabels()[serviceaccount.LastUsedLabelKey]
if shouldLabel {
if !ok || lastUsed != time.Now().UTC().Format(dateFormat) {
return false, nil
}
t.Logf("The secret %s has been labeled with %s", secret.Name, lastUsed)
return true, nil
}
if ok {
t.Fatalf("Secret %s should not have the lastUsed label", secret.Name)
}
return true, nil
})
if err != nil {
t.Fatalf("Failed to check secret last used label for secret: %s, shouldLabel: %v, error: %v", secret.Name, shouldLabel, err)
}
}
func removeInvalidLabel(ctx context.Context, c clientset.Interface, t *testing.T, secret *v1.Secret) {
lastUsed := secret.GetLabels()[serviceaccount.LastUsedLabelKey]
patchContent, err := json.Marshal(applyv1.Secret(secret.Name, secret.Namespace).WithLabels(map[string]string{serviceaccount.InvalidSinceLabelKey: "", serviceaccount.LastUsedLabelKey: lastUsed}))
if err != nil {
t.Fatalf("Failed to marshal invalid since label, err: %v", err)
}
t.Logf("Patch the secret by removing the invalid label.")
if _, err := c.CoreV1().Secrets(secret.Namespace).Patch(ctx, secret.Name, types.MergePatchType, patchContent, metav1.PatchOptions{}); err != nil {
t.Fatalf("Failed to remove invalid since label, err: %v", err)
}
err = wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, func(ctx context.Context) (bool, error) {
secret, err = c.CoreV1().Secrets(secret.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed to get secret: %s, err: %v", secret.Name, err)
}
invalidSince := secret.GetLabels()[serviceaccount.InvalidSinceLabelKey]
if invalidSince != "" {
t.Log("Patch has not completed.")
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("Failed to patch secret: %s, err: %v", secret.Name, err)
}
}
func patchSecret(ctx context.Context, c clientset.Interface, t *testing.T, lastUsed string, secret *v1.Secret) {
patchContent, err := json.Marshal(applyv1.Secret(secret.Name, secret.Namespace).WithUID(secret.UID).WithLabels(map[string]string{serviceaccount.InvalidSinceLabelKey: "", serviceaccount.LastUsedLabelKey: lastUsed}))
if err != nil {
t.Fatalf("Failed to marshal invalid since label, err: %v", err)
}
t.Logf("Patch the secret by removing the invalid label.")
if _, err := c.CoreV1().Secrets(secret.Namespace).Patch(ctx, secret.Name, types.MergePatchType, patchContent, metav1.PatchOptions{}); err != nil {
t.Fatalf("Failed to remove invalid since label, err: %v", err)
}
err = wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, func(ctx context.Context) (bool, error) {
secret, err = c.CoreV1().Secrets(secret.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("Failed to get secret: %s, err: %v", secret.Name, err)
}
lastUsedString := secret.GetLabels()[serviceaccount.LastUsedLabelKey]
if lastUsedString != lastUsed {
t.Log("Patch has not completed.")
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("Failed to patch secret: %s, err: %v", secret.Name, err)
}
}
func startLegacyServiceAccountTokenCleaner(ctx context.Context, client clientset.Interface, fakeClock clock.Clock, informers clientinformers.SharedInformerFactory) { func startLegacyServiceAccountTokenCleaner(ctx context.Context, client clientset.Interface, fakeClock clock.Clock, informers clientinformers.SharedInformerFactory) {
legacySATokenCleaner, _ := serviceaccountcontroller.NewLegacySATokenCleaner( legacySATokenCleaner, _ := serviceaccountcontroller.NewLegacySATokenCleaner(
informers.Core().V1().ServiceAccounts(), informers.Core().V1().ServiceAccounts(),
@ -224,7 +359,33 @@ func startLegacyServiceAccountTokenCleaner(ctx context.Context, client clientset
go legacySATokenCleaner.Run(ctx) go legacySATokenCleaner.Run(ctx)
} }
func createAutotokenMountedPod(c clientset.Interface, ns, secretName string, podLister listersv1.PodLister) (*v1.Pod, error) { func doServiceAccountAPIReadRequest(ctx context.Context, t *testing.T, c clientset.Interface, ns string, authenticated bool) {
readOps := []testOperation{
func() error {
_, err := c.CoreV1().Secrets(ns).List(context.TODO(), metav1.ListOptions{})
return err
},
func() error {
_, err := c.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{})
return err
},
}
for _, op := range readOps {
err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, func(ctx context.Context) (bool, error) {
err := op()
if authenticated && err != nil || !authenticated && err == nil {
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("Failed to check secret token authentication: error: %v", err)
}
}
}
func createAutotokenMountedPod(ctx context.Context, t *testing.T, c clientset.Interface, ns, secretName string, podLister listersv1.PodLister) *v1.Pod {
pod := &v1.Pod{ pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "token-bound-pod", Name: "token-bound-pod",
@ -239,14 +400,17 @@ func createAutotokenMountedPod(c clientset.Interface, ns, secretName string, pod
} }
pod, err := c.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{}) pod, err := c.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create pod with token (%s:%s) bound, err: %v", ns, secretName, err) t.Fatalf("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) { err = wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, func(ctx context.Context) (bool, error) {
pod, err = podLister.Pods(ns).Get("token-bound-pod") pod, err = podLister.Pods(ns).Get("token-bound-pod")
if err != nil { if err != nil {
return false, fmt.Errorf("failed to get pod with token (%s:%s) bound, err: %v", ns, secretName, err) return false, nil
} }
return true, nil return true, nil
}) })
return pod, nil if err != nil {
t.Fatalf("Failed to wait auto-token mounted pod: err: %v", err)
}
return pod
} }