istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/multiplecontrolplanes/main_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 multiplecontrolplanes
    19  
    20  import (
    21  	"fmt"
    22  	"net/http"
    23  	"testing"
    24  
    25  	"istio.io/istio/pkg/config/protocol"
    26  	"istio.io/istio/pkg/http/headers"
    27  	"istio.io/istio/pkg/test/echo/common/scheme"
    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/common/deployment"
    32  	"istio.io/istio/pkg/test/framework/components/istio"
    33  	"istio.io/istio/pkg/test/framework/components/namespace"
    34  	"istio.io/istio/pkg/test/framework/label"
    35  	"istio.io/istio/pkg/test/framework/resource"
    36  	"istio.io/istio/pkg/test/framework/resource/config/apply"
    37  )
    38  
    39  var (
    40  	// Istio System Namespaces
    41  	userGroup1NS namespace.Instance
    42  	userGroup2NS namespace.Instance
    43  
    44  	// Application Namespaces.
    45  	// echo1NS is under userGroup1NS controlplane and echo2NS and echo3NS are under userGroup2NS controlplane
    46  	echo1NS    namespace.Instance
    47  	echo2NS    namespace.Instance
    48  	echo3NS    namespace.Instance
    49  	externalNS namespace.Instance
    50  	apps       deployment.Echos
    51  )
    52  
    53  // TestMain defines the entrypoint for multiple controlplane tests using revisions and discoverySelectors.
    54  func TestMain(m *testing.M) {
    55  	// nolint: staticcheck
    56  	framework.
    57  		NewSuite(m).
    58  		RequireMultiPrimary().
    59  		// Requires two CPs with specific names to be configured.
    60  		Label(label.CustomSetup).
    61  		// We are deploying two isolated environments, which CNI doesn't support.
    62  		// We could deploy one of the usergroups as the CNI owner, but for now we skip
    63  		SkipIf("CNI is not suppored", func(ctx resource.Context) bool {
    64  			c, _ := istio.DefaultConfig(ctx)
    65  			return c.EnableCNI
    66  		}).
    67  		SetupParallel(
    68  			namespace.Setup(&userGroup1NS, namespace.Config{Prefix: "usergroup-1", Labels: map[string]string{"usergroup": "usergroup-1"}}),
    69  			namespace.Setup(&userGroup2NS, namespace.Config{Prefix: "usergroup-2", Labels: map[string]string{"usergroup": "usergroup-2"}})).
    70  		Setup(istio.Setup(nil, func(ctx resource.Context, cfg *istio.Config) {
    71  			s := ctx.Settings()
    72  			// TODO test framework has to be enhanced to use istioNamespace in istioctl commands used for VM config
    73  			s.SkipWorkloadClasses = append(s.SkipWorkloadClasses, echo.VM)
    74  			s.DisableDefaultExternalServiceConnectivity = true
    75  
    76  			cfg.Values["global.istioNamespace"] = userGroup1NS.Name()
    77  			cfg.SystemNamespace = userGroup1NS.Name()
    78  			cfg.ControlPlaneValues = fmt.Sprintf(`
    79  namespace: %s
    80  revision: usergroup-1
    81  meshConfig:
    82    # REGISTRY_ONLY on one control plane is used to verify custom resources scoping
    83    outboundTrafficPolicy:
    84      mode: REGISTRY_ONLY
    85    # CR scoping requires discoverySelectors to be configured
    86    discoverySelectors:
    87      - matchLabels:
    88          usergroup: usergroup-1
    89  values:
    90    global:
    91      istioNamespace: %s`,
    92  				userGroup1NS.Name(), userGroup1NS.Name())
    93  		})).
    94  		Setup(istio.Setup(nil, func(ctx resource.Context, cfg *istio.Config) {
    95  			s := ctx.Settings()
    96  			// TODO test framework has to be enhanced to use istioNamespace in istioctl commands used for VM config
    97  			s.SkipWorkloadClasses = append(s.SkipWorkloadClasses, echo.VM)
    98  
    99  			cfg.Values["global.istioNamespace"] = userGroup2NS.Name()
   100  			cfg.SystemNamespace = userGroup2NS.Name()
   101  			cfg.ControlPlaneValues = fmt.Sprintf(`
   102  namespace: %s
   103  revision: usergroup-2
   104  meshConfig:
   105    # CR scoping requires discoverySelectors to be configured
   106    discoverySelectors:
   107      - matchLabels:
   108          usergroup: usergroup-2
   109  values:
   110    global:
   111      istioNamespace: %s`,
   112  				userGroup2NS.Name(), userGroup2NS.Name())
   113  		})).
   114  		SetupParallel(
   115  			// application namespaces are labeled according to the required control plane ownership.
   116  			namespace.Setup(&echo1NS, namespace.Config{Prefix: "echo1", Inject: true, Revision: "usergroup-1", Labels: map[string]string{"usergroup": "usergroup-1"}}),
   117  			namespace.Setup(&echo2NS, namespace.Config{Prefix: "echo2", Inject: true, Revision: "usergroup-2", Labels: map[string]string{"usergroup": "usergroup-2"}}),
   118  			namespace.Setup(&echo3NS, namespace.Config{Prefix: "echo3", Inject: true, Revision: "usergroup-2", Labels: map[string]string{"usergroup": "usergroup-2"}}),
   119  			namespace.Setup(&externalNS, namespace.Config{Prefix: "external", Inject: false})).
   120  		SetupParallel(
   121  			deployment.Setup(&apps, deployment.Config{
   122  				Namespaces: []namespace.Getter{
   123  					namespace.Future(&echo1NS),
   124  					namespace.Future(&echo2NS),
   125  					namespace.Future(&echo3NS),
   126  				},
   127  				ExternalNamespace: namespace.Future(&externalNS),
   128  			})).
   129  		Run()
   130  }
   131  
   132  // TestMultiControlPlane sets up two distinct istio control planes and verify if resources and traffic are properly isolated
   133  func TestMultiControlPlane(t *testing.T) {
   134  	framework.NewTest(t).
   135  		Run(func(t framework.TestContext) {
   136  			// configure peerauthentication per system namespace
   137  			restrictUserGroups(t)
   138  
   139  			testCases := []struct {
   140  				name       string
   141  				statusCode int
   142  				from       echo.Instances
   143  				to         echo.Instances
   144  			}{
   145  				{
   146  					name:       "workloads within same usergroup can communicate, same namespace",
   147  					statusCode: http.StatusOK,
   148  					from:       apps.NS[0].A,
   149  					to:         apps.NS[0].B,
   150  				},
   151  				{
   152  					name:       "workloads within same usergroup can communicate, different namespaces",
   153  					statusCode: http.StatusOK,
   154  					from:       apps.NS[1].A,
   155  					to:         apps.NS[2].B,
   156  				},
   157  				{
   158  					name:       "workloads within different usergroups cannot communicate, registry only",
   159  					statusCode: http.StatusBadGateway,
   160  					from:       apps.NS[0].A,
   161  					to:         apps.NS[1].B,
   162  				},
   163  				{
   164  					name:       "workloads within different usergroups cannot communicate, default passthrough",
   165  					statusCode: http.StatusServiceUnavailable,
   166  					from:       apps.NS[2].B,
   167  					to:         apps.NS[0].B,
   168  				},
   169  			}
   170  
   171  			for _, tc := range testCases {
   172  				t.NewSubTest(tc.name).Run(func(t framework.TestContext) {
   173  					tc.from[0].CallOrFail(t, echo.CallOptions{
   174  						To: tc.to,
   175  						Port: echo.Port{
   176  							Protocol:    protocol.HTTP,
   177  							ServicePort: 80,
   178  						},
   179  						Check: check.And(
   180  							check.ErrorOrStatus(tc.statusCode),
   181  						),
   182  					})
   183  				})
   184  			}
   185  		})
   186  }
   187  
   188  // TestCustomResourceScoping sets up a CustomResource and verifies that the configuration is not leaked to namespaces owned by a different control plane
   189  func TestCustomResourceScoping(t *testing.T) {
   190  	framework.NewTest(t).
   191  		Run(func(t framework.TestContext) {
   192  			// allow access to external service only for app-ns-2 namespace which is under usergroup-2
   193  			allowExternalService(t, apps.NS[1].Namespace.Name(), externalNS.Name(), "usergroup-2")
   194  
   195  			testCases := []struct {
   196  				name       string
   197  				statusCode int
   198  				from       echo.Instances
   199  			}{
   200  				{
   201  					name:       "workloads in SE configured namespace can reach external service",
   202  					statusCode: http.StatusOK,
   203  					from:       apps.NS[1].A,
   204  				},
   205  				{
   206  					name:       "workloads in non-SE configured namespace, but same usergroup can reach external service",
   207  					statusCode: http.StatusOK,
   208  					from:       apps.NS[2].A,
   209  				},
   210  				{
   211  					name:       "workloads in non-SE configured usergroup cannot reach external service",
   212  					statusCode: http.StatusBadGateway,
   213  					from:       apps.NS[0].A,
   214  				},
   215  			}
   216  			for _, tc := range testCases {
   217  				t.NewSubTestf(tc.name).Run(func(t framework.TestContext) {
   218  					tc.from[0].CallOrFail(t, echo.CallOptions{
   219  						Address: apps.External.All[0].Address(),
   220  						HTTP: echo.HTTP{
   221  							Headers: HostHeader(apps.External.All[0].Config().DefaultHostHeader),
   222  						},
   223  						Port:   echo.Port{Name: "http", ServicePort: 80},
   224  						Scheme: scheme.HTTP,
   225  						Check: check.And(
   226  							check.ErrorOrStatus(tc.statusCode),
   227  						),
   228  					})
   229  				})
   230  			}
   231  		})
   232  }
   233  
   234  func HostHeader(header string) http.Header {
   235  	return headers.New().WithHost(header).Build()
   236  }
   237  
   238  func restrictUserGroups(t framework.TestContext) {
   239  	for _, ns := range []string{userGroup1NS.Name(), userGroup2NS.Name()} {
   240  		t.ConfigIstio().Eval(ns, map[string]any{
   241  			"Namespace": ns,
   242  		}, `apiVersion: security.istio.io/v1beta1
   243  kind: PeerAuthentication
   244  metadata:
   245    name: "usergroup-peerauth"
   246    namespace: {{ .Namespace }}
   247  spec:
   248    mtls:
   249      mode: STRICT
   250  `).ApplyOrFail(t, apply.NoCleanup)
   251  	}
   252  }
   253  
   254  func allowExternalService(t framework.TestContext, ns string, externalNs string, revision string) {
   255  	t.ConfigIstio().Eval(ns, map[string]any{
   256  		"Namespace": externalNs,
   257  		"Revision":  revision,
   258  	}, `apiVersion: networking.istio.io/v1alpha3
   259  kind: ServiceEntry
   260  metadata:
   261    name: external-service
   262    labels:
   263      istio.io/rev: {{.Revision}}
   264  spec:
   265    hosts:
   266    - "fake.external.com"
   267    location: MESH_EXTERNAL
   268    resolution: DNS
   269    endpoints:
   270    - address: external.{{.Namespace}}.svc.cluster.local
   271    ports:
   272    - name: http
   273      number: 80
   274      protocol: HTTP
   275  `).ApplyOrFail(t, apply.NoCleanup)
   276  }