istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/util/reachability/context.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 reachability
    19  
    20  import (
    21  	"fmt"
    22  	"path/filepath"
    23  	"strings"
    24  	"time"
    25  
    26  	"istio.io/istio/pkg/test/echo/common/scheme"
    27  	"istio.io/istio/pkg/test/framework"
    28  	"istio.io/istio/pkg/test/framework/components/echo"
    29  	"istio.io/istio/pkg/test/framework/components/echo/check"
    30  	"istio.io/istio/pkg/test/framework/components/echo/common/deployment"
    31  	"istio.io/istio/pkg/test/framework/components/echo/match"
    32  	"istio.io/istio/pkg/test/framework/components/namespace"
    33  	"istio.io/istio/pkg/test/framework/resource/config/apply"
    34  	"istio.io/istio/pkg/test/util/retry"
    35  )
    36  
    37  // TestCase represents reachability test cases.
    38  type TestCase struct {
    39  	// ConfigFile is the name of the yaml contains the authentication policy and destination rule CRs
    40  	// that are needed for the test setup.
    41  	// The file is expected in the tests/integration/security/reachability/testdata folder.
    42  	ConfigFile string
    43  	Namespace  namespace.Instance
    44  
    45  	// CallOpts specified the call options for destination service. If not specified, use the default
    46  	// framework provided ones.
    47  	CallOpts []echo.CallOptions
    48  
    49  	// Indicates whether a test should be created for the given configuration.
    50  	Include func(from echo.Instance, opts echo.CallOptions) bool
    51  
    52  	// Indicates whether the test should expect a successful response.
    53  	ExpectSuccess func(from echo.Instance, opts echo.CallOptions) bool
    54  
    55  	// Allows filtering the destinations we expect to reach (optional).
    56  	ExpectDestinations func(from echo.Instance, to echo.Target) echo.Instances
    57  
    58  	// Indicates whether the test should expect a MTLS response.
    59  	ExpectMTLS func(from echo.Instance, opts echo.CallOptions) bool
    60  
    61  	// Indicates whether a test should be run in the multicluster environment.
    62  	// This is a temporary flag during the converting tests into multicluster supported.
    63  	// TODO: Remove this flag when all tests support multicluster
    64  	SkippedForMulticluster bool
    65  }
    66  
    67  var (
    68  	A             echo.Instances
    69  	B             echo.Instances
    70  	C             echo.Instances
    71  	D             echo.Instances
    72  	E             echo.Instances
    73  	Multiversion  echo.Instances
    74  	VM            echo.Instances
    75  	External      echo.Instances
    76  	Naked         echo.Instances
    77  	Headless      echo.Instances
    78  	HeadlessNaked echo.Instances
    79  )
    80  
    81  const (
    82  	ASvc             = "a"
    83  	BSvc             = "b"
    84  	CSvc             = "c"
    85  	DSvc             = "d"
    86  	ESvc             = "e"
    87  	MultiversionSvc  = "multiversion"
    88  	VMSvc            = "vm"
    89  	HeadlessSvc      = "headless"
    90  	NakedSvc         = "naked"
    91  	HeadlessNakedSvc = "headless-naked"
    92  	ExternalSvc      = "external"
    93  )
    94  
    95  // Run runs the given reachability test cases with the context.
    96  func Run(testCases []TestCase, t framework.TestContext) {
    97  	callOptions := []echo.CallOptions{
    98  		{
    99  			Port: echo.Port{
   100  				Name: "http",
   101  			},
   102  			Scheme: scheme.HTTP,
   103  		},
   104  		{
   105  			Port: echo.Port{
   106  				Name: "http",
   107  			},
   108  			Scheme: scheme.WebSocket,
   109  		},
   110  		{
   111  			Port: echo.Port{
   112  				Name: "tcp",
   113  			},
   114  			Scheme: scheme.TCP,
   115  		},
   116  		{
   117  			Port: echo.Port{
   118  				Name: "grpc",
   119  			},
   120  			Scheme: scheme.GRPC,
   121  		},
   122  		{
   123  			Port: echo.Port{
   124  				Name: "https",
   125  			},
   126  			Scheme: scheme.HTTPS,
   127  		},
   128  	}
   129  
   130  	for _, c := range testCases {
   131  		// Create a copy to avoid races, as tests are run in parallel
   132  		c := c
   133  		testName := strings.TrimSuffix(c.ConfigFile, filepath.Ext(c.ConfigFile))
   134  		t.NewSubTest(testName).Run(func(t framework.TestContext) {
   135  			// Apply the policy.
   136  			cfg := t.ConfigIstio().File(c.Namespace.Name(), filepath.Join("./testdata", c.ConfigFile))
   137  			retry.UntilSuccessOrFail(t, func() error {
   138  				t.Logf("[%s] [%v] Apply config %s", testName, time.Now(), c.ConfigFile)
   139  				// TODO(https://github.com/istio/istio/issues/20460) We shouldn't need a retry loop
   140  				return cfg.Apply(apply.Wait)
   141  			})
   142  			for _, clients := range []echo.Instances{A, B, Headless, Naked, HeadlessNaked} {
   143  				for _, from := range clients {
   144  					from := from
   145  					t.NewSubTest(fmt.Sprintf("%s in %s",
   146  						from.Config().Service, from.Config().Cluster.StableName())).Run(func(t framework.TestContext) {
   147  						destinationSets := []echo.Instances{
   148  							A,
   149  							B,
   150  							// only hit same network headless services
   151  							match.Network(from.Config().Cluster.NetworkName()).GetMatches(Headless),
   152  							// only hit same cluster multiversion services
   153  							match.Cluster(from.Config().Cluster).GetMatches(Multiversion),
   154  							// only hit same cluster naked services
   155  							match.Cluster(from.Config().Cluster).GetMatches(Naked),
   156  							VM,
   157  						}
   158  
   159  						for _, to := range destinationSets {
   160  							to := to
   161  							if c.ExpectDestinations != nil {
   162  								to = c.ExpectDestinations(from, to)
   163  							}
   164  							toClusters := to.Clusters()
   165  							if len(toClusters) == 0 {
   166  								continue
   167  							}
   168  							// grabbing the 0th assumes all echos in destinations have the same service name
   169  							if isNakedToVM(from, to) {
   170  								// No need to waste time on these tests which will time out on connection instead of fail-fast
   171  								continue
   172  							}
   173  
   174  							copts := &callOptions
   175  							// If test case specified service call options, use that instead.
   176  							if c.CallOpts != nil {
   177  								copts = &c.CallOpts
   178  							}
   179  							for _, opts := range *copts {
   180  								// Copy the loop variables so they won't change for the subtests.
   181  								opts := opts
   182  
   183  								// Set the target on the call options.
   184  								opts.To = to
   185  								if len(toClusters) == 1 {
   186  									opts.Count = 1
   187  								}
   188  
   189  								// TODO(https://github.com/istio/istio/issues/37629) go back to converge
   190  								opts.Retry.Options = []retry.Option{retry.Converge(1)}
   191  								// TODO(https://github.com/istio/istio/issues/37629) go back to 5s
   192  								opts.Timeout = time.Second * 10
   193  
   194  								expectSuccess := c.ExpectSuccess(from, opts)
   195  								expectMTLS := c.ExpectMTLS(from, opts)
   196  								var tpe string
   197  								if expectSuccess {
   198  									tpe = "positive"
   199  									opts.Check = check.And(
   200  										check.OK(),
   201  										check.ReachedTargetClusters(t))
   202  									if expectMTLS {
   203  										opts.Check = check.And(opts.Check,
   204  											check.MTLSForHTTP())
   205  									}
   206  								} else {
   207  									tpe = "negative"
   208  									opts.Check = check.NotOK()
   209  								}
   210  								include := c.Include
   211  								if include == nil {
   212  									include = func(_ echo.Instance, _ echo.CallOptions) bool { return true }
   213  								}
   214  								if include(from, opts) {
   215  									subTestName := fmt.Sprintf("%s to %s:%s%s %s",
   216  										opts.Scheme,
   217  										to.Config().Service,
   218  										opts.Port.Name,
   219  										opts.HTTP.Path,
   220  										tpe)
   221  
   222  									t.NewSubTest(subTestName).
   223  										Run(func(t framework.TestContext) {
   224  											if (from.Config().IsNaked()) && len(toClusters) > 1 {
   225  												// TODO use echotest to generate the cases that would work for multi-network + naked
   226  												t.Skip("https://github.com/istio/istio/issues/37307")
   227  											}
   228  
   229  											from.CallOrFail(t, opts)
   230  										})
   231  								}
   232  							}
   233  						}
   234  					})
   235  				}
   236  			}
   237  		})
   238  	}
   239  }
   240  
   241  // Exclude calls from naked->VM since naked has no Envoy
   242  // However, no endpoint exists for VM in k8s, so calls from naked->VM will fail, regardless of mTLS
   243  func isNakedToVM(from echo.Instance, to echo.Target) bool {
   244  	return from.Config().IsNaked() && to.Config().IsVM()
   245  }
   246  
   247  func CreateCustomInstances(apps *deployment.SingleNamespaceView) error {
   248  	for index, namespacedName := range apps.EchoNamespace.All.NamespacedNames() {
   249  		switch {
   250  		case namespacedName.Name == ASvc:
   251  			A = apps.EchoNamespace.All[index]
   252  		case namespacedName.Name == BSvc:
   253  			B = apps.EchoNamespace.All[index]
   254  		case namespacedName.Name == CSvc:
   255  			C = apps.EchoNamespace.All[index]
   256  		case namespacedName.Name == DSvc:
   257  			D = apps.EchoNamespace.All[index]
   258  		case namespacedName.Name == ESvc:
   259  			E = apps.EchoNamespace.All[index]
   260  		case namespacedName.Name == HeadlessSvc:
   261  			Headless = apps.EchoNamespace.All[index]
   262  		case namespacedName.Name == HeadlessNakedSvc:
   263  			HeadlessNaked = apps.EchoNamespace.All[index]
   264  		case namespacedName.Name == ExternalSvc:
   265  			External = apps.EchoNamespace.All[index]
   266  		case namespacedName.Name == NakedSvc:
   267  			Naked = apps.EchoNamespace.All[index]
   268  		case namespacedName.Name == VMSvc:
   269  			VM = apps.EchoNamespace.All[index]
   270  		case namespacedName.Name == MultiversionSvc:
   271  			Multiversion = apps.EchoNamespace.All[index]
   272  		}
   273  	}
   274  	return nil
   275  }