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 }