istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/sidecar.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package model
    16  
    17  import (
    18  	"encoding/json"
    19  	"sort"
    20  	"strings"
    21  
    22  	"k8s.io/apimachinery/pkg/types"
    23  
    24  	networking "istio.io/api/networking/v1alpha3"
    25  	"istio.io/istio/pilot/pkg/features"
    26  	"istio.io/istio/pilot/pkg/serviceregistry/provider"
    27  	"istio.io/istio/pkg/config"
    28  	"istio.io/istio/pkg/config/constants"
    29  	"istio.io/istio/pkg/config/host"
    30  	"istio.io/istio/pkg/config/labels"
    31  	"istio.io/istio/pkg/config/protocol"
    32  	"istio.io/istio/pkg/config/schema/kind"
    33  	"istio.io/istio/pkg/maps"
    34  	"istio.io/istio/pkg/slices"
    35  	"istio.io/istio/pkg/util/sets"
    36  )
    37  
    38  const (
    39  	wildcardNamespace = "*"
    40  	currentNamespace  = "."
    41  	wildcardService   = host.Name("*")
    42  )
    43  
    44  var (
    45  	sidecarScopedKnownConfigTypes = sets.New(
    46  		kind.ServiceEntry,
    47  		kind.VirtualService,
    48  		kind.DestinationRule,
    49  		kind.Sidecar,
    50  
    51  		kind.HTTPRoute,
    52  		kind.TCPRoute,
    53  		kind.TLSRoute,
    54  		kind.GRPCRoute,
    55  	)
    56  
    57  	// clusterScopedKnownConfigTypes includes configs when they are in root namespace,
    58  	// they will be applied to all namespaces within the cluster.
    59  	clusterScopedKnownConfigTypes = sets.New(
    60  		kind.EnvoyFilter,
    61  		kind.AuthorizationPolicy,
    62  		kind.RequestAuthentication,
    63  		kind.WasmPlugin,
    64  	)
    65  )
    66  
    67  type hostClassification struct {
    68  	exactHosts sets.Set[host.Name]
    69  	allHosts   []host.Name
    70  }
    71  
    72  // Matches checks if the hostClassification(sidecar egress hosts) matches the Service's hostname
    73  func (hc hostClassification) Matches(h host.Name) bool {
    74  	// exact lookup is fast, so check that first
    75  	if hc.exactHosts.Contains(h) {
    76  		return true
    77  	}
    78  
    79  	// exactHosts not found, fallback to loop allHosts
    80  	hIsWildCarded := h.IsWildCarded()
    81  	for _, importedHost := range hc.allHosts {
    82  		// If both are exact hosts, then fallback is not needed.
    83  		// In this scenario it should be determined by exact lookup.
    84  		if !hIsWildCarded && !importedHost.IsWildCarded() {
    85  			continue
    86  		}
    87  		// Check if the hostnames match per usual hostname matching rules
    88  		if h.SubsetOf(importedHost) {
    89  			return true
    90  		}
    91  	}
    92  	return false
    93  }
    94  
    95  // VSMatches checks if the hostClassification(sidecar egress hosts) matches the VirtualService's host
    96  func (hc hostClassification) VSMatches(vsHost host.Name, useGatewaySemantics bool) bool {
    97  	// first, check exactHosts
    98  	if hc.exactHosts.Contains(vsHost) {
    99  		return true
   100  	}
   101  
   102  	// exactHosts not found, fallback to loop allHosts
   103  	hIsWildCard := vsHost.IsWildCarded()
   104  	for _, importedHost := range hc.allHosts {
   105  		// If both are exact hosts, then fallback is not needed.
   106  		// In this scenario it should be determined by exact lookup.
   107  		if !hIsWildCard && !importedHost.IsWildCarded() {
   108  			continue
   109  		}
   110  
   111  		var match bool
   112  		if useGatewaySemantics {
   113  			// The new way. Matching logic exactly mirrors Service matching
   114  			// If a route defines `*.com` and we import `a.com`, it will not match
   115  			match = vsHost.SubsetOf(importedHost)
   116  		} else {
   117  			// The old way. We check Matches which is bi-directional. This is for backwards compatibility
   118  			match = vsHost.Matches(importedHost)
   119  		}
   120  		if match {
   121  			return true
   122  		}
   123  	}
   124  	return false
   125  }
   126  
   127  // SidecarScope is a wrapper over the Sidecar resource with some
   128  // preprocessed data to determine the list of services, virtualServices,
   129  // and destinationRules that are accessible to a given
   130  // sidecar. Precomputing the list of services, virtual services, dest rules
   131  // for a sidecar improves performance as we no longer need to compute this
   132  // list for every sidecar. We simply have to match a sidecar to a
   133  // SidecarScope. Note that this is not the same as public/private scoped
   134  // services. The list of services seen by every sidecar scope (namespace
   135  // wide or per workload) depends on the imports, the listeners, and other
   136  // settings.
   137  //
   138  // Every proxy workload of SidecarProxy type will always map to a
   139  // SidecarScope object. If the proxy's namespace does not have a user
   140  // specified Sidecar CRD, we will construct one that has a catch all egress
   141  // listener that imports every public service/virtualService in the mesh.
   142  type SidecarScope struct {
   143  	Name string
   144  	// This is the namespace where the sidecar takes effect,
   145  	// maybe different from the ns where sidecar resides if sidecar is in root ns.
   146  	Namespace string
   147  	// The cr itself. Can be nil if we are constructing the default
   148  	// sidecar scope
   149  	Sidecar *networking.Sidecar
   150  
   151  	// Version this sidecar was computed for
   152  	Version string
   153  
   154  	// Set of egress listeners, and their associated services.  A sidecar
   155  	// scope should have either ingress/egress listeners or both.  For
   156  	// every proxy workload that maps to a sidecar API object (or the
   157  	// default object), we will go through every egress listener in the
   158  	// object and process the Envoy listener or RDS based on the imported
   159  	// services/virtual services in that listener.
   160  	EgressListeners []*IstioEgressListenerWrapper
   161  
   162  	// Union of services imported across all egress listeners for use by CDS code.
   163  	services           []*Service
   164  	servicesByHostname map[host.Name]*Service
   165  
   166  	// Destination rules imported across all egress listeners. This
   167  	// contains the computed set based on public/private destination rules
   168  	// as well as the inherited ones, in addition to the wildcard matches
   169  	// such as *.com applying to foo.bar.com. Each hostname in this map
   170  	// corresponds to a service in the services array above. When computing
   171  	// CDS, we simply have to find the matching service and return the
   172  	// destination rule.
   173  	destinationRules        map[host.Name][]*ConsolidatedDestRule
   174  	destinationRulesByNames map[types.NamespacedName]*config.Config
   175  
   176  	// OutboundTrafficPolicy defines the outbound traffic policy for this sidecar.
   177  	// If OutboundTrafficPolicy is ALLOW_ANY traffic to unknown destinations will
   178  	// be forwarded.
   179  	OutboundTrafficPolicy *networking.OutboundTrafficPolicy
   180  
   181  	// Set of known configs this sidecar depends on.
   182  	// This field will be used to determine the config/resource scope
   183  	// which means which config changes will affect the proxies within this scope.
   184  	configDependencies sets.Set[ConfigHash]
   185  }
   186  
   187  // MarshalJSON implements json.Marshaller
   188  func (sc *SidecarScope) MarshalJSON() ([]byte, error) {
   189  	// Json cannot expose unexported fields, so copy the ones we want here
   190  	return json.MarshalIndent(map[string]any{
   191  		"version":               sc.Version,
   192  		"name":                  sc.Name,
   193  		"namespace":             sc.Namespace,
   194  		"outboundTrafficPolicy": sc.OutboundTrafficPolicy,
   195  		"services":              sc.services,
   196  		"servicesByHostname":    sc.servicesByHostname,
   197  		"sidecar":               sc.Sidecar,
   198  		"destinationRules":      sc.destinationRules,
   199  	}, "", "  ")
   200  }
   201  
   202  // IstioEgressListenerWrapper is a wrapper for
   203  // networking.IstioEgressListener object. The wrapper provides performance
   204  // optimizations as it allows us to precompute and store the list of
   205  // services/virtualServices that apply to this listener.
   206  type IstioEgressListenerWrapper struct {
   207  	// The actual IstioEgressListener api object from the Config. It can be
   208  	// nil if this is for the default sidecar scope.
   209  	IstioListener *networking.IstioEgressListener
   210  
   211  	// Specifies whether matching ports is required.
   212  	matchPort bool
   213  
   214  	// List of services imported by this egress listener above.
   215  	// This will be used by LDS and RDS code when
   216  	// building the set of virtual hosts or the tcp filterchain matches for
   217  	// a given listener port. Two listeners, on user specified ports or
   218  	// unix domain sockets could have completely different sets of
   219  	// services. So a global list of services per sidecar scope will be
   220  	// incorrect. Hence the per listener set of services.
   221  	services []*Service
   222  
   223  	// List of virtual services imported by this egress listener above.
   224  	// As with per listener services, this
   225  	// will be used by RDS code to compute the virtual host configs for
   226  	// http listeners, as well as by TCP/TLS filter code to compute the
   227  	// service routing configs and the filter chain matches. We need a
   228  	// virtualService set per listener and not one per sidecarScope because
   229  	// each listener imports an independent set of virtual services.
   230  	// Listener 1 could import a public virtual service for serviceA from
   231  	// namespace A that has some path rewrite, while listener2 could import
   232  	// a private virtual service for serviceA from the local namespace,
   233  	// with a different path rewrite or no path rewrites.
   234  	virtualServices []config.Config
   235  
   236  	// An index of hostname to the namespaced name of the VirtualService containing the most
   237  	// relevant host match. Depending on the `PERSIST_OLDEST_FIRST_HEURISTIC_FOR_VIRTUAL_SERVICE_HOST_MATCHING`
   238  	// feature flag, it could be the most specific host match or the oldest host match.
   239  	mostSpecificWildcardVsIndex map[host.Name]types.NamespacedName
   240  }
   241  
   242  const defaultSidecar = "default-sidecar"
   243  
   244  // DefaultSidecarScopeForGateway builds a SidecarScope contains services and destinationRules for a given gateway/waypoint.
   245  func DefaultSidecarScopeForGateway(ps *PushContext, configNamespace string) *SidecarScope {
   246  	services := ps.servicesExportedToNamespace(configNamespace)
   247  	out := &SidecarScope{
   248  		Name:                    defaultSidecar,
   249  		Namespace:               configNamespace,
   250  		destinationRules:        make(map[host.Name][]*ConsolidatedDestRule),
   251  		destinationRulesByNames: make(map[types.NamespacedName]*config.Config),
   252  		servicesByHostname:      make(map[host.Name]*Service, len(services)),
   253  		Version:                 ps.PushVersion,
   254  	}
   255  
   256  	servicesAdded := make(map[host.Name]sidecarServiceIndex)
   257  	for _, s := range services {
   258  		out.appendSidecarServices(servicesAdded, s)
   259  	}
   260  
   261  	// Now that we have all the services that sidecars using this scope (in
   262  	// this config namespace) will see, identify all the destinationRules
   263  	// that these services need
   264  	for _, s := range out.services {
   265  		if dr := ps.destinationRule(configNamespace, s); dr != nil {
   266  			out.destinationRules[s.Hostname] = dr
   267  			for _, cdr := range dr {
   268  				for _, from := range cdr.from {
   269  					out.destinationRulesByNames[from] = cdr.rule
   270  				}
   271  			}
   272  		}
   273  	}
   274  
   275  	// waypoint need to get vses from the egress listener
   276  	defaultEgressListener := &IstioEgressListenerWrapper{
   277  		virtualServices: ps.VirtualServicesForGateway(configNamespace, constants.IstioMeshGateway),
   278  	}
   279  	out.EgressListeners = []*IstioEgressListenerWrapper{defaultEgressListener}
   280  
   281  	return out
   282  }
   283  
   284  // DefaultSidecarScopeForNamespace is a sidecar scope object with a default catch all egress listener
   285  // that matches the default Istio behavior: a sidecar has listeners for all services in the mesh
   286  // We use this scope when the user has not set any sidecar Config for a given config namespace.
   287  func DefaultSidecarScopeForNamespace(ps *PushContext, configNamespace string) *SidecarScope {
   288  	defaultEgressListener := &IstioEgressListenerWrapper{
   289  		IstioListener: &networking.IstioEgressListener{
   290  			Hosts: []string{"*/*"},
   291  		},
   292  	}
   293  	services := ps.servicesExportedToNamespace(configNamespace)
   294  	defaultEgressListener.virtualServices = ps.VirtualServicesForGateway(configNamespace, constants.IstioMeshGateway)
   295  	defaultEgressListener.mostSpecificWildcardVsIndex = computeWildcardHostVirtualServiceIndex(
   296  		defaultEgressListener.virtualServices, services)
   297  
   298  	out := &SidecarScope{
   299  		Name:                    defaultSidecar,
   300  		Namespace:               configNamespace,
   301  		EgressListeners:         []*IstioEgressListenerWrapper{defaultEgressListener},
   302  		destinationRules:        make(map[host.Name][]*ConsolidatedDestRule),
   303  		destinationRulesByNames: make(map[types.NamespacedName]*config.Config),
   304  		servicesByHostname:      make(map[host.Name]*Service, len(defaultEgressListener.services)),
   305  		configDependencies:      make(sets.Set[ConfigHash]),
   306  		Version:                 ps.PushVersion,
   307  	}
   308  
   309  	servicesAdded := make(map[host.Name]sidecarServiceIndex)
   310  	for _, s := range services {
   311  		out.appendSidecarServices(servicesAdded, s)
   312  	}
   313  	defaultEgressListener.services = out.services
   314  
   315  	// add dependencies on delegate virtual services
   316  	delegates := ps.DelegateVirtualServices(defaultEgressListener.virtualServices)
   317  	for _, delegate := range delegates {
   318  		out.AddConfigDependencies(delegate)
   319  	}
   320  	for _, vs := range defaultEgressListener.virtualServices {
   321  		for _, cfg := range VirtualServiceDependencies(vs) {
   322  			out.AddConfigDependencies(cfg.HashCode())
   323  		}
   324  	}
   325  
   326  	// Now that we have all the services that sidecars using this scope (in
   327  	// this config namespace) will see, identify all the destinationRules
   328  	// that these services need
   329  	for _, s := range out.services {
   330  		if dr := ps.destinationRule(configNamespace, s); dr != nil {
   331  			out.destinationRules[s.Hostname] = dr
   332  			for _, cdr := range dr {
   333  				for _, from := range cdr.from {
   334  					out.destinationRulesByNames[from] = cdr.rule
   335  					out.AddConfigDependencies(ConfigKey{
   336  						Kind:      kind.DestinationRule,
   337  						Name:      from.Name,
   338  						Namespace: from.Namespace,
   339  					}.HashCode())
   340  				}
   341  			}
   342  		}
   343  		out.AddConfigDependencies(ConfigKey{
   344  			Kind:      kind.ServiceEntry,
   345  			Name:      string(s.Hostname),
   346  			Namespace: s.Attributes.Namespace,
   347  		}.HashCode())
   348  	}
   349  
   350  	if ps.Mesh.OutboundTrafficPolicy != nil {
   351  		out.OutboundTrafficPolicy = &networking.OutboundTrafficPolicy{
   352  			Mode: networking.OutboundTrafficPolicy_Mode(ps.Mesh.OutboundTrafficPolicy.Mode),
   353  		}
   354  	}
   355  
   356  	return out
   357  }
   358  
   359  // convertToSidecarScope converts from Sidecar config to SidecarScope object
   360  func convertToSidecarScope(ps *PushContext, sidecarConfig *config.Config, configNamespace string) *SidecarScope {
   361  	if sidecarConfig == nil {
   362  		return DefaultSidecarScopeForNamespace(ps, configNamespace)
   363  	}
   364  
   365  	sidecar := sidecarConfig.Spec.(*networking.Sidecar)
   366  	out := &SidecarScope{
   367  		Name:               sidecarConfig.Name,
   368  		Namespace:          configNamespace,
   369  		Sidecar:            sidecar,
   370  		servicesByHostname: make(map[host.Name]*Service),
   371  		configDependencies: make(sets.Set[ConfigHash]),
   372  		Version:            ps.PushVersion,
   373  	}
   374  
   375  	out.AddConfigDependencies(ConfigKey{
   376  		Kind:      kind.Sidecar,
   377  		Name:      sidecarConfig.Name,
   378  		Namespace: sidecarConfig.Namespace,
   379  	}.HashCode())
   380  
   381  	egressConfigs := sidecar.Egress
   382  	// If egress not set, setup a default listener
   383  	if len(egressConfigs) == 0 {
   384  		egressConfigs = append(egressConfigs, &networking.IstioEgressListener{Hosts: []string{"*/*"}})
   385  	}
   386  	out.EgressListeners = make([]*IstioEgressListenerWrapper, 0, len(egressConfigs))
   387  	for _, e := range egressConfigs {
   388  		out.EgressListeners = append(out.EgressListeners,
   389  			convertIstioListenerToWrapper(ps, configNamespace, e))
   390  	}
   391  
   392  	// Now collect all the imported services across all egress listeners in
   393  	// this sidecar crd. This is needed to generate CDS output
   394  	out.collectImportedServices(ps, configNamespace)
   395  
   396  	// Now that we have all the services that sidecars using this scope (in
   397  	// this config namespace) will see, identify all the destinationRules
   398  	// that these services need
   399  	out.selectDestinationRules(ps, configNamespace)
   400  
   401  	if sidecar.OutboundTrafficPolicy == nil {
   402  		if ps.Mesh.OutboundTrafficPolicy != nil {
   403  			out.OutboundTrafficPolicy = &networking.OutboundTrafficPolicy{
   404  				Mode: networking.OutboundTrafficPolicy_Mode(ps.Mesh.OutboundTrafficPolicy.Mode),
   405  			}
   406  		}
   407  	} else {
   408  		out.OutboundTrafficPolicy = sidecar.OutboundTrafficPolicy
   409  	}
   410  
   411  	return out
   412  }
   413  
   414  func (sc *SidecarScope) collectImportedServices(ps *PushContext, configNamespace string) {
   415  	serviceMatchingPort := func(s *Service, ilw *IstioEgressListenerWrapper, ports sets.Set[int]) *Service {
   416  		if ilw.matchPort {
   417  			return serviceMatchingListenerPort(s, ilw)
   418  		}
   419  		return serviceMatchingVirtualServicePorts(s, ports)
   420  	}
   421  
   422  	servicesAdded := make(map[host.Name]sidecarServiceIndex)
   423  	for _, ilw := range sc.EgressListeners {
   424  		// First add the explicitly requested services, which take priority
   425  		for _, s := range ilw.services {
   426  			sc.appendSidecarServices(servicesAdded, s)
   427  		}
   428  
   429  		// add dependencies on delegate virtual services
   430  		delegates := ps.DelegateVirtualServices(ilw.virtualServices)
   431  		sc.AddConfigDependencies(delegates...)
   432  
   433  		// Infer more possible destinations from virtual services
   434  		// Services chosen here will not override services explicitly requested in ilw.services.
   435  		// That way, if there is ambiguity around what hostname to pick, a user can specify the one they
   436  		// want in the hosts field, and the potentially random choice below won't matter
   437  		for _, vs := range ilw.virtualServices {
   438  			for _, cfg := range VirtualServiceDependencies(vs) {
   439  				sc.AddConfigDependencies(cfg.HashCode())
   440  			}
   441  			v := vs.Spec.(*networking.VirtualService)
   442  			for h, ports := range virtualServiceDestinations(v) {
   443  				byNamespace := ps.ServiceIndex.HostnameAndNamespace[host.Name(h)]
   444  				// Default to this hostname in our config namespace
   445  				if s, ok := byNamespace[configNamespace]; ok {
   446  					// This won't overwrite hostnames that have already been found eg because they were requested in hosts
   447  					if matchedSvc := serviceMatchingPort(s, ilw, ports); matchedSvc != nil {
   448  						sc.appendSidecarServices(servicesAdded, matchedSvc)
   449  					}
   450  				} else {
   451  					// We couldn't find the hostname in our config namespace
   452  					// We have to pick one arbitrarily for now, so we'll pick the first namespace alphabetically
   453  					// TODO: could we choose services more intelligently based on their ports?
   454  					if len(byNamespace) == 0 {
   455  						// This hostname isn't found anywhere
   456  						log.Debugf("Could not find service hostname %s parsed from %s", h, vs.Key())
   457  						continue
   458  					}
   459  					// This won't overwrite hostnames that have already been found eg because they were requested in hosts
   460  					if ns := pickFirstVisibleNamespace(ps, byNamespace, configNamespace); ns != "" {
   461  						if matchedSvc := serviceMatchingPort(byNamespace[ns], ilw, ports); matchedSvc != nil {
   462  							sc.appendSidecarServices(servicesAdded, matchedSvc)
   463  						}
   464  					}
   465  				}
   466  			}
   467  		}
   468  	}
   469  }
   470  
   471  func (sc *SidecarScope) selectDestinationRules(ps *PushContext, configNamespace string) {
   472  	sc.destinationRules = make(map[host.Name][]*ConsolidatedDestRule)
   473  	sc.destinationRulesByNames = make(map[types.NamespacedName]*config.Config)
   474  	for _, s := range sc.services {
   475  		drList := ps.destinationRule(configNamespace, s)
   476  		if drList != nil {
   477  			sc.destinationRules[s.Hostname] = drList
   478  			for _, dr := range drList {
   479  				for _, key := range dr.from {
   480  					sc.AddConfigDependencies(ConfigKey{
   481  						Kind:      kind.DestinationRule,
   482  						Name:      key.Name,
   483  						Namespace: key.Namespace,
   484  					}.HashCode())
   485  
   486  					sc.destinationRulesByNames[key] = dr.rule
   487  				}
   488  			}
   489  		}
   490  		sc.AddConfigDependencies(ConfigKey{
   491  			Kind:      kind.ServiceEntry,
   492  			Name:      string(s.Hostname),
   493  			Namespace: s.Attributes.Namespace,
   494  		}.HashCode())
   495  	}
   496  }
   497  
   498  func convertIstioListenerToWrapper(ps *PushContext, configNamespace string,
   499  	istioListener *networking.IstioEgressListener,
   500  ) *IstioEgressListenerWrapper {
   501  	out := &IstioEgressListenerWrapper{
   502  		IstioListener: istioListener,
   503  		matchPort:     needsPortMatch(istioListener),
   504  	}
   505  
   506  	hostsByNamespace := make(map[string]hostClassification)
   507  	for _, h := range istioListener.Hosts {
   508  		parts := strings.SplitN(h, "/", 2)
   509  		if len(parts) < 2 {
   510  			log.Errorf("Illegal host in sidecar resource: %s, host must be of form namespace/dnsName", h)
   511  			continue
   512  		}
   513  		if parts[0] == currentNamespace {
   514  			parts[0] = configNamespace
   515  		}
   516  
   517  		ns := parts[0]
   518  		hName := host.Name(parts[1])
   519  		if _, exists := hostsByNamespace[ns]; !exists {
   520  			hostsByNamespace[ns] = hostClassification{exactHosts: sets.New[host.Name](), allHosts: make([]host.Name, 0)}
   521  		}
   522  
   523  		// exact hosts are saved separately for map lookup
   524  		if !hName.IsWildCarded() {
   525  			hostsByNamespace[ns].exactHosts.Insert(hName)
   526  		}
   527  
   528  		// allHosts contains the exact hosts and wildcard hosts,
   529  		// since SelectVirtualServices will use `Matches` semantic matching.
   530  		hc := hostsByNamespace[ns]
   531  		hc.allHosts = append(hc.allHosts, hName)
   532  		hostsByNamespace[ns] = hc
   533  	}
   534  
   535  	out.virtualServices = SelectVirtualServices(ps.virtualServiceIndex, configNamespace, hostsByNamespace)
   536  	svces := ps.servicesExportedToNamespace(configNamespace)
   537  	out.services = out.selectServices(svces, configNamespace, hostsByNamespace)
   538  	out.mostSpecificWildcardVsIndex = computeWildcardHostVirtualServiceIndex(out.virtualServices, out.services)
   539  
   540  	return out
   541  }
   542  
   543  // GetEgressListenerForRDS returns the egress listener corresponding to
   544  // the listener port or the bind address or the catch all listener
   545  func (sc *SidecarScope) GetEgressListenerForRDS(port int, bind string) *IstioEgressListenerWrapper {
   546  	if sc == nil {
   547  		return nil
   548  	}
   549  
   550  	for _, e := range sc.EgressListeners {
   551  		// We hit a catchall listener. This is the last listener in the list of listeners
   552  		// return as is
   553  		if e.IstioListener == nil || e.IstioListener.Port == nil {
   554  			return e
   555  		}
   556  
   557  		// Check if the ports match
   558  		// for unix domain sockets (i.e. port == 0), check if the bind is equal to the routeName
   559  		if int(e.IstioListener.Port.Number) == port {
   560  			if port == 0 { // unix domain socket
   561  				if e.IstioListener.Bind == bind {
   562  					return e
   563  				}
   564  				// no match.. continue searching
   565  				continue
   566  			}
   567  			// this is a non-zero port match
   568  			return e
   569  		}
   570  	}
   571  
   572  	// This should never be reached unless user explicitly set an empty array for egress
   573  	// listeners which we actually forbid
   574  	return nil
   575  }
   576  
   577  // HasIngressListener returns if the sidecar scope has ingress listener set
   578  func (sc *SidecarScope) HasIngressListener() bool {
   579  	if sc == nil {
   580  		return false
   581  	}
   582  
   583  	if sc.Sidecar == nil || len(sc.Sidecar.Ingress) == 0 {
   584  		return false
   585  	}
   586  
   587  	return true
   588  }
   589  
   590  // InboundConnectionPoolForPort returns the connection pool settings for a specific inbound port. If there's not a
   591  // setting for that specific port, then the settings at the Sidecar resource are returned. If neither exist,
   592  // then nil is returned so the caller can decide what values to fall back on.
   593  func (sc *SidecarScope) InboundConnectionPoolForPort(port int) *networking.ConnectionPoolSettings {
   594  	if sc == nil || sc.Sidecar == nil {
   595  		return nil
   596  	}
   597  
   598  	for _, in := range sc.Sidecar.Ingress {
   599  		if int(in.Port.Number) == port {
   600  			if in.GetConnectionPool() != nil {
   601  				return in.ConnectionPool
   602  			}
   603  		}
   604  	}
   605  
   606  	// if set, it'll be non-nil and have values (guaranteed by validation); or if unset it'll be nil
   607  	return sc.Sidecar.GetInboundConnectionPool()
   608  }
   609  
   610  // Services returns the list of services imported by this egress listener
   611  func (ilw *IstioEgressListenerWrapper) Services() []*Service {
   612  	return ilw.services
   613  }
   614  
   615  // VirtualServices returns the list of virtual services imported by this
   616  // egress listener
   617  func (ilw *IstioEgressListenerWrapper) VirtualServices() []config.Config {
   618  	return ilw.virtualServices
   619  }
   620  
   621  // MostSpecificWildcardVirtualServiceIndex returns the mostSpecificWildcardVsIndex for this egress
   622  // listener.
   623  func (ilw *IstioEgressListenerWrapper) MostSpecificWildcardVirtualServiceIndex() map[host.Name]types.NamespacedName {
   624  	return ilw.mostSpecificWildcardVsIndex
   625  }
   626  
   627  // DependsOnConfig determines if the proxy depends on the given config.
   628  // Returns whether depends on this config or this kind of config is not scopeZd(unknown to be depended) here.
   629  func (sc *SidecarScope) DependsOnConfig(config ConfigKey, rootNs string) bool {
   630  	if sc == nil {
   631  		return true
   632  	}
   633  
   634  	// This kind of config will trigger a change if made in the root namespace or the same namespace
   635  	if clusterScopedKnownConfigTypes.Contains(config.Kind) {
   636  		return config.Namespace == rootNs || config.Namespace == sc.Namespace
   637  	}
   638  
   639  	// This kind of config is unknown to sidecarScope.
   640  	if _, f := sidecarScopedKnownConfigTypes[config.Kind]; !f {
   641  		return true
   642  	}
   643  
   644  	return sc.configDependencies.Contains(config.HashCode())
   645  }
   646  
   647  func (sc *SidecarScope) GetService(hostname host.Name) *Service {
   648  	if sc == nil {
   649  		return nil
   650  	}
   651  	return sc.servicesByHostname[hostname]
   652  }
   653  
   654  // AddConfigDependencies add extra config dependencies to this scope. This action should be done before the
   655  // SidecarScope being used to avoid concurrent read/write.
   656  func (sc *SidecarScope) AddConfigDependencies(dependencies ...ConfigHash) {
   657  	if sc == nil {
   658  		return
   659  	}
   660  	if sc.configDependencies == nil {
   661  		sc.configDependencies = sets.New(dependencies...)
   662  	} else {
   663  		sc.configDependencies.InsertAll(dependencies...)
   664  	}
   665  }
   666  
   667  // DestinationRule returns a destinationrule for a svc.
   668  func (sc *SidecarScope) DestinationRule(direction TrafficDirection, proxy *Proxy, svc host.Name) *ConsolidatedDestRule {
   669  	destinationRules := sc.destinationRules[svc]
   670  	var catchAllDr *ConsolidatedDestRule
   671  	for _, destRule := range destinationRules {
   672  		destinationRule := destRule.rule.Spec.(*networking.DestinationRule)
   673  		if destinationRule.GetWorkloadSelector() == nil {
   674  			catchAllDr = destRule
   675  		}
   676  		// filter DestinationRule based on workloadSelector for outbound configs.
   677  		// WorkloadSelector configuration is honored only for outbound configuration, because
   678  		// for inbound configuration, the settings at sidecar would be more explicit and the preferred way forward.
   679  		if sc.Namespace == destRule.rule.Namespace &&
   680  			destinationRule.GetWorkloadSelector() != nil && direction == TrafficDirectionOutbound {
   681  			workloadSelector := labels.Instance(destinationRule.GetWorkloadSelector().GetMatchLabels())
   682  			// return destination rule if workload selector matches
   683  			if workloadSelector.SubsetOf(proxy.Labels) {
   684  				return destRule
   685  			}
   686  		}
   687  	}
   688  	// If there is no workload specific destinationRule, return the wild carded dr if present.
   689  	if catchAllDr != nil {
   690  		return catchAllDr
   691  	}
   692  	return nil
   693  }
   694  
   695  // DestinationRuleConfig returns merged destination rules for a svc.
   696  func (sc *SidecarScope) DestinationRuleConfig(direction TrafficDirection, proxy *Proxy, svc host.Name) *config.Config {
   697  	cdr := sc.DestinationRule(direction, proxy, svc)
   698  	if cdr == nil {
   699  		return nil
   700  	}
   701  	return cdr.rule
   702  }
   703  
   704  // Services returns the list of services that are visible to a sidecar.
   705  func (sc *SidecarScope) Services() []*Service {
   706  	return sc.services
   707  }
   708  
   709  // Testing Only. This allows tests to inject a config without having the mock.
   710  func (sc *SidecarScope) SetDestinationRulesForTesting(configs []config.Config) {
   711  	sc.destinationRulesByNames = make(map[types.NamespacedName]*config.Config)
   712  	for _, c := range configs {
   713  		c := c
   714  		sc.destinationRulesByNames[types.NamespacedName{Name: c.Name, Namespace: c.Namespace}] = &c
   715  	}
   716  }
   717  
   718  func (sc *SidecarScope) DestinationRuleByName(name, namespace string) *config.Config {
   719  	if sc == nil {
   720  		return nil
   721  	}
   722  	return sc.destinationRulesByNames[types.NamespacedName{
   723  		Name:      name,
   724  		Namespace: namespace,
   725  	}]
   726  }
   727  
   728  // ServicesForHostname returns a list of services that fall under the hostname provided. This hostname
   729  // can be a wildcard.
   730  func (sc *SidecarScope) ServicesForHostname(hostname host.Name) []*Service {
   731  	if !hostname.IsWildCarded() {
   732  		if svc, f := sc.servicesByHostname[hostname]; f {
   733  			return []*Service{svc}
   734  		}
   735  		return nil
   736  	}
   737  	services := make([]*Service, 0)
   738  	for _, svc := range sc.services {
   739  		if hostname.Matches(svc.Hostname) {
   740  			services = append(services, svc)
   741  		}
   742  	}
   743  	return services
   744  }
   745  
   746  // Return filtered services through the hosts field in the egress portion of the Sidecar config.
   747  // Note that the returned service could be trimmed.
   748  // TODO: support merging services within this egress listener to align with SidecarScope's behavior.
   749  func (ilw *IstioEgressListenerWrapper) selectServices(services []*Service, configNamespace string, hostsByNamespace map[string]hostClassification) []*Service {
   750  	importedServices := make([]*Service, 0)
   751  	wildcardHosts, wnsFound := hostsByNamespace[wildcardNamespace]
   752  	for _, s := range services {
   753  		configNamespace := s.Attributes.Namespace
   754  
   755  		// Check if there is an explicit import of form ns/* or ns/host
   756  		if importedHosts, nsFound := hostsByNamespace[configNamespace]; nsFound {
   757  			if svc := matchingAliasService(importedHosts, matchingService(importedHosts, s, ilw)); svc != nil {
   758  				importedServices = append(importedServices, svc)
   759  				continue
   760  			}
   761  		}
   762  
   763  		// Check if there is an import of form */host or */*
   764  		if wnsFound {
   765  			if svc := matchingAliasService(wildcardHosts, matchingService(wildcardHosts, s, ilw)); svc != nil {
   766  				importedServices = append(importedServices, svc)
   767  			}
   768  		}
   769  	}
   770  
   771  	validServices := make(map[host.Name]string, len(importedServices))
   772  	for _, svc := range importedServices {
   773  		_, f := validServices[svc.Hostname]
   774  		// Select a single namespace for a given hostname.
   775  		// If the same hostname is imported from multiple namespaces, pick the one in the configNamespace
   776  		// If neither are in configNamespace, an arbitrary one will be chosen
   777  		if !f || svc.Attributes.Namespace == configNamespace {
   778  			validServices[svc.Hostname] = svc.Attributes.Namespace
   779  		}
   780  	}
   781  
   782  	// Filter down to just instances in scope for the service
   783  	return slices.FilterInPlace(importedServices, func(svc *Service) bool {
   784  		return validServices[svc.Hostname] == svc.Attributes.Namespace
   785  	})
   786  }
   787  
   788  // Return the original service or a trimmed service which has a subset of the ports in original service.
   789  func matchingService(importedHosts hostClassification, service *Service, ilw *IstioEgressListenerWrapper) *Service {
   790  	if importedHosts.Matches(service.Hostname) {
   791  		if ilw.matchPort {
   792  			return serviceMatchingListenerPort(service, ilw)
   793  		}
   794  		return service
   795  	}
   796  	return nil
   797  }
   798  
   799  // matchingAliasService the original service or a trimmed service which has a subset of aliases, based on imports from sidecar
   800  func matchingAliasService(importedHosts hostClassification, service *Service) *Service {
   801  	if service == nil {
   802  		return nil
   803  	}
   804  	matched := make([]NamespacedHostname, 0, len(service.Attributes.Aliases))
   805  	for _, alias := range service.Attributes.Aliases {
   806  		if importedHosts.Matches(alias.Hostname) {
   807  			matched = append(matched, alias)
   808  		}
   809  	}
   810  
   811  	if len(matched) == len(service.Attributes.Aliases) {
   812  		return service
   813  	}
   814  	service = service.DeepCopy()
   815  	service.Attributes.Aliases = matched
   816  	return service
   817  }
   818  
   819  // serviceMatchingListenerPort constructs service with listener port.
   820  func serviceMatchingListenerPort(service *Service, ilw *IstioEgressListenerWrapper) *Service {
   821  	for _, port := range service.Ports {
   822  		if port.Port == int(ilw.IstioListener.Port.GetNumber()) {
   823  			sc := service.DeepCopy()
   824  			sc.Ports = []*Port{port}
   825  			return sc
   826  		}
   827  	}
   828  	return nil
   829  }
   830  
   831  func serviceMatchingVirtualServicePorts(service *Service, vsDestPorts sets.Set[int]) *Service {
   832  	// A value of 0 in vsDestPorts is used as a sentinel to indicate a dependency
   833  	// on every port of the service.
   834  	if len(vsDestPorts) == 0 || vsDestPorts.Contains(0) {
   835  		return service
   836  	}
   837  
   838  	foundPorts := make([]*Port, 0)
   839  	for _, port := range service.Ports {
   840  		if vsDestPorts.Contains(port.Port) {
   841  			foundPorts = append(foundPorts, port)
   842  		}
   843  	}
   844  
   845  	if len(foundPorts) == len(service.Ports) {
   846  		return service
   847  	}
   848  
   849  	if len(foundPorts) > 0 {
   850  		sc := service.DeepCopy()
   851  		sc.Ports = foundPorts
   852  		return sc
   853  	}
   854  
   855  	// If the service has more than one port, and the Virtual Service only
   856  	// specifies destination ports not found in the service, we'll simply
   857  	// not add the service to the sidecar as an optimization, because
   858  	// traffic will not route properly anyway. This matches the above
   859  	// behavior in serviceMatchingListenerPort for ports specified on the
   860  	// sidecar egress listener.
   861  	log.Warnf("Failed to find any VirtualService destination ports %v exposed by Service %s", vsDestPorts, service.Hostname)
   862  	return nil
   863  }
   864  
   865  // computeWildcardHostVirtualServiceIndex computes the wildcardHostVirtualServiceIndex for a given
   866  // (sorted) list of virtualServices. This is used to optimize the lookup of the most specific wildcard host.
   867  //
   868  // N.B the caller MUST presort virtualServices based on the desired precedence for duplicate hostnames.
   869  // This function will persist that order and not overwrite any previous entries for a given hostname.
   870  func computeWildcardHostVirtualServiceIndex(virtualServices []config.Config, services []*Service) map[host.Name]types.NamespacedName {
   871  	fqdnVirtualServiceHostIndex := make(map[host.Name]config.Config, len(virtualServices))
   872  	wildcardVirtualServiceHostIndex := make(map[host.Name]config.Config, len(virtualServices))
   873  	for _, vs := range virtualServices {
   874  		v := vs.Spec.(*networking.VirtualService)
   875  		for _, h := range v.Hosts {
   876  			// We may have duplicate (not just overlapping) hosts; assume the list of VS is sorted already
   877  			// and never overwrite existing entries
   878  			if host.Name(h).IsWildCarded() {
   879  				_, exists := wildcardVirtualServiceHostIndex[host.Name(h)]
   880  				if !exists {
   881  					wildcardVirtualServiceHostIndex[host.Name(h)] = vs
   882  				}
   883  			} else {
   884  				_, exists := fqdnVirtualServiceHostIndex[host.Name(h)]
   885  				if !exists {
   886  					fqdnVirtualServiceHostIndex[host.Name(h)] = vs
   887  				}
   888  			}
   889  		}
   890  	}
   891  
   892  	mostSpecificWildcardVsIndex := make(map[host.Name]types.NamespacedName)
   893  	comparator := MostSpecificHostMatch[config.Config]
   894  	if features.PersistOldestWinsHeuristicForVirtualServiceHostMatching {
   895  		comparator = OldestMatchingHost
   896  	}
   897  	for _, svc := range services {
   898  		_, ref, exists := comparator(svc.Hostname, fqdnVirtualServiceHostIndex, wildcardVirtualServiceHostIndex)
   899  		if !exists {
   900  			// This svc doesn't have a virtualService; skip
   901  			continue
   902  		}
   903  		mostSpecificWildcardVsIndex[svc.Hostname] = ref.NamespacedName()
   904  	}
   905  
   906  	return mostSpecificWildcardVsIndex
   907  }
   908  
   909  func needsPortMatch(l *networking.IstioEgressListener) bool {
   910  	// If a listener is defined with a port, we should match services with port except in the following case.
   911  	//  - If Port's protocol is proxy protocol(HTTP_PROXY) in which case the egress listener is used as generic egress http proxy.
   912  	return l != nil && l.Port.GetNumber() != 0 &&
   913  		protocol.Parse(l.Port.Protocol) != protocol.HTTP_PROXY
   914  }
   915  
   916  type sidecarServiceIndex struct {
   917  	svc   *Service
   918  	index int // index record the position of the svc in slice
   919  }
   920  
   921  // append services to the sidecar scope, and merge services with the same hostname.
   922  func (sc *SidecarScope) appendSidecarServices(servicesAdded map[host.Name]sidecarServiceIndex, s *Service) {
   923  	if s == nil {
   924  		return
   925  	}
   926  	if foundSvc, found := servicesAdded[s.Hostname]; !found {
   927  		sc.services = append(sc.services, s)
   928  		servicesAdded[s.Hostname] = sidecarServiceIndex{s, len(sc.services) - 1}
   929  		sc.servicesByHostname[s.Hostname] = s
   930  	} else {
   931  		existing := foundSvc.svc
   932  		// We do not merge k8s service with any other services from other registries
   933  		if existing.Attributes.ServiceRegistry == provider.Kubernetes && s.Attributes.ServiceRegistry != provider.Kubernetes {
   934  			log.Debugf("Service %s/%s from registry %s ignored as there is an existing service in Kubernetes already %s/%s/%s",
   935  				s.Attributes.Namespace, s.Hostname, s.Attributes.ServiceRegistry,
   936  				existing.Attributes.Namespace, existing.Hostname, existing.Attributes.ServiceRegistry)
   937  			return
   938  		}
   939  		// In some scenarios, there may be multiple Services defined for the same hostname due to ServiceEntry allowing
   940  		// arbitrary hostnames. In these cases, we want to pick the first Service, which is the oldest. This ensures
   941  		// newly created Services cannot take ownership unexpectedly. However, the Service is from Kubernetes it should
   942  		// take precedence over ones not. This prevents someone from "domain squatting" on the hostname before a Kubernetes Service is created.
   943  		if existing.Attributes.ServiceRegistry != provider.Kubernetes && s.Attributes.ServiceRegistry == provider.Kubernetes {
   944  			log.Debugf("Service %s/%s from registry %s ignored as there is a Kubernetes service with the same host name %s/%s/%s",
   945  				existing.Attributes.Namespace, existing.Hostname, existing.Attributes.ServiceRegistry,
   946  				s.Attributes.Namespace, s.Hostname, s.Attributes.ServiceRegistry)
   947  			// replace service in slice
   948  			sc.services[foundSvc.index] = s
   949  			// Update index as well, so that future reads will merge into the new service
   950  			foundSvc.svc = s
   951  			servicesAdded[foundSvc.svc.Hostname] = foundSvc
   952  			sc.servicesByHostname[s.Hostname] = s
   953  			return
   954  		}
   955  
   956  		if !canMergeServices(existing, s) {
   957  			log.Debugf("Service %s/%s from registry %s ignored by %s/%s/%s", s.Attributes.Namespace, s.Hostname, s.Attributes.ServiceRegistry,
   958  				existing.Attributes.Namespace, existing.Hostname, existing.Attributes.ServiceRegistry)
   959  			return
   960  		}
   961  
   962  		// If it comes here, it means we can merge the services.
   963  		// Merge the ports to service when each listener generates partial service.
   964  		// We only merge if the found service is in the same namespace as the one we're trying to add
   965  		copied := foundSvc.svc.DeepCopy()
   966  		for _, p := range s.Ports {
   967  			found := false
   968  			for _, osp := range copied.Ports {
   969  				if p.Port == osp.Port {
   970  					found = true
   971  					break
   972  				}
   973  			}
   974  			if !found {
   975  				copied.Ports = append(copied.Ports, p)
   976  			}
   977  		}
   978  		// replace service in slice
   979  		sc.services[foundSvc.index] = copied
   980  		// Update index as well, so that future reads will merge into the new service
   981  		foundSvc.svc = copied
   982  		servicesAdded[foundSvc.svc.Hostname] = foundSvc
   983  		// update the existing service in the map to the merged one
   984  		sc.servicesByHostname[s.Hostname] = copied
   985  	}
   986  }
   987  
   988  func canMergeServices(s1, s2 *Service) bool {
   989  	// Hostname has been compared in the caller `appendSidecarServices`, so we donot need to compare again.
   990  	if s1.Attributes.Namespace != s2.Attributes.Namespace {
   991  		return false
   992  	}
   993  	if s1.Resolution != s2.Resolution {
   994  		return false
   995  	}
   996  	// kuberneres service registry has been checked before
   997  	if s1.Attributes.ServiceRegistry != s2.Attributes.ServiceRegistry {
   998  		return false
   999  	}
  1000  
  1001  	if !maps.Equal(s1.Attributes.Labels, s2.Attributes.Labels) {
  1002  		return false
  1003  	}
  1004  
  1005  	if !maps.Equal(s1.Attributes.LabelSelectors, s2.Attributes.LabelSelectors) {
  1006  		return false
  1007  	}
  1008  
  1009  	if !maps.Equal(s1.Attributes.ExportTo, s2.Attributes.ExportTo) {
  1010  		return false
  1011  	}
  1012  
  1013  	return true
  1014  }
  1015  
  1016  // Pick the Service namespace visible to the configNamespace namespace.
  1017  // If it does not exist, return an empty string,
  1018  // If there are more than one, pick the first alphabetically.
  1019  func pickFirstVisibleNamespace(ps *PushContext, byNamespace map[string]*Service, configNamespace string) string {
  1020  	nss := make([]string, 0, len(byNamespace))
  1021  	for ns := range byNamespace {
  1022  		if ps.IsServiceVisible(byNamespace[ns], configNamespace) {
  1023  			nss = append(nss, ns)
  1024  		}
  1025  	}
  1026  	if len(nss) > 0 {
  1027  		sort.Strings(nss)
  1028  		return nss[0]
  1029  	}
  1030  	return ""
  1031  }