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 }