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 }