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