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