istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/context.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  	"fmt"
    20  	"net"
    21  	"regexp"
    22  	"sort"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    29  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    30  	anypb "google.golang.org/protobuf/types/known/anypb"
    31  	"google.golang.org/protobuf/types/known/structpb"
    32  
    33  	meshconfig "istio.io/api/mesh/v1alpha1"
    34  	"istio.io/istio/pilot/pkg/credentials"
    35  	"istio.io/istio/pilot/pkg/features"
    36  	istionetworking "istio.io/istio/pilot/pkg/networking"
    37  	"istio.io/istio/pilot/pkg/serviceregistry/util/label"
    38  	"istio.io/istio/pilot/pkg/trustbundle"
    39  	networkutil "istio.io/istio/pilot/pkg/util/network"
    40  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    41  	"istio.io/istio/pkg/cluster"
    42  	"istio.io/istio/pkg/config/constants"
    43  	"istio.io/istio/pkg/config/host"
    44  	"istio.io/istio/pkg/config/mesh"
    45  	"istio.io/istio/pkg/ledger"
    46  	"istio.io/istio/pkg/maps"
    47  	pm "istio.io/istio/pkg/model"
    48  	"istio.io/istio/pkg/monitoring"
    49  	"istio.io/istio/pkg/network"
    50  	"istio.io/istio/pkg/spiffe"
    51  	"istio.io/istio/pkg/util/identifier"
    52  	netutil "istio.io/istio/pkg/util/net"
    53  	"istio.io/istio/pkg/util/protomarshal"
    54  	"istio.io/istio/pkg/util/sets"
    55  	"istio.io/istio/pkg/xds"
    56  )
    57  
    58  type (
    59  	Node                    = pm.Node
    60  	NodeMetadata            = pm.NodeMetadata
    61  	NodeMetaProxyConfig     = pm.NodeMetaProxyConfig
    62  	NodeType                = pm.NodeType
    63  	BootstrapNodeMetadata   = pm.BootstrapNodeMetadata
    64  	TrafficInterceptionMode = pm.TrafficInterceptionMode
    65  	PodPort                 = pm.PodPort
    66  	StringBool              = pm.StringBool
    67  	IPMode                  = pm.IPMode
    68  )
    69  
    70  const (
    71  	SidecarProxy = pm.SidecarProxy
    72  	Router       = pm.Router
    73  	Waypoint     = pm.Waypoint
    74  	Ztunnel      = pm.Ztunnel
    75  
    76  	IPv4 = pm.IPv4
    77  	IPv6 = pm.IPv6
    78  	Dual = pm.Dual
    79  )
    80  
    81  var _ mesh.Holder = &Environment{}
    82  
    83  func NewEnvironment() *Environment {
    84  	var cache XdsCache
    85  	if features.EnableXDSCaching {
    86  		cache = NewXdsCache()
    87  	} else {
    88  		cache = DisabledCache{}
    89  	}
    90  	return &Environment{
    91  		pushContext:   NewPushContext(),
    92  		Cache:         cache,
    93  		EndpointIndex: NewEndpointIndex(cache),
    94  	}
    95  }
    96  
    97  // Environment provides an aggregate environmental API for Pilot
    98  type Environment struct {
    99  	// Discovery interface for listing services and instances.
   100  	ServiceDiscovery
   101  
   102  	// Config interface for listing routing rules
   103  	ConfigStore
   104  
   105  	// Watcher is the watcher for the mesh config (to be merged into the config store)
   106  	mesh.Watcher
   107  
   108  	// NetworksWatcher (loaded from a config map) provides information about the
   109  	// set of networks inside a mesh and how to route to endpoints in each
   110  	// network. Each network provides information about the endpoints in a
   111  	// routable L3 network. A single routable L3 network can have one or more
   112  	// service registries.
   113  	NetworksWatcher mesh.NetworksWatcher
   114  
   115  	NetworkManager *NetworkManager
   116  
   117  	// mutex used for protecting Environment.pushContext
   118  	mutex sync.RWMutex
   119  	// pushContext holds information during push generation. It is reset on config change, at the beginning
   120  	// of the pushAll. It will hold all errors and stats and possibly caches needed during the entire cache computation.
   121  	// DO NOT USE EXCEPT FOR TESTS AND HANDLING OF NEW CONNECTIONS.
   122  	// ALL USE DURING A PUSH SHOULD USE THE ONE CREATED AT THE
   123  	// START OF THE PUSH, THE GLOBAL ONE MAY CHANGE AND REFLECT A DIFFERENT
   124  	// CONFIG AND PUSH
   125  	pushContext *PushContext
   126  
   127  	// DomainSuffix provides a default domain for the Istio server.
   128  	DomainSuffix string
   129  
   130  	ledger ledger.Ledger
   131  
   132  	// TrustBundle: List of Mesh TrustAnchors
   133  	TrustBundle *trustbundle.TrustBundle
   134  
   135  	clusterLocalServices ClusterLocalProvider
   136  
   137  	CredentialsController credentials.MulticlusterController
   138  
   139  	GatewayAPIController GatewayController
   140  
   141  	// EndpointShards for a service. This is a global (per-server) list, built from
   142  	// incremental updates. This is keyed by service and namespace
   143  	EndpointIndex *EndpointIndex
   144  
   145  	// Cache for XDS resources.
   146  	Cache XdsCache
   147  }
   148  
   149  func (e *Environment) Mesh() *meshconfig.MeshConfig {
   150  	if e != nil && e.Watcher != nil {
   151  		return e.Watcher.Mesh()
   152  	}
   153  	return nil
   154  }
   155  
   156  func (e *Environment) MeshNetworks() *meshconfig.MeshNetworks {
   157  	if e != nil && e.NetworksWatcher != nil {
   158  		return e.NetworksWatcher.Networks()
   159  	}
   160  	return nil
   161  }
   162  
   163  // SetPushContext sets the push context with lock protected
   164  func (e *Environment) SetPushContext(pc *PushContext) {
   165  	e.mutex.Lock()
   166  	defer e.mutex.Unlock()
   167  	e.pushContext = pc
   168  }
   169  
   170  // PushContext returns the push context with lock protected
   171  func (e *Environment) PushContext() *PushContext {
   172  	e.mutex.RLock()
   173  	defer e.mutex.RUnlock()
   174  	return e.pushContext
   175  }
   176  
   177  // GetDiscoveryAddress parses the DiscoveryAddress specified via MeshConfig.
   178  func (e *Environment) GetDiscoveryAddress() (host.Name, string, error) {
   179  	proxyConfig := mesh.DefaultProxyConfig()
   180  	if e.Mesh().DefaultConfig != nil {
   181  		proxyConfig = e.Mesh().DefaultConfig
   182  	}
   183  	hostname, port, err := net.SplitHostPort(proxyConfig.DiscoveryAddress)
   184  	if err != nil {
   185  		return "", "", fmt.Errorf("invalid Istiod Address: %s, %v", proxyConfig.DiscoveryAddress, err)
   186  	}
   187  	if _, err := strconv.Atoi(port); err != nil {
   188  		return "", "", fmt.Errorf("invalid Istiod Port: %s, %s, %v", port, proxyConfig.DiscoveryAddress, err)
   189  	}
   190  	return host.Name(hostname), port, nil
   191  }
   192  
   193  func (e *Environment) AddMeshHandler(h func()) {
   194  	if e != nil && e.Watcher != nil {
   195  		e.Watcher.AddMeshHandler(h)
   196  	}
   197  }
   198  
   199  func (e *Environment) AddNetworksHandler(h func()) {
   200  	if e != nil && e.NetworksWatcher != nil {
   201  		e.NetworksWatcher.AddNetworksHandler(h)
   202  	}
   203  }
   204  
   205  func (e *Environment) AddMetric(metric monitoring.Metric, key string, proxyID, msg string) {
   206  	if e != nil {
   207  		e.PushContext().AddMetric(metric, key, proxyID, msg)
   208  	}
   209  }
   210  
   211  func (e *Environment) Version() string {
   212  	if x := e.GetLedger(); x != nil {
   213  		return x.RootHash()
   214  	}
   215  	return ""
   216  }
   217  
   218  // Init initializes the Environment for use.
   219  func (e *Environment) Init() {
   220  	// Use a default DomainSuffix, if none was provided.
   221  	if len(e.DomainSuffix) == 0 {
   222  		e.DomainSuffix = constants.DefaultClusterLocalDomain
   223  	}
   224  
   225  	// Create the cluster-local service registry.
   226  	e.clusterLocalServices = NewClusterLocalProvider(e)
   227  }
   228  
   229  func (e *Environment) InitNetworksManager(updater XDSUpdater) (err error) {
   230  	e.NetworkManager, err = NewNetworkManager(e, updater)
   231  	return
   232  }
   233  
   234  func (e *Environment) ClusterLocal() ClusterLocalProvider {
   235  	return e.clusterLocalServices
   236  }
   237  
   238  func (e *Environment) GetLedger() ledger.Ledger {
   239  	return e.ledger
   240  }
   241  
   242  func (e *Environment) SetLedger(l ledger.Ledger) {
   243  	e.ledger = l
   244  }
   245  
   246  func (e *Environment) GetProxyConfigOrDefault(ns string, labels, annotations map[string]string, meshConfig *meshconfig.MeshConfig) *meshconfig.ProxyConfig {
   247  	push := e.PushContext()
   248  	if push != nil && push.ProxyConfigs != nil {
   249  		if generatedProxyConfig := push.ProxyConfigs.EffectiveProxyConfig(
   250  			&NodeMetadata{
   251  				Namespace:   ns,
   252  				Labels:      labels,
   253  				Annotations: annotations,
   254  			}, meshConfig); generatedProxyConfig != nil {
   255  			return generatedProxyConfig
   256  		}
   257  	}
   258  	return mesh.DefaultProxyConfig()
   259  }
   260  
   261  // Resources is an alias for array of marshaled resources.
   262  type Resources = []*discovery.Resource
   263  
   264  // DeletedResources is an alias for array of strings that represent removed resources in delta.
   265  type DeletedResources = []string
   266  
   267  func AnyToUnnamedResources(r []*anypb.Any) Resources {
   268  	a := make(Resources, 0, len(r))
   269  	for _, rr := range r {
   270  		a = append(a, &discovery.Resource{Resource: rr})
   271  	}
   272  	return a
   273  }
   274  
   275  // XdsUpdates include information about the subset of updated resources.
   276  // See for example EDS incremental updates.
   277  type XdsUpdates = sets.Set[ConfigKey]
   278  
   279  // XdsLogDetails contains additional metadata that is captured by Generators and used by xds processors
   280  // like Ads and Delta to uniformly log.
   281  type XdsLogDetails struct {
   282  	Incremental    bool
   283  	AdditionalInfo string
   284  }
   285  
   286  var DefaultXdsLogDetails = XdsLogDetails{}
   287  
   288  // XdsResourceGenerator creates the response for a typeURL DiscoveryRequest or DeltaDiscoveryRequest. If no generator
   289  // is associated with a Proxy, the default (a networking.core.ConfigGenerator instance) will be used.
   290  // The server may associate a different generator based on client metadata. Different
   291  // WatchedResources may use same or different Generator.
   292  // Note: any errors returned will completely close the XDS stream. Use with caution; typically and empty
   293  // or no response is preferred.
   294  type XdsResourceGenerator interface {
   295  	// Generate generates the Sotw resources for Xds.
   296  	Generate(proxy *Proxy, w *WatchedResource, req *PushRequest) (Resources, XdsLogDetails, error)
   297  }
   298  
   299  // XdsDeltaResourceGenerator generates Sotw and delta resources.
   300  type XdsDeltaResourceGenerator interface {
   301  	XdsResourceGenerator
   302  	// GenerateDeltas returns the changed and removed resources, along with whether or not delta was actually used.
   303  	GenerateDeltas(proxy *Proxy, req *PushRequest, w *WatchedResource) (Resources, DeletedResources, XdsLogDetails, bool, error)
   304  }
   305  
   306  // Proxy contains information about an specific instance of a proxy (envoy sidecar, gateway,
   307  // etc). The Proxy is initialized when a sidecar connects to Pilot, and populated from
   308  // 'node' info in the protocol as well as data extracted from registries.
   309  //
   310  // In current Istio implementation nodes use a 4-parts '~' delimited ID.
   311  // Type~IPAddress~ID~Domain
   312  type Proxy struct {
   313  	sync.RWMutex
   314  
   315  	// Type specifies the node type. First part of the ID.
   316  	Type NodeType
   317  
   318  	// IPAddresses is the IP addresses of the proxy used to identify it and its
   319  	// co-located service instances. Example: "10.60.1.6". In some cases, the host
   320  	// where the proxy and service instances reside may have more than one IP address
   321  	IPAddresses []string
   322  
   323  	// ID is the unique platform-specific sidecar proxy ID. For k8s it is the pod ID and
   324  	// namespace <podName.namespace>.
   325  	ID string
   326  
   327  	// Locality is the location of where Envoy proxy runs. This is extracted from
   328  	// the registry where possible. If the registry doesn't provide a locality for the
   329  	// proxy it will use the one sent via ADS that can be configured in the Envoy bootstrap
   330  	Locality *core.Locality
   331  
   332  	// DNSDomain defines the DNS domain suffix for short hostnames (e.g.
   333  	// "default.svc.cluster.local")
   334  	DNSDomain string
   335  
   336  	// ConfigNamespace defines the namespace where this proxy resides
   337  	// for the purposes of network scoping.
   338  	// NOTE: DO NOT USE THIS FIELD TO CONSTRUCT DNS NAMES
   339  	ConfigNamespace string
   340  
   341  	// Labels specifies the set of workload instance (ex: k8s pod) labels associated with this node.
   342  	// Labels can be different from that in Metadata because of pod labels update after startup,
   343  	// while NodeMetadata.Labels are set during bootstrap.
   344  	Labels map[string]string
   345  
   346  	// Metadata key-value pairs extending the Node identifier
   347  	Metadata *NodeMetadata
   348  
   349  	// the sidecarScope associated with the proxy
   350  	SidecarScope *SidecarScope
   351  
   352  	// the sidecarScope associated with the proxy previously
   353  	PrevSidecarScope *SidecarScope
   354  
   355  	// The merged gateways associated with the proxy if this is a Router
   356  	MergedGateway *MergedGateway
   357  
   358  	// PrevMergedGateway contains information about merged gateway associated with the proxy previously
   359  	PrevMergedGateway *PrevMergedGateway
   360  
   361  	// ServiceTargets contains a list of all Services associated with the proxy, contextualized for this particular proxy.
   362  	// These are unique to this proxy, as the port information is specific to it - while a ServicePort is shared with the
   363  	// service, the target port may be distinct per-endpoint. So this maintains a view specific to this proxy.
   364  	// ServiceTargets will maintain a list entry for each Service-port, so if we have 2 services each with 3 ports, we
   365  	// would have 6 entries.
   366  	ServiceTargets []ServiceTarget
   367  
   368  	// Istio version associated with the Proxy
   369  	IstioVersion *IstioVersion
   370  
   371  	// VerifiedIdentity determines whether a proxy had its identity verified. This
   372  	// generally occurs by JWT or mTLS authentication. This can be false when
   373  	// connecting over plaintext. If this is set to true, we can verify the proxy has
   374  	// access to ConfigNamespace namespace. However, other options such as node type
   375  	// are not part of an Istio identity and thus are not verified.
   376  	VerifiedIdentity *spiffe.Identity
   377  
   378  	// IPMode of proxy.
   379  	ipMode IPMode
   380  
   381  	// GlobalUnicastIP stores the global unicast IP if available, otherwise nil
   382  	GlobalUnicastIP string
   383  
   384  	// XdsResourceGenerator is used to generate resources for the node, based on the PushContext.
   385  	// If nil, the default networking/core v2 generator is used. This field can be set
   386  	// at connect time, based on node metadata, to trigger generation of a different style
   387  	// of configuration.
   388  	XdsResourceGenerator XdsResourceGenerator
   389  
   390  	// WatchedResources contains the list of watched resources for the proxy, keyed by the DiscoveryRequest TypeUrl.
   391  	WatchedResources map[string]*WatchedResource
   392  
   393  	// XdsNode is the xDS node identifier
   394  	XdsNode *core.Node
   395  
   396  	workloadEntryName        string
   397  	workloadEntryAutoCreated bool
   398  
   399  	// LastPushContext stores the most recent push context for this proxy. This will be monotonically
   400  	// increasing in version. Requests should send config based on this context; not the global latest.
   401  	// Historically, the latest was used which can cause problems when computing whether a push is
   402  	// required, as the computed sidecar scope version would not monotonically increase.
   403  	LastPushContext *PushContext
   404  	// LastPushTime records the time of the last push. This is used in conjunction with
   405  	// LastPushContext; the XDS cache depends on knowing the time of the PushContext to determine if a
   406  	// key is stale or not.
   407  	LastPushTime time.Time
   408  }
   409  
   410  type WatchedResource = xds.WatchedResource
   411  
   412  var istioVersionRegexp = regexp.MustCompile(`^([1-9]+)\.([0-9]+)(\.([0-9]+))?`)
   413  
   414  // GetView returns a restricted view of the mesh for this proxy. The view can be
   415  // restricted by network (via ISTIO_META_REQUESTED_NETWORK_VIEW).
   416  // If not set, we assume that the proxy wants to see endpoints in any network.
   417  func (node *Proxy) GetView() ProxyView {
   418  	return newProxyView(node)
   419  }
   420  
   421  // InNetwork returns true if the proxy is on the given network, or if either
   422  // the proxy's network or the given network is unspecified ("").
   423  func (node *Proxy) InNetwork(network network.ID) bool {
   424  	return node == nil || identifier.IsSameOrEmpty(network.String(), node.Metadata.Network.String())
   425  }
   426  
   427  // InCluster returns true if the proxy is in the given cluster, or if either
   428  // the proxy's cluster id or the given cluster id is unspecified ("").
   429  func (node *Proxy) InCluster(cluster cluster.ID) bool {
   430  	return node == nil || identifier.IsSameOrEmpty(cluster.String(), node.Metadata.ClusterID.String())
   431  }
   432  
   433  // IsWaypointProxy returns true if the proxy is acting as a waypoint proxy in an ambient mesh.
   434  func (node *Proxy) IsWaypointProxy() bool {
   435  	return node.Type == Waypoint
   436  }
   437  
   438  // IsZTunnel returns true if the proxy is acting as a ztunnel in an ambient mesh.
   439  func (node *Proxy) IsZTunnel() bool {
   440  	return node.Type == Ztunnel
   441  }
   442  
   443  // IsAmbient returns true if the proxy is acting as either a ztunnel or a waypoint proxy in an ambient mesh.
   444  func (node *Proxy) IsAmbient() bool {
   445  	return node.IsWaypointProxy() || node.IsZTunnel()
   446  }
   447  
   448  // IstioVersion encodes the Istio version of the proxy. This is a low key way to
   449  // do semver style comparisons and generate the appropriate envoy config
   450  type IstioVersion struct {
   451  	Major int
   452  	Minor int
   453  	Patch int
   454  }
   455  
   456  var MaxIstioVersion = &IstioVersion{Major: 65535, Minor: 65535, Patch: 65535}
   457  
   458  // Compare returns -1/0/1 if version is less than, equal or greater than inv
   459  // To compare only on major, call this function with { X, -1, -1}.
   460  // to compare only on major & minor, call this function with {X, Y, -1}.
   461  func (pversion *IstioVersion) Compare(inv *IstioVersion) int {
   462  	// check major
   463  	if r := compareVersion(pversion.Major, inv.Major); r != 0 {
   464  		return r
   465  	}
   466  
   467  	// check minor
   468  	if inv.Minor > -1 {
   469  		if r := compareVersion(pversion.Minor, inv.Minor); r != 0 {
   470  			return r
   471  		}
   472  
   473  		// check patch
   474  		if inv.Patch > -1 {
   475  			if r := compareVersion(pversion.Patch, inv.Patch); r != 0 {
   476  				return r
   477  			}
   478  		}
   479  	}
   480  	return 0
   481  }
   482  
   483  func compareVersion(ov, nv int) int {
   484  	if ov == nv {
   485  		return 0
   486  	}
   487  	if ov < nv {
   488  		return -1
   489  	}
   490  	return 1
   491  }
   492  
   493  var NodeTypes = [...]NodeType{SidecarProxy, Router, Waypoint, Ztunnel}
   494  
   495  // SetSidecarScope identifies the sidecar scope object associated with this
   496  // proxy and updates the proxy Node. This is a convenience hack so that
   497  // callers can simply call push.Services(node) while the implementation of
   498  // push.Services can return the set of services from the proxyNode's
   499  // sidecar scope or from the push context's set of global services. Similar
   500  // logic applies to push.VirtualServices and push.DestinationRule. The
   501  // short cut here is useful only for CDS and parts of RDS generation code.
   502  //
   503  // Listener generation code will still use the SidecarScope object directly
   504  // as it needs the set of services for each listener port.
   505  func (node *Proxy) SetSidecarScope(ps *PushContext) {
   506  	sidecarScope := node.SidecarScope
   507  
   508  	switch node.Type {
   509  	case SidecarProxy:
   510  		node.SidecarScope = ps.getSidecarScope(node, node.Labels)
   511  	case Router, Waypoint:
   512  		// Gateways should just have a default scope with egress: */*
   513  		node.SidecarScope = ps.getSidecarScope(node, nil)
   514  	}
   515  	node.PrevSidecarScope = sidecarScope
   516  }
   517  
   518  func (node *Proxy) VersionGreaterAndEqual(inv *IstioVersion) bool {
   519  	if inv == nil {
   520  		return true
   521  	}
   522  	return node.IstioVersion.Compare(inv) >= 0
   523  }
   524  
   525  // SetGatewaysForProxy merges the Gateway objects associated with this
   526  // proxy and caches the merged object in the proxy Node. This is a convenience hack so that
   527  // callers can simply call push.MergedGateways(node) instead of having to
   528  // fetch all the gateways and invoke the merge call in multiple places (lds/rds).
   529  // Must be called after ServiceTargets are set
   530  func (node *Proxy) SetGatewaysForProxy(ps *PushContext) {
   531  	if node.Type != Router {
   532  		return
   533  	}
   534  	var prevMergedGateway MergedGateway
   535  	if node.MergedGateway != nil {
   536  		prevMergedGateway = *node.MergedGateway
   537  	}
   538  	node.MergedGateway = ps.mergeGateways(node)
   539  	node.PrevMergedGateway = &PrevMergedGateway{
   540  		ContainsAutoPassthroughGateways: prevMergedGateway.ContainsAutoPassthroughGateways,
   541  		AutoPassthroughSNIHosts:         prevMergedGateway.GetAutoPassthrughGatewaySNIHosts(),
   542  	}
   543  }
   544  
   545  func (node *Proxy) SetServiceTargets(serviceDiscovery ServiceDiscovery) {
   546  	instances := serviceDiscovery.GetProxyServiceTargets(node)
   547  
   548  	// Keep service instances in order of creation/hostname.
   549  	sort.SliceStable(instances, func(i, j int) bool {
   550  		if instances[i].Service != nil && instances[j].Service != nil {
   551  			if !instances[i].Service.CreationTime.Equal(instances[j].Service.CreationTime) {
   552  				return instances[i].Service.CreationTime.Before(instances[j].Service.CreationTime)
   553  			}
   554  			// Additionally, sort by hostname just in case services created automatically at the same second.
   555  			return instances[i].Service.Hostname < instances[j].Service.Hostname
   556  		}
   557  		return true
   558  	})
   559  
   560  	node.ServiceTargets = instances
   561  }
   562  
   563  // SetWorkloadLabels will set the node.Labels.
   564  // It merges both node meta labels and workload labels and give preference to workload labels.
   565  func (node *Proxy) SetWorkloadLabels(env *Environment) {
   566  	// If this is VM proxy, do not override labels at all, because in istio test we use pod to simulate VM.
   567  	if node.IsVM() {
   568  		node.Labels = node.Metadata.Labels
   569  		return
   570  	}
   571  	labels := env.GetProxyWorkloadLabels(node)
   572  	if labels != nil {
   573  		node.Labels = make(map[string]string, len(labels)+len(node.Metadata.StaticLabels))
   574  		// we can't just equate proxy workload labels to node meta labels as it may be customized by user
   575  		// with `ISTIO_METAJSON_LABELS` env (pkg/bootstrap/config.go extractAttributesMetadata).
   576  		// so, we fill the `ISTIO_METAJSON_LABELS` as well.
   577  		for k, v := range node.Metadata.StaticLabels {
   578  			node.Labels[k] = v
   579  		}
   580  		for k, v := range labels {
   581  			node.Labels[k] = v
   582  		}
   583  	} else {
   584  		// If could not find pod labels, fallback to use the node metadata labels.
   585  		node.Labels = node.Metadata.Labels
   586  	}
   587  }
   588  
   589  // DiscoverIPMode discovers the IP Versions supported by Proxy based on its IP addresses.
   590  func (node *Proxy) DiscoverIPMode() {
   591  	node.ipMode = pm.DiscoverIPMode(node.IPAddresses)
   592  	node.GlobalUnicastIP = networkutil.GlobalUnicastIP(node.IPAddresses)
   593  }
   594  
   595  // SupportsIPv4 returns true if proxy supports IPv4 addresses.
   596  func (node *Proxy) SupportsIPv4() bool {
   597  	return node.ipMode == IPv4 || node.ipMode == Dual
   598  }
   599  
   600  // SupportsIPv6 returns true if proxy supports IPv6 addresses.
   601  func (node *Proxy) SupportsIPv6() bool {
   602  	return node.ipMode == IPv6 || node.ipMode == Dual
   603  }
   604  
   605  // IsIPv6 returns true if proxy only supports IPv6 addresses.
   606  func (node *Proxy) IsIPv6() bool {
   607  	return node.ipMode == IPv6
   608  }
   609  
   610  func (node *Proxy) IsDualStack() bool {
   611  	return node.ipMode == Dual
   612  }
   613  
   614  // GetIPMode returns proxy's ipMode
   615  func (node *Proxy) GetIPMode() IPMode {
   616  	return node.ipMode
   617  }
   618  
   619  // ParseMetadata parses the opaque Metadata from an Envoy Node into string key-value pairs.
   620  // Any non-string values are ignored.
   621  func ParseMetadata(metadata *structpb.Struct) (*NodeMetadata, error) {
   622  	if metadata == nil {
   623  		return &NodeMetadata{}, nil
   624  	}
   625  
   626  	bootstrapNodeMeta, err := ParseBootstrapNodeMetadata(metadata)
   627  	if err != nil {
   628  		return nil, err
   629  	}
   630  	return &bootstrapNodeMeta.NodeMetadata, nil
   631  }
   632  
   633  // ParseBootstrapNodeMetadata parses the opaque Metadata from an Envoy Node into string key-value pairs.
   634  func ParseBootstrapNodeMetadata(metadata *structpb.Struct) (*BootstrapNodeMetadata, error) {
   635  	if metadata == nil {
   636  		return &BootstrapNodeMetadata{}, nil
   637  	}
   638  
   639  	b, err := protomarshal.MarshalProtoNames(metadata)
   640  	if err != nil {
   641  		return nil, fmt.Errorf("failed to read node metadata %v: %v", metadata, err)
   642  	}
   643  	meta := &BootstrapNodeMetadata{}
   644  	if err := json.Unmarshal(b, meta); err != nil {
   645  		return nil, fmt.Errorf("failed to unmarshal node metadata (%v): %v", string(b), err)
   646  	}
   647  	return meta, nil
   648  }
   649  
   650  // ParseServiceNodeWithMetadata parse the Envoy Node from the string generated by ServiceNode
   651  // function and the metadata.
   652  func ParseServiceNodeWithMetadata(nodeID string, metadata *NodeMetadata) (*Proxy, error) {
   653  	parts := strings.Split(nodeID, serviceNodeSeparator)
   654  	out := &Proxy{
   655  		Metadata: metadata,
   656  	}
   657  
   658  	if len(parts) != 4 {
   659  		return out, fmt.Errorf("missing parts in the service node %q", nodeID)
   660  	}
   661  
   662  	if !pm.IsApplicationNodeType(NodeType(parts[0])) {
   663  		return out, fmt.Errorf("invalid node type (valid types: %v) in the service node %q", NodeTypes, nodeID)
   664  	}
   665  	out.Type = NodeType(parts[0])
   666  
   667  	// Get all IP Addresses from Metadata
   668  	if hasValidIPAddresses(metadata.InstanceIPs) {
   669  		out.IPAddresses = metadata.InstanceIPs
   670  	} else if netutil.IsValidIPAddress(parts[1]) {
   671  		// Fall back, use IP from node id, it's only for backward-compatibility, IP should come from metadata
   672  		out.IPAddresses = append(out.IPAddresses, parts[1])
   673  	}
   674  
   675  	// Does query from ingress or router have to carry valid IP address?
   676  	if len(out.IPAddresses) == 0 {
   677  		return out, fmt.Errorf("no valid IP address in the service node id or metadata")
   678  	}
   679  
   680  	out.ID = parts[2]
   681  	out.DNSDomain = parts[3]
   682  	if len(metadata.IstioVersion) == 0 {
   683  		log.Warnf("Istio Version is not found in metadata for %v, which may have undesirable side effects", out.ID)
   684  	}
   685  	out.IstioVersion = ParseIstioVersion(metadata.IstioVersion)
   686  	return out, nil
   687  }
   688  
   689  // ParseIstioVersion parses a version string and returns IstioVersion struct
   690  func ParseIstioVersion(ver string) *IstioVersion {
   691  	// strip the release- prefix if any and extract the version string
   692  	ver = istioVersionRegexp.FindString(strings.TrimPrefix(ver, "release-"))
   693  
   694  	if ver == "" {
   695  		// return very large values assuming latest version
   696  		return MaxIstioVersion
   697  	}
   698  
   699  	parts := strings.Split(ver, ".")
   700  	// we are guaranteed to have at least major and minor based on the regex
   701  	major, _ := strconv.Atoi(parts[0])
   702  	minor, _ := strconv.Atoi(parts[1])
   703  	// Assume very large patch release if not set
   704  	patch := 65535
   705  	if len(parts) > 2 {
   706  		patch, _ = strconv.Atoi(parts[2])
   707  	}
   708  	return &IstioVersion{Major: major, Minor: minor, Patch: patch}
   709  }
   710  
   711  // GetOrDefault returns either the value, or the default if the value is empty. Useful when retrieving node metadata fields.
   712  func GetOrDefault(s string, def string) string {
   713  	return pm.GetOrDefault(s, def)
   714  }
   715  
   716  // GetProxyConfigNamespace extracts the namespace associated with the proxy
   717  // from the proxy metadata or the proxy ID
   718  func GetProxyConfigNamespace(proxy *Proxy) string {
   719  	if proxy == nil {
   720  		return ""
   721  	}
   722  
   723  	// First look for ISTIO_META_CONFIG_NAMESPACE
   724  	// All newer proxies (from Istio 1.1 onwards) are supposed to supply this
   725  	if len(proxy.Metadata.Namespace) > 0 {
   726  		return proxy.Metadata.Namespace
   727  	}
   728  
   729  	// if not found, for backward compatibility, extract the namespace from
   730  	// the proxy domain. this is a k8s specific hack and should be enabled
   731  	parts := strings.Split(proxy.DNSDomain, ".")
   732  	if len(parts) > 1 { // k8s will have namespace.<domain>
   733  		return parts[0]
   734  	}
   735  
   736  	return ""
   737  }
   738  
   739  const (
   740  	serviceNodeSeparator = "~"
   741  )
   742  
   743  // ParsePort extracts port number from a valid proxy address
   744  func ParsePort(addr string) int {
   745  	_, sPort, err := net.SplitHostPort(addr)
   746  	if sPort == "" {
   747  		return 0
   748  	}
   749  	if err != nil {
   750  		log.Warn(err)
   751  	}
   752  	port, pErr := strconv.Atoi(sPort)
   753  	if pErr != nil {
   754  		log.Warn(pErr)
   755  	}
   756  
   757  	return port
   758  }
   759  
   760  // hasValidIPAddresses returns true if the input ips are all valid, otherwise returns false.
   761  func hasValidIPAddresses(ipAddresses []string) bool {
   762  	if len(ipAddresses) == 0 {
   763  		return false
   764  	}
   765  	for _, ipAddress := range ipAddresses {
   766  		if !netutil.IsValidIPAddress(ipAddress) {
   767  			return false
   768  		}
   769  	}
   770  	return true
   771  }
   772  
   773  const (
   774  	// InterceptionNone indicates that the workload is not using IPtables for traffic interception
   775  	InterceptionNone TrafficInterceptionMode = "NONE"
   776  
   777  	// InterceptionTproxy implies traffic intercepted by IPtables with TPROXY mode
   778  	InterceptionTproxy TrafficInterceptionMode = "TPROXY"
   779  
   780  	// InterceptionRedirect implies traffic intercepted by IPtables with REDIRECT mode
   781  	// This is our default mode
   782  	InterceptionRedirect TrafficInterceptionMode = "REDIRECT"
   783  )
   784  
   785  // GetInterceptionMode extracts the interception mode associated with the proxy
   786  // from the proxy metadata
   787  func (node *Proxy) GetInterceptionMode() TrafficInterceptionMode {
   788  	if node == nil {
   789  		return InterceptionRedirect
   790  	}
   791  
   792  	switch node.Metadata.InterceptionMode {
   793  	case "TPROXY":
   794  		return InterceptionTproxy
   795  	case "REDIRECT":
   796  		return InterceptionRedirect
   797  	case "NONE":
   798  		return InterceptionNone
   799  	}
   800  
   801  	return InterceptionRedirect
   802  }
   803  
   804  // IsUnprivileged returns true if the proxy has explicitly indicated that it is
   805  // unprivileged, i.e. it cannot bind to the privileged ports 1-1023.
   806  func (node *Proxy) IsUnprivileged() bool {
   807  	if node == nil || node.Metadata == nil {
   808  		return false
   809  	}
   810  	// expect explicit "true" value
   811  	unprivileged, _ := strconv.ParseBool(node.Metadata.UnprivilegedPod)
   812  	return unprivileged
   813  }
   814  
   815  // CanBindToPort returns true if the proxy can bind to a given port.
   816  func (node *Proxy) CanBindToPort(bindTo bool, port uint32) bool {
   817  	if bindTo {
   818  		if IsPrivilegedPort(port) && node.IsUnprivileged() {
   819  			return false
   820  		}
   821  		if node.Metadata != nil &&
   822  			(node.Metadata.EnvoyPrometheusPort == int(port) || node.Metadata.EnvoyStatusPort == int(port)) {
   823  			// can not bind to port that already bound by proxy static listener
   824  			return false
   825  		}
   826  	}
   827  	return true
   828  }
   829  
   830  // IsPrivilegedPort returns true if a given port is in the range 1-1023.
   831  func IsPrivilegedPort(port uint32) bool {
   832  	// check for 0 is important because:
   833  	// 1) technically, 0 is not a privileged port; any process can ask to bind to 0
   834  	// 2) this function will be receiving 0 on input in the case of UDS listeners
   835  	return 0 < port && port < 1024
   836  }
   837  
   838  func (node *Proxy) IsVM() bool {
   839  	// TODO use node metadata to indicate that this is a VM instead of the TestVMLabel
   840  	return node.Metadata.Labels[constants.TestVMLabel] != ""
   841  }
   842  
   843  func (node *Proxy) IsProxylessGrpc() bool {
   844  	return node.Metadata != nil && node.Metadata.Generator == "grpc"
   845  }
   846  
   847  func (node *Proxy) GetNodeName() string {
   848  	if node.Metadata != nil && len(node.Metadata.NodeName) > 0 {
   849  		return node.Metadata.NodeName
   850  	}
   851  	// fall back to get the node name from labels
   852  	// this can happen for an "old" proxy with no `Metadata.NodeName` set
   853  	// TODO: remove this when 1.16 is EOL?
   854  	return node.Labels[label.LabelHostname]
   855  }
   856  
   857  func (node *Proxy) GetClusterID() cluster.ID {
   858  	if node == nil || node.Metadata == nil {
   859  		return ""
   860  	}
   861  	return node.Metadata.ClusterID
   862  }
   863  
   864  func (node *Proxy) GetNamespace() string {
   865  	if node == nil || node.Metadata == nil {
   866  		return ""
   867  	}
   868  	return node.Metadata.Namespace
   869  }
   870  
   871  func (node *Proxy) GetIstioVersion() string {
   872  	if node == nil || node.Metadata == nil {
   873  		return ""
   874  	}
   875  	return node.Metadata.IstioVersion
   876  }
   877  
   878  func (node *Proxy) GetID() string {
   879  	if node == nil {
   880  		return ""
   881  	}
   882  	return node.ID
   883  }
   884  
   885  func (node *Proxy) FuzzValidate() bool {
   886  	if node.Metadata == nil {
   887  		return false
   888  	}
   889  	found := false
   890  	for _, t := range NodeTypes {
   891  		if node.Type == t {
   892  			found = true
   893  			break
   894  		}
   895  	}
   896  	if !found {
   897  		return false
   898  	}
   899  	return len(node.IPAddresses) != 0
   900  }
   901  
   902  func (node *Proxy) EnableHBONEListen() bool {
   903  	return node.IsAmbient() || (features.EnableSidecarHBONEListening && bool(node.Metadata.EnableHBONE))
   904  }
   905  
   906  func (node *Proxy) SetWorkloadEntry(name string, create bool) {
   907  	node.Lock()
   908  	defer node.Unlock()
   909  	node.workloadEntryName = name
   910  	node.workloadEntryAutoCreated = create
   911  }
   912  
   913  func (node *Proxy) WorkloadEntry() (string, bool) {
   914  	node.RLock()
   915  	defer node.RUnlock()
   916  	return node.workloadEntryName, node.workloadEntryAutoCreated
   917  }
   918  
   919  // CloneWatchedResources clones the watched resources, both the keys and values are shallow copy.
   920  func (node *Proxy) CloneWatchedResources() map[string]*WatchedResource {
   921  	node.RLock()
   922  	defer node.RUnlock()
   923  	return maps.Clone(node.WatchedResources)
   924  }
   925  
   926  func (node *Proxy) GetWatchedResourceTypes() sets.String {
   927  	node.RLock()
   928  	defer node.RUnlock()
   929  
   930  	ret := sets.NewWithLength[string](len(node.WatchedResources))
   931  	for typeURL := range node.WatchedResources {
   932  		ret.Insert(typeURL)
   933  	}
   934  	return ret
   935  }
   936  
   937  func (node *Proxy) GetWatchedResource(typeURL string) *WatchedResource {
   938  	node.RLock()
   939  	defer node.RUnlock()
   940  
   941  	return node.WatchedResources[typeURL]
   942  }
   943  
   944  func (node *Proxy) NewWatchedResource(typeURL string, names []string) {
   945  	node.Lock()
   946  	defer node.Unlock()
   947  
   948  	node.WatchedResources[typeURL] = &WatchedResource{TypeUrl: typeURL, ResourceNames: names}
   949  	// For all EDS requests that we have already responded with in the same stream let us
   950  	// force the response. It is important to respond to those requests for Envoy to finish
   951  	// warming of those resources(Clusters).
   952  	// This can happen with the following sequence
   953  	// 1. Envoy disconnects and reconnects to Istiod.
   954  	// 2. Envoy sends EDS request and we respond with it.
   955  	// 3. Envoy sends CDS request and we respond with clusters.
   956  	// 4. Envoy detects a change in cluster state and tries to warm those clusters and send EDS request for them.
   957  	// 5. We should respond to the EDS request with Endpoints to let Envoy finish cluster warming.
   958  	// Refer to https://github.com/envoyproxy/envoy/issues/13009 for more details.
   959  	for _, dependent := range WarmingDependencies(typeURL) {
   960  		if dwr, exists := node.WatchedResources[dependent]; exists {
   961  			dwr.AlwaysRespond = true
   962  		}
   963  	}
   964  }
   965  
   966  // WarmingDependencies returns the dependent typeURLs that need to be responded with
   967  // for warming of this typeURL.
   968  func WarmingDependencies(typeURL string) []string {
   969  	switch typeURL {
   970  	case v3.ClusterType:
   971  		return []string{v3.EndpointType}
   972  	default:
   973  		return nil
   974  	}
   975  }
   976  
   977  func (node *Proxy) AddOrUpdateWatchedResource(r *WatchedResource) {
   978  	if r == nil {
   979  		return
   980  	}
   981  	node.Lock()
   982  	defer node.Unlock()
   983  	node.WatchedResources[r.TypeUrl] = r
   984  }
   985  
   986  func (node *Proxy) UpdateWatchedResource(typeURL string, updateFn func(*WatchedResource) *WatchedResource) {
   987  	node.Lock()
   988  	defer node.Unlock()
   989  	r := node.WatchedResources[typeURL]
   990  	r = updateFn(r)
   991  	if r != nil {
   992  		node.WatchedResources[typeURL] = r
   993  	} else {
   994  		delete(node.WatchedResources, typeURL)
   995  	}
   996  }
   997  
   998  func (node *Proxy) DeleteWatchedResource(typeURL string) {
   999  	node.Lock()
  1000  	defer node.Unlock()
  1001  
  1002  	delete(node.WatchedResources, typeURL)
  1003  }
  1004  
  1005  // SupportsEnvoyExtendedJwt indicates that the proxy JWT extension is capable of
  1006  // replacing istio_authn filter.
  1007  func (node *Proxy) SupportsEnvoyExtendedJwt() bool {
  1008  	return node.IstioVersion == nil ||
  1009  		node.IstioVersion.Compare(&IstioVersion{Major: 1, Minor: 21, Patch: -1}) >= 0
  1010  }
  1011  
  1012  type GatewayController interface {
  1013  	ConfigStoreController
  1014  	// Reconcile updates the internal state of the gateway controller for a given input. This should be
  1015  	// called before any List/Get calls if the state has changed
  1016  	Reconcile(ctx *PushContext) error
  1017  	// SecretAllowed determines if a SDS credential is accessible to a given namespace.
  1018  	// For example, for resourceName of `kubernetes-gateway://ns-name/secret-name` and namespace of `ingress-ns`,
  1019  	// this would return true only if there was a policy allowing `ingress-ns` to access Secrets in the `ns-name` namespace.
  1020  	SecretAllowed(resourceName string, namespace string) bool
  1021  }
  1022  
  1023  // OutboundListenerClass is a helper to turn a NodeType for outbound to a ListenerClass.
  1024  func OutboundListenerClass(t NodeType) istionetworking.ListenerClass {
  1025  	if t == Router {
  1026  		return istionetworking.ListenerClassGateway
  1027  	}
  1028  	return istionetworking.ListenerClassSidecarOutbound
  1029  }