istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/ca_custom_root/trust_domain_validation_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 cacustomroot
    19  
    20  import (
    21  	"fmt"
    22  	"os"
    23  	"path"
    24  	"testing"
    25  
    26  	"istio.io/istio/pkg/test/echo/common/scheme"
    27  	"istio.io/istio/pkg/test/env"
    28  	"istio.io/istio/pkg/test/framework"
    29  	"istio.io/istio/pkg/test/framework/components/echo"
    30  	"istio.io/istio/pkg/test/framework/components/echo/check"
    31  	"istio.io/istio/pkg/test/framework/components/echo/match"
    32  )
    33  
    34  const (
    35  	httpPlaintext = "http-plaintext"
    36  	httpMTLS      = "http-mtls"
    37  	tcpPlaintext  = "tcp-plaintext"
    38  	tcpMTLS       = "tcp-mtls"
    39  	tcpWL         = "tcp-wl"
    40  	passThrough   = "tcp-mtls-pass-through"
    41  
    42  	// policy to enable mTLS in client and server:
    43  	// ports with plaintext: 8090 (http) and 8092 (tcp)
    44  	// ports with mTLS: 8091 (http), 8093 (tcp) and 9000 (tcp passthrough).
    45  	policy = `
    46  apiVersion: security.istio.io/v1beta1
    47  kind: PeerAuthentication
    48  metadata:
    49    name: "mtls"
    50  spec:
    51    selector:
    52      matchLabels:
    53        app: server
    54    mtls:
    55      mode: STRICT
    56    portLevelMtls:
    57      8090:
    58        mode: DISABLE
    59      8092:
    60        mode: DISABLE
    61  ---
    62  apiVersion: networking.istio.io/v1alpha3
    63  kind: DestinationRule
    64  metadata:
    65    name: server
    66  spec:
    67    host: server.%s.svc.cluster.local
    68    trafficPolicy:
    69      tls:
    70        mode: ISTIO_MUTUAL
    71      portLevelSettings:
    72      - port:
    73          number: 8090
    74        tls:
    75          mode: DISABLE
    76      - port:
    77          number: 8092
    78        tls:
    79          mode: DISABLE
    80  `
    81  )
    82  
    83  // TestTrustDomainValidation tests the trust domain validation when mTLS is enabled.
    84  // The trust domain validation should reject a request if it's not from the trust domains configured in the mesh config.
    85  // The test uses naked client (no sidecar) with custom certificates of different trust domains and covers the following:
    86  // - plaintext requests are not affected
    87  // - same trust domain (cluster.local) and aliases (trust-domain-foo and trust-domain-bar)
    88  // - works for both HTTP and TCP protocol
    89  // - works for pass through filter chains
    90  func TestTrustDomainValidation(t *testing.T) {
    91  	framework.NewTest(t).Run(
    92  		func(ctx framework.TestContext) {
    93  			testNS := apps.EchoNamespace.Namespace
    94  
    95  			ctx.ConfigIstio().YAML(testNS.Name(), fmt.Sprintf(policy, testNS.Name())).ApplyOrFail(ctx)
    96  
    97  			trustDomains := map[string]struct {
    98  				cert string
    99  				key  string
   100  			}{
   101  				"foo": {
   102  					cert: readFile(ctx, "workload-foo-cert.pem"),
   103  					key:  readFile(ctx, "workload-foo-key.pem"),
   104  				},
   105  				"bar": {
   106  					cert: readFile(ctx, "workload-bar-cert.pem"),
   107  					key:  readFile(ctx, "workload-bar-key.pem"),
   108  				},
   109  			}
   110  
   111  			for _, cluster := range ctx.Clusters() {
   112  				ctx.NewSubTest(fmt.Sprintf("From %s", cluster.StableName())).Run(func(t framework.TestContext) {
   113  					// naked: only test app without sidecar, send requests from trust domain aliases
   114  					// client: app with sidecar, send request from cluster.local
   115  					// server: app with sidecar, verify requests from cluster.local or trust domain aliases
   116  					client := match.Cluster(cluster).FirstOrFail(t, client)
   117  					naked := match.Cluster(cluster).FirstOrFail(t, apps.Naked)
   118  					server := match.Cluster(cluster).FirstOrFail(t, server)
   119  					verify := func(ctx framework.TestContext, from echo.Instance, td, port string, s scheme.Instance, allow bool) {
   120  						ctx.Helper()
   121  						want := "allow"
   122  						if !allow {
   123  							want = "deny"
   124  						}
   125  						name := fmt.Sprintf("%s[%s]->server:%s[%s]", from.Config().Service, td, port, want)
   126  						ctx.NewSubTest(name).Run(func(t framework.TestContext) {
   127  							t.Helper()
   128  							opt := echo.CallOptions{
   129  								To:    server,
   130  								Count: 1,
   131  								Port: echo.Port{
   132  									Name: port,
   133  								},
   134  								Address: "server",
   135  								Scheme:  s,
   136  								TLS: echo.TLS{
   137  									Cert: trustDomains[td].cert,
   138  									Key:  trustDomains[td].key,
   139  								},
   140  							}
   141  							if port == passThrough {
   142  								// Manually make the request for pass through port.
   143  								opt = echo.CallOptions{
   144  									ToWorkload: server,
   145  									Port:       echo.Port{Name: tcpWL},
   146  									TLS: echo.TLS{
   147  										Cert: trustDomains[td].cert,
   148  										Key:  trustDomains[td].key,
   149  									},
   150  									Check: check.OK(),
   151  								}
   152  							}
   153  							if !allow {
   154  								opt.Check = check.ErrorContains("tls: unknown certificate")
   155  							}
   156  							from.CallOrFail(t, opt)
   157  						})
   158  					}
   159  
   160  					// Request using plaintext should always allowed.
   161  					verify(t, client, "plaintext", httpPlaintext, scheme.HTTP, true)
   162  					verify(t, client, "plaintext", tcpPlaintext, scheme.TCP, true)
   163  					verify(t, naked, "plaintext", httpPlaintext, scheme.HTTP, true)
   164  					verify(t, naked, "plaintext", tcpPlaintext, scheme.TCP, true)
   165  
   166  					// Request from local trust domain should always allowed.
   167  					verify(t, client, "cluster.local", httpMTLS, scheme.HTTP, true)
   168  					verify(t, client, "cluster.local", tcpMTLS, scheme.TCP, true)
   169  
   170  					// Trust domain foo is added as trust domain alias.
   171  					// Request from trust domain bar should be denied.
   172  					// Request from trust domain foo should be allowed.
   173  					verify(t, naked, "bar", httpMTLS, scheme.HTTPS, false)
   174  					verify(t, naked, "bar", tcpMTLS, scheme.TCP, false)
   175  					verify(t, naked, "bar", passThrough, scheme.TCP, false)
   176  					verify(t, naked, "foo", httpMTLS, scheme.HTTPS, true)
   177  					verify(t, naked, "foo", tcpMTLS, scheme.TCP, true)
   178  					verify(t, naked, "foo", passThrough, scheme.TCP, true)
   179  				})
   180  			}
   181  		})
   182  }
   183  
   184  func readFile(ctx framework.TestContext, name string) string {
   185  	data, err := os.ReadFile(path.Join(env.IstioSrc, "samples/certs", name))
   186  	if err != nil {
   187  		ctx.Fatal(err)
   188  	}
   189  	return string(data)
   190  }