istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/peer_authentication_simulation_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package core_test 16 17 import ( 18 "testing" 19 20 "istio.io/istio/pilot/pkg/model" 21 "istio.io/istio/pilot/pkg/simulation" 22 "istio.io/istio/pilot/test/xds" 23 ) 24 25 // TestPeerAuthenticationPassthrough tests the PeerAuthentication policy applies correctly on the passthrough filter chain, 26 // including both global configuration and port level configuration. 27 func TestPeerAuthenticationPassthrough(t *testing.T) { 28 paStrict := ` 29 apiVersion: security.istio.io/v1beta1 30 kind: PeerAuthentication 31 metadata: 32 name: default 33 spec: 34 selector: 35 matchLabels: 36 app: foo 37 mtls: 38 mode: STRICT 39 ---` 40 paDisable := ` 41 apiVersion: security.istio.io/v1beta1 42 kind: PeerAuthentication 43 metadata: 44 name: default 45 spec: 46 selector: 47 matchLabels: 48 app: foo 49 mtls: 50 mode: DISABLE 51 ---` 52 paPermissive := ` 53 apiVersion: security.istio.io/v1beta1 54 kind: PeerAuthentication 55 metadata: 56 name: default 57 spec: 58 selector: 59 matchLabels: 60 app: foo 61 mtls: 62 mode: PERMISSIVE 63 ---` 64 paStrictWithDisableOnPort9000 := ` 65 apiVersion: security.istio.io/v1beta1 66 kind: PeerAuthentication 67 metadata: 68 name: default 69 spec: 70 selector: 71 matchLabels: 72 app: foo 73 mtls: 74 mode: STRICT 75 portLevelMtls: 76 9000: 77 mode: DISABLE 78 ---` 79 paDisableWithStrictOnPort9000 := ` 80 apiVersion: security.istio.io/v1beta1 81 kind: PeerAuthentication 82 metadata: 83 name: default 84 spec: 85 selector: 86 matchLabels: 87 app: foo 88 mtls: 89 mode: DISABLE 90 portLevelMtls: 91 9000: 92 mode: STRICT 93 ---` 94 paDisableWithPermissiveOnPort9000 := ` 95 apiVersion: security.istio.io/v1beta1 96 kind: PeerAuthentication 97 metadata: 98 name: default 99 spec: 100 selector: 101 matchLabels: 102 app: foo 103 mtls: 104 mode: DISABLE 105 portLevelMtls: 106 9000: 107 mode: PERMISSIVE 108 ---` 109 sePort8000 := ` 110 apiVersion: networking.istio.io/v1alpha3 111 kind: ServiceEntry 112 metadata: 113 name: se 114 spec: 115 hosts: 116 - foo.bar 117 endpoints: 118 - address: 1.1.1.1 119 location: MESH_INTERNAL 120 resolution: STATIC 121 ports: 122 - name: http 123 number: 8000 124 protocol: HTTP 125 ---` 126 mkCall := func(port int, tls simulation.TLSMode) simulation.Call { 127 r := simulation.Call{Protocol: simulation.HTTP, Port: port, CallMode: simulation.CallModeInbound, TLS: tls} 128 if tls == simulation.MTLS { 129 r.Alpn = "istio" 130 } 131 return r 132 } 133 cases := []struct { 134 name string 135 config string 136 calls []simulation.Expect 137 }{ 138 { 139 name: "global disable", 140 config: paDisable, 141 calls: []simulation.Expect{ 142 { 143 Name: "mtls", 144 Call: mkCall(8000, simulation.MTLS), 145 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 146 }, 147 { 148 Name: "plaintext", 149 Call: mkCall(8000, simulation.Plaintext), 150 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 151 }, 152 }, 153 }, 154 { 155 name: "global strict", 156 config: paStrict, 157 calls: []simulation.Expect{ 158 { 159 Name: "plaintext", 160 Call: mkCall(8000, simulation.Plaintext), 161 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 162 }, 163 { 164 Name: "mtls", 165 Call: mkCall(8000, simulation.MTLS), 166 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 167 }, 168 }, 169 }, 170 { 171 name: "global permissive", 172 config: paPermissive, 173 calls: []simulation.Expect{ 174 { 175 Name: "plaintext", 176 Call: mkCall(8000, simulation.Plaintext), 177 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 178 }, 179 { 180 Name: "mtls", 181 Call: mkCall(8000, simulation.MTLS), 182 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 183 }, 184 }, 185 }, 186 { 187 name: "global disable and port 9000 strict", 188 config: paDisableWithStrictOnPort9000, 189 calls: []simulation.Expect{ 190 { 191 Name: "plaintext on port 8000", 192 Call: mkCall(8000, simulation.Plaintext), 193 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 194 }, 195 { 196 Name: "mtls on port 8000", 197 Call: mkCall(8000, simulation.MTLS), 198 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 199 }, 200 { 201 Name: "plaintext port 9000", 202 Call: mkCall(9000, simulation.Plaintext), 203 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 204 }, 205 { 206 Name: "mtls port 9000", 207 Call: mkCall(9000, simulation.MTLS), 208 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 209 }, 210 }, 211 }, 212 { 213 name: "global disable and port 9000 strict not in service", 214 config: paDisableWithStrictOnPort9000 + sePort8000, 215 calls: []simulation.Expect{ 216 { 217 Name: "plaintext on port 8000", 218 Call: mkCall(8000, simulation.Plaintext), 219 Result: simulation.Result{ClusterMatched: "inbound|8000||"}, 220 }, 221 { 222 Name: "mtls on port 8000", 223 Call: mkCall(8000, simulation.MTLS), 224 // This will send an mTLS request to plaintext HTTP port, which is expected to fail 225 Result: simulation.Result{Error: simulation.ErrProtocolError}, 226 }, 227 { 228 Name: "plaintext port 9000", 229 Call: mkCall(9000, simulation.Plaintext), 230 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 231 }, 232 { 233 Name: "mtls port 9000", 234 Call: mkCall(9000, simulation.MTLS), 235 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 236 }, 237 }, 238 }, 239 { 240 name: "global strict and port 9000 plaintext", 241 config: paStrictWithDisableOnPort9000, 242 calls: []simulation.Expect{ 243 { 244 Name: "plaintext on port 8000", 245 Call: mkCall(8000, simulation.Plaintext), 246 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 247 }, 248 { 249 Name: "mtls on port 8000", 250 Call: mkCall(8000, simulation.MTLS), 251 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 252 }, 253 { 254 Name: "plaintext port 9000", 255 Call: mkCall(9000, simulation.Plaintext), 256 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 257 }, 258 { 259 Name: "mtls port 9000", 260 Call: mkCall(9000, simulation.MTLS), 261 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 262 }, 263 }, 264 }, 265 { 266 name: "global strict and port 9000 plaintext not in service", 267 config: paStrictWithDisableOnPort9000 + sePort8000, 268 calls: []simulation.Expect{ 269 { 270 Name: "plaintext on port 8000", 271 Call: mkCall(8000, simulation.Plaintext), 272 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 273 }, 274 { 275 Name: "mtls on port 8000", 276 Call: mkCall(8000, simulation.MTLS), 277 Result: simulation.Result{ClusterMatched: "inbound|8000||"}, 278 }, 279 { 280 Name: "plaintext port 9000", 281 Call: mkCall(9000, simulation.Plaintext), 282 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 283 }, 284 { 285 Name: "mtls port 9000", 286 Call: mkCall(9000, simulation.MTLS), 287 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 288 }, 289 }, 290 }, 291 { 292 name: "global plaintext and port 9000 permissive", 293 config: paDisableWithPermissiveOnPort9000, 294 calls: []simulation.Expect{ 295 { 296 Name: "plaintext on port 8000", 297 Call: mkCall(8000, simulation.Plaintext), 298 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 299 }, 300 { 301 Name: "mtls on port 8000", 302 Call: mkCall(8000, simulation.MTLS), 303 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 304 }, 305 { 306 Name: "plaintext port 9000", 307 Call: mkCall(9000, simulation.Plaintext), 308 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 309 }, 310 { 311 Name: "mtls port 9000", 312 Call: mkCall(9000, simulation.MTLS), 313 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 314 }, 315 }, 316 }, 317 { 318 name: "global plaintext and port 9000 permissive not in service", 319 config: paDisableWithPermissiveOnPort9000 + sePort8000, 320 calls: []simulation.Expect{ 321 { 322 Name: "plaintext on port 8000", 323 Call: mkCall(8000, simulation.Plaintext), 324 Result: simulation.Result{ClusterMatched: "inbound|8000||"}, 325 }, 326 { 327 Name: "mtls on port 8000", 328 Call: mkCall(8000, simulation.MTLS), 329 // We match the plaintext HTTP filter chain, which is a protocol error (as expected) 330 Result: simulation.Result{Error: simulation.ErrProtocolError}, 331 }, 332 { 333 Name: "plaintext port 9000", 334 Call: mkCall(9000, simulation.Plaintext), 335 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 336 }, 337 { 338 Name: "mtls port 9000", 339 Call: mkCall(9000, simulation.MTLS), 340 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 341 }, 342 }, 343 }, 344 } 345 proxy := &model.Proxy{ 346 Labels: map[string]string{"app": "foo"}, 347 Metadata: &model.NodeMetadata{Labels: map[string]string{"app": "foo"}}, 348 } 349 for _, tt := range cases { 350 runSimulationTest(t, proxy, xds.FakeOptions{}, simulationTest{ 351 name: tt.name, 352 config: tt.config, 353 calls: tt.calls, 354 }) 355 } 356 } 357 358 // TestPeerAuthenticationWithSidecar tests the PeerAuthentication policy applies correctly to filter chain generated from 359 // either the service or sidecar resource. 360 func TestPeerAuthenticationWithSidecar(t *testing.T) { 361 pa := ` 362 apiVersion: security.istio.io/v1beta1 363 kind: PeerAuthentication 364 metadata: 365 name: default 366 spec: 367 selector: 368 matchLabels: 369 app: foo 370 mtls: 371 mode: STRICT 372 portLevelMtls: 373 9090: 374 mode: DISABLE 375 ---` 376 sidecar := ` 377 apiVersion: networking.istio.io/v1alpha3 378 kind: Sidecar 379 metadata: 380 labels: 381 app: foo 382 name: sidecar 383 spec: 384 ingress: 385 - defaultEndpoint: 127.0.0.1:8080 386 port: 387 name: tls 388 number: 8080 389 protocol: TCP 390 - defaultEndpoint: 127.0.0.1:9090 391 port: 392 name: plaintext 393 number: 9090 394 protocol: TCP 395 egress: 396 - hosts: 397 - "*/*" 398 workloadSelector: 399 labels: 400 app: foo 401 ---` 402 partialSidecar := ` 403 apiVersion: networking.istio.io/v1alpha3 404 kind: Sidecar 405 metadata: 406 labels: 407 app: foo 408 name: sidecar 409 spec: 410 ingress: 411 - defaultEndpoint: 127.0.0.1:8080 412 port: 413 name: tls 414 number: 8080 415 protocol: TCP 416 egress: 417 - hosts: 418 - "*/*" 419 workloadSelector: 420 labels: 421 app: foo 422 ---` 423 instancePorts := ` 424 apiVersion: networking.istio.io/v1alpha3 425 kind: ServiceEntry 426 metadata: 427 name: se 428 spec: 429 hosts: 430 - foo.bar 431 endpoints: 432 - address: 1.1.1.1 433 labels: 434 app: foo 435 location: MESH_INTERNAL 436 resolution: STATIC 437 ports: 438 - name: tls 439 number: 8080 440 protocol: TCP 441 - name: plaintext 442 number: 9090 443 protocol: TCP 444 ---` 445 instanceNoPorts := ` 446 apiVersion: networking.istio.io/v1alpha3 447 kind: ServiceEntry 448 metadata: 449 name: se 450 spec: 451 hosts: 452 - foo.bar 453 endpoints: 454 - address: 1.1.1.1 455 labels: 456 app: foo 457 location: MESH_INTERNAL 458 resolution: STATIC 459 ports: 460 - name: random 461 number: 5050 462 protocol: TCP 463 ---` 464 mkCall := func(port int, tls simulation.TLSMode) simulation.Call { 465 return simulation.Call{Protocol: simulation.TCP, Port: port, CallMode: simulation.CallModeInbound, TLS: tls} 466 } 467 cases := []struct { 468 name string 469 config string 470 calls []simulation.Expect 471 }{ 472 { 473 name: "service, no sidecar", 474 config: pa + instancePorts, 475 calls: []simulation.Expect{ 476 { 477 Name: "plaintext on tls port", 478 Call: mkCall(8080, simulation.Plaintext), 479 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 480 }, 481 { 482 Name: "tls on tls port", 483 Call: mkCall(8080, simulation.MTLS), 484 Result: simulation.Result{ClusterMatched: "inbound|8080||"}, 485 }, 486 { 487 Name: "plaintext on plaintext port", 488 Call: mkCall(9090, simulation.Plaintext), 489 Result: simulation.Result{ClusterMatched: "inbound|9090||"}, 490 }, 491 { 492 Name: "tls on plaintext port", 493 Call: mkCall(9090, simulation.MTLS), 494 // TLS is fine here; we are not sniffing TLS at all so anything is allowed 495 Result: simulation.Result{ClusterMatched: "inbound|9090||"}, 496 }, 497 }, 498 }, 499 { 500 name: "service, full sidecar", 501 config: pa + sidecar + instancePorts, 502 calls: []simulation.Expect{ 503 { 504 Name: "plaintext on tls port", 505 Call: mkCall(8080, simulation.Plaintext), 506 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 507 }, 508 { 509 Name: "tls on tls port", 510 Call: mkCall(8080, simulation.MTLS), 511 Result: simulation.Result{ClusterMatched: "inbound|8080||"}, 512 }, 513 { 514 Name: "plaintext on plaintext port", 515 Call: mkCall(9090, simulation.Plaintext), 516 Result: simulation.Result{ClusterMatched: "inbound|9090||"}, 517 }, 518 { 519 Name: "tls on plaintext port", 520 Call: mkCall(9090, simulation.MTLS), 521 // TLS is fine here; we are not sniffing TLS at all so anything is allowed 522 Result: simulation.Result{ClusterMatched: "inbound|9090||"}, 523 }, 524 }, 525 }, 526 { 527 name: "no service, no sidecar", 528 config: pa + instanceNoPorts, 529 calls: []simulation.Expect{ 530 { 531 Name: "plaintext on tls port", 532 Call: mkCall(8080, simulation.Plaintext), 533 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 534 }, 535 { 536 Name: "tls on tls port", 537 Call: mkCall(8080, simulation.MTLS), 538 // no ports defined, so we will passthrough 539 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 540 }, 541 { 542 Name: "plaintext on plaintext port", 543 Call: mkCall(9090, simulation.Plaintext), 544 // no ports defined, so we will passthrough 545 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 546 }, 547 { 548 Name: "tls on plaintext port", 549 Call: mkCall(9090, simulation.MTLS), 550 // no ports defined, so we will passthrough 551 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 552 }, 553 }, 554 }, 555 { 556 name: "no service, full sidecar", 557 config: pa + sidecar + instanceNoPorts, 558 calls: []simulation.Expect{ 559 { 560 Name: "plaintext on tls port", 561 Call: mkCall(8080, simulation.Plaintext), 562 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 563 }, 564 { 565 Name: "tls on tls port", 566 Call: mkCall(8080, simulation.MTLS), 567 Result: simulation.Result{ClusterMatched: "inbound|8080||"}, 568 }, 569 { 570 Name: "plaintext on plaintext port", 571 Call: mkCall(9090, simulation.Plaintext), 572 Result: simulation.Result{ClusterMatched: "inbound|9090||"}, 573 }, 574 { 575 Name: "tls on plaintext port", 576 Call: mkCall(9090, simulation.MTLS), 577 // TLS is fine here; we are not sniffing TLS at all so anything is allowed 578 Result: simulation.Result{ClusterMatched: "inbound|9090||"}, 579 }, 580 }, 581 }, 582 { 583 name: "service, partial sidecar", 584 config: pa + partialSidecar + instancePorts, 585 calls: []simulation.Expect{ 586 { 587 Name: "plaintext on tls port", 588 Call: mkCall(8080, simulation.Plaintext), 589 Result: simulation.Result{Error: simulation.ErrNoFilterChain}, 590 }, 591 { 592 Name: "tls on tls port", 593 Call: mkCall(8080, simulation.MTLS), 594 Result: simulation.Result{ClusterMatched: "inbound|8080||"}, 595 }, 596 // Despite being defined in the Service, we get no filter chain since its not in Sidecar 597 { 598 Name: "plaintext on plaintext port", 599 Call: mkCall(9090, simulation.Plaintext), 600 // port 9090 not defined in partialSidecar and will use plain text, plaintext request should pass. 601 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 602 }, 603 { 604 Name: "tls on plaintext port", 605 Call: mkCall(9090, simulation.MTLS), 606 // no ports defined, so we will passthrough 607 Result: simulation.Result{ClusterMatched: "InboundPassthroughClusterIpv4"}, 608 }, 609 }, 610 }, 611 } 612 proxy := &model.Proxy{ 613 Labels: map[string]string{"app": "foo"}, 614 Metadata: &model.NodeMetadata{Labels: map[string]string{"app": "foo"}}, 615 } 616 for _, tt := range cases { 617 runSimulationTest(t, proxy, xds.FakeOptions{}, simulationTest{ 618 name: tt.name, 619 config: tt.config, 620 calls: tt.calls, 621 }) 622 } 623 }