istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/jwt_test.go (about) 1 //go:build integ 2 // +build integ 3 4 // Copyright Istio Authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package security 19 20 import ( 21 "fmt" 22 "net/http" 23 "strings" 24 "testing" 25 26 "istio.io/istio/pkg/config/protocol" 27 "istio.io/istio/pkg/http/headers" 28 "istio.io/istio/pkg/test/framework" 29 "istio.io/istio/pkg/test/framework/components/crd" 30 "istio.io/istio/pkg/test/framework/components/echo" 31 "istio.io/istio/pkg/test/framework/components/echo/check" 32 "istio.io/istio/pkg/test/framework/components/echo/config" 33 "istio.io/istio/pkg/test/framework/components/echo/config/param" 34 "istio.io/istio/pkg/test/framework/components/istio" 35 "istio.io/istio/pkg/test/framework/components/istio/ingress" 36 "istio.io/istio/pkg/test/framework/label" 37 "istio.io/istio/tests/common/jwt" 38 ) 39 40 // TestRequestAuthentication tests beta authn policy for jwt. 41 func TestRequestAuthentication(t *testing.T) { 42 payload1 := strings.Split(jwt.TokenIssuer1, ".")[1] 43 payload2 := strings.Split(jwt.TokenIssuer2, ".")[1] 44 payload3 := strings.Split(jwt.TokenIssuer1WithNestedClaims2, ".")[1] 45 framework.NewTest(t). 46 Label(label.IPv4). // https://github.com/istio/istio/issues/35835 47 Run(func(t framework.TestContext) { 48 type testCase struct { 49 name string 50 customizeCall func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) 51 } 52 53 newTest := func(policy string, cases []testCase) func(framework.TestContext) { 54 return func(t framework.TestContext) { 55 if len(policy) > 0 { 56 // Apply the policy for all targets. 57 config.New(t). 58 Source(config.File(policy).WithParams(param.Params{ 59 "JWTServer": jwtServer, 60 })). 61 BuildAll(nil, apps.Ns1.All). 62 Apply() 63 } 64 65 newTrafficTest(t, apps.Ns1.All.Instances()).Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 66 for _, c := range cases { 67 t.NewSubTest(c.name).Run(func(t framework.TestContext) { 68 opts := echo.CallOptions{ 69 To: to, 70 Port: echo.Port{ 71 Name: "http", 72 }, 73 } 74 75 // Apply any custom options for the test. 76 c.customizeCall(t, from, &opts) 77 78 from.CallOrFail(t, opts) 79 }) 80 } 81 }) 82 } 83 } 84 85 t.NewSubTest("authn-only").Run(newTest("testdata/requestauthn/authn-only.yaml.tmpl", []testCase{ 86 { 87 name: "valid-token-noauthz", 88 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 89 opts.HTTP.Path = "/valid-token-noauthz" 90 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1).Build() 91 opts.Check = check.And( 92 check.OK(), 93 check.ReachedTargetClusters(t), 94 check.RequestHeaders(map[string]string{ 95 headers.Authorization: "", 96 "X-Test-Payload": payload1, 97 "X-Jwt-Iss": "test-issuer-1@istio.io", 98 "X-Jwt-Iat": "1562182722", 99 })) 100 }, 101 }, 102 { 103 name: "valid-token-2-noauthz", 104 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 105 opts.HTTP.Path = "/valid-token-2-noauthz" 106 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer2).Build() 107 opts.Check = check.And( 108 check.OK(), 109 check.ReachedTargetClusters(t), 110 check.RequestHeaders(map[string]string{ 111 headers.Authorization: "", 112 "X-Test-Payload": payload2, 113 })) 114 }, 115 }, 116 { 117 name: "expired-token-noauthz", 118 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 119 opts.HTTP.Path = "/expired-token-noauthz" 120 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenExpired).Build() 121 opts.Check = check.Status(http.StatusUnauthorized) 122 }, 123 }, 124 { 125 name: "expired-token-cors-preflight-request-allowed", 126 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 127 opts.HTTP.Path = "/expired-token-cors-preflight-request-allowed" 128 opts.HTTP.Method = "OPTIONS" 129 opts.HTTP.Headers = headers.New(). 130 WithAuthz(jwt.TokenExpired). 131 With(headers.AccessControlRequestMethod, "POST"). 132 With(headers.Origin, "https://istio.io"). 133 Build() 134 opts.Check = check.And( 135 check.OK(), 136 check.ReachedTargetClusters(t)) 137 }, 138 }, 139 { 140 name: "expired-token-bad-cors-preflight-request-rejected", 141 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 142 opts.HTTP.Path = "/expired-token-cors-preflight-request-allowed" 143 opts.HTTP.Method = "OPTIONS" 144 opts.HTTP.Headers = headers.New(). 145 WithAuthz(jwt.TokenExpired). 146 With(headers.AccessControlRequestMethod, "POST"). 147 // the required Origin header is missing. 148 Build() 149 opts.Check = check.Status(http.StatusUnauthorized) 150 }, 151 }, 152 { 153 name: "no-token-noauthz", 154 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 155 opts.HTTP.Path = "/no-token-noauthz" 156 opts.Check = check.And( 157 check.OK(), 158 check.ReachedTargetClusters(t)) 159 }, 160 }, 161 { 162 name: "valid-nested-claim-token", 163 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 164 opts.HTTP.Path = "/valid-token-noauthz" 165 opts.HTTP.Headers = headers.New(). 166 With("Authorization", "Bearer "+jwt.TokenIssuer1WithNestedClaims2). 167 With("X-Jwt-Nested-Claim", "value_to_be_replaced"). 168 Build() 169 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1WithNestedClaims2).Build() 170 opts.Check = check.And( 171 check.OK(), 172 check.ReachedTargetClusters(t), 173 check.RequestHeaders(map[string]string{ 174 headers.Authorization: "", 175 "X-Test-Payload": payload3, 176 "X-Jwt-Iss": "test-issuer-1@istio.io", 177 "X-Jwt-Iat": "1604008018", 178 "X-Jwt-Nested-Claim": "valueC", 179 })) 180 }, 181 }, 182 })) 183 184 t.NewSubTest("authn-authz").Run(newTest("testdata/requestauthn/authn-authz.yaml.tmpl", []testCase{ 185 { 186 name: "valid-token", 187 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 188 opts.HTTP.Path = "/valid-token" 189 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1).Build() 190 opts.Check = check.And( 191 check.OK(), 192 check.ReachedTargetClusters(t), 193 check.RequestHeader(headers.Authorization, "")) 194 }, 195 }, 196 { 197 name: "expired-token", 198 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 199 opts.HTTP.Path = "/expired-token" 200 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenExpired).Build() 201 opts.Check = check.Status(http.StatusUnauthorized) 202 }, 203 }, 204 { 205 name: "no-token", 206 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 207 opts.HTTP.Path = "/no-token" 208 opts.Check = check.Status(http.StatusForbidden) 209 }, 210 }, 211 })) 212 213 t.NewSubTest("no-authn-authz").Run(newTest("", []testCase{ 214 { 215 name: "no-authn-authz", 216 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 217 opts.HTTP.Path = "/no-authn-authz" 218 opts.Check = check.And( 219 check.OK(), 220 check.ReachedTargetClusters(t)) 221 }, 222 }, 223 })) 224 225 t.NewSubTest("forward").Run(newTest("testdata/requestauthn/forward.yaml.tmpl", []testCase{ 226 { 227 name: "valid-token-forward", 228 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 229 opts.HTTP.Path = "/valid-token-forward" 230 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1).Build() 231 opts.Check = check.And( 232 check.OK(), 233 check.ReachedTargetClusters(t), 234 check.RequestHeaders(map[string]string{ 235 headers.Authorization: "Bearer " + jwt.TokenIssuer1, 236 "X-Test-Payload": payload1, 237 })) 238 }, 239 }, 240 })) 241 242 t.NewSubTest("remote").Run(newTest("testdata/requestauthn/remote.yaml.tmpl", []testCase{ 243 { 244 name: "valid-token-forward-remote-jwks", 245 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 246 opts.HTTP.Path = "/valid-token-forward-remote-jwks" 247 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1).Build() 248 opts.Check = check.And( 249 check.OK(), 250 check.ReachedTargetClusters(t), 251 check.RequestHeaders(map[string]string{ 252 headers.Authorization: "Bearer " + jwt.TokenIssuer1, 253 "X-Test-Payload": payload1, 254 })) 255 }, 256 }, 257 })) 258 259 t.NewSubTest("timeout").Run(newTest("testdata/requestauthn/timeout.yaml.tmpl", []testCase{ 260 { 261 name: "valid-token-forward-remote-jwks", 262 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 263 opts.HTTP.Path = "/valid-token-forward-remote-jwks" 264 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1).Build() 265 opts.Check = check.And( 266 check.NotOK(), 267 check.Status(http.StatusUnauthorized)) 268 }, 269 }, 270 })) 271 272 t.NewSubTest("aud").Run(newTest("testdata/requestauthn/aud.yaml.tmpl", []testCase{ 273 { 274 name: "invalid-aud", 275 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 276 opts.HTTP.Path = "/valid-aud" 277 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1).Build() 278 opts.Check = check.Status(http.StatusForbidden) 279 }, 280 }, 281 { 282 name: "valid-aud", 283 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 284 opts.HTTP.Path = "/valid-aud" 285 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1WithAud).Build() 286 opts.Check = check.And( 287 check.OK(), 288 check.ReachedTargetClusters(t)) 289 }, 290 }, 291 { 292 name: "verify-policies-are-combined", 293 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 294 opts.HTTP.Path = "/verify-policies-are-combined" 295 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer2).Build() 296 opts.Check = check.And( 297 check.OK(), 298 check.ReachedTargetClusters(t)) 299 }, 300 }, 301 })) 302 303 t.NewSubTest("invalid-jwks").Run(newTest("testdata/requestauthn/invalid-jwks.yaml.tmpl", []testCase{ 304 { 305 name: "invalid-jwks-valid-token-noauthz", 306 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 307 opts.HTTP.Path = "" 308 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenIssuer1).Build() 309 opts.Check = check.Status(http.StatusUnauthorized) 310 }, 311 }, 312 { 313 name: "invalid-jwks-expired-token-noauthz", 314 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 315 opts.HTTP.Path = "/invalid-jwks-valid-token-noauthz" 316 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenExpired).Build() 317 opts.Check = check.Status(http.StatusUnauthorized) 318 }, 319 }, 320 { 321 name: "invalid-jwks-no-token-noauthz", 322 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 323 opts.HTTP.Path = "/invalid-jwks-no-token-noauthz" 324 opts.Check = check.And( 325 check.OK(), 326 check.ReachedTargetClusters(t)) 327 }, 328 }, 329 })) 330 331 t.NewSubTest("headers-params").Run(newTest("testdata/requestauthn/headers-params.yaml.tmpl", []testCase{ 332 { 333 name: "valid-params", 334 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 335 opts.HTTP.Path = "/valid-token?token=" + jwt.TokenIssuer1 336 opts.Check = check.And( 337 check.OK(), 338 check.ReachedTargetClusters(t)) 339 }, 340 }, 341 { 342 name: "valid-params-secondary", 343 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 344 opts.HTTP.Path = "/valid-token?secondary_token=" + jwt.TokenIssuer1 345 opts.Check = check.And( 346 check.OK(), 347 check.ReachedTargetClusters(t)) 348 }, 349 }, 350 { 351 name: "invalid-params", 352 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 353 opts.HTTP.Path = "/valid-token?token_value=" + jwt.TokenIssuer1 354 opts.Check = check.Status(http.StatusForbidden) 355 }, 356 }, 357 { 358 name: "valid-token-set", 359 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 360 opts.HTTP.Path = "/valid-token?token=" + jwt.TokenIssuer1 + "&secondary_token=" + jwt.TokenIssuer1 361 opts.Check = check.And( 362 check.OK(), 363 check.ReachedTargetClusters(t)) 364 }, 365 }, 366 { 367 name: "invalid-token-set", 368 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 369 opts.HTTP.Path = "/valid-token?token=" + jwt.TokenIssuer1 + "&secondary_token=" + jwt.TokenExpired 370 opts.Check = check.Status(http.StatusUnauthorized) 371 }, 372 }, 373 { 374 name: "valid-header", 375 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 376 opts.HTTP.Path = "" 377 opts.HTTP.Headers = headers.New(). 378 With("X-Jwt-Token", "Value "+jwt.TokenIssuer1). 379 Build() 380 opts.Check = check.And( 381 check.OK(), 382 check.ReachedTargetClusters(t)) 383 }, 384 }, 385 { 386 name: "valid-header-secondary", 387 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 388 opts.HTTP.Path = "" 389 opts.HTTP.Headers = headers.New(). 390 With("Auth-Token", "Token "+jwt.TokenIssuer1). 391 Build() 392 opts.Check = check.And( 393 check.OK(), 394 check.ReachedTargetClusters(t)) 395 }, 396 }, 397 { 398 name: "invalid-header", 399 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 400 opts.HTTP.Path = "" 401 opts.HTTP.Headers = headers.New(). 402 With("Auth-Header-Param", "Bearer "+jwt.TokenIssuer1). 403 Build() 404 opts.Check = check.Status(http.StatusForbidden) 405 }, 406 }, 407 })) 408 }) 409 } 410 411 // TestIngressRequestAuthentication tests beta authn policy for jwt on ingress. 412 // The policy is also set at global namespace, with authorization on ingressgateway. 413 func TestIngressRequestAuthentication(t *testing.T) { 414 framework.NewTest(t). 415 Label(label.IPv4). // https://github.com/istio/istio/issues/35835 416 Run(func(t framework.TestContext) { 417 config.New(t). 418 Source(config.File("testdata/requestauthn/global-jwt.yaml.tmpl").WithParams(param.Params{ 419 param.Namespace.String(): istio.ClaimSystemNamespaceOrFail(t, t), 420 "Services": apps.Ns1.All, 421 "GatewayIstioLabel": i.Settings().IngressGatewayIstioLabel, 422 })). 423 Source(config.File("testdata/requestauthn/ingress.yaml.tmpl").WithParams(param.Params{ 424 param.Namespace.String(): apps.Ns1.Namespace, 425 "GatewayIstioLabel": i.Settings().IngressGatewayIstioLabel, 426 })). 427 BuildAll(nil, apps.Ns1.All). 428 Apply() 429 430 t.NewSubTest("in-mesh-authn").Run(func(t framework.TestContext) { 431 cases := []struct { 432 name string 433 customizeCall func(framework.TestContext, echo.Instance, *echo.CallOptions) 434 }{ 435 { 436 name: "in-mesh-with-expired-token", 437 customizeCall: func(_ framework.TestContext, _ echo.Instance, opts *echo.CallOptions) { 438 opts.HTTP.Headers = headers.New().WithAuthz(jwt.TokenExpired).Build() 439 opts.Check = check.Status(http.StatusUnauthorized) 440 }, 441 }, 442 { 443 name: "in-mesh-without-token", 444 customizeCall: func(t framework.TestContext, from echo.Instance, opts *echo.CallOptions) { 445 opts.Check = check.And( 446 check.OK(), 447 check.ReachedTargetClusters(t)) 448 }, 449 }, 450 } 451 newTrafficTest(t, apps.Ns1.All.Instances()). 452 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 453 for _, c := range cases { 454 t.NewSubTest(c.name).Run(func(t framework.TestContext) { 455 opts := echo.CallOptions{ 456 To: to, 457 Port: echo.Port{ 458 Name: "http", 459 }, 460 } 461 462 // Apply any custom options for the test. 463 c.customizeCall(t, from, &opts) 464 465 from.CallOrFail(t, opts) 466 }) 467 } 468 }) 469 }) 470 471 t.NewSubTest("ingress-authn").Run(func(t framework.TestContext) { 472 cases := []struct { 473 name string 474 customizeCall func(opts *echo.CallOptions, to echo.Target) 475 }{ 476 { 477 name: "deny without token", 478 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 479 opts.HTTP.Path = "/" 480 opts.HTTP.Headers = headers.New(). 481 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 482 Build() 483 opts.Check = check.Status(http.StatusForbidden) 484 }, 485 }, 486 { 487 name: "allow with sub-1 token", 488 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 489 opts.HTTP.Path = "/" 490 opts.HTTP.Headers = headers.New(). 491 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 492 WithAuthz(jwt.TokenIssuer1). 493 Build() 494 opts.Check = check.OK() 495 }, 496 }, 497 { 498 name: "deny with sub-2 token", 499 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 500 opts.HTTP.Path = "/" 501 opts.HTTP.Headers = headers.New(). 502 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 503 WithAuthz(jwt.TokenIssuer2). 504 Build() 505 opts.Check = check.Status(http.StatusForbidden) 506 }, 507 }, 508 { 509 name: "deny with expired token", 510 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 511 opts.HTTP.Path = "/" 512 opts.HTTP.Headers = headers.New(). 513 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 514 WithAuthz(jwt.TokenExpired). 515 Build() 516 opts.Check = check.Status(http.StatusUnauthorized) 517 }, 518 }, 519 { 520 name: "allow with sub-1 token on any.com", 521 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 522 opts.HTTP.Path = "/" 523 opts.HTTP.Headers = headers.New(). 524 WithHost(fmt.Sprintf("any-request-principal-ok.%s.com", to.ServiceName())). 525 WithAuthz(jwt.TokenIssuer1). 526 Build() 527 opts.Check = check.OK() 528 }, 529 }, 530 { 531 name: "allow with sub-2 token on any.com", 532 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 533 opts.HTTP.Path = "/" 534 opts.HTTP.Headers = headers.New(). 535 WithHost(fmt.Sprintf("any-request-principal-ok.%s.com", to.ServiceName())). 536 WithAuthz(jwt.TokenIssuer2). 537 Build() 538 opts.Check = check.OK() 539 }, 540 }, 541 { 542 name: "deny without token on any.com", 543 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 544 opts.HTTP.Path = "/" 545 opts.HTTP.Headers = headers.New(). 546 WithHost(fmt.Sprintf("any-request-principal-ok.%s.com", to.ServiceName())). 547 Build() 548 opts.Check = check.Status(http.StatusForbidden) 549 }, 550 }, 551 { 552 name: "deny with token on other host", 553 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 554 opts.HTTP.Path = "/" 555 opts.HTTP.Headers = headers.New(). 556 WithHost(fmt.Sprintf("other-host.%s.com", to.ServiceName())). 557 WithAuthz(jwt.TokenIssuer1). 558 Build() 559 opts.Check = check.Status(http.StatusForbidden) 560 }, 561 }, 562 { 563 name: "allow healthz", 564 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 565 opts.HTTP.Path = "/healthz" 566 opts.HTTP.Headers = headers.New(). 567 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 568 Build() 569 opts.Check = check.OK() 570 }, 571 }, 572 } 573 574 newTrafficTest(t, apps.Ns1.All.Instances()). 575 RunViaIngress(func(t framework.TestContext, from ingress.Instance, to echo.Target) { 576 for _, c := range cases { 577 t.NewSubTest(c.name).Run(func(t framework.TestContext) { 578 opts := echo.CallOptions{ 579 Port: echo.Port{ 580 Protocol: protocol.HTTP, 581 }, 582 } 583 584 c.customizeCall(&opts, to) 585 586 from.CallOrFail(t, opts) 587 }) 588 } 589 }) 590 }) 591 }) 592 } 593 594 // TestGatewayAPIRequestAuthentication tests beta authn policy for jwt on gateway API. 595 func TestGatewayAPIRequestAuthentication(t *testing.T) { 596 framework.NewTest(t). 597 Label(label.IPv4). // https://github.com/istio/istio/issues/35835 598 Run(func(t framework.TestContext) { 599 crd.DeployGatewayAPIOrSkip(t) 600 config.New(t). 601 Source(config.File("testdata/requestauthn/gateway-api.yaml.tmpl").WithParams(param.Params{ 602 param.Namespace.String(): apps.Ns1.Namespace, 603 })). 604 Source(config.File("testdata/requestauthn/gateway-jwt.yaml.tmpl").WithParams(param.Params{ 605 param.Namespace.String(): apps.Ns1.Namespace, 606 "Services": apps.Ns1.A.Append(apps.Ns1.B).Services(), 607 })). 608 BuildAll(nil, apps.Ns1.A.Append(apps.Ns1.B).Services()). 609 Apply() 610 611 t.NewSubTest("gateway-authn").Run(func(t framework.TestContext) { 612 cases := []struct { 613 name string 614 customizeCall func(opts *echo.CallOptions, to echo.Target) 615 }{ 616 { 617 name: "deny without token", 618 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 619 opts.HTTP.Path = "/" 620 opts.HTTP.Headers = headers.New(). 621 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 622 Build() 623 opts.Check = check.Status(http.StatusForbidden) 624 }, 625 }, 626 { 627 name: "allow with sub-1 token", 628 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 629 opts.HTTP.Path = "/" 630 opts.HTTP.Headers = headers.New(). 631 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 632 WithAuthz(jwt.TokenIssuer1). 633 Build() 634 opts.Check = check.OK() 635 }, 636 }, 637 { 638 name: "allow with sub-1 token despite \"ignored\" RequestAuthentication (enableGatewayAPISelectorPolicies flag = true)", 639 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 640 opts.HTTP.Path = "/" 641 opts.HTTP.Headers = headers.New(). 642 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 643 WithAuthz(jwt.TokenIssuer3). 644 Build() 645 opts.Check = check.OK() 646 }, 647 }, 648 { 649 name: "deny with sub-2 token", 650 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 651 opts.HTTP.Path = "/" 652 opts.HTTP.Headers = headers.New(). 653 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 654 WithAuthz(jwt.TokenIssuer2). 655 Build() 656 opts.Check = check.Status(http.StatusForbidden) 657 }, 658 }, 659 { 660 name: "deny with expired token", 661 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 662 opts.HTTP.Path = "/" 663 opts.HTTP.Headers = headers.New(). 664 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 665 WithAuthz(jwt.TokenExpired). 666 Build() 667 opts.Check = check.Status(http.StatusUnauthorized) 668 }, 669 }, 670 { 671 name: "allow with sub-1 token on any.com", 672 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 673 opts.HTTP.Path = "/" 674 opts.HTTP.Headers = headers.New(). 675 WithHost(fmt.Sprintf("any-request-principal-ok.%s.com", to.ServiceName())). 676 WithAuthz(jwt.TokenIssuer1). 677 Build() 678 opts.Check = check.OK() 679 }, 680 }, 681 { 682 name: "allow with sub-2 token on any.com", 683 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 684 opts.HTTP.Path = "/" 685 opts.HTTP.Headers = headers.New(). 686 WithHost(fmt.Sprintf("any-request-principal-ok.%s.com", to.ServiceName())). 687 WithAuthz(jwt.TokenIssuer2). 688 Build() 689 opts.Check = check.OK() 690 }, 691 }, 692 { 693 name: "deny without token on any.com", 694 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 695 opts.HTTP.Path = "/" 696 opts.HTTP.Headers = headers.New(). 697 WithHost(fmt.Sprintf("any-request-principal-ok.%s.com", to.ServiceName())). 698 Build() 699 opts.Check = check.Status(http.StatusForbidden) 700 }, 701 }, 702 { 703 name: "deny with token on other host", 704 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 705 opts.HTTP.Path = "/" 706 opts.HTTP.Headers = headers.New(). 707 WithHost(fmt.Sprintf("other-host.%s.com", to.ServiceName())). 708 WithAuthz(jwt.TokenIssuer1). 709 Build() 710 opts.Check = check.Status(http.StatusForbidden) 711 }, 712 }, 713 { 714 name: "allow healthz", 715 customizeCall: func(opts *echo.CallOptions, to echo.Target) { 716 opts.HTTP.Path = "/healthz" 717 opts.HTTP.Headers = headers.New(). 718 WithHost(fmt.Sprintf("example.%s.com", to.ServiceName())). 719 Build() 720 opts.Check = check.OK() 721 }, 722 }, 723 } 724 725 newTrafficTest(t, apps.Ns1.A.Append(apps.Ns1.B)). 726 RunViaGatewayIngress("istio", func(t framework.TestContext, from ingress.Instance, to echo.Target) { 727 for _, c := range cases { 728 t.NewSubTest(c.name).Run(func(t framework.TestContext) { 729 opts := echo.CallOptions{ 730 Port: echo.Port{ 731 Protocol: protocol.HTTP, 732 }, 733 } 734 735 c.customizeCall(&opts, to) 736 737 from.CallOrFail(t, opts) 738 }) 739 } 740 }) 741 }) 742 }) 743 }