| /* |
| Copyright 2014 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 ( |
| "errors" |
| "reflect" |
| "testing" |
| "time" |
| |
| "github.com/davecgh/go-spew/spew" |
| "gopkg.in/square/go-jose.v2/jwt" |
| "k8s.io/klog" |
| |
| "k8s.io/api/core/v1" |
| apierrors "k8s.io/apimachinery/pkg/api/errors" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/runtime/schema" |
| utilrand "k8s.io/apimachinery/pkg/util/rand" |
| "k8s.io/client-go/informers" |
| "k8s.io/client-go/kubernetes/fake" |
| core "k8s.io/client-go/testing" |
| api "k8s.io/kubernetes/pkg/apis/core" |
| "k8s.io/kubernetes/pkg/controller" |
| ) |
| |
| type testGenerator struct { |
| Token string |
| Err error |
| } |
| |
| func (t *testGenerator) GenerateToken(sc *jwt.Claims, pc interface{}) (string, error) { |
| return t.Token, t.Err |
| } |
| |
| // emptySecretReferences is used by a service account without any secrets |
| func emptySecretReferences() []v1.ObjectReference { |
| return []v1.ObjectReference{} |
| } |
| |
| // missingSecretReferences is used by a service account that references secrets which do no exist |
| func missingSecretReferences() []v1.ObjectReference { |
| return []v1.ObjectReference{{Name: "missing-secret-1"}} |
| } |
| |
| // regularSecretReferences is used by a service account that references secrets which are not ServiceAccountTokens |
| func regularSecretReferences() []v1.ObjectReference { |
| return []v1.ObjectReference{{Name: "regular-secret-1"}} |
| } |
| |
| // tokenSecretReferences is used by a service account that references a ServiceAccountToken secret |
| func tokenSecretReferences() []v1.ObjectReference { |
| return []v1.ObjectReference{{Name: "token-secret-1"}} |
| } |
| |
| // addTokenSecretReference adds a reference to the ServiceAccountToken that will be created |
| func addTokenSecretReference(refs []v1.ObjectReference) []v1.ObjectReference { |
| return addNamedTokenSecretReference(refs, "default-token-xn8fg") |
| } |
| |
| // addNamedTokenSecretReference adds a reference to the named ServiceAccountToken |
| func addNamedTokenSecretReference(refs []v1.ObjectReference, name string) []v1.ObjectReference { |
| return append(refs, v1.ObjectReference{Name: name}) |
| } |
| |
| // serviceAccount returns a service account with the given secret refs |
| func serviceAccount(secretRefs []v1.ObjectReference) *v1.ServiceAccount { |
| return &v1.ServiceAccount{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "default", |
| UID: "12345", |
| Namespace: "default", |
| ResourceVersion: "1", |
| }, |
| Secrets: secretRefs, |
| } |
| } |
| |
| // updatedServiceAccount returns a service account with the resource version modified |
| func updatedServiceAccount(secretRefs []v1.ObjectReference) *v1.ServiceAccount { |
| sa := serviceAccount(secretRefs) |
| sa.ResourceVersion = "2" |
| return sa |
| } |
| |
| // opaqueSecret returns a persisted non-ServiceAccountToken secret named "regular-secret-1" |
| func opaqueSecret() *v1.Secret { |
| return &v1.Secret{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "regular-secret-1", |
| Namespace: "default", |
| UID: "23456", |
| ResourceVersion: "1", |
| }, |
| Type: "Opaque", |
| Data: map[string][]byte{ |
| "mykey": []byte("mydata"), |
| }, |
| } |
| } |
| |
| // createdTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret. |
| // Named "default-token-xn8fg", since that is the first generated name after rand.Seed(1) |
| func createdTokenSecret(overrideName ...string) *v1.Secret { |
| return namedCreatedTokenSecret("default-token-xn8fg") |
| } |
| |
| // namedTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret with the given name. |
| func namedCreatedTokenSecret(name string) *v1.Secret { |
| return &v1.Secret{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: name, |
| Namespace: "default", |
| Annotations: map[string]string{ |
| v1.ServiceAccountNameKey: "default", |
| v1.ServiceAccountUIDKey: "12345", |
| }, |
| }, |
| Type: v1.SecretTypeServiceAccountToken, |
| Data: map[string][]byte{ |
| "token": []byte("ABC"), |
| "ca.crt": []byte("CA Data"), |
| "namespace": []byte("default"), |
| }, |
| } |
| } |
| |
| // serviceAccountTokenSecret returns an existing ServiceAccountToken secret named "token-secret-1" |
| func serviceAccountTokenSecret() *v1.Secret { |
| return &v1.Secret{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "token-secret-1", |
| Namespace: "default", |
| UID: "23456", |
| ResourceVersion: "1", |
| Annotations: map[string]string{ |
| v1.ServiceAccountNameKey: "default", |
| v1.ServiceAccountUIDKey: "12345", |
| }, |
| }, |
| Type: v1.SecretTypeServiceAccountToken, |
| Data: map[string][]byte{ |
| "token": []byte("ABC"), |
| "ca.crt": []byte("CA Data"), |
| "namespace": []byte("default"), |
| }, |
| } |
| } |
| |
| // serviceAccountTokenSecretWithoutTokenData returns an existing ServiceAccountToken secret that lacks token data |
| func serviceAccountTokenSecretWithoutTokenData() *v1.Secret { |
| secret := serviceAccountTokenSecret() |
| delete(secret.Data, v1.ServiceAccountTokenKey) |
| return secret |
| } |
| |
| // serviceAccountTokenSecretWithoutCAData returns an existing ServiceAccountToken secret that lacks ca data |
| func serviceAccountTokenSecretWithoutCAData() *v1.Secret { |
| secret := serviceAccountTokenSecret() |
| delete(secret.Data, v1.ServiceAccountRootCAKey) |
| return secret |
| } |
| |
| // serviceAccountTokenSecretWithCAData returns an existing ServiceAccountToken secret with the specified ca data |
| func serviceAccountTokenSecretWithCAData(data []byte) *v1.Secret { |
| secret := serviceAccountTokenSecret() |
| secret.Data[v1.ServiceAccountRootCAKey] = data |
| return secret |
| } |
| |
| // serviceAccountTokenSecretWithoutNamespaceData returns an existing ServiceAccountToken secret that lacks namespace data |
| func serviceAccountTokenSecretWithoutNamespaceData() *v1.Secret { |
| secret := serviceAccountTokenSecret() |
| delete(secret.Data, v1.ServiceAccountNamespaceKey) |
| return secret |
| } |
| |
| // serviceAccountTokenSecretWithNamespaceData returns an existing ServiceAccountToken secret with the specified namespace data |
| func serviceAccountTokenSecretWithNamespaceData(data []byte) *v1.Secret { |
| secret := serviceAccountTokenSecret() |
| secret.Data[v1.ServiceAccountNamespaceKey] = data |
| return secret |
| } |
| |
| type reaction struct { |
| verb string |
| resource string |
| reactor func(t *testing.T) core.ReactionFunc |
| } |
| |
| func TestTokenCreation(t *testing.T) { |
| testcases := map[string]struct { |
| ClientObjects []runtime.Object |
| |
| IsAsync bool |
| MaxRetries int |
| |
| Reactors []reaction |
| |
| ExistingServiceAccount *v1.ServiceAccount |
| ExistingSecrets []*v1.Secret |
| |
| AddedServiceAccount *v1.ServiceAccount |
| UpdatedServiceAccount *v1.ServiceAccount |
| DeletedServiceAccount *v1.ServiceAccount |
| AddedSecret *v1.Secret |
| AddedSecretLocal *v1.Secret |
| UpdatedSecret *v1.Secret |
| DeletedSecret *v1.Secret |
| |
| ExpectedActions []core.Action |
| }{ |
| "new serviceaccount with no secrets": { |
| ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences())}, |
| |
| AddedServiceAccount: serviceAccount(emptySecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), |
| }, |
| }, |
| "new serviceaccount with no secrets encountering create error": { |
| ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences())}, |
| MaxRetries: 10, |
| IsAsync: true, |
| Reactors: []reaction{{ |
| verb: "create", |
| resource: "secrets", |
| reactor: func(t *testing.T) core.ReactionFunc { |
| i := 0 |
| return func(core.Action) (bool, runtime.Object, error) { |
| i++ |
| if i < 3 { |
| return true, nil, apierrors.NewForbidden(api.Resource("secrets"), "foo", errors.New("No can do")) |
| } |
| return false, nil, nil |
| } |
| }, |
| }}, |
| AddedServiceAccount: serviceAccount(emptySecretReferences()), |
| ExpectedActions: []core.Action{ |
| // Attempt 1 |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()), |
| |
| // Attempt 2 |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-txhzt")), |
| |
| // Attempt 3 |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-vnmz7")), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addNamedTokenSecretReference(emptySecretReferences(), "default-token-vnmz7"))), |
| }, |
| }, |
| "new serviceaccount with no secrets encountering unending create error": { |
| ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, |
| MaxRetries: 2, |
| IsAsync: true, |
| Reactors: []reaction{{ |
| verb: "create", |
| resource: "secrets", |
| reactor: func(t *testing.T) core.ReactionFunc { |
| return func(core.Action) (bool, runtime.Object, error) { |
| return true, nil, apierrors.NewForbidden(api.Resource("secrets"), "foo", errors.New("No can do")) |
| } |
| }, |
| }}, |
| |
| AddedServiceAccount: serviceAccount(emptySecretReferences()), |
| ExpectedActions: []core.Action{ |
| // Attempt |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()), |
| // Retry 1 |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-txhzt")), |
| // Retry 2 |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-vnmz7")), |
| }, |
| }, |
| "new serviceaccount with missing secrets": { |
| ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())}, |
| |
| AddedServiceAccount: serviceAccount(missingSecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))), |
| }, |
| }, |
| "new serviceaccount with missing secrets and a local secret in the cache": { |
| ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())}, |
| |
| AddedServiceAccount: serviceAccount(tokenSecretReferences()), |
| AddedSecretLocal: serviceAccountTokenSecret(), |
| ExpectedActions: []core.Action{}, |
| }, |
| "new serviceaccount with non-token secrets": { |
| ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), opaqueSecret()}, |
| |
| AddedServiceAccount: serviceAccount(regularSecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))), |
| }, |
| }, |
| "new serviceaccount with token secrets": { |
| ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences()), serviceAccountTokenSecret()}, |
| ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()}, |
| |
| AddedServiceAccount: serviceAccount(tokenSecretReferences()), |
| ExpectedActions: []core.Action{}, |
| }, |
| "new serviceaccount with no secrets with resource conflict": { |
| ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences()), createdTokenSecret()}, |
| IsAsync: true, |
| MaxRetries: 1, |
| |
| AddedServiceAccount: serviceAccount(emptySecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| }, |
| }, |
| "updated serviceaccount with no secrets": { |
| ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences())}, |
| |
| UpdatedServiceAccount: serviceAccount(emptySecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), |
| }, |
| }, |
| "updated serviceaccount with missing secrets": { |
| ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())}, |
| |
| UpdatedServiceAccount: serviceAccount(missingSecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))), |
| }, |
| }, |
| "updated serviceaccount with non-token secrets": { |
| ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), opaqueSecret()}, |
| |
| UpdatedServiceAccount: serviceAccount(regularSecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))), |
| }, |
| }, |
| "updated serviceaccount with token secrets": { |
| ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()}, |
| |
| UpdatedServiceAccount: serviceAccount(tokenSecretReferences()), |
| ExpectedActions: []core.Action{}, |
| }, |
| "updated serviceaccount with no secrets with resource conflict": { |
| ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences())}, |
| IsAsync: true, |
| MaxRetries: 1, |
| |
| UpdatedServiceAccount: serviceAccount(emptySecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| }, |
| }, |
| |
| "deleted serviceaccount with no secrets": { |
| DeletedServiceAccount: serviceAccount(emptySecretReferences()), |
| ExpectedActions: []core.Action{}, |
| }, |
| "deleted serviceaccount with missing secrets": { |
| DeletedServiceAccount: serviceAccount(missingSecretReferences()), |
| ExpectedActions: []core.Action{}, |
| }, |
| "deleted serviceaccount with non-token secrets": { |
| ClientObjects: []runtime.Object{opaqueSecret()}, |
| |
| DeletedServiceAccount: serviceAccount(regularSecretReferences()), |
| ExpectedActions: []core.Action{}, |
| }, |
| "deleted serviceaccount with token secrets": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, |
| ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()}, |
| |
| DeletedServiceAccount: serviceAccount(tokenSecretReferences()), |
| ExpectedActions: []core.Action{ |
| core.NewDeleteAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| }, |
| }, |
| |
| "added secret without serviceaccount": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, |
| |
| AddedSecret: serviceAccountTokenSecret(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewDeleteAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| }, |
| }, |
| "added secret with serviceaccount": { |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| AddedSecret: serviceAccountTokenSecret(), |
| ExpectedActions: []core.Action{}, |
| }, |
| "added token secret without token data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| AddedSecret: serviceAccountTokenSecretWithoutTokenData(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), |
| }, |
| }, |
| "added token secret without ca data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| AddedSecret: serviceAccountTokenSecretWithoutCAData(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), |
| }, |
| }, |
| "added token secret with mismatched ca data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| AddedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), |
| }, |
| }, |
| "added token secret without namespace data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| AddedSecret: serviceAccountTokenSecretWithoutNamespaceData(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), |
| }, |
| }, |
| "added token secret with custom namespace data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| AddedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), |
| ExpectedActions: []core.Action{ |
| // no update is performed... the custom namespace is preserved |
| }, |
| }, |
| |
| "updated secret without serviceaccount": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, |
| |
| UpdatedSecret: serviceAccountTokenSecret(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewDeleteAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| }, |
| }, |
| "updated secret with serviceaccount": { |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| UpdatedSecret: serviceAccountTokenSecret(), |
| ExpectedActions: []core.Action{}, |
| }, |
| "updated token secret without token data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), |
| }, |
| }, |
| "updated token secret without ca data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| UpdatedSecret: serviceAccountTokenSecretWithoutCAData(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), |
| }, |
| }, |
| "updated token secret with mismatched ca data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| UpdatedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), |
| }, |
| }, |
| "updated token secret without namespace data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| UpdatedSecret: serviceAccountTokenSecretWithoutNamespaceData(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), |
| }, |
| }, |
| "updated token secret with custom namespace data": { |
| ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| UpdatedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), |
| ExpectedActions: []core.Action{ |
| // no update is performed... the custom namespace is preserved |
| }, |
| }, |
| |
| "deleted secret without serviceaccount": { |
| DeletedSecret: serviceAccountTokenSecret(), |
| ExpectedActions: []core.Action{}, |
| }, |
| "deleted secret with serviceaccount with reference": { |
| ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences())}, |
| ExistingServiceAccount: serviceAccount(tokenSecretReferences()), |
| |
| DeletedSecret: serviceAccountTokenSecret(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(emptySecretReferences())), |
| }, |
| }, |
| "deleted secret with serviceaccount without reference": { |
| ExistingServiceAccount: serviceAccount(emptySecretReferences()), |
| |
| DeletedSecret: serviceAccountTokenSecret(), |
| ExpectedActions: []core.Action{ |
| core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), |
| }, |
| }, |
| } |
| |
| for k, tc := range testcases { |
| klog.Infof(k) |
| |
| // Re-seed to reset name generation |
| utilrand.Seed(1) |
| |
| generator := &testGenerator{Token: "ABC"} |
| |
| client := fake.NewSimpleClientset(tc.ClientObjects...) |
| for _, reactor := range tc.Reactors { |
| client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactor(t)) |
| } |
| informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) |
| secretInformer := informers.Core().V1().Secrets().Informer() |
| secrets := secretInformer.GetStore() |
| serviceAccounts := informers.Core().V1().ServiceAccounts().Informer().GetStore() |
| controller, err := NewTokensController(informers.Core().V1().ServiceAccounts(), informers.Core().V1().Secrets(), client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data"), MaxRetries: tc.MaxRetries}) |
| if err != nil { |
| t.Fatalf("error creating Tokens controller: %v", err) |
| } |
| |
| if tc.ExistingServiceAccount != nil { |
| serviceAccounts.Add(tc.ExistingServiceAccount) |
| } |
| for _, s := range tc.ExistingSecrets { |
| secrets.Add(s) |
| } |
| |
| if tc.AddedServiceAccount != nil { |
| serviceAccounts.Add(tc.AddedServiceAccount) |
| controller.queueServiceAccountSync(tc.AddedServiceAccount) |
| } |
| if tc.UpdatedServiceAccount != nil { |
| serviceAccounts.Add(tc.UpdatedServiceAccount) |
| controller.queueServiceAccountUpdateSync(nil, tc.UpdatedServiceAccount) |
| } |
| if tc.DeletedServiceAccount != nil { |
| serviceAccounts.Delete(tc.DeletedServiceAccount) |
| controller.queueServiceAccountSync(tc.DeletedServiceAccount) |
| } |
| if tc.AddedSecret != nil { |
| secrets.Add(tc.AddedSecret) |
| controller.queueSecretSync(tc.AddedSecret) |
| } |
| if tc.AddedSecretLocal != nil { |
| controller.updatedSecrets.Mutation(tc.AddedSecretLocal) |
| } |
| if tc.UpdatedSecret != nil { |
| secrets.Add(tc.UpdatedSecret) |
| controller.queueSecretUpdateSync(nil, tc.UpdatedSecret) |
| } |
| if tc.DeletedSecret != nil { |
| secrets.Delete(tc.DeletedSecret) |
| controller.queueSecretSync(tc.DeletedSecret) |
| } |
| |
| // This is the longest we'll wait for async tests |
| timeout := time.Now().Add(30 * time.Second) |
| waitedForAdditionalActions := false |
| |
| for { |
| if controller.syncServiceAccountQueue.Len() > 0 { |
| controller.syncServiceAccount() |
| } |
| if controller.syncSecretQueue.Len() > 0 { |
| controller.syncSecret() |
| } |
| |
| // The queues still have things to work on |
| if controller.syncServiceAccountQueue.Len() > 0 || controller.syncSecretQueue.Len() > 0 { |
| continue |
| } |
| |
| // If we expect this test to work asynchronously... |
| if tc.IsAsync { |
| // if we're still missing expected actions within our test timeout |
| if len(client.Actions()) < len(tc.ExpectedActions) && time.Now().Before(timeout) { |
| // wait for the expected actions (without hotlooping) |
| time.Sleep(time.Millisecond) |
| continue |
| } |
| |
| // if we exactly match our expected actions, wait a bit to make sure no other additional actions show up |
| if len(client.Actions()) == len(tc.ExpectedActions) && !waitedForAdditionalActions { |
| time.Sleep(time.Second) |
| waitedForAdditionalActions = true |
| continue |
| } |
| } |
| |
| break |
| } |
| |
| if controller.syncServiceAccountQueue.Len() > 0 { |
| t.Errorf("%s: unexpected items in service account queue: %d", k, controller.syncServiceAccountQueue.Len()) |
| } |
| if controller.syncSecretQueue.Len() > 0 { |
| t.Errorf("%s: unexpected items in secret queue: %d", k, controller.syncSecretQueue.Len()) |
| } |
| |
| actions := client.Actions() |
| 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("%s:\nExpected:\n%s\ngot:\n%s", k, spew.Sdump(expectedAction), spew.Sdump(action)) |
| continue |
| } |
| } |
| |
| if len(tc.ExpectedActions) > len(actions) { |
| t.Errorf("%s: %d additional expected actions", k, len(tc.ExpectedActions)-len(actions)) |
| for _, a := range tc.ExpectedActions[len(actions):] { |
| t.Logf(" %+v", a) |
| } |
| } |
| } |
| } |