istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/push_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 "cmp" 19 "encoding/json" 20 "fmt" 21 "math" 22 "sort" 23 "strings" 24 "sync" 25 "time" 26 27 "go.uber.org/atomic" 28 "k8s.io/apimachinery/pkg/types" 29 30 extensions "istio.io/api/extensions/v1alpha1" 31 meshconfig "istio.io/api/mesh/v1alpha1" 32 networking "istio.io/api/networking/v1alpha3" 33 "istio.io/api/security/v1beta1" 34 "istio.io/istio/pilot/pkg/features" 35 "istio.io/istio/pilot/pkg/serviceregistry/provider" 36 "istio.io/istio/pkg/cluster" 37 "istio.io/istio/pkg/config" 38 "istio.io/istio/pkg/config/constants" 39 "istio.io/istio/pkg/config/host" 40 "istio.io/istio/pkg/config/labels" 41 "istio.io/istio/pkg/config/schema/gvk" 42 "istio.io/istio/pkg/config/schema/kind" 43 "istio.io/istio/pkg/config/security" 44 "istio.io/istio/pkg/config/visibility" 45 "istio.io/istio/pkg/jwt" 46 "istio.io/istio/pkg/monitoring" 47 "istio.io/istio/pkg/network" 48 "istio.io/istio/pkg/slices" 49 "istio.io/istio/pkg/spiffe" 50 "istio.io/istio/pkg/util/sets" 51 "istio.io/istio/pkg/workloadapi" 52 "istio.io/istio/pkg/xds" 53 ) 54 55 // Metrics is an interface for capturing metrics on a per-node basis. 56 type Metrics interface { 57 // AddMetric will add an case to the metric for the given node. 58 AddMetric(metric monitoring.Metric, key string, proxyID, msg string) 59 } 60 61 var _ Metrics = &PushContext{} 62 63 // serviceIndex is an index of all services by various fields for easy access during push. 64 type serviceIndex struct { 65 // privateByNamespace are services that can reachable within the same namespace, with exportTo "." 66 privateByNamespace map[string][]*Service 67 // public are services reachable within the mesh with exportTo "*" 68 public []*Service 69 // exportedToNamespace are services that were made visible to this namespace 70 // by an exportTo explicitly specifying this namespace. 71 exportedToNamespace map[string][]*Service 72 73 // HostnameAndNamespace has all services, indexed by hostname then namespace. 74 HostnameAndNamespace map[host.Name]map[string]*Service `json:"-"` 75 76 // instancesByPort contains a map of service key and instances by port. It is stored here 77 // to avoid recomputations during push. This caches instanceByPort calls with empty labels. 78 // Call InstancesByPort directly when instances need to be filtered by actual labels. 79 instancesByPort map[string]map[int][]*IstioEndpoint 80 } 81 82 func newServiceIndex() serviceIndex { 83 return serviceIndex{ 84 public: []*Service{}, 85 privateByNamespace: map[string][]*Service{}, 86 exportedToNamespace: map[string][]*Service{}, 87 HostnameAndNamespace: map[host.Name]map[string]*Service{}, 88 instancesByPort: map[string]map[int][]*IstioEndpoint{}, 89 } 90 } 91 92 // exportToDefaults contains the default exportTo values. 93 type exportToDefaults struct { 94 service sets.Set[visibility.Instance] 95 virtualService sets.Set[visibility.Instance] 96 destinationRule sets.Set[visibility.Instance] 97 } 98 99 // virtualServiceIndex is the index of virtual services by various fields. 100 type virtualServiceIndex struct { 101 exportedToNamespaceByGateway map[types.NamespacedName][]config.Config 102 // this contains all the virtual services with exportTo "." and current namespace. The keys are namespace,gateway. 103 privateByNamespaceAndGateway map[types.NamespacedName][]config.Config 104 // This contains all virtual services whose exportTo is "*", keyed by gateway 105 publicByGateway map[string][]config.Config 106 // root vs namespace/name ->delegate vs virtualservice gvk/namespace/name 107 delegates map[ConfigKey][]ConfigKey 108 109 // This contains destination hosts of virtual services, keyed by gateway's namespace/name, 110 // only used when PILOT_FILTER_GATEWAY_CLUSTER_CONFIG is enabled 111 destinationsByGateway map[string]sets.String 112 113 // Map of VS hostname -> referenced hostnames 114 referencedDestinations map[string]sets.String 115 } 116 117 func newVirtualServiceIndex() virtualServiceIndex { 118 out := virtualServiceIndex{ 119 publicByGateway: map[string][]config.Config{}, 120 privateByNamespaceAndGateway: map[types.NamespacedName][]config.Config{}, 121 exportedToNamespaceByGateway: map[types.NamespacedName][]config.Config{}, 122 delegates: map[ConfigKey][]ConfigKey{}, 123 referencedDestinations: map[string]sets.String{}, 124 } 125 if features.FilterGatewayClusterConfig { 126 out.destinationsByGateway = make(map[string]sets.String) 127 } 128 return out 129 } 130 131 // destinationRuleIndex is the index of destination rules by various fields. 132 type destinationRuleIndex struct { 133 // namespaceLocal contains all public/private dest rules pertaining to a service defined in a given namespace. 134 namespaceLocal map[string]*consolidatedDestRules 135 // exportedByNamespace contains all dest rules pertaining to a service exported by a namespace. 136 exportedByNamespace map[string]*consolidatedDestRules 137 rootNamespaceLocal *consolidatedDestRules 138 } 139 140 func newDestinationRuleIndex() destinationRuleIndex { 141 return destinationRuleIndex{ 142 namespaceLocal: map[string]*consolidatedDestRules{}, 143 exportedByNamespace: map[string]*consolidatedDestRules{}, 144 } 145 } 146 147 // sidecarIndex is the index of sidecar rules 148 type sidecarIndex struct { 149 // user configured sidecars for each namespace if available. 150 sidecarsByNamespace map[string][]*SidecarScope 151 // the Sidecar for the root namespace (if present). This applies to any namespace without its own Sidecar. 152 meshRootSidecarConfig *config.Config 153 // meshRootSidecarsByNamespace contains the default sidecar for namespaces that do not have a sidecar. 154 // These are converted from root namespace sidecar if it exists. 155 // These are lazy-loaded. Access protected by derivedSidecarMutex. 156 meshRootSidecarsByNamespace map[string]*SidecarScope 157 // defaultSidecarsByNamespace contains the default sidecar for namespaces that do not have a sidecar, 158 // These are *always* computed from DefaultSidecarScopeForNamespace i.e. a sidecar that has listeners 159 // for all services in the mesh. This will be used if there is no sidecar specified in root namespace. 160 // These are lazy-loaded. Access protected by derivedSidecarMutex. 161 defaultSidecarsByNamespace map[string]*SidecarScope 162 // sidecarsForGatewayByNamespace contains the default sidecar for gateways and waypoints, 163 // These are *always* computed from DefaultSidecarScopeForGateway. 164 // These are lazy-loaded. Access protected by derivedSidecarMutex. 165 sidecarsForGatewayByNamespace map[string]*SidecarScope 166 167 // mutex to protect derived sidecars i.e. not specified by user. 168 derivedSidecarMutex *sync.RWMutex 169 } 170 171 func newSidecarIndex() sidecarIndex { 172 return sidecarIndex{ 173 sidecarsByNamespace: map[string][]*SidecarScope{}, 174 meshRootSidecarsByNamespace: map[string]*SidecarScope{}, 175 defaultSidecarsByNamespace: map[string]*SidecarScope{}, 176 sidecarsForGatewayByNamespace: map[string]*SidecarScope{}, 177 derivedSidecarMutex: &sync.RWMutex{}, 178 } 179 } 180 181 // gatewayIndex is the index of gateways by various fields. 182 type gatewayIndex struct { 183 // namespace contains gateways by namespace. 184 namespace map[string][]config.Config 185 // all contains all gateways. 186 all []config.Config 187 } 188 189 func newGatewayIndex() gatewayIndex { 190 return gatewayIndex{ 191 namespace: map[string][]config.Config{}, 192 all: []config.Config{}, 193 } 194 } 195 196 type serviceAccountKey struct { 197 hostname host.Name 198 namespace string 199 } 200 201 // PushContext tracks the status of a push - metrics and errors. 202 // Metrics are reset after a push - at the beginning all 203 // values are zero, and when push completes the status is reset. 204 // The struct is exposed in a debug endpoint - fields public to allow 205 // easy serialization as json. 206 type PushContext struct { 207 proxyStatusMutex sync.RWMutex 208 // ProxyStatus is keyed by the error code, and holds a map keyed 209 // by the ID. 210 ProxyStatus map[string]map[string]ProxyPushStatus 211 212 // Synthesized from env.Mesh 213 exportToDefaults exportToDefaults 214 215 // ServiceIndex is the index of services by various fields. 216 ServiceIndex serviceIndex 217 218 // serviceAccounts contains a map of hostname and port to service accounts. 219 serviceAccounts map[serviceAccountKey][]string 220 221 // virtualServiceIndex is the index of virtual services by various fields. 222 virtualServiceIndex virtualServiceIndex 223 224 // destinationRuleIndex is the index of destination rules by various fields. 225 destinationRuleIndex destinationRuleIndex 226 227 // gatewayIndex is the index of gateways. 228 gatewayIndex gatewayIndex 229 230 // clusterLocalHosts extracted from the MeshConfig 231 clusterLocalHosts ClusterLocalHosts 232 233 // sidecarIndex stores sidecar resources 234 sidecarIndex sidecarIndex 235 236 // envoy filters for each namespace including global config namespace 237 envoyFiltersByNamespace map[string][]*EnvoyFilterWrapper 238 239 // wasm plugins for each namespace including global config namespace 240 wasmPluginsByNamespace map[string][]*WasmPluginWrapper 241 242 // AuthnPolicies contains Authn policies by namespace. 243 AuthnPolicies *AuthenticationPolicies `json:"-"` 244 245 // AuthzPolicies stores the existing authorization policies in the cluster. Could be nil if there 246 // are no authorization policies in the cluster. 247 AuthzPolicies *AuthorizationPolicies `json:"-"` 248 249 // Telemetry stores the existing Telemetry resources for the cluster. 250 Telemetry *Telemetries `json:"-"` 251 252 // ProxyConfig stores the existing ProxyConfig resources for the cluster. 253 ProxyConfigs *ProxyConfigs `json:"-"` 254 255 // The following data is either a global index or used in the inbound path. 256 // Namespace specific views do not apply here. 257 258 // Mesh configuration for the mesh. 259 Mesh *meshconfig.MeshConfig `json:"-"` 260 261 // PushVersion describes the push version this push context was computed for 262 PushVersion string 263 264 // LedgerVersion is the version of the configuration ledger 265 LedgerVersion string 266 267 // JwtKeyResolver holds a reference to the JWT key resolver instance. 268 JwtKeyResolver *JwksResolver 269 270 // GatewayAPIController holds a reference to the gateway API controller. 271 GatewayAPIController GatewayController 272 273 // cache gateways addresses for each network 274 // this is mainly used for kubernetes multi-cluster scenario 275 networkMgr *NetworkManager 276 277 Networks *meshconfig.MeshNetworks 278 279 InitDone atomic.Bool 280 initializeMutex sync.Mutex 281 ambientIndex AmbientIndexes 282 } 283 284 type consolidatedDestRules struct { 285 // Map of dest rule host to the list of namespaces to which this destination rule has been exported to 286 exportTo map[host.Name]sets.Set[visibility.Instance] 287 // Map of dest rule host and the merged destination rules for that host. 288 // Only stores specific non-wildcard destination rules 289 specificDestRules map[host.Name][]*ConsolidatedDestRule 290 // Map of dest rule host and the merged destination rules for that host. 291 // Only stores wildcard destination rules 292 wildcardDestRules map[host.Name][]*ConsolidatedDestRule 293 } 294 295 // ConsolidatedDestRule represents a dr and from which it is consolidated. 296 type ConsolidatedDestRule struct { 297 // rule is merged from the following destinationRules. 298 rule *config.Config 299 // the original dest rules from which above rule is merged. 300 from []types.NamespacedName 301 } 302 303 // XDSUpdater is used for direct updates of the xDS model and incremental push. 304 // Pilot uses multiple registries - for example each K8S cluster is a registry 305 // instance. Each registry is responsible for tracking a set 306 // of endpoints associated with mesh services, and calling the EDSUpdate on changes. 307 // A registry may group endpoints for a service in smaller subsets - for example by 308 // deployment, or to deal with very large number of endpoints for a service. We want 309 // to avoid passing around large objects - like full list of endpoints for a registry, 310 // or the full list of endpoints for a service across registries, since it limits 311 // scalability. 312 // 313 // Future optimizations will include grouping the endpoints by labels, gateway or region to 314 // reduce the time when subsetting or split-horizon is used. This design assumes pilot 315 // tracks all endpoints in the mesh and they fit in RAM - so limit is few M endpoints. 316 // It is possible to split the endpoint tracking in future. 317 type XDSUpdater interface { 318 // EDSUpdate is called when the list of endpoints or labels in a Service is changed. 319 // For each cluster and hostname, the full list of active endpoints (including empty list) 320 // must be sent. The shard name is used as a key - current implementation is using the 321 // registry name. 322 EDSUpdate(shard ShardKey, hostname string, namespace string, entry []*IstioEndpoint) 323 324 // EDSCacheUpdate is called when the list of endpoints or labels in a Service is changed. 325 // For each cluster and hostname, the full list of active endpoints (including empty list) 326 // must be sent. The shard name is used as a key - current implementation is using the 327 // registry name. 328 // Note: the difference with `EDSUpdate` is that it only update the cache rather than requesting a push 329 EDSCacheUpdate(shard ShardKey, hostname string, namespace string, entry []*IstioEndpoint) 330 331 // SvcUpdate is called when a service definition is updated/deleted. 332 SvcUpdate(shard ShardKey, hostname string, namespace string, event Event) 333 334 // ConfigUpdate is called to notify the XDS server of config updates and request a push. 335 // The requests may be collapsed and throttled. 336 ConfigUpdate(req *PushRequest) 337 338 // ProxyUpdate is called to notify the XDS server to send a push to the specified proxy. 339 // The requests may be collapsed and throttled. 340 ProxyUpdate(clusterID cluster.ID, ip string) 341 342 // RemoveShard removes all endpoints for the given shard key 343 RemoveShard(shardKey ShardKey) 344 } 345 346 // PushRequest defines a request to push to proxies 347 // It is used to send updates to the config update debouncer and pass to the PushQueue. 348 type PushRequest struct { 349 // Full determines whether a full push is required or not. If false, an incremental update will be sent. 350 // Incremental pushes: 351 // * Do not recompute the push context 352 // * Do not recompute proxy state (such as ServiceInstances) 353 // * Are not reported in standard metrics such as push time 354 // As a result, configuration updates should never be incremental. Generally, only EDS will set this, but 355 // in the future SDS will as well. 356 Full bool 357 358 // ConfigsUpdated keeps track of configs that have changed. 359 // This is used as an optimization to avoid unnecessary pushes to proxies that are scoped with a Sidecar. 360 // If this is empty, then all proxies will get an update. 361 // Otherwise only proxies depend on these configs will get an update. 362 // The kind of resources are defined in pkg/config/schemas. 363 ConfigsUpdated sets.Set[ConfigKey] 364 365 // Push stores the push context to use for the update. This may initially be nil, as we will 366 // debounce changes before a PushContext is eventually created. 367 Push *PushContext 368 369 // Start represents the time a push was started. This represents the time of adding to the PushQueue. 370 // Note that this does not include time spent debouncing. 371 Start time.Time 372 373 // Reason represents the reason for requesting a push. This should only be a fixed set of values, 374 // to avoid unbounded cardinality in metrics. If this is not set, it may be automatically filled in later. 375 // There should only be multiple reasons if the push request is the result of two distinct triggers, rather than 376 // classifying a single trigger as having multiple reasons. 377 Reason ReasonStats 378 379 // Delta defines the resources that were added or removed as part of this push request. 380 // This is set only on requests from the client which change the set of resources they (un)subscribe from. 381 Delta ResourceDelta 382 } 383 384 type ResourceDelta = xds.ResourceDelta 385 386 type ReasonStats map[TriggerReason]int 387 388 func NewReasonStats(reasons ...TriggerReason) ReasonStats { 389 ret := make(ReasonStats) 390 for _, reason := range reasons { 391 ret.Add(reason) 392 } 393 return ret 394 } 395 396 func (r ReasonStats) Add(reason TriggerReason) { 397 r[reason]++ 398 } 399 400 func (r ReasonStats) Merge(other ReasonStats) { 401 for reason, count := range other { 402 r[reason] += count 403 } 404 } 405 406 func (r ReasonStats) CopyMerge(other ReasonStats) ReasonStats { 407 if len(r) == 0 { 408 return other 409 } 410 if len(other) == 0 { 411 return r 412 } 413 414 merged := make(ReasonStats, len(r)+len(other)) 415 merged.Merge(r) 416 merged.Merge(other) 417 418 return merged 419 } 420 421 func (r ReasonStats) Count() int { 422 var ret int 423 for _, count := range r { 424 ret += count 425 } 426 return ret 427 } 428 429 func (r ReasonStats) Has(reason TriggerReason) bool { 430 return r[reason] > 0 431 } 432 433 type TriggerReason string 434 435 // If adding a new reason, update xds/monitoring.go:triggerMetric 436 const ( 437 // EndpointUpdate describes a push triggered by an Endpoint change 438 EndpointUpdate TriggerReason = "endpoint" 439 // HeadlessEndpointUpdate describes a push triggered by an Endpoint change for headless service 440 HeadlessEndpointUpdate TriggerReason = "headlessendpoint" 441 // ConfigUpdate describes a push triggered by a config (generally and Istio CRD) change. 442 ConfigUpdate TriggerReason = "config" 443 // ServiceUpdate describes a push triggered by a Service change 444 ServiceUpdate TriggerReason = "service" 445 // ProxyUpdate describes a push triggered by a change to an individual proxy (such as label change) 446 ProxyUpdate TriggerReason = "proxy" 447 // GlobalUpdate describes a push triggered by a change to global config, such as mesh config 448 GlobalUpdate TriggerReason = "global" 449 // AmbientUpdate describes a push triggered by a change to ambient mesh config 450 AmbientUpdate TriggerReason = "ambient" 451 // UnknownTrigger describes a push triggered by an unknown reason 452 UnknownTrigger TriggerReason = "unknown" 453 // DebugTrigger describes a push triggered for debugging 454 DebugTrigger TriggerReason = "debug" 455 // SecretTrigger describes a push triggered for a Secret change 456 SecretTrigger TriggerReason = "secret" 457 // NetworksTrigger describes a push triggered for Networks change 458 NetworksTrigger TriggerReason = "networks" 459 // ProxyRequest describes a push triggered based on proxy request 460 ProxyRequest TriggerReason = "proxyrequest" 461 // DependentResource describes a push triggered based on a proxy request for a 462 // resource that depends on this resource (e.g. a CDS request triggers an EDS response as well) 463 // This is mainly used in Delta for now. 464 DependentResource TriggerReason = "depdendentresource" 465 // NamespaceUpdate describes a push triggered by a Namespace change 466 NamespaceUpdate TriggerReason = "namespace" 467 // ClusterUpdate describes a push triggered by a Cluster change 468 ClusterUpdate TriggerReason = "cluster" 469 ) 470 471 // Merge two update requests together 472 // Merge behaves similarly to a list append; usage should in the form `a = a.merge(b)`. 473 // Importantly, Merge may decide to allocate a new PushRequest object or reuse the existing one - both 474 // inputs should not be used after completion. 475 func (pr *PushRequest) Merge(other *PushRequest) *PushRequest { 476 if pr == nil { 477 return other 478 } 479 if other == nil { 480 return pr 481 } 482 483 // Keep the first (older) start time 484 485 // Merge the two reasons. Note that we shouldn't deduplicate here, or we would under count 486 if len(other.Reason) > 0 { 487 if pr.Reason == nil { 488 pr.Reason = make(map[TriggerReason]int) 489 } 490 pr.Reason.Merge(other.Reason) 491 } 492 493 // If either is full we need a full push 494 pr.Full = pr.Full || other.Full 495 496 // The other push context is presumed to be later and more up to date 497 if other.Push != nil { 498 pr.Push = other.Push 499 } 500 501 // Do not merge when any one is empty 502 if len(pr.ConfigsUpdated) == 0 || len(other.ConfigsUpdated) == 0 { 503 pr.ConfigsUpdated = nil 504 } else { 505 for conf := range other.ConfigsUpdated { 506 pr.ConfigsUpdated.Insert(conf) 507 } 508 } 509 510 return pr 511 } 512 513 // CopyMerge two update requests together. Unlike Merge, this will not mutate either input. 514 // This should be used when we are modifying a shared PushRequest (typically any time it's in the context 515 // of a single proxy) 516 func (pr *PushRequest) CopyMerge(other *PushRequest) *PushRequest { 517 if pr == nil { 518 return other 519 } 520 if other == nil { 521 return pr 522 } 523 524 var reason ReasonStats 525 if len(pr.Reason)+len(other.Reason) > 0 { 526 reason = make(ReasonStats) 527 reason.Merge(pr.Reason) 528 reason.Merge(other.Reason) 529 } 530 merged := &PushRequest{ 531 // Keep the first (older) start time 532 Start: pr.Start, 533 534 // If either is full we need a full push 535 Full: pr.Full || other.Full, 536 537 // The other push context is presumed to be later and more up to date 538 Push: other.Push, 539 540 // Merge the two reasons. Note that we shouldn't deduplicate here, or we would under count 541 Reason: reason, 542 } 543 544 // Do not merge when any one is empty 545 if len(pr.ConfigsUpdated) > 0 && len(other.ConfigsUpdated) > 0 { 546 merged.ConfigsUpdated = make(sets.Set[ConfigKey], len(pr.ConfigsUpdated)+len(other.ConfigsUpdated)) 547 merged.ConfigsUpdated.Merge(pr.ConfigsUpdated) 548 merged.ConfigsUpdated.Merge(other.ConfigsUpdated) 549 } 550 551 return merged 552 } 553 554 func (pr *PushRequest) IsRequest() bool { 555 return len(pr.Reason) == 1 && pr.Reason.Has(ProxyRequest) 556 } 557 558 func (pr *PushRequest) IsProxyUpdate() bool { 559 return pr.Reason.Has(ProxyUpdate) 560 } 561 562 func (pr *PushRequest) PushReason() string { 563 if pr.IsRequest() { 564 return " request" 565 } 566 return "" 567 } 568 569 // ProxyPushStatus represents an event captured during config push to proxies. 570 // It may contain additional message and the affected proxy. 571 type ProxyPushStatus struct { 572 Proxy string `json:"proxy,omitempty"` 573 Message string `json:"message,omitempty"` 574 } 575 576 // AddMetric will add an case to the metric. 577 func (ps *PushContext) AddMetric(metric monitoring.Metric, key string, proxyID, msg string) { 578 if ps == nil { 579 log.Infof("Metric without context %s %v %s", key, proxyID, msg) 580 return 581 } 582 ps.proxyStatusMutex.Lock() 583 defer ps.proxyStatusMutex.Unlock() 584 585 metricMap, f := ps.ProxyStatus[metric.Name()] 586 if !f { 587 metricMap = map[string]ProxyPushStatus{} 588 ps.ProxyStatus[metric.Name()] = metricMap 589 } 590 ev := ProxyPushStatus{Message: msg, Proxy: proxyID} 591 metricMap[key] = ev 592 } 593 594 var ( 595 596 // EndpointNoPod tracks endpoints without an associated pod. This is an error condition, since 597 // we can't figure out the labels. It may be a transient problem, if endpoint is processed before 598 // pod. 599 EndpointNoPod = monitoring.NewGauge( 600 "endpoint_no_pod", 601 "Endpoints without an associated pod.", 602 ) 603 604 // ProxyStatusNoService represents proxies not selected by any service 605 // This can be normal - for workloads that act only as client, or are not covered by a Service. 606 // It can also be an error, for example in cases the Endpoint list of a service was not updated by the time 607 // the sidecar calls. 608 // Updated by GetProxyServiceTargets 609 ProxyStatusNoService = monitoring.NewGauge( 610 "pilot_no_ip", 611 "Pods not found in the endpoint table, possibly invalid.", 612 ) 613 614 // ProxyStatusEndpointNotReady represents proxies found not be ready. 615 // Updated by GetProxyServiceTargets. Normal condition when starting 616 // an app with readiness, error if it doesn't change to 0. 617 ProxyStatusEndpointNotReady = monitoring.NewGauge( 618 "pilot_endpoint_not_ready", 619 "Endpoint found in unready state.", 620 ) 621 622 // ProxyStatusConflictOutboundListenerTCPOverTCP metric tracks number of 623 // TCP listeners that conflicted with existing TCP listeners on same port 624 ProxyStatusConflictOutboundListenerTCPOverTCP = monitoring.NewGauge( 625 "pilot_conflict_outbound_listener_tcp_over_current_tcp", 626 "Number of conflicting tcp listeners with current tcp listener.", 627 ) 628 629 // ProxyStatusConflictInboundListener tracks cases of multiple inbound 630 // listeners - 2 services selecting the same port of the pod. 631 ProxyStatusConflictInboundListener = monitoring.NewGauge( 632 "pilot_conflict_inbound_listener", 633 "Number of conflicting inbound listeners.", 634 ) 635 636 // DuplicatedClusters tracks duplicate clusters seen while computing CDS 637 DuplicatedClusters = monitoring.NewGauge( 638 "pilot_duplicate_envoy_clusters", 639 "Duplicate envoy clusters caused by service entries with same hostname", 640 ) 641 642 // DNSNoEndpointClusters tracks dns clusters without endpoints 643 DNSNoEndpointClusters = monitoring.NewGauge( 644 "pilot_dns_cluster_without_endpoints", 645 "DNS clusters without endpoints caused by the endpoint field in "+ 646 "STRICT_DNS type cluster is not set or the corresponding subset cannot select any endpoint", 647 ) 648 649 // ProxyStatusClusterNoInstances tracks clusters (services) without workloads. 650 ProxyStatusClusterNoInstances = monitoring.NewGauge( 651 "pilot_eds_no_instances", 652 "Number of clusters without instances.", 653 ) 654 655 // DuplicatedDomains tracks rejected VirtualServices due to duplicated hostname. 656 DuplicatedDomains = monitoring.NewGauge( 657 "pilot_vservice_dup_domain", 658 "Virtual services with dup domains.", 659 ) 660 661 // DuplicatedSubsets tracks duplicate subsets that we rejected while merging multiple destination rules for same host 662 DuplicatedSubsets = monitoring.NewGauge( 663 "pilot_destrule_subsets", 664 "Duplicate subsets across destination rules for same host", 665 ) 666 667 // totalVirtualServices tracks the total number of virtual service 668 totalVirtualServices = monitoring.NewGauge( 669 "pilot_virt_services", 670 "Total virtual services known to pilot.", 671 ) 672 673 // LastPushStatus preserves the metrics and data collected during lasts global push. 674 // It can be used by debugging tools to inspect the push event. It will be reset after each push with the 675 // new version. 676 LastPushStatus *PushContext 677 // LastPushMutex will protect the LastPushStatus 678 LastPushMutex sync.Mutex 679 680 // All metrics we registered. 681 metrics = []monitoring.Metric{ 682 DNSNoEndpointClusters, 683 EndpointNoPod, 684 ProxyStatusNoService, 685 ProxyStatusEndpointNotReady, 686 ProxyStatusConflictOutboundListenerTCPOverTCP, 687 ProxyStatusConflictInboundListener, 688 DuplicatedClusters, 689 ProxyStatusClusterNoInstances, 690 DuplicatedDomains, 691 DuplicatedSubsets, 692 } 693 ) 694 695 // NewPushContext creates a new PushContext structure to track push status. 696 func NewPushContext() *PushContext { 697 return &PushContext{ 698 ServiceIndex: newServiceIndex(), 699 virtualServiceIndex: newVirtualServiceIndex(), 700 destinationRuleIndex: newDestinationRuleIndex(), 701 sidecarIndex: newSidecarIndex(), 702 envoyFiltersByNamespace: map[string][]*EnvoyFilterWrapper{}, 703 gatewayIndex: newGatewayIndex(), 704 ProxyStatus: map[string]map[string]ProxyPushStatus{}, 705 serviceAccounts: map[serviceAccountKey][]string{}, 706 } 707 } 708 709 // AddPublicServices adds the services to context public services - mainly used in tests. 710 func (ps *PushContext) AddPublicServices(services []*Service) { 711 ps.ServiceIndex.public = append(ps.ServiceIndex.public, services...) 712 } 713 714 // AddServiceInstances adds instances to the context service instances - mainly used in tests. 715 func (ps *PushContext) AddServiceInstances(service *Service, instances map[int][]*IstioEndpoint) { 716 svcKey := service.Key() 717 for port, inst := range instances { 718 if _, exists := ps.ServiceIndex.instancesByPort[svcKey]; !exists { 719 ps.ServiceIndex.instancesByPort[svcKey] = make(map[int][]*IstioEndpoint) 720 } 721 ps.ServiceIndex.instancesByPort[svcKey][port] = append(ps.ServiceIndex.instancesByPort[svcKey][port], inst...) 722 } 723 } 724 725 // StatusJSON implements json.Marshaller, with a lock. 726 func (ps *PushContext) StatusJSON() ([]byte, error) { 727 if ps == nil { 728 return []byte{'{', '}'}, nil 729 } 730 ps.proxyStatusMutex.RLock() 731 defer ps.proxyStatusMutex.RUnlock() 732 return json.MarshalIndent(ps.ProxyStatus, "", " ") 733 } 734 735 // OnConfigChange is called when a config change is detected. 736 func (ps *PushContext) OnConfigChange() { 737 LastPushMutex.Lock() 738 LastPushStatus = ps 739 LastPushMutex.Unlock() 740 ps.UpdateMetrics() 741 } 742 743 // UpdateMetrics will update the prometheus metrics based on the 744 // current status of the push. 745 func (ps *PushContext) UpdateMetrics() { 746 ps.proxyStatusMutex.RLock() 747 defer ps.proxyStatusMutex.RUnlock() 748 749 for _, pm := range metrics { 750 mmap := ps.ProxyStatus[pm.Name()] 751 pm.Record(float64(len(mmap))) 752 } 753 } 754 755 // It is called after virtual service short host name is resolved to FQDN 756 func virtualServiceDestinations(v *networking.VirtualService) map[string]sets.Set[int] { 757 if v == nil { 758 return nil 759 } 760 761 out := make(map[string]sets.Set[int]) 762 763 addDestination := func(host string, port *networking.PortSelector) { 764 // Use the value 0 as a sentinel indicating that one of the destinations 765 // in the Virtual Service does not specify a port for this host. 766 pn := 0 767 if port != nil { 768 pn = int(port.Number) 769 } 770 sets.InsertOrNew(out, host, pn) 771 } 772 773 for _, h := range v.Http { 774 for _, r := range h.Route { 775 if r.Destination != nil { 776 addDestination(r.Destination.Host, r.Destination.GetPort()) 777 } 778 } 779 if h.Mirror != nil { 780 addDestination(h.Mirror.Host, h.Mirror.GetPort()) 781 } 782 for _, m := range h.Mirrors { 783 if m.Destination != nil { 784 addDestination(m.Destination.Host, m.Destination.GetPort()) 785 } 786 } 787 } 788 for _, t := range v.Tcp { 789 for _, r := range t.Route { 790 if r.Destination != nil { 791 addDestination(r.Destination.Host, r.Destination.GetPort()) 792 } 793 } 794 } 795 for _, t := range v.Tls { 796 for _, r := range t.Route { 797 if r.Destination != nil { 798 addDestination(r.Destination.Host, r.Destination.GetPort()) 799 } 800 } 801 } 802 803 return out 804 } 805 806 func (ps *PushContext) ExtraWaypointServices(proxy *Proxy) sets.String { 807 return ps.extraServicesForProxy(proxy) 808 } 809 810 // GatewayServices returns the set of services which are referred from the proxy gateways. 811 func (ps *PushContext) GatewayServices(proxy *Proxy) []*Service { 812 svcs := proxy.SidecarScope.services 813 814 // MergedGateway will be nil when there are no configs in the 815 // system during initial installation. 816 if proxy.MergedGateway == nil { 817 return nil 818 } 819 820 // host set. 821 hostsFromGateways := ps.extraServicesForProxy(proxy) 822 for _, gw := range proxy.MergedGateway.GatewayNameForServer { 823 hostsFromGateways.Merge(ps.virtualServiceIndex.destinationsByGateway[gw]) 824 } 825 log.Debugf("GatewayServices: gateway %v is exposing these hosts:%v", proxy.ID, hostsFromGateways) 826 827 gwSvcs := make([]*Service, 0, len(svcs)) 828 829 for _, s := range svcs { 830 svcHost := string(s.Hostname) 831 832 if _, ok := hostsFromGateways[svcHost]; ok { 833 gwSvcs = append(gwSvcs, s) 834 } 835 } 836 837 log.Debugf("GatewayServices: gateways len(services)=%d, len(filtered)=%d", len(svcs), len(gwSvcs)) 838 839 return gwSvcs 840 } 841 842 func (ps *PushContext) ServicesAttachedToMesh() map[string]sets.String { 843 return ps.virtualServiceIndex.referencedDestinations 844 } 845 846 func (ps *PushContext) ServiceAttachedToGateway(hostname string, proxy *Proxy) bool { 847 gw := proxy.MergedGateway 848 // MergedGateway will be nil when there are no configs in the 849 // system during initial installation. 850 if gw == nil { 851 return false 852 } 853 if gw.ContainsAutoPassthroughGateways { 854 return true 855 } 856 for _, g := range gw.GatewayNameForServer { 857 if hosts := ps.virtualServiceIndex.destinationsByGateway[g]; hosts != nil { 858 if hosts.Contains(hostname) { 859 return true 860 } 861 } 862 } 863 return ps.extraServicesForProxy(proxy).Contains(hostname) 864 } 865 866 // wellknownProviders is a list of all known providers. 867 // This exists 868 var wellknownProviders = sets.New( 869 "envoy_ext_authz_http", 870 "envoy_ext_authz_grpc", 871 "zipkin", 872 "lightstep", 873 "datadog", 874 "opencensus", 875 "stackdriver", 876 "prometheus", 877 "skywalking", 878 "envoy_http_als", 879 "envoy_tcp_als", 880 "envoy_otel_als", 881 "opentelemetry", 882 "envoy_file_access_log", 883 ) 884 885 func AssertProvidersHandled(expected int) { 886 if expected != len(wellknownProviders) { 887 panic(fmt.Sprintf("Not all providers handled; This function handles %v but there are %v known providers", expected, len(wellknownProviders))) 888 } 889 } 890 891 // addHostsFromMeshConfigProvidersHandled contains the number of providers we handle below. 892 // This is to ensure this stays in sync as new handlers are added 893 // STOP. DO NOT UPDATE THIS WITHOUT UPDATING extraServicesForProxy. 894 const addHostsFromMeshConfigProvidersHandled = 14 895 896 // extraServicesForProxy returns a subset of services referred from the proxy gateways, including: 897 // 1. MeshConfig.ExtensionProviders 898 // 2. RequestAuthentication.JwtRules.JwksUri 899 // TODO: include cluster from EnvoyFilter such as global ratelimit [demo](https://istio.io/latest/docs/tasks/policy-enforcement/rate-limit/#global-rate-limit) 900 func (ps *PushContext) extraServicesForProxy(proxy *Proxy) sets.String { 901 hosts := sets.String{} 902 // add services from MeshConfig.ExtensionProviders 903 for _, prov := range ps.Mesh.ExtensionProviders { 904 switch p := prov.Provider.(type) { 905 case *meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzHttp: 906 hosts.Insert(p.EnvoyExtAuthzHttp.Service) 907 case *meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzGrpc: 908 hosts.Insert(p.EnvoyExtAuthzGrpc.Service) 909 case *meshconfig.MeshConfig_ExtensionProvider_Zipkin: 910 hosts.Insert(p.Zipkin.Service) 911 //nolint: staticcheck // Lightstep deprecated 912 case *meshconfig.MeshConfig_ExtensionProvider_Lightstep: 913 hosts.Insert(p.Lightstep.Service) 914 case *meshconfig.MeshConfig_ExtensionProvider_Datadog: 915 hosts.Insert(p.Datadog.Service) 916 case *meshconfig.MeshConfig_ExtensionProvider_Skywalking: 917 hosts.Insert(p.Skywalking.Service) 918 case *meshconfig.MeshConfig_ExtensionProvider_Opencensus: 919 //nolint: staticcheck 920 hosts.Insert(p.Opencensus.Service) 921 case *meshconfig.MeshConfig_ExtensionProvider_Opentelemetry: 922 hosts.Insert(p.Opentelemetry.Service) 923 case *meshconfig.MeshConfig_ExtensionProvider_EnvoyHttpAls: 924 hosts.Insert(p.EnvoyHttpAls.Service) 925 case *meshconfig.MeshConfig_ExtensionProvider_EnvoyTcpAls: 926 hosts.Insert(p.EnvoyTcpAls.Service) 927 case *meshconfig.MeshConfig_ExtensionProvider_EnvoyOtelAls: 928 hosts.Insert(p.EnvoyOtelAls.Service) 929 case *meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog: // No services 930 case *meshconfig.MeshConfig_ExtensionProvider_Prometheus: // No services 931 case *meshconfig.MeshConfig_ExtensionProvider_Stackdriver: // No services 932 } 933 } 934 // add services from RequestAuthentication.JwtRules.JwksUri 935 if features.JwksFetchMode != jwt.Istiod { 936 forWorkload := PolicyMatcherForProxy(proxy) 937 jwtPolicies := ps.AuthnPolicies.GetJwtPoliciesForWorkload(forWorkload) 938 for _, cfg := range jwtPolicies { 939 rules := cfg.Spec.(*v1beta1.RequestAuthentication).JwtRules 940 for _, r := range rules { 941 if uri := r.GetJwksUri(); len(uri) > 0 { 942 jwksInfo, err := security.ParseJwksURI(uri) 943 if err == nil { 944 hosts.Insert(jwksInfo.Hostname.String()) 945 } 946 } 947 } 948 } 949 } 950 return hosts 951 } 952 953 // servicesExportedToNamespace returns the list of services that are visible to a namespace. 954 // namespace "" indicates all namespaces 955 func (ps *PushContext) servicesExportedToNamespace(ns string) []*Service { 956 var out []*Service 957 958 // First add private services and explicitly exportedTo services 959 if ns == NamespaceAll { 960 out = make([]*Service, 0, len(ps.ServiceIndex.privateByNamespace)+len(ps.ServiceIndex.public)) 961 for _, privateServices := range ps.ServiceIndex.privateByNamespace { 962 out = append(out, privateServices...) 963 } 964 } else { 965 out = make([]*Service, 0, len(ps.ServiceIndex.privateByNamespace[ns])+ 966 len(ps.ServiceIndex.exportedToNamespace[ns])+len(ps.ServiceIndex.public)) 967 out = append(out, ps.ServiceIndex.privateByNamespace[ns]...) 968 out = append(out, ps.ServiceIndex.exportedToNamespace[ns]...) 969 } 970 971 // Second add public services 972 out = append(out, ps.ServiceIndex.public...) 973 974 return out 975 } 976 977 // GetAllServices returns the total services within the mesh. 978 // Note: per proxy services should use SidecarScope.Services. 979 func (ps *PushContext) GetAllServices() []*Service { 980 return ps.servicesExportedToNamespace(NamespaceAll) 981 } 982 983 // ServiceForHostname returns the service associated with a given hostname following SidecarScope 984 func (ps *PushContext) ServiceForHostname(proxy *Proxy, hostname host.Name) *Service { 985 if proxy != nil && proxy.SidecarScope != nil { 986 return proxy.SidecarScope.servicesByHostname[hostname] 987 } 988 989 // SidecarScope shouldn't be null here. If it is, we can't disambiguate the hostname to use for a namespace, 990 // so the selection must be undefined. 991 for _, service := range ps.ServiceIndex.HostnameAndNamespace[hostname] { 992 return service 993 } 994 995 // No service found 996 return nil 997 } 998 999 // IsServiceVisible returns true if the input service is visible to the given namespace. 1000 func (ps *PushContext) IsServiceVisible(service *Service, namespace string) bool { 1001 if service == nil { 1002 return false 1003 } 1004 1005 ns := service.Attributes.Namespace 1006 if service.Attributes.ExportTo.IsEmpty() { 1007 if ps.exportToDefaults.service.Contains(visibility.Private) { 1008 return ns == namespace 1009 } else if ps.exportToDefaults.service.Contains(visibility.Public) { 1010 return true 1011 } 1012 } 1013 1014 return service.Attributes.ExportTo.Contains(visibility.Public) || 1015 (service.Attributes.ExportTo.Contains(visibility.Private) && ns == namespace) || 1016 service.Attributes.ExportTo.Contains(visibility.Instance(namespace)) 1017 } 1018 1019 // VirtualServicesForGateway lists all virtual services bound to the specified gateways 1020 // This replaces store.VirtualServices. Used only by the gateways 1021 // Sidecars use the egressListener.VirtualServices(). 1022 // 1023 // Note that for generating the imported virtual services of sidecar egress 1024 // listener, we don't call this function to copy configs for performance issues. 1025 // Instead, we pass the virtualServiceIndex directly into SelectVirtualServices 1026 // function. 1027 func (ps *PushContext) VirtualServicesForGateway(proxyNamespace, gateway string) []config.Config { 1028 name := types.NamespacedName{ 1029 Namespace: proxyNamespace, 1030 Name: gateway, 1031 } 1032 res := make([]config.Config, 0, len(ps.virtualServiceIndex.privateByNamespaceAndGateway[name])+ 1033 len(ps.virtualServiceIndex.exportedToNamespaceByGateway[name])+ 1034 len(ps.virtualServiceIndex.publicByGateway[gateway])) 1035 res = append(res, ps.virtualServiceIndex.privateByNamespaceAndGateway[name]...) 1036 res = append(res, ps.virtualServiceIndex.exportedToNamespaceByGateway[name]...) 1037 // Favor same-namespace Gateway routes, to give the "consumer override" preference. 1038 // We do 2 iterations here to avoid extra allocations. 1039 for _, vs := range ps.virtualServiceIndex.publicByGateway[gateway] { 1040 if UseGatewaySemantics(vs) && vs.Namespace == proxyNamespace { 1041 res = append(res, vs) 1042 } 1043 } 1044 for _, vs := range ps.virtualServiceIndex.publicByGateway[gateway] { 1045 if !(UseGatewaySemantics(vs) && vs.Namespace == proxyNamespace) { 1046 res = append(res, vs) 1047 } 1048 } 1049 1050 return res 1051 } 1052 1053 // DelegateVirtualServices lists all the delegate virtual services configkeys associated with the provided virtual services 1054 func (ps *PushContext) DelegateVirtualServices(vses []config.Config) []ConfigHash { 1055 var out []ConfigHash 1056 for _, vs := range vses { 1057 for _, delegate := range ps.virtualServiceIndex.delegates[ConfigKey{Kind: kind.VirtualService, Namespace: vs.Namespace, Name: vs.Name}] { 1058 out = append(out, delegate.HashCode()) 1059 } 1060 } 1061 return out 1062 } 1063 1064 // getSidecarScope returns a SidecarScope object associated with the 1065 // proxy. The SidecarScope object is a semi-processed view of the service 1066 // registry, and config state associated with the sidecar crd. The scope contains 1067 // a set of inbound and outbound listeners, services/configs per listener, 1068 // etc. The sidecar scopes are precomputed in the initSidecarContext 1069 // function based on the Sidecar API objects in each namespace. If there is 1070 // no sidecar api object, a default sidecarscope is assigned to the 1071 // namespace which enables connectivity to all services in the mesh. 1072 // 1073 // Callers can check if the sidecarScope is from user generated object or not 1074 // by checking the sidecarScope.Config field, that contains the user provided config 1075 func (ps *PushContext) getSidecarScope(proxy *Proxy, workloadLabels labels.Instance) *SidecarScope { 1076 // TODO: logic to merge multiple sidecar resources 1077 // Currently we assume that there will be only one sidecar config for a namespace. 1078 sidecars, hasSidecar := ps.sidecarIndex.sidecarsByNamespace[proxy.ConfigNamespace] 1079 switch proxy.Type { 1080 case Router, Waypoint: 1081 ps.sidecarIndex.derivedSidecarMutex.Lock() 1082 defer ps.sidecarIndex.derivedSidecarMutex.Unlock() 1083 1084 // Gateways always use default sidecar scope. 1085 if sc, f := ps.sidecarIndex.defaultSidecarsByNamespace[proxy.ConfigNamespace]; f { 1086 return sc 1087 } 1088 1089 if sc, f := ps.sidecarIndex.sidecarsForGatewayByNamespace[proxy.ConfigNamespace]; f { 1090 return sc 1091 } 1092 1093 // We need to compute this namespace 1094 computed := DefaultSidecarScopeForGateway(ps, proxy.ConfigNamespace) 1095 ps.sidecarIndex.sidecarsForGatewayByNamespace[proxy.ConfigNamespace] = computed 1096 return computed 1097 case SidecarProxy: 1098 if hasSidecar { 1099 for _, wrapper := range sidecars { 1100 if wrapper.Sidecar != nil { 1101 sidecar := wrapper.Sidecar 1102 // if there is no workload selector, the config applies to all workloads 1103 // if there is a workload selector, check for matching workload labels 1104 if sidecar.GetWorkloadSelector() != nil { 1105 workloadSelector := labels.Instance(sidecar.GetWorkloadSelector().GetLabels()) 1106 // exclude workload selector that not match 1107 if !workloadSelector.SubsetOf(workloadLabels) { 1108 continue 1109 } 1110 } 1111 1112 // it is guaranteed sidecars with selectors are put in front 1113 // and the sidecars are sorted by creation timestamp, 1114 // return exact/wildcard matching one directly 1115 return wrapper 1116 } 1117 // this happens at last, it is the default sidecar scope 1118 return wrapper 1119 } 1120 } 1121 ps.sidecarIndex.derivedSidecarMutex.Lock() 1122 defer ps.sidecarIndex.derivedSidecarMutex.Unlock() 1123 1124 if ps.sidecarIndex.meshRootSidecarConfig != nil { 1125 if sc, exists := ps.sidecarIndex.meshRootSidecarsByNamespace[proxy.ConfigNamespace]; exists { 1126 // We have already computed the scope for this namespace, just return it. 1127 return sc 1128 } 1129 // We need to compute this namespace 1130 computed := convertToSidecarScope(ps, ps.sidecarIndex.meshRootSidecarConfig, proxy.ConfigNamespace) 1131 ps.sidecarIndex.meshRootSidecarsByNamespace[proxy.ConfigNamespace] = computed 1132 return computed 1133 } 1134 if sc, exists := ps.sidecarIndex.defaultSidecarsByNamespace[proxy.ConfigNamespace]; exists { 1135 // We have already computed the scope for this namespace, just return it. 1136 return sc 1137 } 1138 // We need to compute this namespace 1139 computed := convertToSidecarScope(ps, ps.sidecarIndex.meshRootSidecarConfig, proxy.ConfigNamespace) 1140 ps.sidecarIndex.defaultSidecarsByNamespace[proxy.ConfigNamespace] = computed 1141 return computed 1142 } 1143 return nil 1144 } 1145 1146 // destinationRule returns a destination rule for a service name in a given namespace. 1147 func (ps *PushContext) destinationRule(proxyNameSpace string, service *Service) []*ConsolidatedDestRule { 1148 if service == nil { 1149 return nil 1150 } 1151 // If the proxy config namespace is same as the root config namespace 1152 // look for dest rules in the service's namespace first. This hack is needed 1153 // because sometimes, istio-system tends to become the root config namespace. 1154 // Destination rules are defined here for global purposes. We do not want these 1155 // catch all destination rules to be the only dest rule, when processing CDS for 1156 // proxies like the istio-ingressgateway or istio-egressgateway. 1157 // If there are no service specific dest rules, we will end up picking up the same 1158 // rules anyway, later in the code 1159 1160 // 1. select destination rule from proxy config namespace 1161 if proxyNameSpace != ps.Mesh.RootNamespace { 1162 // search through the DestinationRules in proxy's namespace first 1163 if ps.destinationRuleIndex.namespaceLocal[proxyNameSpace] != nil { 1164 if _, drs, ok := MostSpecificHostMatch(service.Hostname, 1165 ps.destinationRuleIndex.namespaceLocal[proxyNameSpace].specificDestRules, 1166 ps.destinationRuleIndex.namespaceLocal[proxyNameSpace].wildcardDestRules, 1167 ); ok { 1168 return drs 1169 } 1170 } 1171 } else { 1172 // If this is a namespace local DR in the same namespace, this must be meant for this proxy, so we do not 1173 // need to worry about overriding other DRs with *.local type rules here. If we ignore this, then exportTo=. in 1174 // root namespace would always be ignored 1175 if _, drs, ok := MostSpecificHostMatch(service.Hostname, 1176 ps.destinationRuleIndex.rootNamespaceLocal.specificDestRules, 1177 ps.destinationRuleIndex.rootNamespaceLocal.wildcardDestRules, 1178 ); ok { 1179 return drs 1180 } 1181 } 1182 1183 // 2. select destination rule from service namespace 1184 svcNs := service.Attributes.Namespace 1185 1186 // This can happen when finding the subset labels for a proxy in root namespace. 1187 // Because based on a pure cluster's fqdn, we do not know the service and 1188 // construct a fake service without setting Attributes at all. 1189 if svcNs == "" { 1190 for _, svc := range ps.servicesExportedToNamespace(proxyNameSpace) { 1191 if service.Hostname == svc.Hostname && svc.Attributes.Namespace != "" { 1192 svcNs = svc.Attributes.Namespace 1193 break 1194 } 1195 } 1196 } 1197 1198 // 3. if no private/public rule matched in the calling proxy's namespace, 1199 // check the target service's namespace for exported rules 1200 if svcNs != "" { 1201 if out := ps.getExportedDestinationRuleFromNamespace(svcNs, service.Hostname, proxyNameSpace); out != nil { 1202 return out 1203 } 1204 } 1205 1206 // 4. if no public/private rule in calling proxy's namespace matched, and no public rule in the 1207 // target service's namespace matched, search for any exported destination rule in the config root namespace 1208 if out := ps.getExportedDestinationRuleFromNamespace(ps.Mesh.RootNamespace, service.Hostname, proxyNameSpace); out != nil { 1209 return out 1210 } 1211 1212 return nil 1213 } 1214 1215 func (ps *PushContext) getExportedDestinationRuleFromNamespace(owningNamespace string, hostname host.Name, clientNamespace string) []*ConsolidatedDestRule { 1216 if ps.destinationRuleIndex.exportedByNamespace[owningNamespace] != nil { 1217 if specificHostname, drs, ok := MostSpecificHostMatch(hostname, 1218 ps.destinationRuleIndex.exportedByNamespace[owningNamespace].specificDestRules, 1219 ps.destinationRuleIndex.exportedByNamespace[owningNamespace].wildcardDestRules, 1220 ); ok { 1221 // Check if the dest rule for this host is actually exported to the proxy's (client) namespace 1222 exportToSet := ps.destinationRuleIndex.exportedByNamespace[owningNamespace].exportTo[specificHostname] 1223 if exportToSet.IsEmpty() || exportToSet.Contains(visibility.Public) || exportToSet.Contains(visibility.Instance(clientNamespace)) { 1224 return drs 1225 } 1226 } 1227 } 1228 return nil 1229 } 1230 1231 // IsClusterLocal indicates whether the endpoints for the service should only be accessible to clients 1232 // within the cluster. 1233 func (ps *PushContext) IsClusterLocal(service *Service) bool { 1234 if ps == nil || service == nil { 1235 return false 1236 } 1237 return ps.clusterLocalHosts.IsClusterLocal(service.Hostname) 1238 } 1239 1240 // InitContext will initialize the data structures used for code generation. 1241 // This should be called before starting the push, from the thread creating 1242 // the push context. 1243 func (ps *PushContext) InitContext(env *Environment, oldPushContext *PushContext, pushReq *PushRequest) error { 1244 // Acquire a lock to ensure we don't concurrently initialize the same PushContext. 1245 // If this does happen, one thread will block then exit early from InitDone=true 1246 ps.initializeMutex.Lock() 1247 defer ps.initializeMutex.Unlock() 1248 if ps.InitDone.Load() { 1249 return nil 1250 } 1251 1252 ps.Mesh = env.Mesh() 1253 ps.Networks = env.MeshNetworks() 1254 ps.LedgerVersion = env.Version() 1255 1256 // Must be initialized first as initServiceRegistry/VirtualServices/Destrules 1257 // use the default export map. 1258 ps.initDefaultExportMaps() 1259 1260 // create new or incremental update 1261 if pushReq == nil || oldPushContext == nil || !oldPushContext.InitDone.Load() || len(pushReq.ConfigsUpdated) == 0 { 1262 if err := ps.createNewContext(env); err != nil { 1263 return err 1264 } 1265 } else { 1266 if err := ps.updateContext(env, oldPushContext, pushReq); err != nil { 1267 return err 1268 } 1269 } 1270 1271 ps.networkMgr = env.NetworkManager 1272 1273 ps.clusterLocalHosts = env.ClusterLocal().GetClusterLocalHosts() 1274 1275 ps.InitDone.Store(true) 1276 return nil 1277 } 1278 1279 func (ps *PushContext) createNewContext(env *Environment) error { 1280 ps.initServiceRegistry(env, nil) 1281 1282 if err := ps.initKubernetesGateways(env); err != nil { 1283 return err 1284 } 1285 1286 ps.initVirtualServices(env) 1287 1288 ps.initDestinationRules(env) 1289 ps.initAuthnPolicies(env) 1290 1291 ps.initAuthorizationPolicies(env) 1292 ps.initTelemetry(env) 1293 ps.initProxyConfigs(env) 1294 ps.initWasmPlugins(env) 1295 ps.initEnvoyFilters(env, nil, nil) 1296 ps.initGateways(env) 1297 ps.initAmbient(env) 1298 1299 // Must be initialized in the end 1300 ps.initSidecarScopes(env) 1301 return nil 1302 } 1303 1304 func (ps *PushContext) updateContext( 1305 env *Environment, 1306 oldPushContext *PushContext, 1307 pushReq *PushRequest, 1308 ) error { 1309 var servicesChanged, virtualServicesChanged, destinationRulesChanged, gatewayChanged, 1310 authnChanged, authzChanged, envoyFiltersChanged, sidecarsChanged, telemetryChanged, gatewayAPIChanged, 1311 wasmPluginsChanged, proxyConfigsChanged bool 1312 1313 changedEnvoyFilters := sets.New[ConfigKey]() 1314 1315 for conf := range pushReq.ConfigsUpdated { 1316 switch conf.Kind { 1317 case kind.ServiceEntry, kind.DNSName: 1318 servicesChanged = true 1319 case kind.DestinationRule: 1320 destinationRulesChanged = true 1321 case kind.VirtualService: 1322 virtualServicesChanged = true 1323 case kind.Gateway: 1324 gatewayChanged = true 1325 case kind.Sidecar: 1326 sidecarsChanged = true 1327 case kind.WasmPlugin: 1328 wasmPluginsChanged = true 1329 case kind.EnvoyFilter: 1330 envoyFiltersChanged = true 1331 if features.OptimizedConfigRebuild { 1332 changedEnvoyFilters.Insert(conf) 1333 } 1334 case kind.AuthorizationPolicy: 1335 authzChanged = true 1336 case kind.RequestAuthentication, 1337 kind.PeerAuthentication: 1338 authnChanged = true 1339 case kind.HTTPRoute, kind.TCPRoute, kind.TLSRoute, kind.GRPCRoute, kind.GatewayClass, kind.KubernetesGateway, kind.ReferenceGrant: 1340 gatewayAPIChanged = true 1341 // VS and GW are derived from gatewayAPI, so if it changed we need to update those as well 1342 virtualServicesChanged = true 1343 gatewayChanged = true 1344 case kind.Telemetry: 1345 telemetryChanged = true 1346 case kind.ProxyConfig: 1347 proxyConfigsChanged = true 1348 } 1349 } 1350 1351 if servicesChanged { 1352 // Services have changed. initialize service registry 1353 ps.initServiceRegistry(env, pushReq.ConfigsUpdated) 1354 } else { 1355 // make sure we copy over things that would be generated in initServiceRegistry 1356 ps.ServiceIndex = oldPushContext.ServiceIndex 1357 ps.serviceAccounts = oldPushContext.serviceAccounts 1358 } 1359 1360 if servicesChanged || gatewayAPIChanged { 1361 // Gateway status depends on services, so recompute if they change as well 1362 if err := ps.initKubernetesGateways(env); err != nil { 1363 return err 1364 } 1365 } 1366 1367 if virtualServicesChanged { 1368 ps.initVirtualServices(env) 1369 } else { 1370 ps.virtualServiceIndex = oldPushContext.virtualServiceIndex 1371 } 1372 1373 if destinationRulesChanged { 1374 ps.initDestinationRules(env) 1375 } else { 1376 ps.destinationRuleIndex = oldPushContext.destinationRuleIndex 1377 } 1378 1379 if authnChanged { 1380 ps.initAuthnPolicies(env) 1381 } else { 1382 ps.AuthnPolicies = oldPushContext.AuthnPolicies 1383 } 1384 1385 if authzChanged { 1386 ps.initAuthorizationPolicies(env) 1387 } else { 1388 ps.AuthzPolicies = oldPushContext.AuthzPolicies 1389 } 1390 1391 if telemetryChanged { 1392 ps.initTelemetry(env) 1393 } else { 1394 ps.Telemetry = oldPushContext.Telemetry 1395 } 1396 1397 if proxyConfigsChanged { 1398 ps.initProxyConfigs(env) 1399 } else { 1400 ps.ProxyConfigs = oldPushContext.ProxyConfigs 1401 } 1402 1403 if wasmPluginsChanged { 1404 ps.initWasmPlugins(env) 1405 } else { 1406 ps.wasmPluginsByNamespace = oldPushContext.wasmPluginsByNamespace 1407 } 1408 1409 if envoyFiltersChanged { 1410 ps.initEnvoyFilters(env, changedEnvoyFilters, oldPushContext.envoyFiltersByNamespace) 1411 } else { 1412 ps.envoyFiltersByNamespace = oldPushContext.envoyFiltersByNamespace 1413 } 1414 1415 if gatewayChanged { 1416 ps.initGateways(env) 1417 } else { 1418 ps.gatewayIndex = oldPushContext.gatewayIndex 1419 } 1420 1421 ps.initAmbient(env) 1422 1423 // Must be initialized in the end 1424 // Sidecars need to be updated if services, virtual services, destination rules, or the sidecar configs change 1425 if servicesChanged || virtualServicesChanged || destinationRulesChanged || sidecarsChanged { 1426 ps.initSidecarScopes(env) 1427 } else { 1428 // new ADS connection may insert new entry to computedSidecarsByNamespace/gatewayDefaultSidecarsByNamespace. 1429 oldPushContext.sidecarIndex.derivedSidecarMutex.RLock() 1430 ps.sidecarIndex = oldPushContext.sidecarIndex 1431 oldPushContext.sidecarIndex.derivedSidecarMutex.RUnlock() 1432 } 1433 1434 return nil 1435 } 1436 1437 // Caches list of services in the registry, and creates a map 1438 // of hostname to service 1439 func (ps *PushContext) initServiceRegistry(env *Environment, configsUpdate sets.Set[ConfigKey]) { 1440 // Sort the services in order of creation. 1441 allServices := SortServicesByCreationTime(env.Services()) 1442 if features.EnableExternalNameAlias { 1443 resolveServiceAliases(allServices, configsUpdate) 1444 } 1445 1446 for _, s := range allServices { 1447 portMap := map[string]int{} 1448 ports := sets.New[int]() 1449 for _, port := range s.Ports { 1450 portMap[port.Name] = port.Port 1451 ports.Insert(port.Port) 1452 } 1453 1454 svcKey := s.Key() 1455 if _, ok := ps.ServiceIndex.instancesByPort[svcKey]; !ok { 1456 ps.ServiceIndex.instancesByPort[svcKey] = make(map[int][]*IstioEndpoint) 1457 } 1458 shards, ok := env.EndpointIndex.ShardsForService(string(s.Hostname), s.Attributes.Namespace) 1459 if ok { 1460 instancesByPort := shards.CopyEndpoints(portMap, ports) 1461 // Iterate over the instances and add them to the service index to avoid overiding the existing port instances. 1462 for port, instances := range instancesByPort { 1463 ps.ServiceIndex.instancesByPort[svcKey][port] = instances 1464 } 1465 } 1466 if _, f := ps.ServiceIndex.HostnameAndNamespace[s.Hostname]; !f { 1467 ps.ServiceIndex.HostnameAndNamespace[s.Hostname] = map[string]*Service{} 1468 } 1469 // In some scenarios, there may be multiple Services defined for the same hostname due to ServiceEntry allowing 1470 // arbitrary hostnames. In these cases, we want to pick the first Service, which is the oldest. This ensures 1471 // newly created Services cannot take ownership unexpectedly. 1472 // However, the Service is from Kubernetes it should take precedence over ones not. This prevents someone from 1473 // "domain squatting" on the hostname before a Kubernetes Service is created. 1474 if existing := ps.ServiceIndex.HostnameAndNamespace[s.Hostname][s.Attributes.Namespace]; existing != nil && 1475 !(existing.Attributes.ServiceRegistry != provider.Kubernetes && s.Attributes.ServiceRegistry == provider.Kubernetes) { 1476 log.Debugf("Service %s/%s from registry %s ignored by %s/%s/%s", s.Attributes.Namespace, s.Hostname, s.Attributes.ServiceRegistry, 1477 existing.Attributes.ServiceRegistry, existing.Attributes.Namespace, existing.Hostname) 1478 } else { 1479 ps.ServiceIndex.HostnameAndNamespace[s.Hostname][s.Attributes.Namespace] = s 1480 } 1481 1482 ns := s.Attributes.Namespace 1483 if s.Attributes.ExportTo.IsEmpty() { 1484 if ps.exportToDefaults.service.Contains(visibility.Private) { 1485 ps.ServiceIndex.privateByNamespace[ns] = append(ps.ServiceIndex.privateByNamespace[ns], s) 1486 } else if ps.exportToDefaults.service.Contains(visibility.Public) { 1487 ps.ServiceIndex.public = append(ps.ServiceIndex.public, s) 1488 } 1489 } else { 1490 // if service has exportTo *, make it public and ignore all other exportTos. 1491 // if service does not have exportTo *, but has exportTo ~ - i.e. not visible to anyone, ignore all exportTos. 1492 // if service has exportTo ., replace with current namespace. 1493 if s.Attributes.ExportTo.Contains(visibility.Public) { 1494 ps.ServiceIndex.public = append(ps.ServiceIndex.public, s) 1495 continue 1496 } else if s.Attributes.ExportTo.Contains(visibility.None) { 1497 continue 1498 } 1499 // . or other namespaces 1500 for exportTo := range s.Attributes.ExportTo { 1501 if exportTo == visibility.Private || string(exportTo) == ns { 1502 // exportTo with same namespace is effectively private 1503 ps.ServiceIndex.privateByNamespace[ns] = append(ps.ServiceIndex.privateByNamespace[ns], s) 1504 } else { 1505 // exportTo is a specific target namespace 1506 ps.ServiceIndex.exportedToNamespace[string(exportTo)] = append(ps.ServiceIndex.exportedToNamespace[string(exportTo)], s) 1507 } 1508 } 1509 } 1510 } 1511 1512 ps.initServiceAccounts(env, allServices) 1513 } 1514 1515 // resolveServiceAliases sets the Aliases attributes on all services. The incoming Service's will just have AliasFor set, 1516 // but in our usage we often need the opposite: for a given service, what are all the aliases? 1517 // resolveServiceAliases walks this 'graph' of services and updates the Alias field in-place. 1518 func resolveServiceAliases(allServices []*Service, configsUpdated sets.Set[ConfigKey]) { 1519 // rawAlias builds a map of Service -> AliasFor. So this will be ExternalName -> Service. 1520 // In an edge case, we can have ExternalName -> ExternalName; we resolve that below. 1521 rawAlias := map[NamespacedHostname]host.Name{} 1522 for _, s := range allServices { 1523 if s.Resolution != Alias { 1524 continue 1525 } 1526 nh := NamespacedHostname{ 1527 Hostname: s.Hostname, 1528 Namespace: s.Attributes.Namespace, 1529 } 1530 rawAlias[nh] = host.Name(s.Attributes.K8sAttributes.ExternalName) 1531 } 1532 1533 // unnamespacedRawAlias is like rawAlias but without namespaces. 1534 // This is because an `ExternalName` isn't namespaced. If there is a conflict, the behavior is undefined. 1535 // This is split from above as a minor optimization to right-size the map 1536 unnamespacedRawAlias := make(map[host.Name]host.Name, len(rawAlias)) 1537 for k, v := range rawAlias { 1538 unnamespacedRawAlias[k.Hostname] = v 1539 } 1540 1541 // resolvedAliases builds a map of Alias -> Concrete, fully resolving through multiple hops. 1542 // Ex: Alias1 -> Alias2 -> Concrete will flatten to Alias1 -> Concrete. 1543 resolvedAliases := make(map[NamespacedHostname]host.Name, len(rawAlias)) 1544 for alias, referencedService := range rawAlias { 1545 // referencedService may be another alias or a concrete service. 1546 if _, f := unnamespacedRawAlias[referencedService]; !f { 1547 // Common case: alias pointing to a concrete service 1548 resolvedAliases[alias] = referencedService 1549 continue 1550 } 1551 // Otherwise, we need to traverse the alias "graph". 1552 // In an obscure edge case, a user could make a loop, so we will need to handle that. 1553 seen := sets.New(alias.Hostname, referencedService) 1554 for { 1555 n, f := unnamespacedRawAlias[referencedService] 1556 if !f { 1557 // The destination we are pointing to is not an alias, so this is the terminal step 1558 resolvedAliases[alias] = referencedService 1559 break 1560 } 1561 if seen.InsertContains(n) { 1562 // We did a loop! 1563 // Kubernetes will make these NXDomain, so we can just treat it like it doesn't exist at all 1564 break 1565 } 1566 referencedService = n 1567 } 1568 } 1569 1570 // aliasesForService builds a map of Concrete -> []Aliases 1571 // This basically reverses our resolvedAliased map, which is Alias -> Concrete, 1572 aliasesForService := map[host.Name][]NamespacedHostname{} 1573 for alias, concrete := range resolvedAliases { 1574 aliasesForService[concrete] = append(aliasesForService[concrete], alias) 1575 1576 // We also need to update configsUpdated, such that any "alias" updated also marks the concrete service as updated. 1577 aliasKey := ConfigKey{ 1578 Kind: kind.ServiceEntry, 1579 Name: alias.Hostname.String(), 1580 Namespace: alias.Namespace, 1581 } 1582 // Alias. We should mark all the concrete services as updated as well. 1583 if configsUpdated.Contains(aliasKey) { 1584 // We only have the hostname, but we need the namespace... 1585 for _, svc := range allServices { 1586 if svc.Hostname == concrete { 1587 configsUpdated.Insert(ConfigKey{ 1588 Kind: kind.ServiceEntry, 1589 Name: concrete.String(), 1590 Namespace: svc.Attributes.Namespace, 1591 }) 1592 } 1593 } 1594 } 1595 } 1596 // Sort aliases so order is deterministic. 1597 for _, v := range aliasesForService { 1598 slices.SortFunc(v, func(a, b NamespacedHostname) int { 1599 if r := cmp.Compare(a.Namespace, b.Namespace); r != 0 { 1600 return r 1601 } 1602 return cmp.Compare(a.Hostname, b.Hostname) 1603 }) 1604 } 1605 1606 // Finally, we can traverse all services and update the ones that have aliases 1607 for i, s := range allServices { 1608 if aliases, f := aliasesForService[s.Hostname]; f { 1609 // This service has an alias; set it. We need to make a copy since the underlying Service is shared 1610 s = s.DeepCopy() 1611 s.Attributes.Aliases = aliases 1612 allServices[i] = s 1613 } 1614 } 1615 } 1616 1617 // SortServicesByCreationTime sorts the list of services in ascending order by their creation time (if available). 1618 func SortServicesByCreationTime(services []*Service) []*Service { 1619 slices.SortStableFunc(services, func(a, b *Service) int { 1620 if r := a.CreationTime.Compare(b.CreationTime); r != 0 { 1621 return r 1622 } 1623 // If creation time is the same, then behavior is nondeterministic. In this case, we can 1624 // pick an arbitrary but consistent ordering based on name and namespace, which is unique. 1625 // CreationTimestamp is stored in seconds, so this is not uncommon. 1626 an := a.Attributes.Name + "." + a.Attributes.Namespace 1627 bn := b.Attributes.Name + "." + b.Attributes.Namespace 1628 return cmp.Compare(an, bn) 1629 }) 1630 return services 1631 } 1632 1633 // Caches list of service accounts in the registry 1634 func (ps *PushContext) initServiceAccounts(env *Environment, services []*Service) { 1635 for _, svc := range services { 1636 var accounts sets.String 1637 // First get endpoint level service accounts 1638 shard, f := env.EndpointIndex.ShardsForService(string(svc.Hostname), svc.Attributes.Namespace) 1639 if f { 1640 shard.RLock() 1641 // copy here to reduce the lock time 1642 // endpoints could update frequently, so the longer it locks, the more likely it will block other threads. 1643 accounts = shard.ServiceAccounts.Copy() 1644 shard.RUnlock() 1645 } 1646 if len(svc.ServiceAccounts) > 0 { 1647 if accounts == nil { 1648 accounts = sets.New(svc.ServiceAccounts...) 1649 } else { 1650 accounts = accounts.InsertAll(svc.ServiceAccounts...) 1651 } 1652 } 1653 sa := sets.SortedList(spiffe.ExpandWithTrustDomains(accounts, ps.Mesh.TrustDomainAliases)) 1654 key := serviceAccountKey{ 1655 hostname: svc.Hostname, 1656 namespace: svc.Attributes.Namespace, 1657 } 1658 ps.serviceAccounts[key] = sa 1659 } 1660 } 1661 1662 // Caches list of authentication policies 1663 func (ps *PushContext) initAuthnPolicies(env *Environment) { 1664 ps.AuthnPolicies = initAuthenticationPolicies(env) 1665 } 1666 1667 // Caches list of virtual services 1668 func (ps *PushContext) initVirtualServices(env *Environment) { 1669 ps.virtualServiceIndex.exportedToNamespaceByGateway = map[types.NamespacedName][]config.Config{} 1670 ps.virtualServiceIndex.privateByNamespaceAndGateway = map[types.NamespacedName][]config.Config{} 1671 ps.virtualServiceIndex.publicByGateway = map[string][]config.Config{} 1672 ps.virtualServiceIndex.referencedDestinations = map[string]sets.String{} 1673 1674 if features.FilterGatewayClusterConfig { 1675 ps.virtualServiceIndex.destinationsByGateway = make(map[string]sets.String) 1676 } 1677 1678 virtualServices := env.List(gvk.VirtualService, NamespaceAll) 1679 1680 // values returned from ConfigStore.List are immutable. 1681 // Therefore, we make a copy 1682 vservices := make([]config.Config, len(virtualServices)) 1683 1684 for i := range vservices { 1685 vservices[i] = virtualServices[i].DeepCopy() 1686 } 1687 1688 totalVirtualServices.Record(float64(len(virtualServices))) 1689 1690 // convert all shortnames in virtual services into FQDNs 1691 for _, r := range vservices { 1692 resolveVirtualServiceShortnames(r.Spec.(*networking.VirtualService), r.Meta) 1693 } 1694 1695 vservices, ps.virtualServiceIndex.delegates = mergeVirtualServicesIfNeeded(vservices, ps.exportToDefaults.virtualService) 1696 1697 for _, virtualService := range vservices { 1698 ns := virtualService.Namespace 1699 rule := virtualService.Spec.(*networking.VirtualService) 1700 gwNames := getGatewayNames(rule) 1701 if len(rule.ExportTo) == 0 { 1702 // No exportTo in virtualService. Use the global default 1703 // We only honor ., * 1704 if ps.exportToDefaults.virtualService.Contains(visibility.Private) { 1705 // add to local namespace only 1706 private := ps.virtualServiceIndex.privateByNamespaceAndGateway 1707 for _, gw := range gwNames { 1708 n := types.NamespacedName{Namespace: ns, Name: gw} 1709 private[n] = append(private[n], virtualService) 1710 } 1711 } else if ps.exportToDefaults.virtualService.Contains(visibility.Public) { 1712 for _, gw := range gwNames { 1713 ps.virtualServiceIndex.publicByGateway[gw] = append(ps.virtualServiceIndex.publicByGateway[gw], virtualService) 1714 } 1715 } 1716 } else { 1717 exportToSet := sets.NewWithLength[visibility.Instance](len(rule.ExportTo)) 1718 for _, e := range rule.ExportTo { 1719 exportToSet.Insert(visibility.Instance(e)) 1720 } 1721 // if vs has exportTo ~ - i.e. not visible to anyone, ignore all exportTos 1722 // if vs has exportTo *, make public and ignore all other exportTos 1723 // if vs has exportTo ., replace with current namespace 1724 if exportToSet.Contains(visibility.Public) { 1725 for _, gw := range gwNames { 1726 ps.virtualServiceIndex.publicByGateway[gw] = append(ps.virtualServiceIndex.publicByGateway[gw], virtualService) 1727 } 1728 } else if !exportToSet.Contains(visibility.None) { 1729 // . or other namespaces 1730 for exportTo := range exportToSet { 1731 if exportTo == visibility.Private || string(exportTo) == ns { 1732 // add to local namespace only 1733 for _, gw := range gwNames { 1734 n := types.NamespacedName{Namespace: ns, Name: gw} 1735 ps.virtualServiceIndex.privateByNamespaceAndGateway[n] = append(ps.virtualServiceIndex.privateByNamespaceAndGateway[n], virtualService) 1736 } 1737 } else { 1738 exported := ps.virtualServiceIndex.exportedToNamespaceByGateway 1739 // add to local namespace only 1740 for _, gw := range gwNames { 1741 n := types.NamespacedName{Namespace: string(exportTo), Name: gw} 1742 exported[n] = append(exported[n], virtualService) 1743 } 1744 } 1745 } 1746 } 1747 } 1748 1749 if features.FilterGatewayClusterConfig { 1750 for _, gw := range gwNames { 1751 if gw == constants.IstioMeshGateway { 1752 continue 1753 } 1754 for host := range virtualServiceDestinations(rule) { 1755 sets.InsertOrNew(ps.virtualServiceIndex.destinationsByGateway, gw, host) 1756 } 1757 } 1758 } 1759 1760 // For mesh virtual services, build a map of host -> referenced destinations 1761 if features.EnableAmbientWaypoints && (len(rule.Gateways) == 0 || slices.Contains(rule.Gateways, constants.IstioMeshGateway)) { 1762 for host := range virtualServiceDestinations(rule) { 1763 for _, rhost := range rule.Hosts { 1764 if _, f := ps.virtualServiceIndex.referencedDestinations[rhost]; !f { 1765 ps.virtualServiceIndex.referencedDestinations[rhost] = sets.New[string]() 1766 } 1767 ps.virtualServiceIndex.referencedDestinations[rhost].Insert(host) 1768 } 1769 } 1770 } 1771 } 1772 } 1773 1774 var meshGateways = []string{constants.IstioMeshGateway} 1775 1776 func getGatewayNames(vs *networking.VirtualService) []string { 1777 if len(vs.Gateways) == 0 { 1778 return meshGateways 1779 } 1780 res := make([]string, 0, len(vs.Gateways)) 1781 res = append(res, vs.Gateways...) 1782 return res 1783 } 1784 1785 func (ps *PushContext) initDefaultExportMaps() { 1786 ps.exportToDefaults.destinationRule = sets.New[visibility.Instance]() 1787 if ps.Mesh.DefaultDestinationRuleExportTo != nil { 1788 for _, e := range ps.Mesh.DefaultDestinationRuleExportTo { 1789 ps.exportToDefaults.destinationRule.Insert(visibility.Instance(e)) 1790 } 1791 } else { 1792 // default to * 1793 ps.exportToDefaults.destinationRule.Insert(visibility.Public) 1794 } 1795 1796 ps.exportToDefaults.service = sets.New[visibility.Instance]() 1797 if ps.Mesh.DefaultServiceExportTo != nil { 1798 for _, e := range ps.Mesh.DefaultServiceExportTo { 1799 ps.exportToDefaults.service.Insert(visibility.Instance(e)) 1800 } 1801 } else { 1802 ps.exportToDefaults.service.Insert(visibility.Public) 1803 } 1804 1805 ps.exportToDefaults.virtualService = sets.New[visibility.Instance]() 1806 if ps.Mesh.DefaultVirtualServiceExportTo != nil { 1807 for _, e := range ps.Mesh.DefaultVirtualServiceExportTo { 1808 ps.exportToDefaults.virtualService.Insert(visibility.Instance(e)) 1809 } 1810 } else { 1811 ps.exportToDefaults.virtualService.Insert(visibility.Public) 1812 } 1813 } 1814 1815 // initSidecarScopes synthesizes Sidecar CRDs into objects called 1816 // SidecarScope. The SidecarScope object is a semi-processed view of the 1817 // service registry, and config state associated with the sidecar CRD. The 1818 // scope contains a set of inbound and outbound listeners, services/configs 1819 // per listener, etc. The sidecar scopes are precomputed based on the 1820 // Sidecar API objects in each namespace. If there is no sidecar api object 1821 // for a namespace, a default sidecarscope is assigned to the namespace 1822 // which enables connectivity to all services in the mesh. 1823 // 1824 // When proxies connect to Pilot, we identify the sidecar scope associated 1825 // with the proxy and derive listeners/routes/clusters based on the sidecar 1826 // scope. 1827 func (ps *PushContext) initSidecarScopes(env *Environment) { 1828 rawSidecarConfigs := env.List(gvk.Sidecar, NamespaceAll) 1829 1830 sortConfigByCreationTime(rawSidecarConfigs) 1831 1832 sidecarConfigs := make([]config.Config, 0, len(rawSidecarConfigs)) 1833 for _, sidecarConfig := range rawSidecarConfigs { 1834 sidecar := sidecarConfig.Spec.(*networking.Sidecar) 1835 // sidecars with selector take preference 1836 if sidecar.WorkloadSelector != nil { 1837 sidecarConfigs = append(sidecarConfigs, sidecarConfig) 1838 } 1839 } 1840 for _, sidecarConfig := range rawSidecarConfigs { 1841 sidecar := sidecarConfig.Spec.(*networking.Sidecar) 1842 // sidecars without selector placed behind 1843 if sidecar.WorkloadSelector == nil { 1844 sidecarConfigs = append(sidecarConfigs, sidecarConfig) 1845 } 1846 } 1847 1848 // Hold reference root namespace's sidecar config 1849 // Root namespace can have only one sidecar config object 1850 // Currently we expect that it has no workloadSelectors 1851 var rootNSConfig *config.Config 1852 for i, sidecarConfig := range sidecarConfigs { 1853 if sidecarConfig.Namespace == ps.Mesh.RootNamespace && 1854 sidecarConfig.Spec.(*networking.Sidecar).WorkloadSelector == nil { 1855 rootNSConfig = &sidecarConfigs[i] 1856 break 1857 } 1858 } 1859 ps.sidecarIndex.meshRootSidecarConfig = rootNSConfig 1860 1861 ps.sidecarIndex.sidecarsByNamespace = make(map[string][]*SidecarScope) 1862 ps.convertSidecarScopes(sidecarConfigs) 1863 } 1864 1865 func (ps *PushContext) convertSidecarScopes(sidecarConfigs []config.Config) { 1866 if len(sidecarConfigs) == 0 { 1867 return 1868 } 1869 if features.ConvertSidecarScopeConcurrency > 1 { 1870 ps.concurrentConvertToSidecarScope(sidecarConfigs) 1871 } else { 1872 for _, sidecarConfig := range sidecarConfigs { 1873 ps.sidecarIndex.sidecarsByNamespace[sidecarConfig.Namespace] = append(ps.sidecarIndex.sidecarsByNamespace[sidecarConfig.Namespace], 1874 convertToSidecarScope(ps, &sidecarConfig, sidecarConfig.Namespace)) 1875 } 1876 } 1877 } 1878 1879 func (ps *PushContext) concurrentConvertToSidecarScope(sidecarConfigs []config.Config) { 1880 type taskItem struct { 1881 idx int 1882 cfg config.Config 1883 } 1884 1885 var wg sync.WaitGroup 1886 taskItems := make(chan taskItem) 1887 sidecarScopes := make([]*SidecarScope, len(sidecarConfigs)) 1888 for i := 0; i < features.ConvertSidecarScopeConcurrency; i++ { 1889 wg.Add(1) 1890 go func() { 1891 defer wg.Done() 1892 for { 1893 item, ok := <-taskItems 1894 if !ok { 1895 break 1896 } 1897 sc := convertToSidecarScope(ps, &item.cfg, item.cfg.Namespace) 1898 sidecarScopes[item.idx] = sc 1899 } 1900 }() 1901 } 1902 1903 // note: sidecarScopes order matters and needs to be kept in the same order as sidecarConfigs. 1904 // The order indicates priority, see getSidecarScope. 1905 for idx, cfg := range sidecarConfigs { 1906 taskItems <- taskItem{idx: idx, cfg: cfg} 1907 } 1908 1909 close(taskItems) 1910 wg.Wait() 1911 1912 for _, sc := range sidecarScopes { 1913 ps.sidecarIndex.sidecarsByNamespace[sc.Namespace] = append(ps.sidecarIndex.sidecarsByNamespace[sc.Namespace], sc) 1914 } 1915 } 1916 1917 // Split out of DestinationRule expensive conversions - once per push. 1918 func (ps *PushContext) initDestinationRules(env *Environment) { 1919 configs := env.List(gvk.DestinationRule, NamespaceAll) 1920 1921 // values returned from ConfigStore.List are immutable. 1922 // Therefore, we make a copy 1923 destRules := make([]config.Config, len(configs)) 1924 for i := range destRules { 1925 destRules[i] = configs[i] 1926 } 1927 1928 ps.setDestinationRules(destRules) 1929 } 1930 1931 func newConsolidatedDestRules() *consolidatedDestRules { 1932 return &consolidatedDestRules{ 1933 exportTo: map[host.Name]sets.Set[visibility.Instance]{}, 1934 specificDestRules: map[host.Name][]*ConsolidatedDestRule{}, 1935 wildcardDestRules: map[host.Name][]*ConsolidatedDestRule{}, 1936 } 1937 } 1938 1939 // Testing Only. This allows tests to inject a config without having the mock. 1940 func (ps *PushContext) SetDestinationRulesForTesting(configs []config.Config) { 1941 ps.setDestinationRules(configs) 1942 } 1943 1944 // sortConfigBySelectorAndCreationTime sorts the list of config objects based on priority and creation time. 1945 func sortConfigBySelectorAndCreationTime(configs []config.Config) []config.Config { 1946 creationTimeComparator := sortByCreationComparator(configs) 1947 // Define a comparator function for sorting configs by priority and creation time 1948 comparator := func(i, j int) bool { 1949 idr := configs[i].Spec.(*networking.DestinationRule) 1950 jdr := configs[j].Spec.(*networking.DestinationRule) 1951 1952 // Check if one of the configs has priority set to true 1953 if idr.GetWorkloadSelector() != nil && jdr.GetWorkloadSelector() == nil { 1954 return true 1955 } else if idr.GetWorkloadSelector() == nil && jdr.GetWorkloadSelector() != nil { 1956 return false 1957 } 1958 1959 // If priority is the same or neither has priority, fallback to creation time ordering 1960 return creationTimeComparator(i, j) 1961 } 1962 1963 // Sort the configs using the defined comparator function 1964 sort.Slice(configs, comparator) 1965 return configs 1966 } 1967 1968 // setDestinationRules updates internal structures using a set of configs. 1969 // Split out of DestinationRule expensive conversions, computed once per push. 1970 // This will not work properly for Sidecars, which will precompute their 1971 // destination rules on init. 1972 func (ps *PushContext) setDestinationRules(configs []config.Config) { 1973 // Sort by time first. So if two destination rule have top level traffic policies 1974 // we take the first one. 1975 sortConfigBySelectorAndCreationTime(configs) 1976 namespaceLocalDestRules := make(map[string]*consolidatedDestRules) 1977 exportedDestRulesByNamespace := make(map[string]*consolidatedDestRules) 1978 rootNamespaceLocalDestRules := newConsolidatedDestRules() 1979 1980 for i := range configs { 1981 rule := configs[i].Spec.(*networking.DestinationRule) 1982 1983 rule.Host = string(ResolveShortnameToFQDN(rule.Host, configs[i].Meta)) 1984 var exportToSet sets.Set[visibility.Instance] 1985 1986 // destination rules with workloadSelector should not be exported to other namespaces 1987 if rule.GetWorkloadSelector() == nil { 1988 exportToSet = sets.NewWithLength[visibility.Instance](len(rule.ExportTo)) 1989 for _, e := range rule.ExportTo { 1990 exportToSet.Insert(visibility.Instance(e)) 1991 } 1992 } else { 1993 exportToSet = sets.New[visibility.Instance](visibility.Private) 1994 } 1995 1996 // add only if the dest rule is exported with . or * or explicit exportTo containing this namespace 1997 // The global exportTo doesn't matter here (its either . or * - both of which are applicable here) 1998 if exportToSet.IsEmpty() || exportToSet.Contains(visibility.Public) || exportToSet.Contains(visibility.Private) || 1999 exportToSet.Contains(visibility.Instance(configs[i].Namespace)) { 2000 // Store in an index for the config's namespace 2001 // a proxy from this namespace will first look here for the destination rule for a given service 2002 // This pool consists of both public/private destination rules. 2003 if _, exist := namespaceLocalDestRules[configs[i].Namespace]; !exist { 2004 namespaceLocalDestRules[configs[i].Namespace] = newConsolidatedDestRules() 2005 } 2006 // Merge this destination rule with any public/private dest rules for same host in the same namespace 2007 // If there are no duplicates, the dest rule will be added to the list 2008 ps.mergeDestinationRule(namespaceLocalDestRules[configs[i].Namespace], configs[i], exportToSet) 2009 } 2010 2011 isPrivateOnly := false 2012 // No exportTo in destinationRule. Use the global default 2013 // We only honor . and * 2014 if exportToSet.IsEmpty() && ps.exportToDefaults.destinationRule.Contains(visibility.Private) { 2015 isPrivateOnly = true 2016 } else if exportToSet.Len() == 1 && (exportToSet.Contains(visibility.Private) || exportToSet.Contains(visibility.Instance(configs[i].Namespace))) { 2017 isPrivateOnly = true 2018 } 2019 2020 if !isPrivateOnly { 2021 if _, exist := exportedDestRulesByNamespace[configs[i].Namespace]; !exist { 2022 exportedDestRulesByNamespace[configs[i].Namespace] = newConsolidatedDestRules() 2023 } 2024 // Merge this destination rule with any other exported dest rule for the same host in the same namespace 2025 // If there are no duplicates, the dest rule will be added to the list 2026 ps.mergeDestinationRule(exportedDestRulesByNamespace[configs[i].Namespace], configs[i], exportToSet) 2027 } else if configs[i].Namespace == ps.Mesh.RootNamespace { 2028 // Keep track of private root namespace destination rules 2029 ps.mergeDestinationRule(rootNamespaceLocalDestRules, configs[i], exportToSet) 2030 } 2031 } 2032 2033 ps.destinationRuleIndex.namespaceLocal = namespaceLocalDestRules 2034 ps.destinationRuleIndex.exportedByNamespace = exportedDestRulesByNamespace 2035 ps.destinationRuleIndex.rootNamespaceLocal = rootNamespaceLocalDestRules 2036 } 2037 2038 // pre computes all AuthorizationPolicies per namespace 2039 func (ps *PushContext) initAuthorizationPolicies(env *Environment) { 2040 ps.AuthzPolicies = GetAuthorizationPolicies(env) 2041 } 2042 2043 func (ps *PushContext) initTelemetry(env *Environment) { 2044 ps.Telemetry = getTelemetries(env) 2045 } 2046 2047 func (ps *PushContext) initProxyConfigs(env *Environment) { 2048 ps.ProxyConfigs = GetProxyConfigs(env.ConfigStore, env.Mesh()) 2049 } 2050 2051 // pre computes WasmPlugins per namespace 2052 func (ps *PushContext) initWasmPlugins(env *Environment) { 2053 wasmplugins := env.List(gvk.WasmPlugin, NamespaceAll) 2054 2055 sortConfigByCreationTime(wasmplugins) 2056 ps.wasmPluginsByNamespace = map[string][]*WasmPluginWrapper{} 2057 for _, plugin := range wasmplugins { 2058 if pluginWrapper := convertToWasmPluginWrapper(plugin); pluginWrapper != nil { 2059 ps.wasmPluginsByNamespace[plugin.Namespace] = append(ps.wasmPluginsByNamespace[plugin.Namespace], pluginWrapper) 2060 } 2061 } 2062 } 2063 2064 // WasmPlugins return the WasmPluginWrappers of a proxy. 2065 func (ps *PushContext) WasmPlugins(proxy *Proxy) map[extensions.PluginPhase][]*WasmPluginWrapper { 2066 return ps.WasmPluginsByListenerInfo(proxy, anyListener, WasmPluginTypeAny) 2067 } 2068 2069 func (ps *PushContext) WasmPluginsByName(proxy *Proxy, names []types.NamespacedName) []*WasmPluginWrapper { 2070 res := make([]*WasmPluginWrapper, 0, len(names)) 2071 for _, n := range names { 2072 if n.Namespace != proxy.ConfigNamespace && n.Namespace != ps.Mesh.RootNamespace { 2073 log.Warnf("proxy requested invalid WASM configuration: %v", n) 2074 continue 2075 } 2076 for _, wsm := range ps.wasmPluginsByNamespace[n.Namespace] { 2077 if wsm.Name == n.Name { 2078 res = append(res, wsm) 2079 break 2080 } 2081 } 2082 } 2083 return res 2084 } 2085 2086 // WasmPluginsByListenerInfo return the WasmPluginWrappers which are matched with TrafficSelector in the given proxy. 2087 func (ps *PushContext) WasmPluginsByListenerInfo(proxy *Proxy, info WasmPluginListenerInfo, 2088 pluginType WasmPluginType, 2089 ) map[extensions.PluginPhase][]*WasmPluginWrapper { 2090 if proxy == nil { 2091 return nil 2092 } 2093 2094 var lookupInNamespaces []string 2095 matchedPlugins := make(map[extensions.PluginPhase][]*WasmPluginWrapper) 2096 2097 if proxy.ConfigNamespace != ps.Mesh.RootNamespace { 2098 // Only check the root namespace if the (workload) namespace is not already the root namespace 2099 // to avoid double inclusion. 2100 lookupInNamespaces = []string{proxy.ConfigNamespace, ps.Mesh.RootNamespace} 2101 } else { 2102 lookupInNamespaces = []string{proxy.ConfigNamespace} 2103 } 2104 2105 selectionOpts := PolicyMatcherForProxy(proxy).WithService(info.Service) 2106 for _, ns := range lookupInNamespaces { 2107 if wasmPlugins, ok := ps.wasmPluginsByNamespace[ns]; ok { 2108 for _, plugin := range wasmPlugins { 2109 if plugin.MatchListener(selectionOpts, info) && plugin.MatchType(pluginType) { 2110 matchedPlugins[plugin.Phase] = append(matchedPlugins[plugin.Phase], plugin) 2111 } 2112 } 2113 } 2114 } 2115 2116 // sort slices by priority 2117 for i, slice := range matchedPlugins { 2118 sort.SliceStable(slice, func(i, j int) bool { 2119 iPriority := int32(math.MinInt32) 2120 if prio := slice[i].Priority; prio != nil { 2121 iPriority = prio.Value 2122 } 2123 jPriority := int32(math.MinInt32) 2124 if prio := slice[j].Priority; prio != nil { 2125 jPriority = prio.Value 2126 } 2127 return iPriority > jPriority 2128 }) 2129 matchedPlugins[i] = slice 2130 } 2131 2132 return matchedPlugins 2133 } 2134 2135 // pre computes envoy filters per namespace 2136 func (ps *PushContext) initEnvoyFilters(env *Environment, changed sets.Set[ConfigKey], previousIndex map[string][]*EnvoyFilterWrapper) { 2137 envoyFilterConfigs := env.List(gvk.EnvoyFilter, NamespaceAll) 2138 var previous map[ConfigKey]*EnvoyFilterWrapper 2139 if features.OptimizedConfigRebuild { 2140 previous = make(map[ConfigKey]*EnvoyFilterWrapper) 2141 for namespace, nsEnvoyFilters := range previousIndex { 2142 for _, envoyFilter := range nsEnvoyFilters { 2143 previous[ConfigKey{Kind: kind.EnvoyFilter, Namespace: namespace, Name: envoyFilter.Name}] = envoyFilter 2144 } 2145 } 2146 } 2147 2148 sort.Slice(envoyFilterConfigs, func(i, j int) bool { 2149 ifilter := envoyFilterConfigs[i].Spec.(*networking.EnvoyFilter) 2150 jfilter := envoyFilterConfigs[j].Spec.(*networking.EnvoyFilter) 2151 if ifilter.Priority != jfilter.Priority { 2152 return ifilter.Priority < jfilter.Priority 2153 } 2154 // If priority is same fallback to name and creation timestamp, else use priority. 2155 // If creation time is the same, then behavior is nondeterministic. In this case, we can 2156 // pick an arbitrary but consistent ordering based on name and namespace, which is unique. 2157 // CreationTimestamp is stored in seconds, so this is not uncommon. 2158 if envoyFilterConfigs[i].CreationTimestamp != envoyFilterConfigs[j].CreationTimestamp { 2159 return envoyFilterConfigs[i].CreationTimestamp.Before(envoyFilterConfigs[j].CreationTimestamp) 2160 } 2161 in := envoyFilterConfigs[i].Name + "." + envoyFilterConfigs[i].Namespace 2162 jn := envoyFilterConfigs[j].Name + "." + envoyFilterConfigs[j].Namespace 2163 return in < jn 2164 }) 2165 2166 for _, envoyFilterConfig := range envoyFilterConfigs { 2167 var efw *EnvoyFilterWrapper 2168 if features.OptimizedConfigRebuild { 2169 key := ConfigKey{Kind: kind.EnvoyFilter, Namespace: envoyFilterConfig.Namespace, Name: envoyFilterConfig.Name} 2170 if prev, ok := previous[key]; ok && !changed.Contains(key) { 2171 // Reuse the previous EnvoyFilterWrapper if it exists and hasn't changed when optimized config rebuild is enabled 2172 efw = prev 2173 } 2174 } 2175 // Rebuild the envoy filter in all other cases. 2176 if efw == nil { 2177 efw = convertToEnvoyFilterWrapper(&envoyFilterConfig) 2178 } 2179 ps.envoyFiltersByNamespace[envoyFilterConfig.Namespace] = append(ps.envoyFiltersByNamespace[envoyFilterConfig.Namespace], efw) 2180 } 2181 } 2182 2183 // EnvoyFilters return the merged EnvoyFilterWrapper of a proxy 2184 func (ps *PushContext) EnvoyFilters(proxy *Proxy) *EnvoyFilterWrapper { 2185 // this should never happen 2186 if proxy == nil { 2187 return nil 2188 } 2189 var matchedEnvoyFilters []*EnvoyFilterWrapper 2190 // EnvoyFilters supports inheritance (global ones plus namespace local ones). 2191 // First get all the filter configs from the config root namespace 2192 // and then add the ones from proxy's own namespace 2193 if ps.Mesh.RootNamespace != "" { 2194 matchedEnvoyFilters = ps.getMatchedEnvoyFilters(proxy, ps.Mesh.RootNamespace) 2195 } 2196 2197 // To prevent duplicate envoyfilters in case root namespace equals proxy's namespace 2198 if proxy.ConfigNamespace != ps.Mesh.RootNamespace { 2199 matched := ps.getMatchedEnvoyFilters(proxy, proxy.ConfigNamespace) 2200 matchedEnvoyFilters = append(matchedEnvoyFilters, matched...) 2201 } 2202 2203 sort.Slice(matchedEnvoyFilters, func(i, j int) bool { 2204 ifilter := matchedEnvoyFilters[i] 2205 jfilter := matchedEnvoyFilters[j] 2206 if ifilter.Priority != jfilter.Priority { 2207 return ifilter.Priority < jfilter.Priority 2208 } 2209 // Prefer root namespace filters over non-root namespace filters. 2210 if ifilter.Namespace != jfilter.Namespace && 2211 (ifilter.Namespace == ps.Mesh.RootNamespace || jfilter.Namespace == ps.Mesh.RootNamespace) { 2212 return ifilter.Namespace == ps.Mesh.RootNamespace 2213 } 2214 if ifilter.creationTime != jfilter.creationTime { 2215 return ifilter.creationTime.Before(jfilter.creationTime) 2216 } 2217 in := ifilter.Name + "." + ifilter.Namespace 2218 jn := jfilter.Name + "." + jfilter.Namespace 2219 return in < jn 2220 }) 2221 var out *EnvoyFilterWrapper 2222 if len(matchedEnvoyFilters) > 0 { 2223 out = &EnvoyFilterWrapper{ 2224 // no need populate workloadSelector, as it is not used later. 2225 Patches: make(map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper), 2226 } 2227 // merge EnvoyFilterWrapper 2228 for _, efw := range matchedEnvoyFilters { 2229 for applyTo, cps := range efw.Patches { 2230 for _, cp := range cps { 2231 if proxyMatch(proxy, cp) { 2232 out.Patches[applyTo] = append(out.Patches[applyTo], cp) 2233 } 2234 } 2235 } 2236 } 2237 } 2238 2239 return out 2240 } 2241 2242 // if there is no workload selector, the config applies to all workloads 2243 // if there is a workload selector, check for matching workload labels 2244 func (ps *PushContext) getMatchedEnvoyFilters(proxy *Proxy, namespaces string) []*EnvoyFilterWrapper { 2245 matchedEnvoyFilters := make([]*EnvoyFilterWrapper, 0) 2246 for _, efw := range ps.envoyFiltersByNamespace[namespaces] { 2247 if efw.workloadSelector == nil || efw.workloadSelector.SubsetOf(proxy.Labels) { 2248 matchedEnvoyFilters = append(matchedEnvoyFilters, efw) 2249 } 2250 } 2251 return matchedEnvoyFilters 2252 } 2253 2254 // HasEnvoyFilters checks if an EnvoyFilter exists with the given name at the given namespace. 2255 func (ps *PushContext) HasEnvoyFilters(name, namespace string) bool { 2256 for _, efw := range ps.envoyFiltersByNamespace[namespace] { 2257 if efw.Name == name { 2258 return true 2259 } 2260 } 2261 return false 2262 } 2263 2264 // pre computes gateways per namespace 2265 func (ps *PushContext) initGateways(env *Environment) { 2266 gatewayConfigs := env.List(gvk.Gateway, NamespaceAll) 2267 2268 sortConfigByCreationTime(gatewayConfigs) 2269 2270 if features.ScopeGatewayToNamespace { 2271 ps.gatewayIndex.namespace = make(map[string][]config.Config) 2272 for _, gatewayConfig := range gatewayConfigs { 2273 if _, exists := ps.gatewayIndex.namespace[gatewayConfig.Namespace]; !exists { 2274 ps.gatewayIndex.namespace[gatewayConfig.Namespace] = make([]config.Config, 0) 2275 } 2276 ps.gatewayIndex.namespace[gatewayConfig.Namespace] = append(ps.gatewayIndex.namespace[gatewayConfig.Namespace], gatewayConfig) 2277 } 2278 } else { 2279 ps.gatewayIndex.all = gatewayConfigs 2280 } 2281 } 2282 2283 func (ps *PushContext) initAmbient(env *Environment) { 2284 ps.ambientIndex = env 2285 } 2286 2287 // InternalGatewayServiceAnnotation represents the hostname of the service a gateway will use. This is 2288 // only used internally to transfer information from the Kubernetes Gateway API to the Istio Gateway API 2289 // which does not have a field to represent this. 2290 // The format is a comma separated list of hostnames. For example, "ingress.istio-system.svc.cluster.local,ingress.example.com" 2291 // The Gateway will apply to all ServiceInstances of these services, *in the same namespace as the Gateway*. 2292 const InternalGatewayServiceAnnotation = "internal.istio.io/gateway-service" 2293 2294 type gatewayWithInstances struct { 2295 gateway config.Config 2296 // If true, ports that are not present in any instance will be used directly (without targetPort translation) 2297 // This supports the legacy behavior of selecting gateways by pod label selector 2298 legacyGatewaySelector bool 2299 instances []ServiceTarget 2300 } 2301 2302 func (ps *PushContext) mergeGateways(proxy *Proxy) *MergedGateway { 2303 // this should never happen 2304 if proxy == nil { 2305 return nil 2306 } 2307 gatewayInstances := make([]gatewayWithInstances, 0) 2308 2309 var configs []config.Config 2310 if features.ScopeGatewayToNamespace { 2311 configs = ps.gatewayIndex.namespace[proxy.ConfigNamespace] 2312 } else { 2313 configs = ps.gatewayIndex.all 2314 } 2315 2316 for _, cfg := range configs { 2317 gw := cfg.Spec.(*networking.Gateway) 2318 if gwsvcstr, f := cfg.Annotations[InternalGatewayServiceAnnotation]; f { 2319 gwsvcs := strings.Split(gwsvcstr, ",") 2320 known := sets.New[string](gwsvcs...) 2321 matchingInstances := make([]ServiceTarget, 0, len(proxy.ServiceTargets)) 2322 for _, si := range proxy.ServiceTargets { 2323 if _, f := known[string(si.Service.Hostname)]; f && si.Service.Attributes.Namespace == cfg.Namespace { 2324 matchingInstances = append(matchingInstances, si) 2325 } 2326 } 2327 // Only if we have a matching instance should we apply the configuration 2328 if len(matchingInstances) > 0 { 2329 gatewayInstances = append(gatewayInstances, gatewayWithInstances{cfg, false, matchingInstances}) 2330 } 2331 } else if gw.GetSelector() == nil { 2332 // no selector. Applies to all workloads asking for the gateway 2333 gatewayInstances = append(gatewayInstances, gatewayWithInstances{cfg, true, proxy.ServiceTargets}) 2334 } else { 2335 gatewaySelector := labels.Instance(gw.GetSelector()) 2336 if gatewaySelector.SubsetOf(proxy.Labels) { 2337 gatewayInstances = append(gatewayInstances, gatewayWithInstances{cfg, true, proxy.ServiceTargets}) 2338 } 2339 } 2340 } 2341 2342 if len(gatewayInstances) == 0 { 2343 return nil 2344 } 2345 2346 return MergeGateways(gatewayInstances, proxy, ps) 2347 } 2348 2349 func (ps *PushContext) NetworkManager() *NetworkManager { 2350 return ps.networkMgr 2351 } 2352 2353 // BestEffortInferServiceMTLSMode infers the mTLS mode for the service + port from all authentication 2354 // policies (both alpha and beta) in the system. The function always returns MTLSUnknown for external service. 2355 // The result is a best effort. It is because the PeerAuthentication is workload-based, this function is unable 2356 // to compute the correct service mTLS mode without knowing service to workload binding. For now, this 2357 // function uses only mesh and namespace level PeerAuthentication and ignore workload & port level policies. 2358 // This function is used to give a hint for auto-mTLS configuration on client side. 2359 func (ps *PushContext) BestEffortInferServiceMTLSMode(tp *networking.TrafficPolicy, service *Service, port *Port) MutualTLSMode { 2360 if service.MeshExternal { 2361 // Only need the authentication mTLS mode when service is not external. 2362 return MTLSUnknown 2363 } 2364 2365 // For passthrough traffic (headless service or explicitly defined in DestinationRule), we look at the instances 2366 // If ALL instances have a sidecar, we enable TLS, otherwise we disable 2367 // TODO(https://github.com/istio/istio/issues/27376) enable mixed deployments 2368 // A service with passthrough resolution is always passthrough, regardless of the TrafficPolicy. 2369 if service.Resolution == Passthrough || tp.GetLoadBalancer().GetSimple() == networking.LoadBalancerSettings_PASSTHROUGH { 2370 instances := ps.ServiceEndpointsByPort(service, port.Port, nil) 2371 if len(instances) == 0 { 2372 return MTLSDisable 2373 } 2374 for _, i := range instances { 2375 // Infer mTls disabled if any of the endpoint is with tls disabled 2376 if i.TLSMode == DisabledTLSModeLabel { 2377 return MTLSDisable 2378 } 2379 } 2380 } 2381 2382 // 2. check mTLS settings from beta policy (i.e PeerAuthentication) at namespace / mesh level. 2383 // If the mode is not unknown, use it. 2384 if serviceMTLSMode := ps.AuthnPolicies.GetNamespaceMutualTLSMode(service.Attributes.Namespace); serviceMTLSMode != MTLSUnknown { 2385 return serviceMTLSMode 2386 } 2387 2388 // Fallback to permissive. 2389 return MTLSPermissive 2390 } 2391 2392 // ServiceEndpointsByPort returns the cached instances by port if it exists. 2393 func (ps *PushContext) ServiceEndpointsByPort(svc *Service, port int, labels labels.Instance) []*IstioEndpoint { 2394 var out []*IstioEndpoint 2395 if instances, exists := ps.ServiceIndex.instancesByPort[svc.Key()][port]; exists { 2396 // Use cached version of instances by port when labels are empty. 2397 if len(labels) == 0 { 2398 return instances 2399 } 2400 // If there are labels, we will filter instances by pod labels. 2401 for _, instance := range instances { 2402 // check that one of the input labels is a subset of the labels 2403 if labels.SubsetOf(instance.Labels) { 2404 out = append(out, instance) 2405 } 2406 } 2407 } 2408 2409 return out 2410 } 2411 2412 // ServiceEndpoints returns the cached instances by svc if exists. 2413 func (ps *PushContext) ServiceEndpoints(svcKey string) map[int][]*IstioEndpoint { 2414 if instances, exists := ps.ServiceIndex.instancesByPort[svcKey]; exists { 2415 return instances 2416 } 2417 2418 return nil 2419 } 2420 2421 // initKubernetesGateways initializes Kubernetes gateway-api objects 2422 func (ps *PushContext) initKubernetesGateways(env *Environment) error { 2423 if env.GatewayAPIController != nil { 2424 ps.GatewayAPIController = env.GatewayAPIController 2425 return env.GatewayAPIController.Reconcile(ps) 2426 } 2427 return nil 2428 } 2429 2430 // ReferenceAllowed determines if a given resource (of type `kind` and name `resourceName`) can be 2431 // accessed by `namespace`, based of specific reference policies. 2432 // Note: this function only determines if a reference is *explicitly* allowed; the reference may not require 2433 // explicit authorization to be made at all in most cases. Today, this only is for allowing cross-namespace 2434 // secret access. 2435 func (ps *PushContext) ReferenceAllowed(kind config.GroupVersionKind, resourceName string, namespace string) bool { 2436 // Currently, only Secret has reference policy, and only implemented by Gateway API controller. 2437 switch kind { 2438 case gvk.Secret: 2439 if ps.GatewayAPIController != nil { 2440 return ps.GatewayAPIController.SecretAllowed(resourceName, namespace) 2441 } 2442 default: 2443 } 2444 return false 2445 } 2446 2447 func (ps *PushContext) ServiceAccounts(hostname host.Name, namespace string) []string { 2448 return ps.serviceAccounts[serviceAccountKey{ 2449 hostname: hostname, 2450 namespace: namespace, 2451 }] 2452 } 2453 2454 // SupportsTunnel checks if a given IP address supports tunneling. 2455 // This currently only accepts workload IPs as arguments; services will always return "false". 2456 func (ps *PushContext) SupportsTunnel(n network.ID, ip string) bool { 2457 // There should be a 1:1 relationship between IP and Workload but the interface doesn't allow this lookup. 2458 // We should get 0 or 1 workloads, so just return the first. 2459 infos, _ := ps.ambientIndex.AddressInformation(sets.New(n.String() + "/" + ip)) 2460 for _, wl := range ExtractWorkloadsFromAddresses(infos) { 2461 if wl.TunnelProtocol == workloadapi.TunnelProtocol_HBONE { 2462 return true 2463 } 2464 } 2465 return false 2466 } 2467 2468 // WorkloadsForWaypoint returns all workloads associated with a given waypoint identified by it's WaypointKey 2469 // Used when calculating the workloads which should be configured for a specific waypoint proxy 2470 func (ps *PushContext) WorkloadsForWaypoint(key WaypointKey) []WorkloadInfo { 2471 return ps.ambientIndex.WorkloadsForWaypoint(key) 2472 } 2473 2474 // ServicesForWaypoint returns all services associated with a given waypoint identified by it's WaypointKey 2475 // Used when calculating the services which should be configured for a specific waypoint proxy 2476 func (ps *PushContext) ServicesForWaypoint(key WaypointKey) []ServiceInfo { 2477 return ps.ambientIndex.ServicesForWaypoint(key) 2478 }