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  }