github.com/cilium/cilium@v1.16.2/pkg/ciliumenvoyconfig/cec_resource_parser_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package ciliumenvoyconfig 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "io" 11 "testing" 12 13 cilium "github.com/cilium/proxy/go/cilium/api" 14 envoy_config_cluster "github.com/cilium/proxy/go/envoy/config/cluster/v3" 15 envoy_config_core "github.com/cilium/proxy/go/envoy/config/core/v3" 16 envoy_config_listener "github.com/cilium/proxy/go/envoy/config/listener/v3" 17 envoy_config_http_healthcheck "github.com/cilium/proxy/go/envoy/extensions/filters/http/health_check/v3" 18 envoy_upstream_codec "github.com/cilium/proxy/go/envoy/extensions/filters/http/upstream_codec/v3" 19 envoy_config_http "github.com/cilium/proxy/go/envoy/extensions/filters/network/http_connection_manager/v3" 20 envoy_config_tcp "github.com/cilium/proxy/go/envoy/extensions/filters/network/tcp_proxy/v3" 21 envoy_config_tls "github.com/cilium/proxy/go/envoy/extensions/transport_sockets/tls/v3" 22 envoy_upstreams_http_v3 "github.com/cilium/proxy/go/envoy/extensions/upstreams/http/v3" 23 "github.com/sirupsen/logrus" 24 "github.com/stretchr/testify/assert" 25 "github.com/stretchr/testify/require" 26 "sigs.k8s.io/yaml" 27 28 "github.com/cilium/cilium/pkg/bpf" 29 "github.com/cilium/cilium/pkg/envoy" 30 cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 31 ) 32 33 type MockPort struct { 34 port uint16 35 cnt int 36 } 37 38 type MockPortAllocator struct { 39 port uint16 40 ports map[string]*MockPort 41 } 42 43 func NewMockPortAllocator() *MockPortAllocator { 44 return &MockPortAllocator{ 45 port: 1024, 46 ports: make(map[string]*MockPort), 47 } 48 } 49 50 func (m *MockPortAllocator) AllocateCRDProxyPort(name string) (uint16, error) { 51 if mp, exists := m.ports[name]; exists { 52 return mp.port, nil 53 } 54 m.port++ 55 m.ports[name] = &MockPort{port: m.port} 56 57 return m.port, nil 58 } 59 60 func (m *MockPortAllocator) AckProxyPort(ctx context.Context, name string) error { 61 mp, exists := m.ports[name] 62 if !exists { 63 return fmt.Errorf("Non-allocated port %s", name) 64 } 65 mp.cnt++ 66 return nil 67 } 68 69 func (m *MockPortAllocator) ReleaseProxyPort(name string) error { 70 mp, exists := m.ports[name] 71 if !exists { 72 return fmt.Errorf("Non-allocated port %s", name) 73 } 74 mp.cnt-- 75 if mp.cnt <= 0 { 76 delete(m.ports, name) 77 } 78 return nil 79 } 80 81 func TestUpstreamInject(t *testing.T) { 82 // 83 // Empty options 84 // 85 var opts envoy_upstreams_http_v3.HttpProtocolOptions 86 changed, err := injectCiliumUpstreamL7Filter(&opts, false) 87 assert.Nil(t, err) 88 assert.True(t, changed) 89 assert.NotNil(t, opts.HttpFilters) 90 assert.Len(t, opts.HttpFilters, 2) 91 assert.Equal(t, "cilium.l7policy", opts.HttpFilters[0].Name) 92 assert.Equal(t, ciliumL7FilterTypeURL, opts.HttpFilters[0].GetTypedConfig().TypeUrl) 93 assert.Equal(t, "envoy.filters.http.upstream_codec", opts.HttpFilters[1].Name) 94 assert.Equal(t, upstreamCodecFilterTypeURL, opts.HttpFilters[1].GetTypedConfig().TypeUrl) 95 // 96 // Check injected UpstreamProtocolOptions 97 // 98 assert.NotNil(t, opts.GetUseDownstreamProtocolConfig()) // no ALPN support 99 100 // already present 101 changed, err = injectCiliumUpstreamL7Filter(&opts, true) 102 assert.Nil(t, err) 103 assert.False(t, changed) 104 assert.NotNil(t, opts.HttpFilters) 105 assert.Len(t, opts.HttpFilters, 2) 106 assert.Equal(t, "cilium.l7policy", opts.HttpFilters[0].Name) 107 assert.Equal(t, ciliumL7FilterTypeURL, opts.HttpFilters[0].GetTypedConfig().TypeUrl) 108 assert.Equal(t, "envoy.filters.http.upstream_codec", opts.HttpFilters[1].Name) 109 assert.Equal(t, upstreamCodecFilterTypeURL, opts.HttpFilters[1].GetTypedConfig().TypeUrl) 110 // 111 // Existing Upstream protocol options are not overridden 112 // 113 assert.NotNil(t, opts.GetUseDownstreamProtocolConfig()) 114 115 // missing codec 116 opts = envoy_upstreams_http_v3.HttpProtocolOptions{ 117 HttpFilters: []*envoy_config_http.HttpFilter{ 118 { 119 Name: "cilium.l7policy", 120 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 121 TypedConfig: toAny(&cilium.L7Policy{}), 122 }, 123 }, 124 }, 125 } 126 changed, err = injectCiliumUpstreamL7Filter(&opts, true) 127 assert.Nil(t, err) 128 assert.True(t, changed) 129 assert.NotNil(t, opts.HttpFilters) 130 assert.Len(t, opts.HttpFilters, 2) 131 assert.Equal(t, "cilium.l7policy", opts.HttpFilters[0].Name) 132 assert.Equal(t, ciliumL7FilterTypeURL, opts.HttpFilters[0].GetTypedConfig().TypeUrl) 133 assert.Equal(t, "envoy.filters.http.upstream_codec", opts.HttpFilters[1].Name) 134 assert.Equal(t, upstreamCodecFilterTypeURL, opts.HttpFilters[1].GetTypedConfig().TypeUrl) 135 assert.NotNil(t, opts.GetAutoConfig()) // with ALPN support 136 137 // codec present 138 opts = envoy_upstreams_http_v3.HttpProtocolOptions{ 139 HttpFilters: []*envoy_config_http.HttpFilter{ 140 { 141 Name: "envoy.filters.http.upstream_codec", 142 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 143 TypedConfig: toAny(&envoy_upstream_codec.UpstreamCodec{}), 144 }, 145 }, 146 }, 147 } 148 changed, err = injectCiliumUpstreamL7Filter(&opts, true) 149 assert.Nil(t, err) 150 assert.True(t, changed) 151 assert.NotNil(t, opts.HttpFilters) 152 assert.Len(t, opts.HttpFilters, 2) 153 assert.Equal(t, "cilium.l7policy", opts.HttpFilters[0].Name) 154 assert.Equal(t, ciliumL7FilterTypeURL, opts.HttpFilters[0].GetTypedConfig().TypeUrl) 155 assert.Equal(t, "envoy.filters.http.upstream_codec", opts.HttpFilters[1].Name) 156 assert.Equal(t, upstreamCodecFilterTypeURL, opts.HttpFilters[1].GetTypedConfig().TypeUrl) 157 assert.NotNil(t, opts.GetAutoConfig()) // with ALPN support 158 159 // wrong order 160 // codec present 161 opts = envoy_upstreams_http_v3.HttpProtocolOptions{ 162 HttpFilters: []*envoy_config_http.HttpFilter{ 163 { 164 Name: "envoy.filters.http.upstream_codec", 165 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 166 TypedConfig: toAny(&envoy_upstream_codec.UpstreamCodec{}), 167 }, 168 }, 169 { 170 Name: "cilium.l7policy", 171 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 172 TypedConfig: toAny(&cilium.L7Policy{}), 173 }, 174 }, 175 }, 176 } 177 changed, err = injectCiliumUpstreamL7Filter(&opts, true) 178 assert.NotNil(t, err) 179 assert.False(t, changed) 180 assert.ErrorContains(t, err, "filter after codec filter: name:\"cilium.l7policy\"") 181 } 182 183 var xds1 = `version_info: "0" 184 resources: 185 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener 186 name: listener_0 187 address: 188 socket_address: 189 address: 127.0.0.1 190 port_value: 10000 191 filter_chains: 192 - filters: 193 - name: envoy.filters.network.http_connection_manager 194 typed_config: 195 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 196 stat_prefix: ingress_http 197 codec_type: AUTO 198 route_config: 199 virtual_hosts: 200 - name: "prometheus_metrics_route" 201 domains: ["*"] 202 routes: 203 - match: 204 path: "/metrics" 205 route: 206 cluster: "/envoy-admin" 207 prefix_rewrite: "/stats/prometheus" 208 use_remote_address: true 209 skip_xff_append: true 210 http_filters: 211 - name: envoy.filters.http.router 212 ` 213 214 func TestCiliumEnvoyConfigSpec(t *testing.T) { 215 jsonBytes, err := yaml.YAMLToJSON([]byte(xds1)) 216 require.NoError(t, err) 217 218 spec := cilium_v2.CiliumEnvoyConfigSpec{} 219 err = json.Unmarshal(jsonBytes, &spec) 220 require.NoError(t, err) 221 222 assert.Len(t, spec.Resources, 1) 223 assert.Equal(t, "type.googleapis.com/envoy.config.listener.v3.Listener", spec.Resources[0].TypeUrl) 224 message, err := spec.Resources[0].UnmarshalNew() 225 require.NoError(t, err) 226 227 listener, ok := message.(*envoy_config_listener.Listener) 228 assert.True(t, ok) 229 assert.Equal(t, "listener_0", listener.Name) 230 } 231 232 var ciliumEnvoyConfig = `apiVersion: cilium.io/v2 233 kind: CiliumEnvoyConfig 234 metadata: 235 name: envoy-prometheus-metrics-listener 236 spec: 237 version_info: "0" 238 resources: 239 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener 240 name: envoy-prometheus-metrics-listener 241 address: 242 socket_address: 243 address: 127.0.0.1 244 port_value: 10000 245 filter_chains: 246 - filters: 247 - name: envoy.filters.network.http_connection_manager 248 typed_config: 249 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 250 stat_prefix: ingress_http 251 codec_type: AUTO 252 rds: 253 route_config_name: local_route 254 use_remote_address: true 255 skip_xff_append: true 256 http_filters: 257 - name: envoy.filters.http.router 258 transport_socket: 259 name: envoy.transport_sockets.tls 260 typed_config: 261 "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext 262 require_client_certificate: true 263 common_tls_context: 264 tls_certificate_sds_secret_configs: 265 - name: cilium-secrets/server-mtls 266 validation_context_sds_secret_config: 267 name: validation_context 268 - "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret 269 name: validation_context 270 validation_context: 271 trusted_ca: 272 filename: /etc/ssl/certs/ca-certificates.crt 273 ` 274 275 func TestCiliumEnvoyConfig(t *testing.T) { 276 logger := logrus.New() 277 logger.SetOutput(io.Discard) 278 parser := cecResourceParser{ 279 logger: logger, 280 portAllocator: NewMockPortAllocator(), 281 } 282 283 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfig)) 284 require.NoError(t, err) 285 cec := &cilium_v2.CiliumEnvoyConfig{} 286 err = json.Unmarshal(jsonBytes, cec) 287 require.NoError(t, err) 288 assert.NotNil(t, cec.Spec.Resources) 289 assert.Len(t, cec.Spec.Resources, 2) 290 assert.Equal(t, "type.googleapis.com/envoy.config.listener.v3.Listener", cec.Spec.Resources[0].TypeUrl) 291 assert.Equal(t, "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret", cec.Spec.Resources[1].TypeUrl) 292 293 resources, err := parser.parseResources("namespace", "name", cec.Spec.Resources, false, false, true) 294 require.NoError(t, err) 295 assert.Len(t, resources.Listeners, 1) 296 assert.Equal(t, "namespace/name/envoy-prometheus-metrics-listener", resources.Listeners[0].Name) 297 assert.Equal(t, uint32(10000), resources.Listeners[0].Address.GetSocketAddress().GetPortValue()) 298 assert.Len(t, resources.Listeners[0].FilterChains, 1) 299 chain := resources.Listeners[0].FilterChains[0] 300 301 assert.NotNil(t, chain.TransportSocket) 302 assert.Equal(t, "envoy.transport_sockets.tls", chain.TransportSocket.Name) 303 msg, err := chain.TransportSocket.GetTypedConfig().UnmarshalNew() 304 require.NoError(t, err) 305 assert.NotNil(t, msg) 306 tls, ok := msg.(*envoy_config_tls.DownstreamTlsContext) 307 assert.True(t, ok) 308 assert.NotNil(t, tls) 309 // 310 // Check that missing SDS config sources are automatically filled in 311 // 312 tlsContext := tls.CommonTlsContext 313 assert.NotNil(t, tlsContext) 314 for _, sc := range tlsContext.TlsCertificateSdsSecretConfigs { 315 checkCiliumXDS(t, sc.SdsConfig) 316 // Check that the already qualified secret name was not changed 317 assert.Equal(t, "cilium-secrets/server-mtls", sc.Name) 318 } 319 sdsConfig := tlsContext.GetValidationContextSdsSecretConfig() 320 assert.NotNil(t, sdsConfig) 321 checkCiliumXDS(t, sdsConfig.SdsConfig) 322 // Check that secret name was qualified 323 assert.Equal(t, "namespace/name/validation_context", sdsConfig.Name) 324 325 assert.Len(t, chain.Filters, 1) 326 assert.Equal(t, "envoy.filters.network.http_connection_manager", chain.Filters[0].Name) 327 message, err := chain.Filters[0].GetTypedConfig().UnmarshalNew() 328 require.NoError(t, err) 329 assert.NotNil(t, message) 330 hcm, ok := message.(*envoy_config_http.HttpConnectionManager) 331 assert.True(t, ok) 332 assert.NotNil(t, hcm) 333 334 // 335 // Check that missing RDS config source is automatically filled in 336 // 337 rds := hcm.GetRds() 338 require.NotNil(t, rds) 339 assert.Equal(t, "namespace/name/local_route", rds.RouteConfigName) 340 checkCiliumXDS(t, rds.GetConfigSource()) 341 342 // 343 // Check that HTTP filters are parsed 344 // 345 assert.Len(t, hcm.HttpFilters, 1) 346 assert.Equal(t, "envoy.filters.http.router", hcm.HttpFilters[0].Name) 347 348 // 349 // Check that secret name was qualified 350 // 351 assert.Equal(t, "namespace/name/validation_context", resources.Secrets[0].Name) 352 } 353 354 var ciliumEnvoyConfigInvalid = `apiVersion: cilium.io/v2 355 kind: CiliumEnvoyConfig 356 metadata: 357 name: envoy-prometheus-metrics-listener 358 spec: 359 version_info: "0" 360 resources: 361 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener 362 name: envoy-prometheus-metrics-listener 363 address: 364 socket_address: 365 address: 127.0.0.1 366 filter_chains: 367 - filters: 368 - name: envoy.filters.network.http_connection_manager 369 typed_config: 370 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 371 stat_prefix: ingress_http 372 codec_type: AUTO 373 rds: 374 route_config_name: local_route 375 use_remote_address: true 376 skip_xff_append: true 377 http_filters: 378 - name: envoy.filters.http.router 379 ` 380 381 func TestCiliumEnvoyConfigValidation(t *testing.T) { 382 logger := logrus.New() 383 logger.SetOutput(io.Discard) 384 parser := cecResourceParser{ 385 logger: logger, 386 portAllocator: NewMockPortAllocator(), 387 } 388 389 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfigInvalid)) 390 require.NoError(t, err) 391 cec := &cilium_v2.CiliumEnvoyConfig{} 392 err = json.Unmarshal(jsonBytes, cec) 393 require.NoError(t, err) 394 assert.NotNil(t, cec.Spec.Resources) 395 assert.Len(t, cec.Spec.Resources, 1) 396 assert.Equal(t, "type.googleapis.com/envoy.config.listener.v3.Listener", cec.Spec.Resources[0].TypeUrl) 397 398 resources, err := parser.parseResources("namespace", "name", cec.Spec.Resources, false, false, false) 399 require.NoError(t, err) 400 assert.Len(t, resources.Listeners, 1) 401 assert.Equal(t, "namespace/name/envoy-prometheus-metrics-listener", resources.Listeners[0].Name) 402 assert.Equal(t, uint32(0), resources.Listeners[0].Address.GetSocketAddress().GetPortValue()) // invalid listener port number 403 assert.Len(t, resources.Listeners[0].FilterChains, 1) 404 chain := resources.Listeners[0].FilterChains[0] 405 assert.Len(t, chain.Filters, 1) 406 assert.Equal(t, "envoy.filters.network.http_connection_manager", chain.Filters[0].Name) 407 message, err := chain.Filters[0].GetTypedConfig().UnmarshalNew() 408 require.NoError(t, err) 409 assert.NotNil(t, message) 410 hcm, ok := message.(*envoy_config_http.HttpConnectionManager) 411 assert.True(t, ok) 412 assert.NotNil(t, hcm) 413 414 // 415 // Check that missing RDS config source is automatically filled in 416 // 417 rds := hcm.GetRds() 418 assert.NotNil(t, rds) 419 assert.Equal(t, "namespace/name/local_route", rds.RouteConfigName) 420 checkCiliumXDS(t, rds.GetConfigSource()) 421 422 // 423 // Check that HTTP filters are parsed 424 // 425 assert.Len(t, hcm.HttpFilters, 1) 426 assert.Equal(t, "envoy.filters.http.router", hcm.HttpFilters[0].Name) 427 428 // 429 // Same with validation fails 430 // 431 resources, err = parser.parseResources("namespace", "name", cec.Spec.Resources, false, false, true) 432 assert.Error(t, err) 433 } 434 435 var ciliumEnvoyConfigNoAddress = `apiVersion: cilium.io/v2 436 kind: CiliumEnvoyConfig 437 metadata: 438 name: envoy-prometheus-metrics-listener 439 spec: 440 version_info: "0" 441 resources: 442 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener 443 name: envoy-prometheus-metrics-listener 444 filter_chains: 445 - filters: 446 - name: envoy.filters.network.http_connection_manager 447 typed_config: 448 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 449 stat_prefix: ingress_http 450 route_config: 451 name: ingress_route 452 virtual_hosts: 453 - name: ingress_route 454 domains: ["*"] 455 routes: 456 - match: 457 prefix: "/" 458 route: 459 cluster: "envoy-ingress" 460 codec_type: AUTO 461 use_remote_address: true 462 skip_xff_append: true 463 http_filters: 464 - name: envoy.filters.http.router 465 ` 466 467 func TestCiliumEnvoyConfigNoAddress(t *testing.T) { 468 logger := logrus.New() 469 logger.SetOutput(io.Discard) 470 parser := cecResourceParser{ 471 logger: logger, 472 portAllocator: NewMockPortAllocator(), 473 } 474 475 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfigNoAddress)) 476 require.NoError(t, err) 477 cec := &cilium_v2.CiliumEnvoyConfig{} 478 err = json.Unmarshal(jsonBytes, cec) 479 require.NoError(t, err) 480 assert.NotNil(t, cec.Spec.Resources) 481 assert.Len(t, cec.Spec.Resources, 1) 482 assert.Equal(t, "type.googleapis.com/envoy.config.listener.v3.Listener", cec.Spec.Resources[0].TypeUrl) 483 484 resources, err := parser.parseResources("namespace", "name", cec.Spec.Resources, false, false, true) 485 require.NoError(t, err) 486 assert.Len(t, resources.Listeners, 1) 487 assert.Equal(t, "namespace/name/envoy-prometheus-metrics-listener", resources.Listeners[0].Name) 488 assert.NotNil(t, resources.Listeners[0].Address) 489 assert.NotNil(t, resources.Listeners[0].Address.GetSocketAddress()) 490 assert.NotEqual(t, 0, resources.Listeners[0].Address.GetSocketAddress().GetPortValue()) 491 assert.Len(t, resources.Listeners[0].FilterChains, 1) 492 chain := resources.Listeners[0].FilterChains[0] 493 assert.Len(t, chain.Filters, 2) 494 assert.Equal(t, "cilium.network", chain.Filters[0].Name) 495 assert.Equal(t, "envoy.filters.network.http_connection_manager", chain.Filters[1].Name) 496 message, err := chain.Filters[1].GetTypedConfig().UnmarshalNew() 497 require.NoError(t, err) 498 assert.NotNil(t, message) 499 hcm, ok := message.(*envoy_config_http.HttpConnectionManager) 500 assert.True(t, ok) 501 assert.NotNil(t, hcm) 502 503 // 504 // Check that missing RDS config source is automatically filled in 505 // 506 rc := hcm.GetRouteConfig() 507 assert.NotNil(t, rc) 508 vh := rc.GetVirtualHosts() 509 assert.Len(t, vh, 1) 510 routes := vh[0].GetRoutes() 511 assert.Len(t, routes, 1) 512 route := routes[0].GetRoute() 513 assert.NotNil(t, route) 514 assert.Equal(t, "namespace/name/envoy-ingress", route.GetCluster()) 515 516 // 517 // Check that HTTP filters are parsed 518 // 519 assert.Len(t, hcm.HttpFilters, 2) 520 assert.Equal(t, "cilium.l7policy", hcm.HttpFilters[0].Name) 521 assert.Equal(t, "envoy.filters.http.router", hcm.HttpFilters[1].Name) 522 } 523 524 var ciliumEnvoyConfigMulti = `apiVersion: cilium.io/v2 525 kind: CiliumEnvoyConfig 526 metadata: 527 name: envoy-prometheus-metrics-listener 528 spec: 529 version_info: "0" 530 resources: 531 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener 532 name: multi-resource-listener 533 address: 534 socket_address: 535 address: 127.0.0.1 536 port_value: 10000 537 filter_chains: 538 - filters: 539 - name: envoy.filters.network.http_connection_manager 540 typed_config: 541 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 542 stat_prefix: ingress_http 543 codec_type: AUTO 544 rds: 545 route_config_name: local_route 546 use_remote_address: true 547 skip_xff_append: true 548 http_filters: 549 - name: envoy.filters.http.router 550 - "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration 551 name: local_route 552 virtual_hosts: 553 - name: local_service 554 domains: ["*"] 555 routes: 556 - match: { prefix: "/" } 557 route: { cluster: some_service } 558 - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster 559 name: some_service 560 connect_timeout: 0.25s 561 lb_policy: ROUND_ROBIN 562 type: EDS 563 transport_socket: 564 name: envoy.transport_sockets.tls 565 typed_config: 566 "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext 567 common_tls_context: 568 tls_certificate_sds_secret_configs: 569 - name: cilium-secrets/client-mtls 570 validation_context_sds_secret_config: 571 name: cilium-secrets/client-mtls 572 - "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment 573 cluster_name: some_service 574 endpoints: 575 - lb_endpoints: 576 - endpoint: 577 address: 578 socket_address: 579 address: 127.0.0.1 580 port_value: 1234 581 - "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment 582 cluster_name: other_service 583 endpoints: 584 - lb_endpoints: 585 - endpoint: 586 address: 587 socket_address: 588 address: "::" 589 port_value: 5678 590 ` 591 592 func TestCiliumEnvoyConfigMulti(t *testing.T) { 593 logger := logrus.New() 594 logger.SetOutput(io.Discard) 595 parser := cecResourceParser{ 596 logger: logger, 597 portAllocator: NewMockPortAllocator(), 598 } 599 600 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfigMulti)) 601 require.NoError(t, err) 602 cec := &cilium_v2.CiliumEnvoyConfig{} 603 err = json.Unmarshal(jsonBytes, cec) 604 require.NoError(t, err) 605 assert.Len(t, cec.Spec.Resources, 5) 606 assert.Equal(t, "type.googleapis.com/envoy.config.listener.v3.Listener", cec.Spec.Resources[0].TypeUrl) 607 608 resources, err := parser.parseResources("namespace", "name", cec.Spec.Resources, false, false, true) 609 require.NoError(t, err) 610 assert.Len(t, resources.Listeners, 1) 611 assert.Equal(t, "namespace/name/multi-resource-listener", resources.Listeners[0].Name) 612 assert.Nil(t, resources.Listeners[0].GetInternalListener()) 613 assert.Equal(t, uint32(10000), resources.Listeners[0].Address.GetSocketAddress().GetPortValue()) 614 assert.Len(t, resources.Listeners[0].FilterChains, 1) 615 chain := resources.Listeners[0].FilterChains[0] 616 assert.Len(t, chain.Filters, 1) 617 assert.Equal(t, "envoy.filters.network.http_connection_manager", chain.Filters[0].Name) 618 message, err := chain.Filters[0].GetTypedConfig().UnmarshalNew() 619 require.NoError(t, err) 620 assert.NotNil(t, message) 621 hcm, ok := message.(*envoy_config_http.HttpConnectionManager) 622 assert.True(t, ok) 623 assert.NotNil(t, hcm) 624 // 625 // Check that missing RDS config source is automatically filled in 626 // 627 rds := hcm.GetRds() 628 assert.NotNil(t, rds) 629 assert.Equal(t, "namespace/name/local_route", rds.RouteConfigName) 630 checkCiliumXDS(t, rds.GetConfigSource()) 631 // 632 // Check that HTTP filters are parsed 633 // 634 assert.Len(t, hcm.HttpFilters, 1) 635 assert.Equal(t, "envoy.filters.http.router", hcm.HttpFilters[0].Name) 636 637 // 638 // Check route resource 639 // 640 assert.Equal(t, "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", cec.Spec.Resources[1].TypeUrl) 641 assert.Len(t, resources.Routes, 1) 642 assert.Equal(t, "namespace/name/local_route", resources.Routes[0].Name) 643 assert.Len(t, resources.Routes[0].VirtualHosts, 1) 644 vh := resources.Routes[0].VirtualHosts[0] 645 assert.Equal(t, "namespace/name/local_service", vh.Name) 646 assert.Len(t, vh.Domains, 1) 647 assert.Equal(t, "*", vh.Domains[0]) 648 assert.Len(t, vh.Routes, 1) 649 assert.NotNil(t, vh.Routes[0].Match) 650 assert.Equal(t, "/", vh.Routes[0].Match.GetPrefix()) 651 assert.NotNil(t, vh.Routes[0].GetRoute()) 652 assert.Equal(t, "namespace/name/some_service", vh.Routes[0].GetRoute().GetCluster()) 653 654 // 655 // Check cluster resource 656 // 657 assert.Equal(t, "type.googleapis.com/envoy.config.cluster.v3.Cluster", cec.Spec.Resources[2].TypeUrl) 658 assert.Len(t, resources.Clusters, 1) 659 assert.Equal(t, "namespace/name/some_service", resources.Clusters[0].Name) 660 assert.Equal(t, int64(0), resources.Clusters[0].ConnectTimeout.Seconds) 661 assert.Equal(t, int32(250000000), resources.Clusters[0].ConnectTimeout.Nanos) 662 assert.Equal(t, envoy_config_cluster.Cluster_ROUND_ROBIN, resources.Clusters[0].LbPolicy) 663 assert.Equal(t, envoy_config_cluster.Cluster_EDS, resources.Clusters[0].GetType()) 664 // 665 // Check that missing EDS config source is automatically filled in 666 // 667 eds := resources.Clusters[0].GetEdsClusterConfig() 668 assert.NotNil(t, eds) 669 checkCiliumXDS(t, eds.GetEdsConfig()) 670 671 assert.NotNil(t, resources.Clusters[0].TransportSocket) 672 assert.Equal(t, "envoy.transport_sockets.tls", resources.Clusters[0].TransportSocket.Name) 673 msg, err := resources.Clusters[0].TransportSocket.GetTypedConfig().UnmarshalNew() 674 require.NoError(t, err) 675 assert.NotNil(t, msg) 676 tls, ok := msg.(*envoy_config_tls.UpstreamTlsContext) 677 assert.True(t, ok) 678 assert.NotNil(t, tls) 679 // 680 // Check that missing SDS config sources are automatically filled in 681 // 682 tlsContext := tls.CommonTlsContext 683 assert.NotNil(t, tlsContext) 684 for _, sc := range tlsContext.TlsCertificateSdsSecretConfigs { 685 checkCiliumXDS(t, sc.SdsConfig) 686 } 687 sdsConfig := tlsContext.GetValidationContextSdsSecretConfig() 688 assert.NotNil(t, sdsConfig) 689 checkCiliumXDS(t, sdsConfig.SdsConfig) 690 691 // 692 // Check 1st endpoint resource 693 // 694 assert.Equal(t, "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", cec.Spec.Resources[3].TypeUrl) 695 assert.Len(t, resources.Endpoints, 2) 696 assert.Equal(t, "namespace/name/some_service", resources.Endpoints[0].ClusterName) 697 assert.Len(t, resources.Endpoints[0].Endpoints, 1) 698 assert.Len(t, resources.Endpoints[0].Endpoints[0].LbEndpoints, 1) 699 addr := resources.Endpoints[0].Endpoints[0].LbEndpoints[0].GetEndpoint().Address 700 assert.NotNil(t, addr) 701 assert.NotNil(t, addr.GetSocketAddress()) 702 assert.Equal(t, "127.0.0.1", addr.GetSocketAddress().GetAddress()) 703 assert.Equal(t, uint32(1234), addr.GetSocketAddress().GetPortValue()) 704 705 // 706 // Check 2nd endpoint resource 707 // 708 assert.Equal(t, "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", cec.Spec.Resources[4].TypeUrl) 709 assert.Len(t, resources.Endpoints, 2) 710 assert.Equal(t, "namespace/name/other_service", resources.Endpoints[1].ClusterName) 711 assert.Len(t, resources.Endpoints[1].Endpoints, 1) 712 assert.Len(t, resources.Endpoints[1].Endpoints[0].LbEndpoints, 1) 713 addr = resources.Endpoints[1].Endpoints[0].LbEndpoints[0].GetEndpoint().Address 714 assert.NotNil(t, addr) 715 assert.NotNil(t, addr.GetSocketAddress()) 716 assert.Equal(t, "::", addr.GetSocketAddress().GetAddress()) 717 assert.Equal(t, uint32(5678), addr.GetSocketAddress().GetPortValue()) 718 } 719 720 var ciliumEnvoyConfigTCPProxy = `apiVersion: cilium.io/v2 721 kind: CiliumEnvoyConfig 722 metadata: 723 name: envoy-test-listener 724 spec: 725 resources: 726 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener 727 name: tcp_proxy_test-2 728 filter_chains: 729 - filters: 730 - name: envoy.filters.network.tcp_proxy 731 typed_config: 732 "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy 733 stat_prefix: tcp_stats 734 cluster: "cluster_0" 735 tunneling_config: 736 hostname: host.com:443 737 use_post: true 738 - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster 739 name: "cluster_0" 740 connect_timeout: 5s 741 # This ensures HTTP/2 POST is used for establishing the tunnel. 742 typed_extension_protocol_options: 743 envoy.extensions.upstreams.http.v3.HttpProtocolOptions: 744 "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions 745 explicit_http_config: 746 http2_protocol_options: {} 747 load_assignment: 748 cluster_name: cluster_0 749 endpoints: 750 - lb_endpoints: 751 - endpoint: 752 address: 753 socket_address: 754 address: 127.0.0.1 755 port_value: 10001 756 ` 757 758 var ciliumEnvoyConfigInternalListener = `apiVersion: cilium.io/v2 759 kind: CiliumEnvoyConfig 760 metadata: 761 name: missing-internal-listener 762 spec: 763 version_info: "0" 764 resources: 765 - "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment 766 cluster_name: internal_listener_cluster 767 endpoints: 768 - lb_endpoints: 769 - endpoint: 770 address: 771 envoy_internal_address: 772 server_listener_name: internal-listener 773 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener 774 name: internal-listener 775 internal_listener: {} 776 ` 777 778 func TestCiliumEnvoyConfigInternalListener(t *testing.T) { 779 logger := logrus.New() 780 logger.SetOutput(io.Discard) 781 parser := cecResourceParser{ 782 logger: logger, 783 portAllocator: NewMockPortAllocator(), 784 } 785 786 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfigInternalListener)) 787 require.NoError(t, err) 788 cec := &cilium_v2.CiliumEnvoyConfig{} 789 err = json.Unmarshal(jsonBytes, cec) 790 require.NoError(t, err) 791 assert.Len(t, cec.Spec.Resources, 2) 792 assert.Equal(t, "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", cec.Spec.Resources[0].TypeUrl) 793 assert.Equal(t, "type.googleapis.com/envoy.config.listener.v3.Listener", cec.Spec.Resources[1].TypeUrl) 794 795 resources, err := parser.parseResources("namespace", "name", cec.Spec.Resources, false, false, true) 796 require.NoError(t, err) 797 798 // 799 // Check internal endpoint resource 800 // 801 assert.Len(t, resources.Endpoints, 1) 802 assert.Equal(t, "namespace/name/internal_listener_cluster", resources.Endpoints[0].ClusterName) 803 assert.Len(t, resources.Endpoints[0].Endpoints, 1) 804 assert.Len(t, resources.Endpoints[0].Endpoints[0].LbEndpoints, 1) 805 addr := resources.Endpoints[0].Endpoints[0].LbEndpoints[0].GetEndpoint().Address 806 assert.NotNil(t, addr) 807 assert.NotNil(t, addr.GetEnvoyInternalAddress()) 808 assert.Equal(t, "namespace/name/internal-listener", addr.GetEnvoyInternalAddress().GetServerListenerName()) 809 810 // 811 // Check internal listener 812 // 813 assert.Len(t, resources.Listeners, 1) 814 assert.Equal(t, "namespace/name/internal-listener", resources.Listeners[0].Name) 815 assert.NotNil(t, resources.Listeners[0].GetInternalListener()) 816 } 817 818 var ciliumEnvoyConfigMissingInternalListener = `apiVersion: cilium.io/v2 819 kind: CiliumEnvoyConfig 820 metadata: 821 name: missing-internal-listener 822 spec: 823 version_info: "0" 824 resources: 825 - "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment 826 cluster_name: internal_listener_cluster 827 endpoints: 828 - lb_endpoints: 829 - endpoint: 830 address: 831 envoy_internal_address: 832 server_listener_name: internal-listener 833 ` 834 835 func TestCiliumEnvoyConfigMissingInternalListener(t *testing.T) { 836 logger := logrus.New() 837 logger.SetOutput(io.Discard) 838 parser := cecResourceParser{ 839 logger: logger, 840 portAllocator: NewMockPortAllocator(), 841 } 842 843 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfigMissingInternalListener)) 844 require.NoError(t, err) 845 cec := &cilium_v2.CiliumEnvoyConfig{} 846 err = json.Unmarshal(jsonBytes, cec) 847 require.NoError(t, err) 848 assert.Len(t, cec.Spec.Resources, 1) 849 assert.Equal(t, "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", cec.Spec.Resources[0].TypeUrl) 850 851 _, err = parser.parseResources("namespace", "name", cec.Spec.Resources, false, false, true) 852 assert.ErrorContains(t, err, "missing internal listener: internal-listener") 853 } 854 855 func TestCiliumEnvoyConfigTCPProxy(t *testing.T) { 856 logger := logrus.New() 857 logger.SetOutput(io.Discard) 858 parser := cecResourceParser{ 859 logger: logger, 860 portAllocator: NewMockPortAllocator(), 861 } 862 863 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfigTCPProxy)) 864 require.NoError(t, err) 865 866 cec := &cilium_v2.CiliumEnvoyConfig{} 867 err = json.Unmarshal(jsonBytes, cec) 868 require.NoError(t, err) 869 assert.NotNil(t, cec.Spec.Resources) 870 assert.Len(t, cec.Spec.Resources, 2) 871 assert.Equal(t, "type.googleapis.com/envoy.config.listener.v3.Listener", cec.Spec.Resources[0].TypeUrl) 872 873 resources, err := parser.parseResources("namespace", "name", cec.Spec.Resources, false, true, true) 874 require.NoError(t, err) 875 assert.Len(t, resources.Listeners, 1) 876 assert.NotNil(t, resources.Listeners[0].Address) 877 assert.NotNil(t, resources.Listeners[0].Address.GetSocketAddress()) 878 assert.NotEqual(t, 0, resources.Listeners[0].Address.GetSocketAddress().GetPortValue()) 879 // 880 // Check injected listener filter config 881 // 882 assert.Len(t, resources.Listeners[0].ListenerFilters, 1) 883 assert.Equal(t, "cilium.bpf_metadata", resources.Listeners[0].ListenerFilters[0].Name) 884 lfMsg, err := resources.Listeners[0].ListenerFilters[0].GetTypedConfig().UnmarshalNew() 885 require.NoError(t, err) 886 assert.NotNil(t, lfMsg) 887 lf, ok := lfMsg.(*cilium.BpfMetadata) 888 assert.True(t, ok) 889 assert.NotNil(t, lf) 890 assert.Equal(t, false, lf.IsIngress) 891 assert.True(t, lf.UseOriginalSourceAddress) 892 assert.Equal(t, bpf.BPFFSRoot(), lf.BpfRoot) 893 assert.Equal(t, false, lf.IsL7Lb) 894 895 assert.Len(t, resources.Listeners[0].FilterChains, 1) 896 chain := resources.Listeners[0].FilterChains[0] 897 assert.Len(t, chain.Filters, 2) 898 assert.Equal(t, "cilium.network", chain.Filters[0].Name) 899 assert.Equal(t, "envoy.filters.network.tcp_proxy", chain.Filters[1].Name) 900 message, err := chain.Filters[1].GetTypedConfig().UnmarshalNew() 901 require.NoError(t, err) 902 assert.NotNil(t, message) 903 tcp, ok := message.(*envoy_config_tcp.TcpProxy) 904 assert.True(t, ok) 905 assert.NotNil(t, tcp) 906 // 907 // Check TCP config 908 // 909 assert.Equal(t, "namespace/name/cluster_0", tcp.GetCluster()) 910 tc := tcp.GetTunnelingConfig() 911 assert.NotNil(t, tc) 912 assert.Equal(t, "host.com:443", tc.Hostname) 913 assert.True(t, tc.UsePost) 914 // 915 // Check cluster resource 916 // 917 assert.Equal(t, "type.googleapis.com/envoy.config.cluster.v3.Cluster", cec.Spec.Resources[1].TypeUrl) 918 assert.Len(t, resources.Clusters, 1) 919 assert.Equal(t, "namespace/name/cluster_0", resources.Clusters[0].Name) 920 assert.Equal(t, int64(5), resources.Clusters[0].ConnectTimeout.Seconds) 921 assert.Equal(t, int32(0), resources.Clusters[0].ConnectTimeout.Nanos) 922 assert.Equal(t, "namespace/name/cluster_0", resources.Clusters[0].LoadAssignment.ClusterName) 923 assert.Len(t, resources.Clusters[0].LoadAssignment.Endpoints, 1) 924 assert.Len(t, resources.Clusters[0].LoadAssignment.Endpoints[0].LbEndpoints, 1) 925 addr := resources.Clusters[0].LoadAssignment.Endpoints[0].LbEndpoints[0].GetEndpoint().Address 926 assert.NotNil(t, addr) 927 assert.NotNil(t, addr.GetSocketAddress()) 928 assert.Equal(t, "127.0.0.1", addr.GetSocketAddress().GetAddress()) 929 assert.Equal(t, uint32(10001), addr.GetSocketAddress().GetPortValue()) 930 } 931 932 var ciliumEnvoyConfigTCPProxyTermination = `apiVersion: cilium.io/v2 933 kind: CiliumEnvoyConfig 934 metadata: 935 name: tcp-proxy-ingress-listener 936 spec: 937 services: 938 - name: tcp-proxy-ingress 939 namespace: cilium-test 940 resources: 941 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener 942 name: envoy-ingress-listener 943 filter_chains: 944 - filters: 945 - name: envoy.filters.network.http_connection_manager 946 typed_config: 947 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 948 stat_prefix: tcp-proxy-ingress-listener 949 route_config: 950 name: local_route 951 virtual_hosts: 952 - name: local_service 953 domains: 954 - "*" 955 routes: 956 - match: 957 prefix: "/" 958 headers: 959 - name: ":method" 960 string_match: 961 exact: "POST" 962 route: 963 cluster: default/service_google 964 upgrade_configs: 965 - upgrade_type: CONNECT 966 connect_config: 967 allow_post: true 968 use_remote_address: true 969 skip_xff_append: true 970 http_filters: 971 - name: envoy.filters.http.router 972 http2_protocol_options: 973 allow_connect: true 974 - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster 975 name: default/service_google 976 connect_timeout: 5s 977 type: LOGICAL_DNS 978 # Comment out the following line to test on v6 networks 979 dns_lookup_family: V4_ONLY 980 lb_policy: ROUND_ROBIN 981 typed_extension_protocol_options: 982 envoy.extensions.upstreams.http.v3.HttpProtocolOptions: 983 "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions 984 explicit_http_config: 985 http2_protocol_options: {} 986 load_assignment: 987 cluster_name: default/service_google 988 endpoints: 989 - lb_endpoints: 990 - endpoint: 991 address: 992 socket_address: 993 address: www.google.com 994 port_value: 443 995 ` 996 997 func TestCiliumEnvoyConfigTCPProxyTermination(t *testing.T) { 998 logger := logrus.New() 999 logger.SetOutput(io.Discard) 1000 parser := cecResourceParser{ 1001 logger: logger, 1002 portAllocator: NewMockPortAllocator(), 1003 } 1004 1005 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfigTCPProxyTermination)) 1006 require.NoError(t, err) 1007 1008 cec := &cilium_v2.CiliumEnvoyConfig{} 1009 err = json.Unmarshal(jsonBytes, cec) 1010 require.NoError(t, err) 1011 assert.NotNil(t, cec.Spec.Resources) 1012 assert.Len(t, cec.Spec.Resources, 2) 1013 assert.Equal(t, "type.googleapis.com/envoy.config.listener.v3.Listener", cec.Spec.Resources[0].TypeUrl) 1014 1015 resources, err := parser.parseResources("namespace", "name", cec.Spec.Resources, true, false, true) 1016 require.NoError(t, err) 1017 assert.Len(t, resources.Listeners, 1) 1018 assert.NotNil(t, resources.Listeners[0].Address) 1019 assert.NotNil(t, resources.Listeners[0].Address.GetSocketAddress()) 1020 assert.NotEqual(t, 0, resources.Listeners[0].Address.GetSocketAddress().GetPortValue()) 1021 // 1022 // Check injected listener filter config 1023 // 1024 assert.Len(t, resources.Listeners[0].ListenerFilters, 1) 1025 assert.Equal(t, "cilium.bpf_metadata", resources.Listeners[0].ListenerFilters[0].Name) 1026 lfMsg, err := resources.Listeners[0].ListenerFilters[0].GetTypedConfig().UnmarshalNew() 1027 require.NoError(t, err) 1028 assert.NotNil(t, lfMsg) 1029 lf, ok := lfMsg.(*cilium.BpfMetadata) 1030 assert.True(t, ok) 1031 assert.NotNil(t, lf) 1032 assert.Equal(t, false, lf.IsIngress) 1033 assert.Equal(t, false, lf.UseOriginalSourceAddress) 1034 assert.Equal(t, bpf.BPFFSRoot(), lf.BpfRoot) 1035 assert.True(t, lf.IsL7Lb) 1036 1037 assert.Len(t, resources.Listeners[0].FilterChains, 1) 1038 chain := resources.Listeners[0].FilterChains[0] 1039 assert.Len(t, chain.Filters, 2) 1040 assert.Equal(t, "cilium.network", chain.Filters[0].Name) 1041 assert.Equal(t, "envoy.filters.network.http_connection_manager", chain.Filters[1].Name) 1042 message, err := chain.Filters[1].GetTypedConfig().UnmarshalNew() 1043 require.NoError(t, err) 1044 assert.NotNil(t, message) 1045 hcm, ok := message.(*envoy_config_http.HttpConnectionManager) 1046 assert.True(t, ok) 1047 assert.NotNil(t, hcm) 1048 // 1049 // Check HTTP config 1050 // 1051 assert.Len(t, hcm.HttpFilters, 2) 1052 assert.Equal(t, "cilium.l7policy", hcm.HttpFilters[0].Name) 1053 assert.Equal(t, "envoy.filters.http.router", hcm.HttpFilters[1].Name) 1054 assert.Equal(t, "namespace/name/local_route", hcm.GetRouteConfig().Name) 1055 assert.Equal(t, "namespace/name/local_service", hcm.GetRouteConfig().VirtualHosts[0].Name) 1056 assert.Equal(t, "default/service_google", hcm.GetRouteConfig().VirtualHosts[0].Routes[0].GetRoute().GetCluster()) 1057 // 1058 // Check cluster resource 1059 // 1060 assert.Equal(t, "type.googleapis.com/envoy.config.cluster.v3.Cluster", cec.Spec.Resources[1].TypeUrl) 1061 assert.Len(t, resources.Clusters, 1) 1062 assert.Equal(t, "default/service_google", resources.Clusters[0].Name) 1063 assert.Equal(t, int64(5), resources.Clusters[0].ConnectTimeout.Seconds) 1064 assert.Equal(t, int32(0), resources.Clusters[0].ConnectTimeout.Nanos) 1065 assert.Equal(t, envoy_config_cluster.Cluster_LOGICAL_DNS, resources.Clusters[0].GetType()) 1066 assert.Equal(t, envoy_config_cluster.Cluster_V4_ONLY, resources.Clusters[0].GetDnsLookupFamily()) 1067 assert.Equal(t, envoy_config_cluster.Cluster_ROUND_ROBIN, resources.Clusters[0].LbPolicy) 1068 1069 assert.Equal(t, "default/service_google", resources.Clusters[0].LoadAssignment.ClusterName) 1070 assert.Len(t, resources.Clusters[0].LoadAssignment.Endpoints, 1) 1071 assert.Len(t, resources.Clusters[0].LoadAssignment.Endpoints[0].LbEndpoints, 1) 1072 addr := resources.Clusters[0].LoadAssignment.Endpoints[0].LbEndpoints[0].GetEndpoint().Address 1073 assert.NotNil(t, addr) 1074 assert.NotNil(t, addr.GetSocketAddress()) 1075 assert.Equal(t, "www.google.com", addr.GetSocketAddress().GetAddress()) 1076 assert.Equal(t, uint32(443), addr.GetSocketAddress().GetPortValue()) 1077 // 1078 // Check upstream filters (injected for L7 LB) 1079 // 1080 assert.NotNil(t, resources.Clusters[0].TypedExtensionProtocolOptions) 1081 assert.NotNil(t, resources.Clusters[0].TypedExtensionProtocolOptions[httpProtocolOptionsType]) 1082 opts := &envoy_upstreams_http_v3.HttpProtocolOptions{} 1083 assert.Nil(t, resources.Clusters[0].TypedExtensionProtocolOptions[httpProtocolOptionsType].UnmarshalTo(opts)) 1084 assert.NotNil(t, opts.HttpFilters) 1085 assert.Equal(t, "cilium.l7policy", opts.HttpFilters[0].Name) 1086 assert.Equal(t, ciliumL7FilterTypeURL, opts.HttpFilters[0].GetTypedConfig().TypeUrl) 1087 assert.Equal(t, "envoy.filters.http.upstream_codec", opts.HttpFilters[1].Name) 1088 assert.Equal(t, upstreamCodecFilterTypeURL, opts.HttpFilters[1].GetTypedConfig().TypeUrl) 1089 } 1090 1091 func checkCiliumXDS(t *testing.T, cs *envoy_config_core.ConfigSource) { 1092 assert.NotNil(t, cs) 1093 assert.Equal(t, envoy_config_core.ApiVersion_V3, cs.ResourceApiVersion) 1094 acs := cs.GetApiConfigSource() 1095 assert.NotNil(t, acs) 1096 assert.Equal(t, envoy_config_core.ApiConfigSource_GRPC, acs.ApiType) 1097 assert.Equal(t, envoy_config_core.ApiVersion_V3, acs.TransportApiVersion) 1098 assert.True(t, acs.SetNodeOnFirstMessageOnly) 1099 assert.Len(t, acs.GrpcServices, 1) 1100 eg := acs.GrpcServices[0].GetEnvoyGrpc() 1101 assert.NotNil(t, eg) 1102 assert.Equal(t, "xds-grpc-cilium", eg.ClusterName) 1103 } 1104 1105 var ciliumEnvoyConfigWithHealthFilter = `apiVersion: cilium.io/v2 1106 kind: CiliumEnvoyConfig 1107 metadata: 1108 namespace: test-namespace 1109 name: test-name 1110 spec: 1111 resources: 1112 - '@type': type.googleapis.com/envoy.config.listener.v3.Listener 1113 name: listener 1114 address: 1115 socketAddress: 1116 address: 100.64.0.100 1117 portValue: 80 1118 filterChains: 1119 - filters: 1120 - name: envoy.filters.network.http_connection_manager 1121 typedConfig: 1122 '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 1123 httpFilters: 1124 - name: envoy.filters.http.health_check 1125 typedConfig: 1126 '@type': type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck 1127 clusterMinHealthyPercentages: 1128 cluster: 1129 value: 20 1130 passThroughMode: false` 1131 1132 func TestCiliumEnvoyConfigtHTTPHealthCheckFilter(t *testing.T) { 1133 logger := logrus.New() 1134 logger.SetOutput(io.Discard) 1135 parser := cecResourceParser{ 1136 logger: logger, 1137 portAllocator: NewMockPortAllocator(), 1138 } 1139 1140 jsonBytes, err := yaml.YAMLToJSON([]byte(ciliumEnvoyConfigWithHealthFilter)) 1141 require.NoError(t, err) 1142 cec := &cilium_v2.CiliumEnvoyConfig{} 1143 err = json.Unmarshal(jsonBytes, cec) 1144 require.NoError(t, err) 1145 1146 resources, err := parser.parseResources(cec.Namespace, cec.Name, cec.Spec.Resources, false, false, false) 1147 require.NoError(t, err) 1148 assert.Len(t, resources.Listeners, 1) 1149 chain := resources.Listeners[0].FilterChains[0] 1150 assert.Len(t, chain.Filters, 1) 1151 assert.Equal(t, "envoy.filters.network.http_connection_manager", chain.Filters[0].Name) 1152 1153 hcmMessage, err := chain.Filters[0].GetTypedConfig().UnmarshalNew() 1154 require.NoError(t, err) 1155 assert.NotNil(t, hcmMessage) 1156 1157 assert.IsType(t, &envoy_config_http.HttpConnectionManager{}, hcmMessage) 1158 pm, err := hcmMessage.(*envoy_config_http.HttpConnectionManager).HttpFilters[0].GetTypedConfig().UnmarshalNew() 1159 assert.NoError(t, err) 1160 1161 assert.IsType(t, &envoy_config_http_healthcheck.HealthCheck{}, pm) 1162 assert.Len(t, pm.(*envoy_config_http_healthcheck.HealthCheck).ClusterMinHealthyPercentages, 1) 1163 assert.Contains(t, pm.(*envoy_config_http_healthcheck.HealthCheck).ClusterMinHealthyPercentages, "test-namespace/test-name/cluster") 1164 } 1165 1166 func TestListenersAddedOrDeleted(t *testing.T) { 1167 var old envoy.Resources 1168 var new envoy.Resources 1169 1170 // Both empty 1171 res := old.ListenersAddedOrDeleted(&new) 1172 assert.Equal(t, false, res) 1173 1174 // new adds a listener 1175 new.Listeners = append(old.Listeners, &envoy_config_listener.Listener{Name: "foo"}) 1176 res = old.ListenersAddedOrDeleted(&new) 1177 assert.True(t, res) 1178 res = new.ListenersAddedOrDeleted(&old) 1179 assert.True(t, res) 1180 1181 // Now both have 'foo' 1182 old.Listeners = append(old.Listeners, &envoy_config_listener.Listener{Name: "foo"}) 1183 res = old.ListenersAddedOrDeleted(&new) 1184 assert.Equal(t, false, res) 1185 res = new.ListenersAddedOrDeleted(&old) 1186 assert.Equal(t, false, res) 1187 1188 // New has no listeners 1189 new.Listeners = nil 1190 res = old.ListenersAddedOrDeleted(&new) 1191 assert.True(t, res) 1192 res = new.ListenersAddedOrDeleted(&old) 1193 assert.True(t, res) 1194 1195 // New has a different listener 1196 new.Listeners = append(new.Listeners, &envoy_config_listener.Listener{Name: "bar"}) 1197 res = old.ListenersAddedOrDeleted(&new) 1198 assert.True(t, res) 1199 res = new.ListenersAddedOrDeleted(&old) 1200 assert.True(t, res) 1201 1202 // New adds the listener in old, but still has the other listener 1203 new.Listeners = append(new.Listeners, &envoy_config_listener.Listener{Name: "foo"}) 1204 res = old.ListenersAddedOrDeleted(&new) 1205 assert.True(t, res) 1206 res = new.ListenersAddedOrDeleted(&old) 1207 assert.True(t, res) 1208 1209 // Same listeners but in different order 1210 old.Listeners = append(old.Listeners, &envoy_config_listener.Listener{Name: "bar"}) 1211 res = old.ListenersAddedOrDeleted(&new) 1212 assert.Equal(t, false, res) 1213 res = new.ListenersAddedOrDeleted(&old) 1214 assert.Equal(t, false, res) 1215 1216 // Old has no listeners 1217 old.Listeners = nil 1218 res = old.ListenersAddedOrDeleted(&new) 1219 assert.True(t, res) 1220 res = new.ListenersAddedOrDeleted(&old) 1221 assert.True(t, res) 1222 }