k8s.io/kubernetes@v1.29.3/pkg/serviceaccount/jwt_test.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package serviceaccount_test
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  
    26  	jose "gopkg.in/square/go-jose.v2"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apiserver/pkg/authentication/authenticator"
    31  	clientset "k8s.io/client-go/kubernetes"
    32  	"k8s.io/client-go/kubernetes/fake"
    33  	typedv1core "k8s.io/client-go/kubernetes/typed/core/v1"
    34  	v1listers "k8s.io/client-go/listers/core/v1"
    35  	"k8s.io/client-go/tools/cache"
    36  	"k8s.io/client-go/util/keyutil"
    37  	serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
    38  	"k8s.io/kubernetes/pkg/serviceaccount"
    39  )
    40  
    41  const otherPublicKey = `-----BEGIN PUBLIC KEY-----
    42  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArXz0QkIG1B5Bj2/W69GH
    43  rsm5e+RC3kE+VTgocge0atqlLBek35tRqLgUi3AcIrBZ/0YctMSWDVcRt5fkhWwe
    44  Lqjj6qvAyNyOkrkBi1NFDpJBjYJtuKHgRhNxXbOzTSNpdSKXTfOkzqv56MwHOP25
    45  yP/NNAODUtr92D5ySI5QX8RbXW+uDn+ixul286PBW/BCrE4tuS88dA0tYJPf8LCu
    46  sqQOwlXYH/rNUg4Pyl9xxhR5DIJR0OzNNfChjw60zieRIt2LfM83fXhwk8IxRGkc
    47  gPZm7ZsipmfbZK2Tkhnpsa4QxDg7zHJPMsB5kxRXW0cQipXcC3baDyN9KBApNXa0
    48  PwIDAQAB
    49  -----END PUBLIC KEY-----`
    50  
    51  const rsaPublicKey = `-----BEGIN PUBLIC KEY-----
    52  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA249XwEo9k4tM8fMxV7zx
    53  OhcrP+WvXn917koM5Qr2ZXs4vo26e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecI
    54  zshKuv1gKIxbbLQMOuK1eA/4HALyEkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG
    55  51RoiMgbQxaCyYxGfNLpLAZK9L0Tctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJU
    56  j7OTh/AjjCnMnkgvKT2tpKxYQ59PgDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEi
    57  B4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRM
    58  WwIDAQAB
    59  -----END PUBLIC KEY-----
    60  `
    61  
    62  // Obtained by:
    63  //
    64  //  1. Serializing rsaPublicKey as DER
    65  //  2. Taking the SHA256 of the DER bytes
    66  //  3. URLSafe Base64-encoding the sha bytes
    67  const rsaKeyID = "JHJehTTTZlsspKHT-GaJxK7Kd1NQgZJu3fyK6K_QDYU"
    68  
    69  // Fake value for testing.
    70  const rsaPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
    71  MIIEowIBAAKCAQEA249XwEo9k4tM8fMxV7zxOhcrP+WvXn917koM5Qr2ZXs4vo26
    72  e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecIzshKuv1gKIxbbLQMOuK1eA/4HALy
    73  EkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG51RoiMgbQxaCyYxGfNLpLAZK9L0T
    74  ctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJUj7OTh/AjjCnMnkgvKT2tpKxYQ59P
    75  gDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEiB4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp
    76  1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRMWwIDAQABAoIBAHJx8GqyCBDNbqk7
    77  e7/hI9iE1S10Wwol5GH2RWxqX28cYMKq+8aE2LI1vPiXO89xOgelk4DN6urX6xjK
    78  ZBF8RRIMQy/e/O2F4+3wl+Nl4vOXV1u6iVXMsD6JRg137mqJf1Fr9elg1bsaRofL
    79  Q7CxPoB8dhS+Qb+hj0DhlqhgA9zG345CQCAds0ZYAZe8fP7bkwrLqZpMn7Dz9WVm
    80  ++YgYYKjuE95kPuup/LtWfA9rJyE/Fws8/jGvRSpVn1XglMLSMKhLd27sE8ZUSV0
    81  2KUzbfRGE0+AnRULRrjpYaPu0XQ2JjdNvtkjBnv27RB89W9Gklxq821eH1Y8got8
    82  FZodjxECgYEA93pz7AQZ2xDs67d1XLCzpX84GxKzttirmyj3OIlxgzVHjEMsvw8v
    83  sjFiBU5xEEQDosrBdSknnlJqyiq1YwWG/WDckr13d8G2RQWoySN7JVmTQfXcLoTu
    84  YGRiiTuoEi3ab3ZqrgGrFgX7T/cHuasbYvzCvhM2b4VIR3aSxU2DTUMCgYEA4x7J
    85  T/ErP6GkU5nKstu/mIXwNzayEO1BJvPYsy7i7EsxTm3xe/b8/6cYOz5fvJLGH5mT
    86  Q8YvuLqBcMwZardrYcwokD55UvNLOyfADDFZ6l3WntIqbA640Ok2g1X4U8J09xIq
    87  ZLIWK1yWbbvi4QCeN5hvWq47e8sIj5QHjIIjRwkCgYEAyNqjltxFN9zmzPDa2d24
    88  EAvOt3pYTYBQ1t9KtqImdL0bUqV6fZ6PsWoPCgt+DBuHb+prVPGP7Bkr/uTmznU/
    89  +AlTO+12NsYLbr2HHagkXE31DEXE7CSLa8RNjN/UKtz4Ohq7vnowJvG35FCz/mb3
    90  FUHbtHTXa2+bGBUOTf/5Hw0CgYBxw0r9EwUhw1qnUYJ5op7OzFAtp+T7m4ul8kCa
    91  SCL8TxGsgl+SQ34opE775dtYfoBk9a0RJqVit3D8yg71KFjOTNAIqHJm/Vyyjc+h
    92  i9rJDSXiuczsAVfLtPVMRfS0J9QkqeG4PIfkQmVLI/CZ2ZBmsqEcX+eFs4ZfPLun
    93  Qsxe2QKBgGuPilIbLeIBDIaPiUI0FwU8v2j8CEQBYvoQn34c95hVQsig/o5z7zlo
    94  UsO0wlTngXKlWdOcCs1kqEhTLrstf48djDxAYAxkw40nzeJOt7q52ib/fvf4/UBy
    95  X024wzbiw1q07jFCyfQmODzURAx1VNT7QVUMdz/N8vy47/H40AZJ
    96  -----END RSA PRIVATE KEY-----
    97  `
    98  
    99  // openssl ecparam -name prime256v1 -genkey -noout -out ecdsa256.pem
   100  // Fake value for testing.
   101  const ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY-----
   102  MHcCAQEEIEZmTmUhuanLjPA2CLquXivuwBDHTt5XYwgIr/kA1LtRoAoGCCqGSM49
   103  AwEHoUQDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPLX2i8uIp/C/ASqiIGUeeKQtX0
   104  /IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg==
   105  -----END EC PRIVATE KEY-----`
   106  
   107  // openssl ec -in ecdsa256.pem -pubout -out ecdsa256pub.pem
   108  const ecdsaPublicKey = `-----BEGIN PUBLIC KEY-----
   109  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPL
   110  X2i8uIp/C/ASqiIGUeeKQtX0/IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg==
   111  -----END PUBLIC KEY-----`
   112  
   113  // Obtained by:
   114  //
   115  //  1. Serializing ecdsaPublicKey as DER
   116  //  2. Taking the SHA256 of the DER bytes
   117  //  3. URLSafe Base64-encoding the sha bytes
   118  const ecdsaKeyID = "SoABiieYuNx4UdqYvZRVeuC6SihxgLrhLy9peHMHpTc"
   119  
   120  func getPrivateKey(data string) interface{} {
   121  	key, err := keyutil.ParsePrivateKeyPEM([]byte(data))
   122  	if err != nil {
   123  		panic(fmt.Errorf("unexpected error parsing private key: %v", err))
   124  	}
   125  	return key
   126  }
   127  
   128  func getPublicKey(data string) interface{} {
   129  	keys, err := keyutil.ParsePublicKeysPEM([]byte(data))
   130  	if err != nil {
   131  		panic(fmt.Errorf("unexpected error parsing public key: %v", err))
   132  	}
   133  	return keys[0]
   134  }
   135  
   136  func TestTokenGenerateAndValidate(t *testing.T) {
   137  	expectedUserName := "system:serviceaccount:test:my-service-account"
   138  	expectedUserUID := "12345"
   139  
   140  	// Related API objects
   141  	serviceAccount := &v1.ServiceAccount{
   142  		ObjectMeta: metav1.ObjectMeta{
   143  			Name:      "my-service-account",
   144  			UID:       "12345",
   145  			Namespace: "test",
   146  		},
   147  	}
   148  	rsaSecret := &v1.Secret{
   149  		ObjectMeta: metav1.ObjectMeta{
   150  			Name:      "my-rsa-secret",
   151  			Namespace: "test",
   152  		},
   153  	}
   154  	invalidAutoSecret := &v1.Secret{
   155  		ObjectMeta: metav1.ObjectMeta{
   156  			Name:      "my-rsa-secret",
   157  			Namespace: "test",
   158  			Labels: map[string]string{
   159  				"kubernetes.io/legacy-token-invalid-since": "2022-12-20",
   160  			},
   161  		},
   162  	}
   163  	ecdsaSecret := &v1.Secret{
   164  		ObjectMeta: metav1.ObjectMeta{
   165  			Name:      "my-ecdsa-secret",
   166  			Namespace: "test",
   167  		},
   168  	}
   169  
   170  	// Generate the RSA token
   171  	rsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey))
   172  	if err != nil {
   173  		t.Fatalf("error making generator: %v", err)
   174  	}
   175  	rsaToken, err := rsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret))
   176  	if err != nil {
   177  		t.Fatalf("error generating token: %v", err)
   178  	}
   179  	if len(rsaToken) == 0 {
   180  		t.Fatalf("no token generated")
   181  	}
   182  	rsaSecret.Data = map[string][]byte{
   183  		"token": []byte(rsaToken),
   184  	}
   185  
   186  	checkJSONWebSignatureHasKeyID(t, rsaToken, rsaKeyID)
   187  
   188  	// Generate RSA token with invalidAutoSecret
   189  	invalidAutoSecretToken, err := rsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *invalidAutoSecret))
   190  	if err != nil {
   191  		t.Fatalf("error generating token: %v", err)
   192  	}
   193  	if len(invalidAutoSecretToken) == 0 {
   194  		t.Fatalf("no token generated")
   195  	}
   196  	invalidAutoSecret.Data = map[string][]byte{
   197  		"token": []byte(invalidAutoSecretToken),
   198  	}
   199  
   200  	checkJSONWebSignatureHasKeyID(t, invalidAutoSecretToken, rsaKeyID)
   201  
   202  	// Generate the ECDSA token
   203  	ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey))
   204  	if err != nil {
   205  		t.Fatalf("error making generator: %v", err)
   206  	}
   207  	ecdsaToken, err := ecdsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *ecdsaSecret))
   208  	if err != nil {
   209  		t.Fatalf("error generating token: %v", err)
   210  	}
   211  	if len(ecdsaToken) == 0 {
   212  		t.Fatalf("no token generated")
   213  	}
   214  	ecdsaSecret.Data = map[string][]byte{
   215  		"token": []byte(ecdsaToken),
   216  	}
   217  
   218  	checkJSONWebSignatureHasKeyID(t, ecdsaToken, ecdsaKeyID)
   219  
   220  	// Generate signer with same keys as RSA signer but different unrecognized issuer
   221  	badIssuerGenerator, err := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey))
   222  	if err != nil {
   223  		t.Fatalf("error making generator: %v", err)
   224  	}
   225  	badIssuerToken, err := badIssuerGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret))
   226  	if err != nil {
   227  		t.Fatalf("error generating token: %v", err)
   228  	}
   229  
   230  	// Generate signer with same keys as RSA signer but different recognized issuer
   231  	differentIssuerGenerator, err := serviceaccount.JWTTokenGenerator("bar", getPrivateKey(rsaPrivateKey))
   232  	if err != nil {
   233  		t.Fatalf("error making generator: %v", err)
   234  	}
   235  	differentIssuerToken, err := differentIssuerGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret))
   236  	if err != nil {
   237  		t.Fatalf("error generating token: %v", err)
   238  	}
   239  
   240  	testCases := map[string]struct {
   241  		Client clientset.Interface
   242  		Keys   []interface{}
   243  		Token  string
   244  
   245  		ExpectedErr      bool
   246  		ExpectedOK       bool
   247  		ExpectedUserName string
   248  		ExpectedUserUID  string
   249  		ExpectedGroups   []string
   250  	}{
   251  		"no keys": {
   252  			Token:       rsaToken,
   253  			Client:      nil,
   254  			Keys:        []interface{}{},
   255  			ExpectedErr: false,
   256  			ExpectedOK:  false,
   257  		},
   258  		"invalid keys (rsa)": {
   259  			Token:       rsaToken,
   260  			Client:      nil,
   261  			Keys:        []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey)},
   262  			ExpectedErr: true,
   263  			ExpectedOK:  false,
   264  		},
   265  		"invalid keys (ecdsa)": {
   266  			Token:       ecdsaToken,
   267  			Client:      nil,
   268  			Keys:        []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey)},
   269  			ExpectedErr: true,
   270  			ExpectedOK:  false,
   271  		},
   272  		"valid key (rsa)": {
   273  			Token:            rsaToken,
   274  			Client:           nil,
   275  			Keys:             []interface{}{getPublicKey(rsaPublicKey)},
   276  			ExpectedErr:      false,
   277  			ExpectedOK:       true,
   278  			ExpectedUserName: expectedUserName,
   279  			ExpectedUserUID:  expectedUserUID,
   280  			ExpectedGroups:   []string{"system:serviceaccounts", "system:serviceaccounts:test"},
   281  		},
   282  		"valid key, invalid issuer (rsa)": {
   283  			Token:       badIssuerToken,
   284  			Client:      nil,
   285  			Keys:        []interface{}{getPublicKey(rsaPublicKey)},
   286  			ExpectedErr: false,
   287  			ExpectedOK:  false,
   288  		},
   289  		"valid key, different issuer (rsa)": {
   290  			Token:            differentIssuerToken,
   291  			Client:           nil,
   292  			Keys:             []interface{}{getPublicKey(rsaPublicKey)},
   293  			ExpectedErr:      false,
   294  			ExpectedOK:       true,
   295  			ExpectedUserName: expectedUserName,
   296  			ExpectedUserUID:  expectedUserUID,
   297  			ExpectedGroups:   []string{"system:serviceaccounts", "system:serviceaccounts:test"},
   298  		},
   299  		"valid key (ecdsa)": {
   300  			Token:            ecdsaToken,
   301  			Client:           nil,
   302  			Keys:             []interface{}{getPublicKey(ecdsaPublicKey)},
   303  			ExpectedErr:      false,
   304  			ExpectedOK:       true,
   305  			ExpectedUserName: expectedUserName,
   306  			ExpectedUserUID:  expectedUserUID,
   307  			ExpectedGroups:   []string{"system:serviceaccounts", "system:serviceaccounts:test"},
   308  		},
   309  		"rotated keys (rsa)": {
   310  			Token:            rsaToken,
   311  			Client:           nil,
   312  			Keys:             []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey), getPublicKey(rsaPublicKey)},
   313  			ExpectedErr:      false,
   314  			ExpectedOK:       true,
   315  			ExpectedUserName: expectedUserName,
   316  			ExpectedUserUID:  expectedUserUID,
   317  			ExpectedGroups:   []string{"system:serviceaccounts", "system:serviceaccounts:test"},
   318  		},
   319  		"rotated keys (ecdsa)": {
   320  			Token:            ecdsaToken,
   321  			Client:           nil,
   322  			Keys:             []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey), getPublicKey(ecdsaPublicKey)},
   323  			ExpectedErr:      false,
   324  			ExpectedOK:       true,
   325  			ExpectedUserName: expectedUserName,
   326  			ExpectedUserUID:  expectedUserUID,
   327  			ExpectedGroups:   []string{"system:serviceaccounts", "system:serviceaccounts:test"},
   328  		},
   329  		"valid lookup": {
   330  			Token:            rsaToken,
   331  			Client:           fake.NewSimpleClientset(serviceAccount, rsaSecret, ecdsaSecret),
   332  			Keys:             []interface{}{getPublicKey(rsaPublicKey)},
   333  			ExpectedErr:      false,
   334  			ExpectedOK:       true,
   335  			ExpectedUserName: expectedUserName,
   336  			ExpectedUserUID:  expectedUserUID,
   337  			ExpectedGroups:   []string{"system:serviceaccounts", "system:serviceaccounts:test"},
   338  		},
   339  		"invalid secret lookup": {
   340  			Token:       rsaToken,
   341  			Client:      fake.NewSimpleClientset(serviceAccount),
   342  			Keys:        []interface{}{getPublicKey(rsaPublicKey)},
   343  			ExpectedErr: true,
   344  			ExpectedOK:  false,
   345  		},
   346  		"invalid serviceaccount lookup": {
   347  			Token:       rsaToken,
   348  			Client:      fake.NewSimpleClientset(rsaSecret, ecdsaSecret),
   349  			Keys:        []interface{}{getPublicKey(rsaPublicKey)},
   350  			ExpectedErr: true,
   351  			ExpectedOK:  false,
   352  		},
   353  		"secret is marked as invalid": {
   354  			Token:       invalidAutoSecretToken,
   355  			Client:      fake.NewSimpleClientset(serviceAccount, invalidAutoSecret),
   356  			Keys:        []interface{}{getPublicKey(rsaPublicKey)},
   357  			ExpectedErr: true,
   358  		},
   359  	}
   360  
   361  	for k, tc := range testCases {
   362  		auds := authenticator.Audiences{"api"}
   363  		getter := serviceaccountcontroller.NewGetterFromClient(
   364  			tc.Client,
   365  			v1listers.NewSecretLister(newIndexer(func(namespace, name string) (interface{}, error) {
   366  				return tc.Client.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
   367  			})),
   368  			v1listers.NewServiceAccountLister(newIndexer(func(namespace, name string) (interface{}, error) {
   369  				return tc.Client.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{})
   370  			})),
   371  			v1listers.NewPodLister(newIndexer(func(namespace, name string) (interface{}, error) {
   372  				return tc.Client.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
   373  			})),
   374  			v1listers.NewNodeLister(newIndexer(func(_, name string) (interface{}, error) {
   375  				return tc.Client.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{})
   376  			})),
   377  		)
   378  		var secretsWriter typedv1core.SecretsGetter
   379  		if tc.Client != nil {
   380  			secretsWriter = tc.Client.CoreV1()
   381  		}
   382  		validator, err := serviceaccount.NewLegacyValidator(tc.Client != nil, getter, secretsWriter)
   383  		if err != nil {
   384  			t.Fatalf("While creating legacy validator, err: %v", err)
   385  		}
   386  		authn := serviceaccount.JWTTokenAuthenticator([]string{serviceaccount.LegacyIssuer, "bar"}, tc.Keys, auds, validator)
   387  
   388  		// An invalid, non-JWT token should always fail
   389  		ctx := authenticator.WithAudiences(context.Background(), auds)
   390  		if _, ok, err := authn.AuthenticateToken(ctx, "invalid token"); err != nil || ok {
   391  			t.Errorf("%s: Expected err=nil, ok=false for non-JWT token", k)
   392  			continue
   393  		}
   394  
   395  		resp, ok, err := authn.AuthenticateToken(ctx, tc.Token)
   396  		if (err != nil) != tc.ExpectedErr {
   397  			t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err)
   398  			continue
   399  		}
   400  
   401  		if ok != tc.ExpectedOK {
   402  			t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok)
   403  			continue
   404  		}
   405  
   406  		if err != nil || !ok {
   407  			continue
   408  		}
   409  
   410  		if resp.User.GetName() != tc.ExpectedUserName {
   411  			t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, resp.User.GetName())
   412  			continue
   413  		}
   414  		if resp.User.GetUID() != tc.ExpectedUserUID {
   415  			t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, resp.User.GetUID())
   416  			continue
   417  		}
   418  		if !reflect.DeepEqual(resp.User.GetGroups(), tc.ExpectedGroups) {
   419  			t.Errorf("%s: Expected groups=%v, got %v", k, tc.ExpectedGroups, resp.User.GetGroups())
   420  			continue
   421  		}
   422  	}
   423  }
   424  
   425  func checkJSONWebSignatureHasKeyID(t *testing.T, jwsString string, expectedKeyID string) {
   426  	jws, err := jose.ParseSigned(jwsString)
   427  	if err != nil {
   428  		t.Fatalf("Error checking for key ID: couldn't parse token: %v", err)
   429  	}
   430  
   431  	if jws.Signatures[0].Header.KeyID != expectedKeyID {
   432  		t.Errorf("Token %q has the wrong KeyID (got %q, want %q)", jwsString, jws.Signatures[0].Header.KeyID, expectedKeyID)
   433  	}
   434  }
   435  
   436  func newIndexer(get func(namespace, name string) (interface{}, error)) cache.Indexer {
   437  	return &fakeIndexer{get: get}
   438  }
   439  
   440  type fakeIndexer struct {
   441  	cache.Indexer
   442  	get func(namespace, name string) (interface{}, error)
   443  }
   444  
   445  func (f *fakeIndexer) GetByKey(key string) (interface{}, bool, error) {
   446  	parts := strings.SplitN(key, "/", 2)
   447  	namespace := parts[0]
   448  	name := ""
   449  	// implies the key does not contain a / (this is a cluster-scoped object)
   450  	if len(parts) == 1 {
   451  		name = parts[0]
   452  		namespace = ""
   453  	}
   454  	if len(parts) == 2 {
   455  		name = parts[1]
   456  	}
   457  	obj, err := f.get(namespace, name)
   458  	return obj, err == nil, err
   459  }