func newServiceAccountTokenGetter(options configapi.MasterConfig) (serviceaccount.ServiceAccountTokenGetter, error) { if options.KubernetesMasterConfig == nil { // When we're running against an external Kubernetes, use the external kubernetes client to validate service account tokens // This prevents infinite auth loops if the privilegedLoopbackKubeClient authenticates using a service account token kubeClient, _, err := configapi.GetKubeClient(options.MasterClients.ExternalKubernetesKubeConfig, options.MasterClients.ExternalKubernetesClientConnectionOverrides) if err != nil { return nil, err } return sacontroller.NewGetterFromClient(clientadapter.FromUnversionedClient(kubeClient)), nil } // TODO: could be hoisted if other Origin code needs direct access to etcd, otherwise discourage this access pattern // as we move to be more on top of Kube. _, kubeStorageFactory, err := kubernetes.BuildDefaultAPIServer(options) if err != nil { return nil, err } storageConfig, err := kubeStorageFactory.NewConfig(kapi.Resource("serviceaccounts")) if err != nil { return nil, err } // TODO: by doing this we will not be able to authenticate while a master quorum is not present - reimplement // as two storages called in succession (non quorum and then quorum). storageConfig.Quorum = true return sacontroller.NewGetterFromStorageInterface(storageConfig, kubeStorageFactory.ResourcePrefix(kapi.Resource("serviceaccounts")), kubeStorageFactory.ResourcePrefix(kapi.Resource("secrets"))), nil }
func newServiceAccountTokenGetter(options configapi.MasterConfig, client *etcdclient.Client) (serviceaccount.ServiceAccountTokenGetter, error) { var tokenGetter serviceaccount.ServiceAccountTokenGetter if options.KubernetesMasterConfig == nil { // When we're running against an external Kubernetes, use the external kubernetes client to validate service account tokens // This prevents infinite auth loops if the privilegedLoopbackKubeClient authenticates using a service account token kubeClient, _, err := configapi.GetKubeClient(options.MasterClients.ExternalKubernetesKubeConfig) if err != nil { return nil, err } tokenGetter = serviceaccount.NewGetterFromClient(kubeClient) } else { // When we're running in-process, go straight to etcd (using the KubernetesStorageVersion/KubernetesStoragePrefix, since service accounts are kubernetes objects) legacyGroup, err := kapilatest.Group(kapi.SchemeGroupVersion.Group) if err != nil { return nil, fmt.Errorf("Error setting up Kubernetes server storage: %v", err) } versionedInterface, err := legacyGroup.InterfacesFor(unversioned.GroupVersion{Group: kapi.SchemeGroupVersion.Group, Version: options.EtcdStorageConfig.KubernetesStorageVersion}) if err != nil { return nil, fmt.Errorf("Error setting up Kubernetes server storage: %v", err) } ketcdHelper := etcdstorage.NewEtcdStorage(client, versionedInterface.Codec, options.EtcdStorageConfig.KubernetesStoragePrefix) tokenGetter = serviceaccount.NewGetterFromStorageInterface(ketcdHelper) } return tokenGetter, nil }
func newServiceAccountTokenGetter(options configapi.MasterConfig, client newetcdclient.Client) (serviceaccount.ServiceAccountTokenGetter, error) { var tokenGetter serviceaccount.ServiceAccountTokenGetter if options.KubernetesMasterConfig == nil { // When we're running against an external Kubernetes, use the external kubernetes client to validate service account tokens // This prevents infinite auth loops if the privilegedLoopbackKubeClient authenticates using a service account token kubeClient, _, err := configapi.GetKubeClient(options.MasterClients.ExternalKubernetesKubeConfig) if err != nil { return nil, err } tokenGetter = sacontroller.NewGetterFromClient(kubeClient) } else { // When we're running in-process, go straight to etcd (using the KubernetesStorageVersion/KubernetesStoragePrefix, since service accounts are kubernetes objects) codec := kapi.Codecs.LegacyCodec(unversioned.GroupVersion{Group: kapi.GroupName, Version: options.EtcdStorageConfig.KubernetesStorageVersion}) ketcdHelper := etcdstorage.NewEtcdStorage(client, codec, options.EtcdStorageConfig.KubernetesStoragePrefix) tokenGetter = sacontroller.NewGetterFromStorageInterface(ketcdHelper) } return tokenGetter, nil }
// startServiceAccountTestServer returns a started server // It is the responsibility of the caller to ensure the returned stopFunc is called func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclient.Config, func()) { deleteAllEtcdKeys() // Listener var m *master.Master apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { m.Handler.ServeHTTP(w, req) })) // Anonymous client config clientConfig := restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} // Root client // TODO: remove rootClient after we refactor pkg/admission to use the clientset. rootClientset := clientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, BearerToken: rootToken}) // Set up two authenticators: // 1. A token authenticator that maps the rootToken to the "root" user // 2. A ServiceAccountToken authenticator that validates ServiceAccount tokens rootTokenAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) { if token == rootToken { return &user.DefaultInfo{rootUserName, "", []string{}}, true, nil } return nil, false, nil }) serviceAccountKey, _ := rsa.GenerateKey(rand.Reader, 2048) serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(rootClientset) serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{&serviceAccountKey.PublicKey}, true, serviceAccountTokenGetter) authenticator := union.New( bearertoken.New(rootTokenAuth), bearertoken.New(serviceAccountTokenAuth), ) // Set up a stub authorizer: // 1. The "root" user is allowed to do anything // 2. ServiceAccounts named "ro" are allowed read-only operations in their namespace // 3. ServiceAccounts named "rw" are allowed any operation in their namespace authorizer := authorizer.AuthorizerFunc(func(attrs authorizer.Attributes) error { username := attrs.GetUserName() ns := attrs.GetNamespace() // If the user is "root"... if username == rootUserName { // allow them to do anything return nil } // If the user is a service account... if serviceAccountNamespace, serviceAccountName, err := serviceaccount.SplitUsername(username); err == nil { // Limit them to their own namespace if serviceAccountNamespace == ns { switch serviceAccountName { case readOnlyServiceAccountName: if attrs.IsReadOnly() { return nil } case readWriteServiceAccountName: return nil } } } return fmt.Errorf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()) }) // Set up admission plugin to auto-assign serviceaccounts to pods serviceAccountAdmission := serviceaccountadmission.NewServiceAccount(rootClientset) masterConfig := framework.NewMasterConfig() masterConfig.EnableIndex = true masterConfig.Authenticator = authenticator masterConfig.Authorizer = authorizer masterConfig.AdmissionControl = serviceAccountAdmission // Create a master and install handlers into mux. m, err := master.New(masterConfig) if err != nil { t.Fatalf("Error in bringing up the master: %v", err) } // Start the service account and service account token controllers tokenController := serviceaccountcontroller.NewTokensController(rootClientset, serviceaccountcontroller.TokensControllerOptions{TokenGenerator: serviceaccount.JWTTokenGenerator(serviceAccountKey)}) tokenController.Run() serviceAccountController := serviceaccountcontroller.NewServiceAccountsController(rootClientset, serviceaccountcontroller.DefaultServiceAccountsControllerOptions()) serviceAccountController.Run() // Start the admission plugin reflectors serviceAccountAdmission.Run() stop := func() { tokenController.Stop() serviceAccountController.Stop() serviceAccountAdmission.Stop() // TODO: Uncomment when fix #19254 // apiServer.Close() } return rootClientset, clientConfig, stop }
// startServiceAccountTestServer returns a started server // It is the responsibility of the caller to ensure the returned stopFunc is called func startServiceAccountTestServer(t *testing.T) (*client.Client, client.Config, func()) { deleteAllEtcdKeys() // Etcd etcdStorage, err := framework.NewEtcdStorage() if err != nil { t.Fatalf("unexpected error: %v", err) } expEtcdStorage, err := framework.NewExtensionsEtcdStorage(nil) if err != nil { t.Fatalf("unexpected error: %v", err) } storageDestinations := master.NewStorageDestinations() storageDestinations.AddAPIGroup("", etcdStorage) storageDestinations.AddAPIGroup("extensions", expEtcdStorage) storageVersions := make(map[string]string) storageVersions[""] = testapi.Default.Version() storageVersions["extensions"] = testapi.Extensions.GroupAndVersion() // Listener var m *master.Master apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { m.Handler.ServeHTTP(w, req) })) // Anonymous client config clientConfig := client.Config{Host: apiServer.URL, Version: testapi.Default.Version()} // Root client rootClient := client.NewOrDie(&client.Config{Host: apiServer.URL, Version: testapi.Default.Version(), BearerToken: rootToken}) // Set up two authenticators: // 1. A token authenticator that maps the rootToken to the "root" user // 2. A ServiceAccountToken authenticator that validates ServiceAccount tokens rootTokenAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) { if token == rootToken { return &user.DefaultInfo{rootUserName, "", "", "", []string{}, ""}, true, nil } return nil, false, nil }) serviceAccountKey, err := rsa.GenerateKey(rand.Reader, 2048) serviceAccountTokenGetter := serviceaccount.NewGetterFromClient(rootClient) serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{&serviceAccountKey.PublicKey}, true, serviceAccountTokenGetter) authenticator := union.New( bearertoken.New(rootTokenAuth), bearertoken.New(serviceAccountTokenAuth), ) // Set up a stub authorizer: // 1. The "root" user is allowed to do anything // 2. ServiceAccounts named "ro" are allowed read-only operations in their namespace // 3. ServiceAccounts named "rw" are allowed any operation in their namespace authorizer := authorizer.AuthorizerFunc(func(attrs authorizer.Attributes) (string, error) { username := attrs.GetUserName() ns := attrs.GetNamespace() // If the user is "root"... if username == rootUserName { // allow them to do anything return "", nil } // If the user is a service account... if serviceAccountNamespace, serviceAccountName, err := serviceaccount.SplitUsername(username); err == nil { // Limit them to their own namespace if serviceAccountNamespace == ns { switch serviceAccountName { case readOnlyServiceAccountName: if attrs.IsReadOnly() { return "", nil } case readWriteServiceAccountName: return "", nil } } } return "", fmt.Errorf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()) }) // Set up admission plugin to auto-assign serviceaccounts to pods serviceAccountAdmission := serviceaccountadmission.NewServiceAccount(rootClient) // Create a master and install handlers into mux. m = master.New(&master.Config{ StorageDestinations: storageDestinations, KubeletClient: client.FakeKubeletClient{}, EnableLogsSupport: false, EnableUISupport: false, EnableIndex: true, APIPrefix: "/api", Authenticator: authenticator, Authorizer: authorizer, AdmissionControl: serviceAccountAdmission, StorageVersions: storageVersions, }) // Start the service account and service account token controllers tokenController := serviceaccount.NewTokensController(rootClient, serviceaccount.TokensControllerOptions{TokenGenerator: serviceaccount.JWTTokenGenerator(serviceAccountKey)}) tokenController.Run() serviceAccountController := serviceaccount.NewServiceAccountsController(rootClient, serviceaccount.DefaultServiceAccountsControllerOptions()) serviceAccountController.Run() // Start the admission plugin reflectors serviceAccountAdmission.Run() stop := func() { tokenController.Stop() serviceAccountController.Stop() serviceAccountAdmission.Stop() apiServer.Close() } return rootClient, clientConfig, stop }
// startServiceAccountTestServer returns a started server // It is the responsibility of the caller to ensure the returned stopFunc is called func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclient.Config, func()) { // Listener h := &framework.MasterHolder{Initialized: make(chan struct{})} apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { <-h.Initialized h.M.GenericAPIServer.Handler.ServeHTTP(w, req) })) // Anonymous client config clientConfig := restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(v1.GroupName).GroupVersion}} // Root client // TODO: remove rootClient after we refactor pkg/admission to use the clientset. rootClientset := clientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(v1.GroupName).GroupVersion}, BearerToken: rootToken}) internalRootClientset := internalclientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(v1.GroupName).GroupVersion}, BearerToken: rootToken}) // Set up two authenticators: // 1. A token authenticator that maps the rootToken to the "root" user // 2. A ServiceAccountToken authenticator that validates ServiceAccount tokens rootTokenAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) { if token == rootToken { return &user.DefaultInfo{Name: rootUserName}, true, nil } return nil, false, nil }) serviceAccountKey, _ := rsa.GenerateKey(rand.Reader, 2048) serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(rootClientset) serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator([]interface{}{&serviceAccountKey.PublicKey}, true, serviceAccountTokenGetter) authenticator := union.New( bearertoken.New(rootTokenAuth), bearertoken.New(serviceAccountTokenAuth), ) // Set up a stub authorizer: // 1. The "root" user is allowed to do anything // 2. ServiceAccounts named "ro" are allowed read-only operations in their namespace // 3. ServiceAccounts named "rw" are allowed any operation in their namespace authorizer := authorizer.AuthorizerFunc(func(attrs authorizer.Attributes) (bool, string, error) { username := "" if user := attrs.GetUser(); user != nil { username = user.GetName() } ns := attrs.GetNamespace() // If the user is "root"... if username == rootUserName { // allow them to do anything return true, "", nil } // If the user is a service account... if serviceAccountNamespace, serviceAccountName, err := serviceaccount.SplitUsername(username); err == nil { // Limit them to their own namespace if serviceAccountNamespace == ns { switch serviceAccountName { case readOnlyServiceAccountName: if attrs.IsReadOnly() { return true, "", nil } case readWriteServiceAccountName: return true, "", nil } } } return false, fmt.Sprintf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()), nil }) // Set up admission plugin to auto-assign serviceaccounts to pods serviceAccountAdmission := serviceaccountadmission.NewServiceAccount(internalRootClientset) masterConfig := framework.NewMasterConfig() masterConfig.GenericConfig.EnableIndex = true masterConfig.GenericConfig.Authenticator = authenticator masterConfig.GenericConfig.Authorizer = authorizer masterConfig.GenericConfig.AdmissionControl = serviceAccountAdmission framework.RunAMasterUsingServer(masterConfig, apiServer, h) // Start the service account and service account token controllers stopCh := make(chan struct{}) tokenController := serviceaccountcontroller.NewTokensController(rootClientset, serviceaccountcontroller.TokensControllerOptions{TokenGenerator: serviceaccount.JWTTokenGenerator(serviceAccountKey)}) go tokenController.Run(1, stopCh) informers := informers.NewSharedInformerFactory(rootClientset, nil, controller.NoResyncPeriodFunc()) serviceAccountController := serviceaccountcontroller.NewServiceAccountsController(informers.ServiceAccounts(), informers.Namespaces(), rootClientset, serviceaccountcontroller.DefaultServiceAccountsControllerOptions()) informers.Start(stopCh) go serviceAccountController.Run(5, stopCh) // Start the admission plugin reflectors serviceAccountAdmission.Run() stop := func() { close(stopCh) serviceAccountAdmission.Stop() apiServer.Close() } return rootClientset, clientConfig, stop }
func TestTokenGenerateAndValidate(t *testing.T) { expectedUserName := "******" expectedUserUID := "12345" // Related API objects serviceAccount := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: "my-service-account", UID: "12345", Namespace: "test", }, } rsaSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: "my-rsa-secret", Namespace: "test", }, } ecdsaSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: "my-ecdsa-secret", Namespace: "test", }, } // Generate the RSA token rsaGenerator := serviceaccount.JWTTokenGenerator(getPrivateKey(rsaPrivateKey)) rsaToken, err := rsaGenerator.GenerateToken(*serviceAccount, *rsaSecret) if err != nil { t.Fatalf("error generating token: %v", err) } if len(rsaToken) == 0 { t.Fatalf("no token generated") } rsaSecret.Data = map[string][]byte{ "token": []byte(rsaToken), } // Generate the ECDSA token ecdsaGenerator := serviceaccount.JWTTokenGenerator(getPrivateKey(ecdsaPrivateKey)) ecdsaToken, err := ecdsaGenerator.GenerateToken(*serviceAccount, *ecdsaSecret) if err != nil { t.Fatalf("error generating token: %v", err) } if len(ecdsaToken) == 0 { t.Fatalf("no token generated") } ecdsaSecret.Data = map[string][]byte{ "token": []byte(ecdsaToken), } testCases := map[string]struct { Client clientset.Interface Keys []interface{} Token string ExpectedErr bool ExpectedOK bool ExpectedUserName string ExpectedUserUID string ExpectedGroups []string }{ "no keys": { Token: rsaToken, Client: nil, Keys: []interface{}{}, ExpectedErr: false, ExpectedOK: false, }, "invalid keys (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid keys (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "valid key (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "valid key (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(ecdsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "rotated keys (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey), getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "rotated keys (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey), getPublicKey(ecdsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "valid lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(serviceAccount, rsaSecret, ecdsaSecret), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "invalid secret lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(serviceAccount), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid serviceaccount lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(rsaSecret, ecdsaSecret), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, } for k, tc := range testCases { getter := serviceaccountcontroller.NewGetterFromClient(tc.Client) authenticator := serviceaccount.JWTTokenAuthenticator(tc.Keys, tc.Client != nil, getter) // An invalid, non-JWT token should always fail if _, ok, err := authenticator.AuthenticateToken("invalid token"); err != nil || ok { t.Errorf("%s: Expected err=nil, ok=false for non-JWT token", k) continue } user, ok, err := authenticator.AuthenticateToken(tc.Token) if (err != nil) != tc.ExpectedErr { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) continue } if ok != tc.ExpectedOK { t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok) continue } if err != nil || !ok { continue } if user.GetName() != tc.ExpectedUserName { t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, user.GetName()) continue } if user.GetUID() != tc.ExpectedUserUID { t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, user.GetUID()) continue } if !reflect.DeepEqual(user.GetGroups(), tc.ExpectedGroups) { t.Errorf("%s: Expected groups=%v, got %v", k, tc.ExpectedGroups, user.GetGroups()) continue } } }
func TestTokenGenerateAndValidate(t *testing.T) { expectedUserName := "******" expectedUserUID := "12345" // Related API objects serviceAccount := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: "my-service-account", UID: "12345", Namespace: "test", }, } secret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: "my-secret", Namespace: "test", }, } // Generate the token generator := serviceaccount.JWTTokenGenerator(getPrivateKey(privateKey)) token, err := generator.GenerateToken(*serviceAccount, *secret) if err != nil { t.Fatalf("error generating token: %v", err) } if len(token) == 0 { t.Fatalf("no token generated") } // "Save" the token secret.Data = map[string][]byte{ "token": []byte(token), } testCases := map[string]struct { Client clientset.Interface Keys []*rsa.PublicKey ExpectedErr bool ExpectedOK bool ExpectedUserName string ExpectedUserUID string ExpectedGroups []string }{ "no keys": { Client: nil, Keys: []*rsa.PublicKey{}, ExpectedErr: false, ExpectedOK: false, }, "invalid keys": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(otherPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "valid key": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "rotated keys": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(otherPublicKey), getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "valid lookup": { Client: fake.NewSimpleClientset(serviceAccount, secret), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "invalid secret lookup": { Client: fake.NewSimpleClientset(serviceAccount), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid serviceaccount lookup": { Client: fake.NewSimpleClientset(secret), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: true, ExpectedOK: false, }, } for k, tc := range testCases { getter := serviceaccountcontroller.NewGetterFromClient(tc.Client) authenticator := serviceaccount.JWTTokenAuthenticator(tc.Keys, tc.Client != nil, getter) user, ok, err := authenticator.AuthenticateToken(token) if (err != nil) != tc.ExpectedErr { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) continue } if ok != tc.ExpectedOK { t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok) continue } if err != nil || !ok { continue } if user.GetName() != tc.ExpectedUserName { t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, user.GetName()) continue } if user.GetUID() != tc.ExpectedUserUID { t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, user.GetUID()) continue } if !reflect.DeepEqual(user.GetGroups(), tc.ExpectedGroups) { t.Errorf("%s: Expected groups=%v, got %v", k, tc.ExpectedGroups, user.GetGroups()) continue } } }