1// Copyright 2018 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 15package client_test 16 17import ( 18 "context" 19 "fmt" 20 "net" 21 "testing" 22 23 v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2" 24 core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 25 listener "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener" 26 route "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" 27 hcm "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2" 28 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2" 29 "github.com/envoyproxy/go-control-plane/pkg/cache/types" 30 "github.com/envoyproxy/go-control-plane/pkg/cache/v2" 31 xds "github.com/envoyproxy/go-control-plane/pkg/server/v2" 32 "github.com/envoyproxy/go-control-plane/pkg/wellknown" 33 34 "google.golang.org/grpc" 35 36 meshconfig "istio.io/api/mesh/v1alpha1" 37 38 "istio.io/istio/mixer/test/client/env" 39 "istio.io/istio/pilot/pkg/model" 40 "istio.io/istio/pilot/pkg/networking" 41 "istio.io/istio/pilot/pkg/networking/plugin" 42 "istio.io/istio/pilot/pkg/networking/plugin/mixer" 43 pilotutil "istio.io/istio/pilot/pkg/networking/util" 44 "istio.io/istio/pkg/config/host" 45 "istio.io/istio/pkg/config/labels" 46) 47 48const ( 49 envoyConf = ` 50admin: 51 access_log_path: {{.AccessLogPath}} 52 address: 53 socket_address: 54 address: 127.0.0.1 55 port_value: {{.Ports.AdminPort}} 56node: 57 id: id 58 cluster: unknown 59 metadata: 60 # these two must come together and they need to be set 61 NODE_UID: pod.ns 62 NODE_NAMESPACE: ns 63dynamic_resources: 64 lds_config: { ads: {} } 65 ads_config: 66 api_type: GRPC 67 grpc_services: 68 envoy_grpc: 69 cluster_name: xds 70static_resources: 71 clusters: 72 - name: xds 73 http2_protocol_options: {} 74 connect_timeout: 5s 75 type: STATIC 76 hosts: 77 - socket_address: 78 address: 127.0.0.1 79 port_value: {{.Ports.DiscoveryPort}} 80 - name: "outbound|||svc.ns3" 81 connect_timeout: 5s 82 type: STATIC 83 hosts: 84 - socket_address: 85 address: 127.0.0.1 86 port_value: {{.Ports.ServerProxyPort}} 87 - name: "inbound|||backend" 88 connect_timeout: 5s 89 type: STATIC 90 hosts: 91 - socket_address: 92 address: 127.0.0.1 93 port_value: {{.Ports.BackendPort}} 94 - name: "outbound|9091||mixer_server" 95 http2_protocol_options: {} 96 connect_timeout: 5s 97 type: STATIC 98 hosts: 99 - socket_address: 100 address: 127.0.0.1 101 port_value: {{.Ports.MixerPort}} 102` 103 104 checkAttributesOkOutbound = ` 105{ 106 "connection.mtls": false, 107 "origin.ip": "[127 0 0 1]", 108 "context.protocol": "http", 109 "context.reporter.kind": "outbound", 110 "context.reporter.uid": "kubernetes://pod2.ns2", 111 "context.proxy_version": "1.1.1", 112 "destination.service.host": "svc.ns3", 113 "destination.service.name": "svc", 114 "destination.service.namespace": "ns3", 115 "destination.service.uid": "istio://ns3/services/svc", 116 "source.uid": "kubernetes://pod2.ns2", 117 "source.namespace": "ns2", 118 "request.headers": { 119 ":method": "GET", 120 ":path": "/echo", 121 ":authority": "*", 122 "x-forwarded-proto": "http", 123 "x-request-id": "*" 124 }, 125 "request.host": "*", 126 "request.path": "/echo", 127 "request.time": "*", 128 "request.useragent": "Go-http-client/1.1", 129 "request.method": "GET", 130 "request.scheme": "http", 131 "request.url_path": "/echo" 132} 133` 134 checkAttributesOkInbound = ` 135{ 136 "connection.mtls": false, 137 "origin.ip": "[127 0 0 1]", 138 "context.protocol": "http", 139 "context.reporter.kind": "inbound", 140 "context.reporter.uid": "kubernetes://pod1.ns2", 141 "context.proxy_version": "1.1.1", 142 "destination.ip": "[0 0 0 0 0 0 0 0 0 0 255 255 127 0 0 1]", 143 "destination.port": "*", 144 "destination.namespace": "ns2", 145 "destination.uid": "kubernetes://pod1.ns2", 146 "destination.mesh.id": "helloworld", 147 "destination.service.host": "svc.ns3", 148 "destination.service.name": "svc", 149 "destination.service.namespace": "ns3", 150 "destination.service.uid": "istio://ns3/services/svc", 151 "source.uid": "kubernetes://pod2.ns2", 152 "request.headers": { 153 ":method": "GET", 154 ":path": "/echo", 155 ":authority": "*", 156 "x-forwarded-proto": "http", 157 "x-request-id": "*" 158 }, 159 "request.host": "*", 160 "request.path": "/echo", 161 "request.time": "*", 162 "request.useragent": "Go-http-client/1.1", 163 "request.method": "GET", 164 "request.scheme": "http", 165 "request.url_path": "/echo" 166} 167` 168 reportAttributesOkOutbound = ` 169{ 170 "connection.mtls": false, 171 "origin.ip": "[127 0 0 1]", 172 "context.protocol": "http", 173 "context.proxy_error_code": "-", 174 "context.reporter.kind": "outbound", 175 "context.reporter.uid": "kubernetes://pod2.ns2", 176 "context.proxy_version": "1.1.1", 177 "destination.ip": "[127 0 0 1]", 178 "destination.port": "*", 179 "destination.service.host": "svc.ns3", 180 "destination.service.name": "svc", 181 "destination.service.namespace": "ns3", 182 "destination.service.uid": "istio://ns3/services/svc", 183 "source.uid": "kubernetes://pod2.ns2", 184 "source.namespace": "ns2", 185 "check.cache_hit": false, 186 "quota.cache_hit": false, 187 "request.headers": { 188 ":method": "GET", 189 ":path": "/echo", 190 ":authority": "*", 191 "x-forwarded-proto": "http", 192 "x-istio-attributes": "-", 193 "x-request-id": "*" 194 }, 195 "request.host": "*", 196 "request.path": "/echo", 197 "request.time": "*", 198 "request.useragent": "Go-http-client/1.1", 199 "request.method": "GET", 200 "request.scheme": "http", 201 "request.size": 0, 202 "request.total_size": "*", 203 "request.url_path": "/echo", 204 "response.time": "*", 205 "response.size": 0, 206 "response.duration": "*", 207 "response.code": 200, 208 "response.headers": { 209 "date": "*", 210 "content-length": "0", 211 ":status": "200", 212 "server": "envoy" 213 }, 214 "response.total_size": "*" 215}` 216 217 reportAttributesOkInbound = ` 218{ 219 "connection.mtls": false, 220 "origin.ip": "[127 0 0 1]", 221 "context.protocol": "http", 222 "context.proxy_error_code": "-", 223 "context.reporter.kind": "inbound", 224 "context.reporter.uid": "kubernetes://pod1.ns2", 225 "context.proxy_version": "1.1.1", 226 "destination.ip": "[0 0 0 0 0 0 0 0 0 0 255 255 127 0 0 1]", 227 "destination.port": "*", 228 "destination.namespace": "ns2", 229 "destination.uid": "kubernetes://pod1.ns2", 230 "destination.mesh.id": "helloworld", 231 "destination.service.host": "svc.ns3", 232 "destination.service.name": "svc", 233 "destination.service.namespace": "ns3", 234 "destination.service.uid": "istio://ns3/services/svc", 235 "source.uid": "kubernetes://pod2.ns2", 236 "check.cache_hit": false, 237 "quota.cache_hit": false, 238 "request.headers": { 239 ":method": "GET", 240 ":path": "/echo", 241 ":authority": "*", 242 "x-forwarded-proto": "http", 243 "x-istio-attributes": "-", 244 "x-request-id": "*" 245 }, 246 "request.host": "*", 247 "request.path": "/echo", 248 "request.time": "*", 249 "request.useragent": "Go-http-client/1.1", 250 "request.method": "GET", 251 "request.scheme": "http", 252 "request.size": 0, 253 "request.total_size": "*", 254 "request.url_path": "/echo", 255 "response.time": "*", 256 "response.size": 0, 257 "response.duration": "*", 258 "response.code": 200, 259 "response.headers": { 260 "date": "*", 261 "content-length": "0", 262 ":status": "200", 263 "server": "envoy" 264 }, 265 "response.total_size": "*" 266}` 267) 268 269func TestPilotPlugin(t *testing.T) { 270 s := env.NewTestSetup(env.PilotPluginTest, t) 271 s.EnvoyTemplate = envoyConf 272 grpcServer := grpc.NewServer() 273 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Ports().DiscoveryPort)) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 snapshots := cache.NewSnapshotCache(true, mock{}, nil) 279 _ = snapshots.SetSnapshot(id, makeSnapshot(s, t)) 280 server := xds.NewServer(context.Background(), snapshots, nil) 281 discovery.RegisterAggregatedDiscoveryServiceServer(grpcServer, server) 282 go func() { 283 _ = grpcServer.Serve(lis) 284 }() 285 defer grpcServer.GracefulStop() 286 287 s.SetMixerSourceUID("pod.ns") 288 289 if err := s.SetUp(); err != nil { 290 t.Fatalf("Failed to setup test: %v", err) 291 } 292 defer s.TearDown() 293 294 s.WaitEnvoyReady() 295 296 // Issues a GET echo request with 0 size body 297 if _, _, err := env.HTTPGet(fmt.Sprintf("http://localhost:%d/echo", s.Ports().ClientProxyPort)); err != nil { 298 t.Errorf("Failed in request: %v", err) 299 } 300 s.VerifyCheck("http-outbound", checkAttributesOkOutbound) 301 s.VerifyCheck("http-inbound", checkAttributesOkInbound) 302 s.VerifyTwoReports("http", reportAttributesOkOutbound, reportAttributesOkInbound) 303} 304 305type mock struct{} 306 307func (mock) ID(*core.Node) string { 308 return id 309} 310func (mock) GetProxyServiceInstances(_ *model.Proxy) ([]*model.ServiceInstance, error) { 311 return nil, nil 312} 313func (mock) GetProxyWorkloadLabels(proxy *model.Proxy) (labels.Collection, error) { 314 return nil, nil 315} 316func (mock) GetService(_ host.Name) (*model.Service, error) { return nil, nil } 317func (mock) InstancesByPort(_ *model.Service, _ int, _ labels.Collection) ([]*model.ServiceInstance, error) { 318 return nil, nil 319} 320func (mock) ManagementPorts(_ string) model.PortList { return nil } 321func (mock) Services() ([]*model.Service, error) { return nil, nil } 322func (mock) WorkloadHealthCheckInfo(_ string) model.ProbeList { return nil } 323func (mock) GetIstioServiceAccounts(_ *model.Service, ports []int) []string { return nil } 324 325const ( 326 id = "id" 327) 328 329var ( 330 svc = model.Service{ 331 Hostname: "svc.ns3", 332 Attributes: model.ServiceAttributes{ 333 Name: "svc", 334 Namespace: "ns3", 335 UID: "istio://ns3/services/svc", 336 }, 337 } 338 pushContext = model.PushContext{ 339 ServiceByHostnameAndNamespace: map[host.Name]map[string]*model.Service{ 340 host.Name("svc.ns3"): { 341 "ns3": &svc, 342 }, 343 }, 344 Mesh: &meshconfig.MeshConfig{ 345 MixerCheckServer: "mixer_server:9091", 346 MixerReportServer: "mixer_server:9091", 347 EnableClientSidePolicyCheck: true, 348 }, 349 ServiceDiscovery: mock{}, 350 } 351 serverParams = plugin.InputParams{ 352 ListenerProtocol: networking.ListenerProtocolHTTP, 353 Node: &model.Proxy{ 354 ID: "pod1.ns2", 355 Type: model.SidecarProxy, 356 IstioVersion: &model.IstioVersion{Major: 1, Minor: 1, Patch: 1}, 357 Metadata: &model.NodeMetadata{MeshID: "helloworld"}, 358 }, 359 ServiceInstance: &model.ServiceInstance{Service: &svc}, 360 Push: &pushContext, 361 } 362 clientParams = plugin.InputParams{ 363 ListenerProtocol: networking.ListenerProtocolHTTP, 364 Node: &model.Proxy{ 365 ID: "pod2.ns2", 366 Type: model.SidecarProxy, 367 IstioVersion: &model.IstioVersion{Major: 1, Minor: 1, Patch: 1}, 368 Metadata: &model.NodeMetadata{MeshID: "helloworld"}, 369 }, 370 Service: &svc, 371 Push: &pushContext, 372 } 373) 374 375func makeRoute(cluster string) *v2.RouteConfiguration { 376 return &v2.RouteConfiguration{ 377 Name: cluster, 378 VirtualHosts: []*route.VirtualHost{{ 379 Name: cluster, 380 Domains: []string{"*"}, 381 Routes: []*route.Route{{ 382 Match: &route.RouteMatch{PathSpecifier: &route.RouteMatch_Prefix{Prefix: "/"}}, 383 Action: &route.Route_Route{Route: &route.RouteAction{ 384 ClusterSpecifier: &route.RouteAction_Cluster{Cluster: cluster}, 385 }}, 386 }}, 387 }}, 388 } 389} 390 391func makeListener(port uint16, route string) (*v2.Listener, *hcm.HttpConnectionManager) { 392 return &v2.Listener{ 393 Name: route, 394 Address: &core.Address{Address: &core.Address_SocketAddress{SocketAddress: &core.SocketAddress{ 395 Address: "127.0.0.1", 396 PortSpecifier: &core.SocketAddress_PortValue{PortValue: uint32(port)}}}}, 397 }, &hcm.HttpConnectionManager{ 398 CodecType: hcm.HttpConnectionManager_AUTO, 399 StatPrefix: route, 400 RouteSpecifier: &hcm.HttpConnectionManager_Rds{ 401 Rds: &hcm.Rds{RouteConfigName: route, ConfigSource: &core.ConfigSource{ 402 ConfigSourceSpecifier: &core.ConfigSource_Ads{Ads: &core.AggregatedConfigSource{}}, 403 }}, 404 }, 405 HttpFilters: []*hcm.HttpFilter{{Name: wellknown.Router}}, 406 } 407} 408 409func makeSnapshot(s *env.TestSetup, t *testing.T) cache.Snapshot { 410 clientListener, clientManager := makeListener(s.Ports().ClientProxyPort, "outbound|||svc.ns3") 411 serverListener, serverManager := makeListener(s.Ports().ServerProxyPort, "inbound|||backend") 412 clientRoute := makeRoute("outbound|||svc.ns3") 413 serverRoute := makeRoute("inbound|||backend") 414 415 p := mixer.NewPlugin() 416 417 serverMutable := networking.MutableObjects{Listener: serverListener, FilterChains: []networking.FilterChain{{}}} 418 if err := p.OnInboundListener(&serverParams, &serverMutable); err != nil { 419 t.Error(err) 420 } 421 serverManager.HttpFilters = append(serverMutable.FilterChains[0].HTTP, serverManager.HttpFilters...) 422 serverListener.FilterChains = []*listener.FilterChain{{Filters: []*listener.Filter{{ 423 Name: "http", 424 ConfigType: &listener.Filter_TypedConfig{TypedConfig: pilotutil.MessageToAny(serverManager)}, 425 }}}} 426 427 clientMutable := networking.MutableObjects{Listener: clientListener, FilterChains: []networking.FilterChain{{}}} 428 if err := p.OnOutboundListener(&clientParams, &clientMutable); err != nil { 429 t.Error(err) 430 } 431 clientManager.HttpFilters = append(clientMutable.FilterChains[0].HTTP, clientManager.HttpFilters...) 432 clientListener.FilterChains = []*listener.FilterChain{{Filters: []*listener.Filter{{ 433 Name: "http", 434 ConfigType: &listener.Filter_TypedConfig{TypedConfig: pilotutil.MessageToAny(clientManager)}, 435 }}}} 436 437 p.OnInboundRouteConfiguration(&serverParams, serverRoute) 438 p.OnOutboundRouteConfiguration(&clientParams, clientRoute) 439 440 snapshot := cache.Snapshot{} 441 snapshot.Resources[types.Route] = cache.NewResources("http", []types.Resource{clientRoute, serverRoute}) 442 snapshot.Resources[types.Listener] = cache.NewResources("http", []types.Resource{clientListener, serverListener}) 443 return snapshot 444} 445