mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
ServiceAccountTokens controller
This commit is contained in:
parent
53d55f4192
commit
0955808668
@ -20,6 +20,7 @@ limitations under the License.
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
@ -40,6 +41,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/namespace"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/namespace"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/resourcequota"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/resourcequota"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/service"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/service"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/serviceaccount"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/volumeclaimbinder"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/volumeclaimbinder"
|
||||||
|
|
||||||
@ -249,6 +251,12 @@ func (s *CMServer) Run(_ []string) error {
|
|||||||
pvclaimBinder.Run()
|
pvclaimBinder.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: generate signed token
|
||||||
|
tokenGenerator := serviceaccount.TokenGeneratorFunc(func(serviceAccount api.ServiceAccount, secret api.Secret) (string, error) {
|
||||||
|
return fmt.Sprintf("serviceaccount:%s:%s:%s:%s", serviceAccount.Namespace, serviceAccount.Name, serviceAccount.UID, secret.Name), nil
|
||||||
|
})
|
||||||
|
serviceaccount.NewTokensController(kubeClient, serviceaccount.DefaultTokenControllerOptions(tokenGenerator)).Run()
|
||||||
|
|
||||||
select {}
|
select {}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1843,6 +1843,8 @@ const (
|
|||||||
ServiceAccountUIDKey = "kubernetes.io/service-account.uid"
|
ServiceAccountUIDKey = "kubernetes.io/service-account.uid"
|
||||||
// ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets
|
// ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets
|
||||||
ServiceAccountTokenKey = "token"
|
ServiceAccountTokenKey = "token"
|
||||||
|
// ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets
|
||||||
|
ServiceAccountKubeconfigKey = "kubernetes.kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SecretList struct {
|
type SecretList struct {
|
||||||
|
@ -228,3 +228,69 @@ func NewInformer(
|
|||||||
}
|
}
|
||||||
return clientState, New(cfg)
|
return clientState, New(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewIndexerInformer returns a cache.Indexer and a controller for populating the index
|
||||||
|
// while also providing event notifications. You should only used the returned
|
||||||
|
// cache.Index for Get/List operations; Add/Modify/Deletes will cause the event
|
||||||
|
// notifications to be faulty.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// * lw is list and watch functions for the source of the resource you want to
|
||||||
|
// be informed of.
|
||||||
|
// * objType is an object of the type that you expect to receive.
|
||||||
|
// * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate
|
||||||
|
// calls, even if nothing changed). Otherwise, re-list will be delayed as
|
||||||
|
// long as possible (until the upstream source closes the watch or times out,
|
||||||
|
// or you stop the controller).
|
||||||
|
// * h is the object you want notifications sent to.
|
||||||
|
//
|
||||||
|
func NewIndexerInformer(
|
||||||
|
lw cache.ListerWatcher,
|
||||||
|
objType runtime.Object,
|
||||||
|
resyncPeriod time.Duration,
|
||||||
|
h ResourceEventHandler,
|
||||||
|
indexers cache.Indexers,
|
||||||
|
) (cache.Indexer, *Controller) {
|
||||||
|
// This will hold the client state, as we know it.
|
||||||
|
clientState := cache.NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers)
|
||||||
|
|
||||||
|
// This will hold incoming changes. Note how we pass clientState in as a
|
||||||
|
// KeyLister, that way resync operations will result in the correct set
|
||||||
|
// of update/delete deltas.
|
||||||
|
fifo := cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, nil, clientState)
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Queue: fifo,
|
||||||
|
ListerWatcher: lw,
|
||||||
|
ObjectType: objType,
|
||||||
|
FullResyncPeriod: resyncPeriod,
|
||||||
|
RetryOnError: false,
|
||||||
|
|
||||||
|
Process: func(obj interface{}) error {
|
||||||
|
// from oldest to newest
|
||||||
|
for _, d := range obj.(cache.Deltas) {
|
||||||
|
switch d.Type {
|
||||||
|
case cache.Sync, cache.Added, cache.Updated:
|
||||||
|
if old, exists, err := clientState.Get(d.Object); err == nil && exists {
|
||||||
|
if err := clientState.Update(d.Object); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.OnUpdate(old, d.Object)
|
||||||
|
} else {
|
||||||
|
if err := clientState.Add(d.Object); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.OnAdd(d.Object)
|
||||||
|
}
|
||||||
|
case cache.Deleted:
|
||||||
|
if err := clientState.Delete(d.Object); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.OnDelete(d.Object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return clientState, New(cfg)
|
||||||
|
}
|
||||||
|
@ -107,6 +107,10 @@ func finalize(kubeClient client.Interface, namespace api.Namespace) (*api.Namesp
|
|||||||
|
|
||||||
// deleteAllContent will delete all content known to the system in a namespace
|
// deleteAllContent will delete all content known to the system in a namespace
|
||||||
func deleteAllContent(kubeClient client.Interface, namespace string) (err error) {
|
func deleteAllContent(kubeClient client.Interface, namespace string) (err error) {
|
||||||
|
err = deleteServiceAccounts(kubeClient, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = deleteServices(kubeClient, namespace)
|
err = deleteServices(kubeClient, namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -217,6 +221,20 @@ func deleteResourceQuotas(kubeClient client.Interface, ns string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteServiceAccounts(kubeClient client.Interface, ns string) error {
|
||||||
|
items, err := kubeClient.ServiceAccounts(ns).List(labels.Everything(), fields.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range items.Items {
|
||||||
|
err := kubeClient.ServiceAccounts(ns).Delete(items.Items[i].Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func deleteServices(kubeClient client.Interface, ns string) error {
|
func deleteServices(kubeClient client.Interface, ns string) error {
|
||||||
items, err := kubeClient.Services(ns).List(labels.Everything())
|
items, err := kubeClient.Services(ns).List(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
19
pkg/serviceaccount/doc.go
Normal file
19
pkg/serviceaccount/doc.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 provides implementations
|
||||||
|
// to manage service accounts and service account tokens
|
||||||
|
package serviceaccount
|
442
pkg/serviceaccount/tokens_controller.go
Normal file
442
pkg/serviceaccount/tokens_controller.go
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/controller/framework"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokensControllerOptions contains options for the TokensController
|
||||||
|
type TokensControllerOptions struct {
|
||||||
|
// TokenGenerator is the generator to use to create new tokens
|
||||||
|
TokenGenerator TokenGenerator
|
||||||
|
// ServiceAccountResync is the time.Duration at which to fully re-list service accounts.
|
||||||
|
// If zero, re-list will be delayed as long as possible
|
||||||
|
ServiceAccountResync time.Duration
|
||||||
|
// SecretResync is the time.Duration at which to fully re-list secrets.
|
||||||
|
// If zero, re-list will be delayed as long as possible
|
||||||
|
SecretResync time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTokenControllerOptions returns
|
||||||
|
func DefaultTokenControllerOptions(tokenGenerator TokenGenerator) TokensControllerOptions {
|
||||||
|
return TokensControllerOptions{TokenGenerator: tokenGenerator}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokensController returns a new *TokensController.
|
||||||
|
func NewTokensController(cl client.Interface, options TokensControllerOptions) *TokensController {
|
||||||
|
e := &TokensController{
|
||||||
|
client: cl,
|
||||||
|
token: options.TokenGenerator,
|
||||||
|
}
|
||||||
|
|
||||||
|
e.serviceAccounts, e.serviceAccountController = framework.NewIndexerInformer(
|
||||||
|
&cache.ListWatch{
|
||||||
|
ListFunc: func() (runtime.Object, error) {
|
||||||
|
return e.client.ServiceAccounts(api.NamespaceAll).List(labels.Everything(), fields.Everything())
|
||||||
|
},
|
||||||
|
WatchFunc: func(rv string) (watch.Interface, error) {
|
||||||
|
return e.client.ServiceAccounts(api.NamespaceAll).Watch(labels.Everything(), fields.Everything(), rv)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&api.ServiceAccount{},
|
||||||
|
options.ServiceAccountResync,
|
||||||
|
framework.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: e.serviceAccountAdded,
|
||||||
|
UpdateFunc: e.serviceAccountUpdated,
|
||||||
|
DeleteFunc: e.serviceAccountDeleted,
|
||||||
|
},
|
||||||
|
cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc},
|
||||||
|
)
|
||||||
|
|
||||||
|
tokenSelector := fields.SelectorFromSet(map[string]string{client.SecretType: string(api.SecretTypeServiceAccountToken)})
|
||||||
|
e.secrets, e.secretController = framework.NewIndexerInformer(
|
||||||
|
&cache.ListWatch{
|
||||||
|
ListFunc: func() (runtime.Object, error) {
|
||||||
|
return e.client.Secrets(api.NamespaceAll).List(labels.Everything(), tokenSelector)
|
||||||
|
},
|
||||||
|
WatchFunc: func(rv string) (watch.Interface, error) {
|
||||||
|
return e.client.Secrets(api.NamespaceAll).Watch(labels.Everything(), tokenSelector, rv)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&api.Secret{},
|
||||||
|
options.SecretResync,
|
||||||
|
framework.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: e.secretAdded,
|
||||||
|
UpdateFunc: e.secretUpdated,
|
||||||
|
DeleteFunc: e.secretDeleted,
|
||||||
|
},
|
||||||
|
cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc},
|
||||||
|
)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokensController manages ServiceAccountToken secrets for ServiceAccount objects
|
||||||
|
type TokensController struct {
|
||||||
|
stopChan chan struct{}
|
||||||
|
|
||||||
|
client client.Interface
|
||||||
|
token TokenGenerator
|
||||||
|
|
||||||
|
serviceAccounts cache.Indexer
|
||||||
|
secrets cache.Indexer
|
||||||
|
|
||||||
|
// Since we join two objects, we'll watch both of them with controllers.
|
||||||
|
serviceAccountController *framework.Controller
|
||||||
|
secretController *framework.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs controller loops and returns immediately
|
||||||
|
func (e *TokensController) Run() {
|
||||||
|
if e.stopChan == nil {
|
||||||
|
e.stopChan = make(chan struct{})
|
||||||
|
go e.serviceAccountController.Run(e.stopChan)
|
||||||
|
go e.secretController.Run(e.stopChan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop gracefully shuts down this controller
|
||||||
|
func (e *TokensController) Stop() {
|
||||||
|
if e.stopChan != nil {
|
||||||
|
close(e.stopChan)
|
||||||
|
e.stopChan = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceAccountAdded reacts to a ServiceAccount creation by creating a corresponding ServiceAccountToken Secret
|
||||||
|
func (e *TokensController) serviceAccountAdded(obj interface{}) {
|
||||||
|
serviceAccount := obj.(*api.ServiceAccount)
|
||||||
|
err := e.createSecretIfNeeded(serviceAccount)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceAccountUpdated reacts to a ServiceAccount update (or re-list) by ensuring a corresponding ServiceAccountToken Secret exists
|
||||||
|
func (e *TokensController) serviceAccountUpdated(oldObj interface{}, newObj interface{}) {
|
||||||
|
newServiceAccount := newObj.(*api.ServiceAccount)
|
||||||
|
err := e.createSecretIfNeeded(newServiceAccount)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceAccountDeleted reacts to a ServiceAccount deletion by deleting all corresponding ServiceAccountToken Secrets
|
||||||
|
func (e *TokensController) serviceAccountDeleted(obj interface{}) {
|
||||||
|
serviceAccount, ok := obj.(*api.ServiceAccount)
|
||||||
|
if !ok {
|
||||||
|
// Unknown type. If we missed a ServiceAccount deletion, the
|
||||||
|
// corresponding secrets will be cleaned up during the Secret re-list
|
||||||
|
return
|
||||||
|
}
|
||||||
|
secrets, err := e.listTokenSecrets(serviceAccount)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, secret := range secrets {
|
||||||
|
if err := e.deleteSecret(secret); err != nil {
|
||||||
|
glog.Errorf("Error deleting secret %s/%s: %v", secret.Namespace, secret.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// secretAdded reacts to a Secret create by ensuring the referenced ServiceAccount exists, and by adding a token to the secret if needed
|
||||||
|
func (e *TokensController) secretAdded(obj interface{}) {
|
||||||
|
secret := obj.(*api.Secret)
|
||||||
|
serviceAccount, err := e.getServiceAccount(secret)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if serviceAccount == nil {
|
||||||
|
if err := e.deleteSecret(secret); err != nil {
|
||||||
|
glog.Errorf("Error deleting secret %s/%s: %v", secret.Namespace, secret.Name, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e.generateTokenIfNeeded(serviceAccount, secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// secretUpdated reacts to a Secret update (or re-list) by deleting the secret (if the referenced ServiceAccount does not exist)
|
||||||
|
func (e *TokensController) secretUpdated(oldObj interface{}, newObj interface{}) {
|
||||||
|
newSecret := newObj.(*api.Secret)
|
||||||
|
newServiceAccount, err := e.getServiceAccount(newSecret)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newServiceAccount == nil {
|
||||||
|
if err := e.deleteSecret(newSecret); err != nil {
|
||||||
|
glog.Errorf("Error deleting secret %s/%s: %v", newSecret.Namespace, newSecret.Name, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e.generateTokenIfNeeded(newServiceAccount, newSecret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// secretDeleted reacts to a Secret being deleted by removing a reference from the corresponding ServiceAccount if needed
|
||||||
|
func (e *TokensController) secretDeleted(obj interface{}) {
|
||||||
|
secret, ok := obj.(*api.Secret)
|
||||||
|
if !ok {
|
||||||
|
// Unknown type. If we missed a Secret deletion, the corresponding ServiceAccount (if it exists)
|
||||||
|
// will get a secret recreated (if needed) during the ServiceAccount re-list
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceAccount, err := e.getServiceAccount(secret)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if serviceAccount == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := e.removeSecretReferenceIfNeeded(serviceAccount, secret.Name); err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSecretIfNeeded makes sure at least one ServiceAccountToken secret exists, and is included in the serviceAccount's Secrets list
|
||||||
|
func (e *TokensController) createSecretIfNeeded(serviceAccount *api.ServiceAccount) error {
|
||||||
|
// If the service account references no secrets, short-circuit and create a new one
|
||||||
|
if len(serviceAccount.Secrets) == 0 {
|
||||||
|
return e.createSecret(serviceAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any existing token secrets are referenced by the service account, return
|
||||||
|
allSecrets, err := e.listTokenSecrets(serviceAccount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
referencedSecrets := getSecretReferences(serviceAccount)
|
||||||
|
for _, secret := range allSecrets {
|
||||||
|
if referencedSecrets.Has(secret.Name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create a new token secret
|
||||||
|
return e.createSecret(serviceAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSecret creates a secret of type ServiceAccountToken for the given ServiceAccount
|
||||||
|
func (e *TokensController) createSecret(serviceAccount *api.ServiceAccount) error {
|
||||||
|
// Build the secret
|
||||||
|
secret := &api.Secret{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: secret.Strategy.GenerateName(fmt.Sprintf("%s-token-", serviceAccount.Name)),
|
||||||
|
Namespace: serviceAccount.Namespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.ServiceAccountNameKey: serviceAccount.Name,
|
||||||
|
api.ServiceAccountUIDKey: string(serviceAccount.UID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: api.SecretTypeServiceAccountToken,
|
||||||
|
Data: map[string][]byte{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the token
|
||||||
|
token, err := e.token.GenerateToken(*serviceAccount, *secret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secret.Data[api.ServiceAccountTokenKey] = []byte(token)
|
||||||
|
|
||||||
|
// Save the secret
|
||||||
|
if _, err := e.client.Secrets(serviceAccount.Namespace).Create(secret); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to update the cache's copy of the service account
|
||||||
|
// so add the secret to a freshly retrieved copy of the service account
|
||||||
|
serviceAccounts := e.client.ServiceAccounts(serviceAccount.Namespace)
|
||||||
|
serviceAccount, err = serviceAccounts.Get(serviceAccount.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
serviceAccount.Secrets = append(serviceAccount.Secrets, api.ObjectReference{Name: secret.Name})
|
||||||
|
|
||||||
|
_, err = serviceAccounts.Update(serviceAccount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateTokenIfNeeded populates the token data for the given Secret if not already set
|
||||||
|
func (e *TokensController) generateTokenIfNeeded(serviceAccount *api.ServiceAccount, secret *api.Secret) error {
|
||||||
|
if secret.Annotations == nil {
|
||||||
|
secret.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
if secret.Data == nil {
|
||||||
|
secret.Data = map[string][]byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenData, ok := secret.Data[api.ServiceAccountTokenKey]
|
||||||
|
if ok && len(tokenData) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the token
|
||||||
|
token, err := e.token.GenerateToken(*serviceAccount, *secret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the token and annotations
|
||||||
|
secret.Data[api.ServiceAccountTokenKey] = []byte(token)
|
||||||
|
secret.Annotations[api.ServiceAccountNameKey] = serviceAccount.Name
|
||||||
|
secret.Annotations[api.ServiceAccountUIDKey] = string(serviceAccount.UID)
|
||||||
|
|
||||||
|
// Save the secret
|
||||||
|
if _, err := e.client.Secrets(secret.Namespace).Update(secret); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSecret deletes the given secret
|
||||||
|
func (e *TokensController) deleteSecret(secret *api.Secret) error {
|
||||||
|
return e.client.Secrets(secret.Namespace).Delete(secret.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeSecretReferenceIfNeeded updates the given ServiceAccount to remove a reference to the given secretName if needed.
|
||||||
|
// Returns whether an update was performed, and any error that occurred
|
||||||
|
func (e *TokensController) removeSecretReferenceIfNeeded(serviceAccount *api.ServiceAccount, secretName string) (bool, error) {
|
||||||
|
// See if the account even referenced the secret
|
||||||
|
if !getSecretReferences(serviceAccount).Has(secretName) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to update the cache's copy of the service account
|
||||||
|
// so remove the secret from a freshly retrieved copy of the service account
|
||||||
|
serviceAccounts := e.client.ServiceAccounts(serviceAccount.Namespace)
|
||||||
|
serviceAccount, err := serviceAccounts.Get(serviceAccount.Name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double-check to see if the account still references the secret
|
||||||
|
if !getSecretReferences(serviceAccount).Has(secretName) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets := []api.ObjectReference{}
|
||||||
|
for _, s := range serviceAccount.Secrets {
|
||||||
|
if s.Name != secretName {
|
||||||
|
secrets = append(secrets, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serviceAccount.Secrets = secrets
|
||||||
|
|
||||||
|
_, err = serviceAccounts.Update(serviceAccount)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getServiceAccount returns the ServiceAccount referenced by the given secret. If the secret is not
|
||||||
|
// of type ServiceAccountToken, or if the referenced ServiceAccount does not exist, nil is returned
|
||||||
|
func (e *TokensController) getServiceAccount(secret *api.Secret) (*api.ServiceAccount, error) {
|
||||||
|
name, uid := serviceAccountNameAndUID(secret)
|
||||||
|
if len(name) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key := &api.ServiceAccount{ObjectMeta: api.ObjectMeta{Namespace: secret.Namespace}}
|
||||||
|
namespaceAccounts, err := e.serviceAccounts.Index("namespace", key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, obj := range namespaceAccounts {
|
||||||
|
serviceAccount := obj.(*api.ServiceAccount)
|
||||||
|
if name != serviceAccount.Name {
|
||||||
|
// Name must match
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(uid) > 0 && uid != string(serviceAccount.UID) {
|
||||||
|
// If UID is specified, it must match
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return serviceAccount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listTokenSecrets returns a list of all of the ServiceAccountToken secrets that
|
||||||
|
// reference the given service account's name and uid
|
||||||
|
func (e *TokensController) listTokenSecrets(serviceAccount *api.ServiceAccount) ([]*api.Secret, error) {
|
||||||
|
key := &api.Secret{ObjectMeta: api.ObjectMeta{Namespace: serviceAccount.Namespace}}
|
||||||
|
namespaceSecrets, err := e.secrets.Index("namespace", key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
items := []*api.Secret{}
|
||||||
|
for _, obj := range namespaceSecrets {
|
||||||
|
secret := obj.(*api.Secret)
|
||||||
|
name, uid := serviceAccountNameAndUID(secret)
|
||||||
|
if name != serviceAccount.Name {
|
||||||
|
// Name must match
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(uid) > 0 && uid != string(serviceAccount.UID) {
|
||||||
|
// If UID is specified, it must match
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, secret)
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceAccountNameAndUID is a helper method to get the ServiceAccount Name and UID from the given secret
|
||||||
|
// Returns "","" if the secret is not a ServiceAccountToken secret
|
||||||
|
// If the name or uid annotation is missing, "" is returned instead
|
||||||
|
func serviceAccountNameAndUID(secret *api.Secret) (string, string) {
|
||||||
|
if secret.Type != api.SecretTypeServiceAccountToken {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
return secret.Annotations[api.ServiceAccountNameKey], secret.Annotations[api.ServiceAccountUIDKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecretReferences(serviceAccount *api.ServiceAccount) util.StringSet {
|
||||||
|
references := util.NewStringSet()
|
||||||
|
for _, secret := range serviceAccount.Secrets {
|
||||||
|
references.Insert(secret.Name)
|
||||||
|
}
|
||||||
|
return references
|
||||||
|
}
|
385
pkg/serviceaccount/tokens_controller_test.go
Normal file
385
pkg/serviceaccount/tokens_controller_test.go
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testGenerator struct {
|
||||||
|
GeneratedServiceAccounts []api.ServiceAccount
|
||||||
|
GeneratedSecrets []api.Secret
|
||||||
|
Token string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testGenerator) GenerateToken(serviceAccount api.ServiceAccount, secret api.Secret) (string, error) {
|
||||||
|
t.GeneratedSecrets = append(t.GeneratedSecrets, secret)
|
||||||
|
t.GeneratedServiceAccounts = append(t.GeneratedServiceAccounts, serviceAccount)
|
||||||
|
return t.Token, t.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// emptySecretReferences is used by a service account without any secrets
|
||||||
|
func emptySecretReferences() []api.ObjectReference {
|
||||||
|
return []api.ObjectReference{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// missingSecretReferences is used by a service account that references secrets which do no exist
|
||||||
|
func missingSecretReferences() []api.ObjectReference {
|
||||||
|
return []api.ObjectReference{{Name: "missing-secret-1"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// regularSecretReferences is used by a service account that references secrets which are not ServiceAccountTokens
|
||||||
|
func regularSecretReferences() []api.ObjectReference {
|
||||||
|
return []api.ObjectReference{{Name: "regular-secret-1"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenSecretReferences is used by a service account that references a ServiceAccountToken secret
|
||||||
|
func tokenSecretReferences() []api.ObjectReference {
|
||||||
|
return []api.ObjectReference{{Name: "token-secret-1"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addTokenSecretReference adds a reference to the ServiceAccountToken that will be created
|
||||||
|
func addTokenSecretReference(refs []api.ObjectReference) []api.ObjectReference {
|
||||||
|
return append(refs, api.ObjectReference{Name: "default-token-fplln"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceAccount returns a service account with the given secret refs
|
||||||
|
func serviceAccount(secretRefs []api.ObjectReference) *api.ServiceAccount {
|
||||||
|
return &api.ServiceAccount{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "default",
|
||||||
|
UID: "12345",
|
||||||
|
Namespace: "default",
|
||||||
|
ResourceVersion: "1",
|
||||||
|
},
|
||||||
|
Secrets: secretRefs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// opaqueSecret returns a persisted non-ServiceAccountToken secret named "regular-secret-1"
|
||||||
|
func opaqueSecret() *api.Secret {
|
||||||
|
return &api.Secret{
|
||||||
|
ObjectMeta: api.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-fplln", since that is the first generated name after rand.Seed(1)
|
||||||
|
func createdTokenSecret() *api.Secret {
|
||||||
|
return &api.Secret{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "default-token-fplln",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.ServiceAccountNameKey: "default",
|
||||||
|
api.ServiceAccountUIDKey: "12345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: api.SecretTypeServiceAccountToken,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"token": []byte("ABC"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceAccountTokenSecret returns an existing ServiceAccountToken secret named "token-secret-1"
|
||||||
|
func serviceAccountTokenSecret() *api.Secret {
|
||||||
|
return &api.Secret{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "token-secret-1",
|
||||||
|
Namespace: "default",
|
||||||
|
UID: "23456",
|
||||||
|
ResourceVersion: "1",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
api.ServiceAccountNameKey: "default",
|
||||||
|
api.ServiceAccountUIDKey: "12345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: api.SecretTypeServiceAccountToken,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"token": []byte("ABC"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceAccountTokenSecretWithoutTokenData returns an existing ServiceAccountToken secret that lacks token data
|
||||||
|
func serviceAccountTokenSecretWithoutTokenData() *api.Secret {
|
||||||
|
secret := serviceAccountTokenSecret()
|
||||||
|
secret.Data = nil
|
||||||
|
return secret
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenCreation(t *testing.T) {
|
||||||
|
testcases := map[string]struct {
|
||||||
|
ClientObjects []runtime.Object
|
||||||
|
|
||||||
|
ExistingServiceAccount *api.ServiceAccount
|
||||||
|
ExistingSecrets []*api.Secret
|
||||||
|
|
||||||
|
AddedServiceAccount *api.ServiceAccount
|
||||||
|
UpdatedServiceAccount *api.ServiceAccount
|
||||||
|
DeletedServiceAccount *api.ServiceAccount
|
||||||
|
AddedSecret *api.Secret
|
||||||
|
UpdatedSecret *api.Secret
|
||||||
|
DeletedSecret *api.Secret
|
||||||
|
|
||||||
|
ExpectedActions []testclient.FakeAction
|
||||||
|
}{
|
||||||
|
"new serviceaccount with no secrets": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()},
|
||||||
|
|
||||||
|
AddedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "create-secret", Value: createdTokenSecret()},
|
||||||
|
{Action: "get-serviceaccount", Value: "default"},
|
||||||
|
{Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(emptySecretReferences()))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"new serviceaccount with missing secrets": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()},
|
||||||
|
|
||||||
|
AddedServiceAccount: serviceAccount(missingSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "create-secret", Value: createdTokenSecret()},
|
||||||
|
{Action: "get-serviceaccount", Value: "default"},
|
||||||
|
{Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(missingSecretReferences()))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"new serviceaccount with non-token secrets": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()},
|
||||||
|
|
||||||
|
AddedServiceAccount: serviceAccount(regularSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "create-secret", Value: createdTokenSecret()},
|
||||||
|
{Action: "get-serviceaccount", Value: "default"},
|
||||||
|
{Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(regularSecretReferences()))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"new serviceaccount with token secrets": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences()), serviceAccountTokenSecret()},
|
||||||
|
ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()},
|
||||||
|
|
||||||
|
AddedServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"updated serviceaccount with no secrets": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()},
|
||||||
|
|
||||||
|
UpdatedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "create-secret", Value: createdTokenSecret()},
|
||||||
|
{Action: "get-serviceaccount", Value: "default"},
|
||||||
|
{Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(emptySecretReferences()))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"updated serviceaccount with missing secrets": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()},
|
||||||
|
|
||||||
|
UpdatedServiceAccount: serviceAccount(missingSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "create-secret", Value: createdTokenSecret()},
|
||||||
|
{Action: "get-serviceaccount", Value: "default"},
|
||||||
|
{Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(missingSecretReferences()))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"updated serviceaccount with non-token secrets": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()},
|
||||||
|
|
||||||
|
UpdatedServiceAccount: serviceAccount(regularSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "create-secret", Value: createdTokenSecret()},
|
||||||
|
{Action: "get-serviceaccount", Value: "default"},
|
||||||
|
{Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(regularSecretReferences()))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"updated serviceaccount with token secrets": {
|
||||||
|
ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()},
|
||||||
|
|
||||||
|
UpdatedServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"deleted serviceaccount with no secrets": {
|
||||||
|
DeletedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
"deleted serviceaccount with missing secrets": {
|
||||||
|
DeletedServiceAccount: serviceAccount(missingSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
"deleted serviceaccount with non-token secrets": {
|
||||||
|
ClientObjects: []runtime.Object{opaqueSecret()},
|
||||||
|
|
||||||
|
DeletedServiceAccount: serviceAccount(regularSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
"deleted serviceaccount with token secrets": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccountTokenSecret()},
|
||||||
|
ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()},
|
||||||
|
|
||||||
|
DeletedServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "delete-secret", Value: "token-secret-1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"added secret without serviceaccount": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccountTokenSecret()},
|
||||||
|
|
||||||
|
AddedSecret: serviceAccountTokenSecret(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "delete-secret", Value: "token-secret-1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"added secret with serviceaccount": {
|
||||||
|
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||||
|
|
||||||
|
AddedSecret: serviceAccountTokenSecret(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
"added token secret without token data": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()},
|
||||||
|
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||||
|
|
||||||
|
AddedSecret: serviceAccountTokenSecretWithoutTokenData(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "update-secret", Value: serviceAccountTokenSecret()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"updated secret without serviceaccount": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccountTokenSecret()},
|
||||||
|
|
||||||
|
UpdatedSecret: serviceAccountTokenSecret(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "delete-secret", Value: "token-secret-1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"updated secret with serviceaccount": {
|
||||||
|
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||||
|
|
||||||
|
UpdatedSecret: serviceAccountTokenSecret(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
"updated token secret without token data": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()},
|
||||||
|
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||||
|
|
||||||
|
UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "update-secret", Value: serviceAccountTokenSecret()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"deleted secret without serviceaccount": {
|
||||||
|
DeletedSecret: serviceAccountTokenSecret(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
"deleted secret with serviceaccount with reference": {
|
||||||
|
ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences())},
|
||||||
|
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||||
|
|
||||||
|
DeletedSecret: serviceAccountTokenSecret(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{
|
||||||
|
{Action: "get-serviceaccount", Value: "default"},
|
||||||
|
{Action: "update-serviceaccount", Value: serviceAccount(emptySecretReferences())},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"deleted secret with serviceaccount without reference": {
|
||||||
|
ExistingServiceAccount: serviceAccount(emptySecretReferences()),
|
||||||
|
|
||||||
|
DeletedSecret: serviceAccountTokenSecret(),
|
||||||
|
ExpectedActions: []testclient.FakeAction{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, tc := range testcases {
|
||||||
|
|
||||||
|
// Re-seed to reset name generation
|
||||||
|
rand.Seed(1)
|
||||||
|
|
||||||
|
generator := &testGenerator{Token: "ABC"}
|
||||||
|
|
||||||
|
client := testclient.NewSimpleFake(tc.ClientObjects...)
|
||||||
|
|
||||||
|
controller := NewTokensController(client, DefaultTokenControllerOptions(generator))
|
||||||
|
|
||||||
|
if tc.ExistingServiceAccount != nil {
|
||||||
|
controller.serviceAccounts.Add(tc.ExistingServiceAccount)
|
||||||
|
}
|
||||||
|
for _, s := range tc.ExistingSecrets {
|
||||||
|
controller.secrets.Add(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.AddedServiceAccount != nil {
|
||||||
|
controller.serviceAccountAdded(tc.AddedServiceAccount)
|
||||||
|
}
|
||||||
|
if tc.UpdatedServiceAccount != nil {
|
||||||
|
controller.serviceAccountUpdated(nil, tc.UpdatedServiceAccount)
|
||||||
|
}
|
||||||
|
if tc.DeletedServiceAccount != nil {
|
||||||
|
controller.serviceAccountDeleted(tc.DeletedServiceAccount)
|
||||||
|
}
|
||||||
|
if tc.AddedSecret != nil {
|
||||||
|
controller.secretAdded(tc.AddedSecret)
|
||||||
|
}
|
||||||
|
if tc.UpdatedSecret != nil {
|
||||||
|
controller.secretUpdated(nil, tc.UpdatedSecret)
|
||||||
|
}
|
||||||
|
if tc.DeletedSecret != nil {
|
||||||
|
controller.secretDeleted(tc.DeletedSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, action := range client.Actions {
|
||||||
|
if len(tc.ExpectedActions) < i+1 {
|
||||||
|
t.Errorf("%s: %d unexpected actions: %+v", k, len(client.Actions)-len(tc.ExpectedActions), client.Actions[i:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedAction := tc.ExpectedActions[i]
|
||||||
|
if expectedAction.Action != action.Action {
|
||||||
|
t.Errorf("%s: Expected %s, got %s", k, expectedAction.Action, action.Action)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expectedAction.Value, action.Value) {
|
||||||
|
t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, expectedAction.Value, action.Value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tc.ExpectedActions) > len(client.Actions) {
|
||||||
|
t.Errorf("%s: %d additional expected actions:%+v", k, len(tc.ExpectedActions)-len(client.Actions), tc.ExpectedActions[len(client.Actions):])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user