k8s.io/kubernetes@v1.29.3/test/integration/auth/svcaccttoken_test.go (about) 1 /* 2 Copyright 2017 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 auth 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/base64" 23 "encoding/json" 24 "fmt" 25 "io" 26 "net/http" 27 "net/url" 28 "reflect" 29 "strconv" 30 "strings" 31 "sync" 32 "testing" 33 "time" 34 35 jose "gopkg.in/square/go-jose.v2" 36 "gopkg.in/square/go-jose.v2/jwt" 37 38 authenticationv1 "k8s.io/api/authentication/v1" 39 v1 "k8s.io/api/core/v1" 40 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 41 "k8s.io/apimachinery/pkg/types" 42 "k8s.io/apiserver/pkg/authentication/authenticator" 43 apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" 44 utilfeature "k8s.io/apiserver/pkg/util/feature" 45 clientset "k8s.io/client-go/kubernetes" 46 "k8s.io/client-go/kubernetes/scheme" 47 "k8s.io/client-go/rest" 48 featuregatetesting "k8s.io/component-base/featuregate/testing" 49 "k8s.io/kubernetes/cmd/kube-apiserver/app/options" 50 "k8s.io/kubernetes/pkg/apis/core" 51 "k8s.io/kubernetes/pkg/controlplane" 52 "k8s.io/kubernetes/pkg/features" 53 "k8s.io/kubernetes/pkg/serviceaccount" 54 "k8s.io/kubernetes/test/integration/framework" 55 "k8s.io/kubernetes/test/utils/ktesting" 56 "k8s.io/utils/ptr" 57 ) 58 59 const ( 60 // This key is for testing purposes only and is not considered secure. 61 ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY----- 62 MHcCAQEEIEZmTmUhuanLjPA2CLquXivuwBDHTt5XYwgIr/kA1LtRoAoGCCqGSM49 63 AwEHoUQDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPLX2i8uIp/C/ASqiIGUeeKQtX0 64 /IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg== 65 -----END EC PRIVATE KEY-----` 66 67 tokenExpirationSeconds = 60*60 + 7 68 ) 69 70 func TestServiceAccountTokenCreate(t *testing.T) { 71 const iss = "https://foo.bar.example.com" 72 aud := authenticator.Audiences{"api"} 73 74 maxExpirationSeconds := int64(60 * 60 * 2) 75 maxExpirationDuration, err := time.ParseDuration(fmt.Sprintf("%ds", maxExpirationSeconds)) 76 if err != nil { 77 t.Fatalf("err: %v", err) 78 } 79 80 var tokenGenerator serviceaccount.TokenGenerator 81 82 _, ctx := ktesting.NewTestContext(t) 83 ctx, cancel := context.WithCancel(ctx) 84 defer cancel() 85 86 // Enable the node token improvements feature gates prior to starting the apiserver, as the node getter is 87 // conditionally passed to the service account token generator based on feature enablement. 88 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenNodeBinding, true)() 89 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenPodNodeInfo, true)() 90 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenNodeBindingValidation, true)() 91 92 // Start the server 93 var serverAddress string 94 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{ 95 ModifyServerRunOptions: func(opts *options.ServerRunOptions) { 96 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 97 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"} 98 opts.Authorization.Modes = []string{"AlwaysAllow"} 99 // Disable token cache so we can check reaction to service account deletion quickly 100 opts.Authentication.TokenSuccessCacheTTL = 0 101 opts.Authentication.TokenFailureCacheTTL = 0 102 // Pin to fixed URLs for easier testing 103 opts.Authentication.ServiceAccounts.JWKSURI = "https:///openid/v1/jwks" 104 opts.Authentication.ServiceAccounts.Issuers = []string{iss} 105 opts.Authentication.APIAudiences = aud 106 }, 107 ModifyServerConfig: func(config *controlplane.Config) { 108 // extract token generator 109 tokenGenerator = config.ExtraConfig.ServiceAccountIssuer 110 111 config.ExtraConfig.ServiceAccountMaxExpiration = maxExpirationDuration 112 config.ExtraConfig.ExtendExpiration = true 113 }, 114 }) 115 defer tearDownFn() 116 117 ns := framework.CreateNamespaceOrDie(kubeClient, "myns", t) 118 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t) 119 120 warningHandler := &recordingWarningHandler{} 121 122 configWithWarningHandler := rest.CopyConfig(kubeConfig) 123 configWithWarningHandler.WarningHandler = warningHandler 124 cs, err := clientset.NewForConfig(configWithWarningHandler) 125 if err != nil { 126 t.Fatalf("err: %v", err) 127 } 128 129 kubeConfig.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 130 rc, err := rest.UnversionedRESTClientFor(kubeConfig) 131 if err != nil { 132 t.Fatal(err) 133 } 134 135 var ( 136 sa = &v1.ServiceAccount{ 137 ObjectMeta: metav1.ObjectMeta{ 138 Name: "test-svcacct", 139 Namespace: ns.Name, 140 }, 141 } 142 node = &v1.Node{ 143 ObjectMeta: metav1.ObjectMeta{ 144 Name: "test-node", 145 }, 146 } 147 pod = &v1.Pod{ 148 ObjectMeta: metav1.ObjectMeta{ 149 Name: "test-pod", 150 Namespace: sa.Namespace, 151 }, 152 Spec: v1.PodSpec{ 153 ServiceAccountName: sa.Name, 154 Containers: []v1.Container{{Name: "test-container", Image: "nginx"}}, 155 }, 156 } 157 scheduledpod = &v1.Pod{ 158 ObjectMeta: metav1.ObjectMeta{ 159 Name: "test-pod", 160 Namespace: sa.Namespace, 161 }, 162 Spec: v1.PodSpec{ 163 ServiceAccountName: sa.Name, 164 NodeName: node.Name, 165 Containers: []v1.Container{{Name: "test-container", Image: "nginx"}}, 166 }, 167 } 168 otherpod = &v1.Pod{ 169 ObjectMeta: metav1.ObjectMeta{ 170 Name: "other-test-pod", 171 Namespace: sa.Namespace, 172 }, 173 Spec: v1.PodSpec{ 174 ServiceAccountName: "other-" + sa.Name, 175 Containers: []v1.Container{{Name: "test-container", Image: "nginx"}}, 176 }, 177 } 178 secret = &v1.Secret{ 179 ObjectMeta: metav1.ObjectMeta{ 180 Name: "test-secret", 181 Namespace: sa.Namespace, 182 }, 183 } 184 wrongUID = types.UID("wrong") 185 noUID = types.UID("") 186 ) 187 188 t.Run("bound to service account", func(t *testing.T) { 189 treq := &authenticationv1.TokenRequest{ 190 Spec: authenticationv1.TokenRequestSpec{ 191 Audiences: []string{"api"}, 192 }, 193 } 194 195 warningHandler.clear() 196 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 197 t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp) 198 } 199 warningHandler.assertEqual(t, nil) 200 sa, delSvcAcct := createDeleteSvcAcct(t, cs, sa) 201 defer delSvcAcct() 202 203 treqWithBadName := treq.DeepCopy() 204 treqWithBadName.Name = "invalid-name" 205 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treqWithBadName, metav1.CreateOptions{}); err == nil || !strings.Contains(err.Error(), "must match the service account name") { 206 t.Fatalf("expected err creating token with mismatched name but got: %#v", resp) 207 } 208 209 treqWithBadNamespace := treq.DeepCopy() 210 treqWithBadNamespace.Namespace = "invalid-namespace" 211 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treqWithBadNamespace, metav1.CreateOptions{}); err == nil || !strings.Contains(err.Error(), "does not match the namespace") { 212 t.Fatalf("expected err creating token with mismatched namespace but got: %#v, %v", resp, err) 213 } 214 215 warningHandler.clear() 216 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 217 if err != nil { 218 t.Fatalf("err: %v", err) 219 } 220 warningHandler.assertEqual(t, nil) 221 222 if treq.Name != sa.Name { 223 t.Errorf("expected name=%s, got %s", sa.Name, treq.Name) 224 } 225 if treq.Namespace != sa.Namespace { 226 t.Errorf("expected namespace=%s, got %s", sa.Namespace, treq.Namespace) 227 } 228 if treq.CreationTimestamp.IsZero() { 229 t.Errorf("expected non-zero creation timestamp") 230 } 231 232 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 233 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 234 checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "pod") 235 checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret") 236 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 237 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 238 239 info := doTokenReview(t, cs, treq, false) 240 if info.Extra != nil { 241 t.Fatalf("expected Extra to be nil but got: %#v", info.Extra) 242 } 243 delSvcAcct() 244 doTokenReview(t, cs, treq, true) 245 }) 246 247 t.Run("bound to service account and pod", func(t *testing.T) { 248 // Disable embedding pod's node info 249 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenPodNodeInfo, false)() 250 treq := &authenticationv1.TokenRequest{ 251 Spec: authenticationv1.TokenRequestSpec{ 252 Audiences: []string{"api"}, 253 BoundObjectRef: &authenticationv1.BoundObjectReference{ 254 Kind: "Pod", 255 APIVersion: "v1", 256 Name: pod.Name, 257 }, 258 }, 259 } 260 261 warningHandler.clear() 262 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 263 t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp) 264 } 265 warningHandler.assertEqual(t, nil) 266 sa, del := createDeleteSvcAcct(t, cs, sa) 267 defer del() 268 269 warningHandler.clear() 270 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 271 t.Fatalf("expected err creating token bound to nonexistant pod but got: %#v", resp) 272 } 273 warningHandler.assertEqual(t, nil) 274 pod, delPod := createDeletePod(t, cs, pod) 275 defer delPod() 276 277 // right uid 278 treq.Spec.BoundObjectRef.UID = pod.UID 279 warningHandler.clear() 280 if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err != nil { 281 t.Fatalf("err: %v", err) 282 } 283 warningHandler.assertEqual(t, nil) 284 // wrong uid 285 treq.Spec.BoundObjectRef.UID = wrongUID 286 warningHandler.clear() 287 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 288 t.Fatalf("expected err creating token bound to pod with wrong uid but got: %#v", resp) 289 } 290 warningHandler.assertEqual(t, nil) 291 // no uid 292 treq.Spec.BoundObjectRef.UID = noUID 293 warningHandler.clear() 294 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 295 if err != nil { 296 t.Fatalf("err: %v", err) 297 } 298 warningHandler.assertEqual(t, nil) 299 300 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 301 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 302 checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name") 303 checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret") 304 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 305 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 306 checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "node") 307 308 info := doTokenReview(t, cs, treq, false) 309 if len(info.Extra) != 2 { 310 t.Fatalf("expected Extra have length of 2 but was length %d: %#v", len(info.Extra), info.Extra) 311 } 312 if expected := map[string]authenticationv1.ExtraValue{ 313 "authentication.kubernetes.io/pod-name": {pod.ObjectMeta.Name}, 314 "authentication.kubernetes.io/pod-uid": {string(pod.ObjectMeta.UID)}, 315 }; !reflect.DeepEqual(info.Extra, expected) { 316 t.Fatalf("unexpected Extra:\ngot:\t%#v\nwant:\t%#v", info.Extra, expected) 317 } 318 delPod() 319 doTokenReview(t, cs, treq, true) 320 }) 321 322 testPodWithAssignedNode := func(node *v1.Node) func(t *testing.T) { 323 return func(t *testing.T) { 324 treq := &authenticationv1.TokenRequest{ 325 Spec: authenticationv1.TokenRequestSpec{ 326 Audiences: []string{"api"}, 327 BoundObjectRef: &authenticationv1.BoundObjectReference{ 328 Kind: "Pod", 329 APIVersion: "v1", 330 Name: scheduledpod.Name, 331 }, 332 }, 333 } 334 335 warningHandler.clear() 336 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 337 t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp) 338 } 339 warningHandler.assertEqual(t, nil) 340 sa, del := createDeleteSvcAcct(t, cs, sa) 341 defer del() 342 343 warningHandler.clear() 344 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 345 t.Fatalf("expected err creating token bound to nonexistant pod but got: %#v", resp) 346 } 347 warningHandler.assertEqual(t, nil) 348 pod, delPod := createDeletePod(t, cs, scheduledpod) 349 defer delPod() 350 351 if node != nil { 352 var delNode func() 353 node, delNode = createDeleteNode(t, cs, node) 354 defer delNode() 355 } 356 357 // right uid 358 treq.Spec.BoundObjectRef.UID = pod.UID 359 warningHandler.clear() 360 if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err != nil { 361 t.Fatalf("err: %v", err) 362 } 363 warningHandler.assertEqual(t, nil) 364 // wrong uid 365 treq.Spec.BoundObjectRef.UID = wrongUID 366 warningHandler.clear() 367 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 368 t.Fatalf("expected err creating token bound to pod with wrong uid but got: %#v", resp) 369 } 370 warningHandler.assertEqual(t, nil) 371 // no uid 372 treq.Spec.BoundObjectRef.UID = noUID 373 warningHandler.clear() 374 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 375 if err != nil { 376 t.Fatalf("err: %v", err) 377 } 378 warningHandler.assertEqual(t, nil) 379 380 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 381 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 382 checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name") 383 checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret") 384 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 385 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 386 387 expectedExtraValues := map[string]authenticationv1.ExtraValue{ 388 "authentication.kubernetes.io/pod-name": {pod.ObjectMeta.Name}, 389 "authentication.kubernetes.io/pod-uid": {string(pod.ObjectMeta.UID)}, 390 } 391 // If the NodeName is set at all, expect it to be included in the claims 392 if pod.Spec.NodeName != "" { 393 checkPayload(t, treq.Status.Token, fmt.Sprintf(`"%s"`, pod.Spec.NodeName), "kubernetes.io", "node", "name") 394 expectedExtraValues["authentication.kubernetes.io/node-name"] = authenticationv1.ExtraValue{pod.Spec.NodeName} 395 } 396 // If the node is non-nil, we expect the UID to be set too 397 if node != nil { 398 checkPayload(t, treq.Status.Token, fmt.Sprintf(`"%s"`, node.UID), "kubernetes.io", "node", "uid") 399 expectedExtraValues["authentication.kubernetes.io/node-uid"] = authenticationv1.ExtraValue{string(node.ObjectMeta.UID)} 400 } 401 402 info := doTokenReview(t, cs, treq, false) 403 if len(info.Extra) != len(expectedExtraValues) { 404 t.Fatalf("expected Extra have length of %d but was length %d: %#v", len(expectedExtraValues), len(info.Extra), info.Extra) 405 } 406 if !reflect.DeepEqual(info.Extra, expectedExtraValues) { 407 t.Fatalf("unexpected Extra:\ngot:\t%#v\nwant:\t%#v", info.Extra, expectedExtraValues) 408 } 409 410 delPod() 411 doTokenReview(t, cs, treq, true) 412 } 413 } 414 415 t.Run("bound to service account and a pod with an assigned nodeName that does not exist", testPodWithAssignedNode(nil)) 416 t.Run("bound to service account and a pod with an assigned nodeName", testPodWithAssignedNode(node)) 417 418 t.Run("fails to bind to a Node if the feature gate is disabled", func(t *testing.T) { 419 // Disable node binding 420 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenNodeBinding, false)() 421 422 // Create ServiceAccount and Node objects 423 sa, del := createDeleteSvcAcct(t, cs, sa) 424 defer del() 425 node, delNode := createDeleteNode(t, cs, node) 426 defer delNode() 427 428 treq := &authenticationv1.TokenRequest{ 429 Spec: authenticationv1.TokenRequestSpec{ 430 Audiences: []string{"api"}, 431 BoundObjectRef: &authenticationv1.BoundObjectReference{ 432 Kind: "Node", 433 APIVersion: "v1", 434 Name: node.Name, 435 UID: node.UID, 436 }, 437 }, 438 } 439 warningHandler.clear() 440 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 441 t.Fatalf("expected err creating token with featuregate disabled but got: %#v", resp) 442 } else if err.Error() != "cannot bind token to a Node object as the \"ServiceAccountTokenNodeBinding\" feature-gate is disabled" { 443 t.Fatalf("expected error due to feature gate being disabled, but got: %s", err.Error()) 444 } 445 warningHandler.assertEqual(t, nil) 446 }) 447 448 t.Run("bound to service account and node", func(t *testing.T) { 449 treq := &authenticationv1.TokenRequest{ 450 Spec: authenticationv1.TokenRequestSpec{ 451 Audiences: []string{"api"}, 452 BoundObjectRef: &authenticationv1.BoundObjectReference{ 453 Kind: "Node", 454 APIVersion: "v1", 455 Name: node.Name, 456 UID: node.UID, 457 }, 458 }, 459 } 460 461 warningHandler.clear() 462 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 463 t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp) 464 } 465 warningHandler.assertEqual(t, nil) 466 sa, del := createDeleteSvcAcct(t, cs, sa) 467 defer del() 468 469 warningHandler.clear() 470 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 471 t.Fatalf("expected err creating token bound to nonexistant node but got: %#v", resp) 472 } 473 warningHandler.assertEqual(t, nil) 474 node, delNode := createDeleteNode(t, cs, node) 475 defer delNode() 476 477 // right uid 478 treq.Spec.BoundObjectRef.UID = node.UID 479 warningHandler.clear() 480 if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err != nil { 481 t.Fatalf("err: %v", err) 482 } 483 warningHandler.assertEqual(t, nil) 484 // wrong uid 485 treq.Spec.BoundObjectRef.UID = wrongUID 486 warningHandler.clear() 487 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 488 t.Fatalf("expected err creating token bound to node with wrong uid but got: %#v", resp) 489 } 490 warningHandler.assertEqual(t, nil) 491 // no uid 492 treq.Spec.BoundObjectRef.UID = noUID 493 warningHandler.clear() 494 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 495 if err != nil { 496 t.Fatalf("err: %v", err) 497 } 498 warningHandler.assertEqual(t, nil) 499 500 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 501 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 502 checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod") 503 checkPayload(t, treq.Status.Token, `"test-node"`, "kubernetes.io", "node", "name") 504 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 505 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 506 507 doTokenReview(t, cs, treq, false) 508 delNode() 509 doTokenReview(t, cs, treq, true) 510 }) 511 512 t.Run("bound to service account and secret", func(t *testing.T) { 513 treq := &authenticationv1.TokenRequest{ 514 Spec: authenticationv1.TokenRequestSpec{ 515 Audiences: []string{"api"}, 516 BoundObjectRef: &authenticationv1.BoundObjectReference{ 517 Kind: "Secret", 518 APIVersion: "v1", 519 Name: secret.Name, 520 UID: secret.UID, 521 }, 522 }, 523 } 524 525 warningHandler.clear() 526 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 527 t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp) 528 } 529 warningHandler.assertEqual(t, nil) 530 sa, del := createDeleteSvcAcct(t, cs, sa) 531 defer del() 532 533 warningHandler.clear() 534 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 535 t.Fatalf("expected err creating token bound to nonexistant secret but got: %#v", resp) 536 } 537 warningHandler.assertEqual(t, nil) 538 secret, delSecret := createDeleteSecret(t, cs, secret) 539 defer delSecret() 540 541 // right uid 542 treq.Spec.BoundObjectRef.UID = secret.UID 543 warningHandler.clear() 544 if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err != nil { 545 t.Fatalf("err: %v", err) 546 } 547 warningHandler.assertEqual(t, nil) 548 // wrong uid 549 treq.Spec.BoundObjectRef.UID = wrongUID 550 warningHandler.clear() 551 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 552 t.Fatalf("expected err creating token bound to secret with wrong uid but got: %#v", resp) 553 } 554 warningHandler.assertEqual(t, nil) 555 // no uid 556 treq.Spec.BoundObjectRef.UID = noUID 557 warningHandler.clear() 558 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 559 if err != nil { 560 t.Fatalf("err: %v", err) 561 } 562 warningHandler.assertEqual(t, nil) 563 564 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 565 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 566 checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod") 567 checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name") 568 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 569 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 570 571 doTokenReview(t, cs, treq, false) 572 delSecret() 573 doTokenReview(t, cs, treq, true) 574 }) 575 576 t.Run("bound to service account and pod running as different service account", func(t *testing.T) { 577 treq := &authenticationv1.TokenRequest{ 578 Spec: authenticationv1.TokenRequestSpec{ 579 Audiences: []string{"api"}, 580 BoundObjectRef: &authenticationv1.BoundObjectReference{ 581 Kind: "Pod", 582 APIVersion: "v1", 583 Name: otherpod.Name, 584 }, 585 }, 586 } 587 588 sa, del := createDeleteSvcAcct(t, cs, sa) 589 defer del() 590 _, del = createDeletePod(t, cs, otherpod) 591 defer del() 592 593 warningHandler.clear() 594 if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err == nil { 595 t.Fatalf("expected err but got: %#v", resp) 596 } 597 warningHandler.assertEqual(t, nil) 598 }) 599 600 t.Run("expired token", func(t *testing.T) { 601 treq := &authenticationv1.TokenRequest{ 602 Spec: authenticationv1.TokenRequestSpec{ 603 Audiences: []string{"api"}, 604 }, 605 } 606 607 sa, del := createDeleteSvcAcct(t, cs, sa) 608 defer del() 609 610 warningHandler.clear() 611 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 612 if err != nil { 613 t.Fatalf("err: %v", err) 614 } 615 warningHandler.assertEqual(t, nil) 616 617 doTokenReview(t, cs, treq, false) 618 619 // backdate the token 620 then := time.Now().Add(-2 * time.Hour) 621 sc := &jwt.Claims{ 622 Subject: apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name), 623 Audience: jwt.Audience([]string{"api"}), 624 IssuedAt: jwt.NewNumericDate(then), 625 NotBefore: jwt.NewNumericDate(then), 626 Expiry: jwt.NewNumericDate(then.Add(time.Duration(60*60) * time.Second)), 627 } 628 coresa := core.ServiceAccount{ 629 ObjectMeta: sa.ObjectMeta, 630 } 631 _, pc, err := serviceaccount.Claims(coresa, nil, nil, nil, 0, 0, nil) 632 if err != nil { 633 t.Fatalf("err calling Claims: %v", err) 634 } 635 tok, err := tokenGenerator.GenerateToken(sc, pc) 636 if err != nil { 637 t.Fatalf("err signing expired token: %v", err) 638 } 639 640 treq.Status.Token = tok 641 doTokenReview(t, cs, treq, true) 642 }) 643 644 t.Run("expiration extended token", func(t *testing.T) { 645 var requestExp int64 = tokenExpirationSeconds 646 treq := &authenticationv1.TokenRequest{ 647 Spec: authenticationv1.TokenRequestSpec{ 648 Audiences: []string{"api"}, 649 ExpirationSeconds: &requestExp, 650 BoundObjectRef: &authenticationv1.BoundObjectReference{ 651 Kind: "Pod", 652 APIVersion: "v1", 653 Name: pod.Name, 654 }, 655 }, 656 } 657 658 sa, del := createDeleteSvcAcct(t, cs, sa) 659 defer del() 660 pod, delPod := createDeletePod(t, cs, pod) 661 defer delPod() 662 treq.Spec.BoundObjectRef.UID = pod.UID 663 664 warningHandler.clear() 665 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 666 if err != nil { 667 t.Fatalf("err: %v", err) 668 } 669 warningHandler.assertEqual(t, nil) 670 671 doTokenReview(t, cs, treq, false) 672 673 // Give some tolerance to avoid flakiness since we are using real time. 674 var leeway int64 = 2 675 actualExpiry := *jwt.NewNumericDate(time.Now().Add(time.Duration(24*365) * time.Hour)) 676 assumedExpiry := *jwt.NewNumericDate(time.Now().Add(time.Duration(requestExp) * time.Second)) 677 exp, err := strconv.ParseInt(getSubObject(t, getPayload(t, treq.Status.Token), "exp"), 10, 64) 678 if err != nil { 679 t.Fatalf("error parsing exp: %v", err) 680 } 681 warnafter, err := strconv.ParseInt(getSubObject(t, getPayload(t, treq.Status.Token), "kubernetes.io", "warnafter"), 10, 64) 682 if err != nil { 683 t.Fatalf("error parsing warnafter: %v", err) 684 } 685 686 if exp < int64(actualExpiry)-leeway || exp > int64(actualExpiry)+leeway { 687 t.Errorf("unexpected token exp %d, should within range of %d +- %d seconds", exp, actualExpiry, leeway) 688 } 689 if warnafter < int64(assumedExpiry)-leeway || warnafter > int64(assumedExpiry)+leeway { 690 t.Errorf("unexpected token warnafter %d, should within range of %d +- %d seconds", warnafter, assumedExpiry, leeway) 691 } 692 693 checkExpiration(t, treq, requestExp) 694 expStatus := treq.Status.ExpirationTimestamp.Time.Unix() 695 if expStatus < int64(assumedExpiry)-leeway || warnafter > int64(assumedExpiry)+leeway { 696 t.Errorf("unexpected expiration returned in tokenrequest status %d, should within range of %d +- %d seconds", expStatus, assumedExpiry, leeway) 697 } 698 }) 699 700 t.Run("extended expiration extended does not apply for other audiences", func(t *testing.T) { 701 var requestExp int64 = tokenExpirationSeconds 702 treq := &authenticationv1.TokenRequest{ 703 Spec: authenticationv1.TokenRequestSpec{ 704 Audiences: []string{"not-the-api", "api"}, 705 ExpirationSeconds: &requestExp, 706 BoundObjectRef: &authenticationv1.BoundObjectReference{ 707 Kind: "Pod", 708 APIVersion: "v1", 709 Name: pod.Name, 710 }, 711 }, 712 } 713 714 sa, del := createDeleteSvcAcct(t, cs, sa) 715 defer del() 716 pod, delPod := createDeletePod(t, cs, pod) 717 defer delPod() 718 treq.Spec.BoundObjectRef.UID = pod.UID 719 720 warningHandler.clear() 721 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 722 if err != nil { 723 t.Fatalf("err: %v", err) 724 } 725 warningHandler.assertEqual(t, nil) 726 727 // Give some tolerance to avoid flakiness since we are using real time. 728 var leeway int64 = 10 729 actualExpiry := *jwt.NewNumericDate(time.Now().Add(time.Duration(60*60) * time.Second)) 730 assumedExpiry := *jwt.NewNumericDate(time.Now().Add(time.Duration(requestExp) * time.Second)) 731 732 warnAfter := getSubObject(t, getPayload(t, treq.Status.Token), "kubernetes.io", "warnafter") 733 if warnAfter != "null" { 734 t.Errorf("warn after should be empty.Found: %s", warnAfter) 735 } 736 737 exp, err := strconv.ParseInt(getSubObject(t, getPayload(t, treq.Status.Token), "exp"), 10, 64) 738 if err != nil { 739 t.Fatalf("error parsing exp: %v", err) 740 } 741 if exp < int64(actualExpiry)-leeway || exp > int64(actualExpiry)+leeway { 742 t.Errorf("unexpected token exp %d, should within range of %d +- %d seconds", exp, actualExpiry, leeway) 743 } 744 745 checkExpiration(t, treq, requestExp) 746 expStatus := treq.Status.ExpirationTimestamp.Time.Unix() 747 if expStatus < int64(assumedExpiry)-leeway { 748 t.Errorf("unexpected expiration returned in tokenrequest status %d, should within range of %d +- %d seconds", expStatus, assumedExpiry, leeway) 749 } 750 }) 751 752 t.Run("a token without an api audience is invalid", func(t *testing.T) { 753 treq := &authenticationv1.TokenRequest{ 754 Spec: authenticationv1.TokenRequestSpec{ 755 Audiences: []string{"not-the-api"}, 756 }, 757 } 758 759 sa, del := createDeleteSvcAcct(t, cs, sa) 760 defer del() 761 762 warningHandler.clear() 763 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 764 if err != nil { 765 t.Fatalf("err: %v", err) 766 } 767 warningHandler.assertEqual(t, nil) 768 769 doTokenReview(t, cs, treq, true) 770 }) 771 772 t.Run("a tokenrequest without an audience is valid against the api", func(t *testing.T) { 773 treq := &authenticationv1.TokenRequest{ 774 Spec: authenticationv1.TokenRequestSpec{}, 775 } 776 777 sa, del := createDeleteSvcAcct(t, cs, sa) 778 defer del() 779 780 warningHandler.clear() 781 treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}) 782 if err != nil { 783 t.Fatalf("err: %v", err) 784 } 785 warningHandler.assertEqual(t, nil) 786 787 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 788 789 doTokenReview(t, cs, treq, false) 790 }) 791 792 t.Run("a token should be invalid after recreating same name pod", func(t *testing.T) { 793 treq := &authenticationv1.TokenRequest{ 794 Spec: authenticationv1.TokenRequestSpec{ 795 Audiences: []string{"api"}, 796 BoundObjectRef: &authenticationv1.BoundObjectReference{ 797 Kind: "Pod", 798 APIVersion: "v1", 799 Name: pod.Name, 800 }, 801 }, 802 } 803 804 sa, del := createDeleteSvcAcct(t, cs, sa) 805 defer del() 806 originalPod, originalDelPod := createDeletePod(t, cs, pod) 807 defer originalDelPod() 808 809 treq.Spec.BoundObjectRef.UID = originalPod.UID 810 warningHandler.clear() 811 if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err != nil { 812 t.Fatalf("err: %v", err) 813 } 814 warningHandler.assertEqual(t, nil) 815 816 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 817 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 818 checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name") 819 checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret") 820 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 821 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 822 823 doTokenReview(t, cs, treq, false) 824 originalDelPod() 825 doTokenReview(t, cs, treq, true) 826 827 _, recreateDelPod := createDeletePod(t, cs, pod) 828 defer recreateDelPod() 829 830 doTokenReview(t, cs, treq, true) 831 }) 832 833 t.Run("a token should be invalid after recreating same name secret", func(t *testing.T) { 834 treq := &authenticationv1.TokenRequest{ 835 Spec: authenticationv1.TokenRequestSpec{ 836 Audiences: []string{"api"}, 837 BoundObjectRef: &authenticationv1.BoundObjectReference{ 838 Kind: "Secret", 839 APIVersion: "v1", 840 Name: secret.Name, 841 UID: secret.UID, 842 }, 843 }, 844 } 845 846 sa, del := createDeleteSvcAcct(t, cs, sa) 847 defer del() 848 849 originalSecret, originalDelSecret := createDeleteSecret(t, cs, secret) 850 defer originalDelSecret() 851 852 treq.Spec.BoundObjectRef.UID = originalSecret.UID 853 warningHandler.clear() 854 if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err != nil { 855 t.Fatalf("err: %v", err) 856 } 857 warningHandler.assertEqual(t, nil) 858 859 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 860 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 861 checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod") 862 checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name") 863 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 864 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 865 866 doTokenReview(t, cs, treq, false) 867 originalDelSecret() 868 doTokenReview(t, cs, treq, true) 869 870 _, recreateDelSecret := createDeleteSecret(t, cs, secret) 871 defer recreateDelSecret() 872 873 doTokenReview(t, cs, treq, true) 874 }) 875 876 t.Run("a token request within expiration time", func(t *testing.T) { 877 normalExpirationTime := maxExpirationSeconds - 10*60 878 treq := &authenticationv1.TokenRequest{ 879 Spec: authenticationv1.TokenRequestSpec{ 880 Audiences: []string{"api"}, 881 ExpirationSeconds: &normalExpirationTime, 882 BoundObjectRef: &authenticationv1.BoundObjectReference{ 883 Kind: "Secret", 884 APIVersion: "v1", 885 Name: secret.Name, 886 UID: secret.UID, 887 }, 888 }, 889 } 890 891 sa, del := createDeleteSvcAcct(t, cs, sa) 892 defer del() 893 894 originalSecret, originalDelSecret := createDeleteSecret(t, cs, secret) 895 defer originalDelSecret() 896 897 treq.Spec.BoundObjectRef.UID = originalSecret.UID 898 warningHandler.clear() 899 if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err != nil { 900 t.Fatalf("err: %v", err) 901 } 902 warningHandler.assertEqual(t, nil) 903 904 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 905 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 906 checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod") 907 checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name") 908 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 909 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 910 checkExpiration(t, treq, normalExpirationTime) 911 912 doTokenReview(t, cs, treq, false) 913 originalDelSecret() 914 doTokenReview(t, cs, treq, true) 915 916 _, recreateDelSecret := createDeleteSecret(t, cs, secret) 917 defer recreateDelSecret() 918 919 doTokenReview(t, cs, treq, true) 920 }) 921 922 t.Run("a token request with out-of-range expiration", func(t *testing.T) { 923 tooLongExpirationTime := maxExpirationSeconds + 10*60 924 treq := &authenticationv1.TokenRequest{ 925 Spec: authenticationv1.TokenRequestSpec{ 926 Audiences: []string{"api"}, 927 ExpirationSeconds: &tooLongExpirationTime, 928 BoundObjectRef: &authenticationv1.BoundObjectReference{ 929 Kind: "Secret", 930 APIVersion: "v1", 931 Name: secret.Name, 932 UID: secret.UID, 933 }, 934 }, 935 } 936 937 sa, del := createDeleteSvcAcct(t, cs, sa) 938 defer del() 939 940 originalSecret, originalDelSecret := createDeleteSecret(t, cs, secret) 941 defer originalDelSecret() 942 943 treq.Spec.BoundObjectRef.UID = originalSecret.UID 944 warningHandler.clear() 945 if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, treq, metav1.CreateOptions{}); err != nil { 946 t.Fatalf("err: %v", err) 947 } 948 warningHandler.assertEqual(t, []string{fmt.Sprintf("requested expiration of %d seconds shortened to %d seconds", tooLongExpirationTime, maxExpirationSeconds)}) 949 950 checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub") 951 checkPayload(t, treq.Status.Token, `["api"]`, "aud") 952 checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod") 953 checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name") 954 checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace") 955 checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name") 956 checkExpiration(t, treq, maxExpirationSeconds) 957 958 doTokenReview(t, cs, treq, false) 959 originalDelSecret() 960 doTokenReview(t, cs, treq, true) 961 962 _, recreateDelSecret := createDeleteSecret(t, cs, secret) 963 defer recreateDelSecret() 964 965 doTokenReview(t, cs, treq, true) 966 }) 967 968 t.Run("a token is valid against the HTTP-provided service account issuer metadata", func(t *testing.T) { 969 sa, del := createDeleteSvcAcct(t, cs, sa) 970 defer del() 971 972 t.Log("get token") 973 warningHandler.clear() 974 tokenRequest, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(ctx, sa.Name, 975 &authenticationv1.TokenRequest{ 976 Spec: authenticationv1.TokenRequestSpec{ 977 Audiences: []string{"api"}, 978 }, 979 }, metav1.CreateOptions{}) 980 if err != nil { 981 t.Fatalf("unexpected error creating token: %v", err) 982 } 983 warningHandler.assertEqual(t, nil) 984 token := tokenRequest.Status.Token 985 if token == "" { 986 t.Fatal("no token") 987 } 988 989 t.Log("get discovery doc") 990 discoveryDoc := struct { 991 Issuer string `json:"issuer"` 992 JWKS string `json:"jwks_uri"` 993 }{} 994 995 // A little convoluted, but the base path is hidden inside the RESTClient. 996 // We can't just use the RESTClient, because it throws away the headers 997 // before returning a result, and we need to check the headers. 998 discoveryURL := rc.Get().AbsPath("/.well-known/openid-configuration").URL().String() 999 resp, err := rc.Client.Get(discoveryURL) 1000 if err != nil { 1001 t.Fatalf("error getting metadata: %v", err) 1002 } 1003 defer resp.Body.Close() 1004 1005 if resp.StatusCode != http.StatusOK { 1006 t.Errorf("got status: %v, want: %v", resp.StatusCode, http.StatusOK) 1007 } 1008 if got, want := resp.Header.Get("Content-Type"), "application/json"; got != want { 1009 t.Errorf("got Content-Type: %v, want: %v", got, want) 1010 } 1011 if got, want := resp.Header.Get("Cache-Control"), "public, max-age=3600"; got != want { 1012 t.Errorf("got Cache-Control: %v, want: %v", got, want) 1013 } 1014 1015 b, err := io.ReadAll(resp.Body) 1016 if err != nil { 1017 t.Fatal(err) 1018 } 1019 md := bytes.NewBuffer(b) 1020 t.Logf("raw discovery doc response:\n---%s\n---", md.String()) 1021 if md.Len() == 0 { 1022 t.Fatal("empty response for discovery doc") 1023 } 1024 1025 if err := json.NewDecoder(md).Decode(&discoveryDoc); err != nil { 1026 t.Fatalf("could not decode metadata: %v", err) 1027 } 1028 if discoveryDoc.Issuer != iss { 1029 t.Fatalf("invalid issuer in discovery doc: got %s, want %s", 1030 discoveryDoc.Issuer, iss) 1031 } 1032 expectJWKSURI := (&url.URL{ 1033 Scheme: "https", 1034 Host: serverAddress, 1035 Path: serviceaccount.JWKSPath, 1036 }).String() 1037 if discoveryDoc.JWKS != expectJWKSURI { 1038 t.Fatalf("unexpected jwks_uri in discovery doc: got %s, want %s", 1039 discoveryDoc.JWKS, expectJWKSURI) 1040 } 1041 1042 // Since the test framework hardcodes the host, we combine our client's 1043 // scheme and host with serviceaccount.JWKSPath. We know that this is what was 1044 // in the discovery doc because we checked that it matched above. 1045 jwksURI := rc.Get().AbsPath(serviceaccount.JWKSPath).URL().String() 1046 t.Log("get jwks from", jwksURI) 1047 resp, err = rc.Client.Get(jwksURI) 1048 if err != nil { 1049 t.Fatalf("error getting jwks: %v", err) 1050 } 1051 defer resp.Body.Close() 1052 1053 if resp.StatusCode != http.StatusOK { 1054 t.Errorf("got status: %v, want: %v", resp.StatusCode, http.StatusOK) 1055 } 1056 if got, want := resp.Header.Get("Content-Type"), "application/jwk-set+json"; got != want { 1057 t.Errorf("got Content-Type: %v, want: %v", got, want) 1058 } 1059 if got, want := resp.Header.Get("Cache-Control"), "public, max-age=3600"; got != want { 1060 t.Errorf("got Cache-Control: %v, want: %v", got, want) 1061 } 1062 1063 b, err = io.ReadAll(resp.Body) 1064 if err != nil { 1065 t.Fatal(err) 1066 } 1067 ks := bytes.NewBuffer(b) 1068 if ks.Len() == 0 { 1069 t.Fatal("empty jwks") 1070 } 1071 t.Logf("raw JWKS: \n---\n%s\n---", ks.String()) 1072 1073 jwks := jose.JSONWebKeySet{} 1074 if err := json.NewDecoder(ks).Decode(&jwks); err != nil { 1075 t.Fatalf("could not decode JWKS: %v", err) 1076 } 1077 if len(jwks.Keys) != 1 { 1078 t.Fatalf("len(jwks.Keys) = %d, want 1", len(jwks.Keys)) 1079 } 1080 key := jwks.Keys[0] 1081 tok, err := jwt.ParseSigned(token) 1082 if err != nil { 1083 t.Fatalf("could not parse token %q: %v", token, err) 1084 } 1085 var claims jwt.Claims 1086 if err := tok.Claims(key, &claims); err != nil { 1087 t.Fatalf("could not validate claims on token: %v", err) 1088 } 1089 if err := claims.Validate(jwt.Expected{Issuer: discoveryDoc.Issuer}); err != nil { 1090 t.Fatalf("invalid claims: %v", err) 1091 } 1092 }) 1093 } 1094 1095 func doTokenReview(t *testing.T, cs clientset.Interface, treq *authenticationv1.TokenRequest, expectErr bool) authenticationv1.UserInfo { 1096 t.Helper() 1097 tries := 0 1098 for { 1099 trev, err := cs.AuthenticationV1().TokenReviews().Create(context.TODO(), &authenticationv1.TokenReview{ 1100 Spec: authenticationv1.TokenReviewSpec{ 1101 Token: treq.Status.Token, 1102 }, 1103 }, metav1.CreateOptions{}) 1104 if err != nil { 1105 t.Fatalf("err: %v", err) 1106 } 1107 t.Logf("status: %+v", trev.Status) 1108 if (trev.Status.Error != "") && !expectErr { 1109 t.Fatalf("expected no error but got: %v", trev.Status.Error) 1110 } 1111 if (trev.Status.Error == "") && expectErr { 1112 // if we expected an error and didn't get one, retry 1113 // to let changes that invalidate the token percolate through informers 1114 if tries < 10 { 1115 tries++ 1116 time.Sleep(100 * time.Millisecond) 1117 t.Logf("expected error but got: %+v, retrying", trev.Status) 1118 continue 1119 } 1120 t.Fatalf("expected error but got: %+v", trev.Status) 1121 } 1122 if !trev.Status.Authenticated && !expectErr { 1123 t.Fatal("expected token to be authenticated but it wasn't") 1124 } 1125 return trev.Status.User 1126 } 1127 } 1128 1129 func checkPayload(t *testing.T, tok string, want string, parts ...string) { 1130 t.Helper() 1131 got := getSubObject(t, getPayload(t, tok), parts...) 1132 if got != want { 1133 t.Errorf("unexpected payload.\nsaw:\t%v\nwant:\t%v", got, want) 1134 } 1135 } 1136 1137 func checkExpiration(t *testing.T, treq *authenticationv1.TokenRequest, expectedExpiration int64) { 1138 t.Helper() 1139 if treq.Spec.ExpirationSeconds == nil { 1140 t.Errorf("unexpected nil expiration seconds.") 1141 } 1142 if *treq.Spec.ExpirationSeconds != expectedExpiration { 1143 t.Errorf("unexpected expiration seconds.\nsaw:\t%d\nwant:\t%d", treq.Spec.ExpirationSeconds, expectedExpiration) 1144 } 1145 } 1146 1147 func getSubObject(t *testing.T, b string, parts ...string) string { 1148 t.Helper() 1149 var obj interface{} 1150 obj = make(map[string]interface{}) 1151 if err := json.Unmarshal([]byte(b), &obj); err != nil { 1152 t.Fatalf("err: %v", err) 1153 } 1154 for _, part := range parts { 1155 obj = obj.(map[string]interface{})[part] 1156 } 1157 out, err := json.Marshal(obj) 1158 if err != nil { 1159 t.Fatalf("err: %v", err) 1160 } 1161 return string(out) 1162 } 1163 1164 func getPayload(t *testing.T, b string) string { 1165 t.Helper() 1166 parts := strings.Split(b, ".") 1167 if len(parts) != 3 { 1168 t.Fatalf("token did not have three parts: %v", b) 1169 } 1170 payload, err := base64.RawURLEncoding.DecodeString(parts[1]) 1171 if err != nil { 1172 t.Fatalf("failed to base64 decode token: %v", err) 1173 } 1174 return string(payload) 1175 } 1176 1177 func createDeleteSvcAcct(t *testing.T, cs clientset.Interface, sa *v1.ServiceAccount) (*v1.ServiceAccount, func()) { 1178 t.Helper() 1179 sa, err := cs.CoreV1().ServiceAccounts(sa.Namespace).Create(context.TODO(), sa, metav1.CreateOptions{}) 1180 if err != nil { 1181 t.Fatalf("err: %v", err) 1182 } 1183 done := false 1184 return sa, func() { 1185 t.Helper() 1186 if done { 1187 return 1188 } 1189 done = true 1190 if err := cs.CoreV1().ServiceAccounts(sa.Namespace).Delete(context.TODO(), sa.Name, metav1.DeleteOptions{}); err != nil { 1191 t.Fatalf("err: %v", err) 1192 } 1193 } 1194 } 1195 1196 func createDeletePod(t *testing.T, cs clientset.Interface, pod *v1.Pod) (*v1.Pod, func()) { 1197 t.Helper() 1198 pod, err := cs.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) 1199 if err != nil { 1200 t.Fatalf("err: %v", err) 1201 } 1202 done := false 1203 return pod, func() { 1204 t.Helper() 1205 if done { 1206 return 1207 } 1208 done = true 1209 if err := cs.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{ 1210 GracePeriodSeconds: ptr.To(int64(0)), 1211 }); err != nil { 1212 t.Fatalf("err: %v", err) 1213 } 1214 } 1215 } 1216 1217 func createDeleteSecret(t *testing.T, cs clientset.Interface, sec *v1.Secret) (*v1.Secret, func()) { 1218 t.Helper() 1219 sec, err := cs.CoreV1().Secrets(sec.Namespace).Create(context.TODO(), sec, metav1.CreateOptions{}) 1220 if err != nil { 1221 t.Fatalf("err: %v", err) 1222 } 1223 done := false 1224 return sec, func() { 1225 t.Helper() 1226 if done { 1227 return 1228 } 1229 done = true 1230 if err := cs.CoreV1().Secrets(sec.Namespace).Delete(context.TODO(), sec.Name, metav1.DeleteOptions{}); err != nil { 1231 t.Fatalf("err: %v", err) 1232 } 1233 } 1234 } 1235 1236 func createDeleteNode(t *testing.T, cs clientset.Interface, node *v1.Node) (*v1.Node, func()) { 1237 t.Helper() 1238 node, err := cs.CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{}) 1239 if err != nil { 1240 t.Fatalf("err: %v", err) 1241 } 1242 done := false 1243 return node, func() { 1244 t.Helper() 1245 if done { 1246 return 1247 } 1248 done = true 1249 if err := cs.CoreV1().Nodes().Delete(context.TODO(), node.Name, metav1.DeleteOptions{}); err != nil { 1250 t.Fatalf("err: %v", err) 1251 } 1252 } 1253 } 1254 1255 type recordingWarningHandler struct { 1256 warnings []string 1257 1258 sync.Mutex 1259 } 1260 1261 func (r *recordingWarningHandler) HandleWarningHeader(code int, agent string, message string) { 1262 r.Lock() 1263 defer r.Unlock() 1264 r.warnings = append(r.warnings, message) 1265 } 1266 1267 func (r *recordingWarningHandler) clear() { 1268 r.Lock() 1269 defer r.Unlock() 1270 r.warnings = nil 1271 } 1272 func (r *recordingWarningHandler) assertEqual(t *testing.T, expected []string) { 1273 t.Helper() 1274 r.Lock() 1275 defer r.Unlock() 1276 if !reflect.DeepEqual(r.warnings, expected) { 1277 t.Errorf("expected\n\t%v\ngot\n\t%v", expected, r.warnings) 1278 } 1279 }