1// Copyright 2018 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 15package model 16 17import ( 18 "encoding/json" 19 "net" 20 "sort" 21 "strings" 22 "sync" 23 "time" 24 25 meshconfig "istio.io/api/mesh/v1alpha1" 26 networking "istio.io/api/networking/v1alpha3" 27 "istio.io/pkg/monitoring" 28 29 "istio.io/istio/pilot/pkg/features" 30 "istio.io/istio/pkg/config/constants" 31 "istio.io/istio/pkg/config/host" 32 "istio.io/istio/pkg/config/labels" 33 "istio.io/istio/pkg/config/protocol" 34 "istio.io/istio/pkg/config/schema/collections" 35 "istio.io/istio/pkg/config/visibility" 36) 37 38var ( 39 defaultClusterLocalNamespaces = []string{"kube-system"} 40) 41 42// Metrics is an interface for capturing metrics on a per-node basis. 43type Metrics interface { 44 // AddMetric will add an case to the metric for the given node. 45 AddMetric(metric monitoring.Metric, key string, proxy *Proxy, msg string) 46} 47 48var _ Metrics = &PushContext{} 49 50// PushContext tracks the status of a push - metrics and errors. 51// Metrics are reset after a push - at the beginning all 52// values are zero, and when push completes the status is reset. 53// The struct is exposed in a debug endpoint - fields public to allow 54// easy serialization as json. 55type PushContext struct { 56 proxyStatusMutex sync.RWMutex 57 // ProxyStatus is keyed by the error code, and holds a map keyed 58 // by the ID. 59 ProxyStatus map[string]map[string]ProxyPushStatus 60 61 // Mutex is used to protect the below store. 62 // All data is set when the PushContext object is populated in `InitContext`, 63 // data should not be changed by plugins. 64 Mutex sync.Mutex `json:"-"` 65 66 // Synthesized from env.Mesh 67 defaultServiceExportTo map[visibility.Instance]bool 68 defaultVirtualServiceExportTo map[visibility.Instance]bool 69 defaultDestinationRuleExportTo map[visibility.Instance]bool 70 71 // Service related 72 // TODO: move to a sub struct 73 74 // privateServices are reachable within the same namespace. 75 privateServicesByNamespace map[string][]*Service 76 // publicServices are services reachable within the mesh. 77 publicServices []*Service 78 // ServiceByHostnameAndNamespace has all services, indexed by hostname then namespace. 79 ServiceByHostnameAndNamespace map[host.Name]map[string]*Service `json:"-"` 80 // ServiceAccounts contains a map of hostname and port to service accounts. 81 ServiceAccounts map[host.Name]map[int][]string `json:"-"` 82 // QuotaSpec has all quota specs 83 QuotaSpec []Config `json:"-"` 84 // QuotaSpecBindings has all quota bindings 85 QuotaSpecBinding []Config `json:"-"` 86 87 // VirtualService related 88 // this contains all the virtual services with exportTo "." and current namespace. The keys are namespace,gateway. 89 privateVirtualServicesByNamespaceAndGateway map[string]map[string][]Config 90 // This contains all virtual services whose exportTo is "*", keyed by gateway 91 publicVirtualServicesByGateway map[string][]Config 92 93 // destination rules are of two types: 94 // namespaceLocalDestRules: all public/private dest rules pertaining to a service defined in a given namespace 95 // namespaceExportedDestRules: all public dest rules pertaining to a service defined in a namespace 96 namespaceLocalDestRules map[string]*processedDestRules 97 namespaceExportedDestRules map[string]*processedDestRules 98 99 // clusterLocalHosts extracted from the MeshConfig 100 clusterLocalHosts host.Names 101 102 // sidecars for each namespace 103 sidecarsByNamespace map[string][]*SidecarScope 104 // envoy filters for each namespace including global config namespace 105 envoyFiltersByNamespace map[string][]*EnvoyFilterWrapper 106 // gateways for each namespace 107 gatewaysByNamespace map[string][]Config 108 allGateways []Config 109 ////////// END //////// 110 111 // The following data is either a global index or used in the inbound path. 112 // Namespace specific views do not apply here. 113 114 // AuthzPolicies stores the existing authorization policies in the cluster. Could be nil if there 115 // are no authorization policies in the cluster. 116 AuthzPolicies *AuthorizationPolicies `json:"-"` 117 118 // Mesh configuration for the mesh. 119 Mesh *meshconfig.MeshConfig `json:"-"` 120 121 // Networks configuration. 122 Networks *meshconfig.MeshNetworks `json:"-"` 123 124 // Discovery interface for listing services and instances. 125 ServiceDiscovery `json:"-"` 126 127 // Config interface for listing routing rules 128 IstioConfigStore `json:"-"` 129 130 // AuthnBetaPolicies contains (beta) Authn policies by namespace. 131 AuthnBetaPolicies *AuthenticationPolicies `json:"-"` 132 133 initDone bool 134 135 Version string 136 137 // cache gateways addresses for each network 138 // this is mainly used for kubernetes multi-cluster scenario 139 networkGateways map[string][]*Gateway 140} 141 142// Gateway is the gateway of a network 143type Gateway struct { 144 // gateway ip address 145 Addr string 146 // gateway port 147 Port uint32 148} 149 150type processedDestRules struct { 151 // List of dest rule hosts. We match with the most specific host first 152 hosts []host.Name 153 // Map of dest rule host and the merged destination rules for that host 154 destRule map[host.Name]*Config 155} 156 157// XDSUpdater is used for direct updates of the xDS model and incremental push. 158// Pilot uses multiple registries - for example each K8S cluster is a registry instance, 159// as well as consul and future EDS or MCP sources. Each registry is responsible for 160// tracking a set of endpoints associated with mesh services, and calling the EDSUpdate 161// on changes. A registry may group endpoints for a service in smaller subsets - for 162// example by deployment, or to deal with very large number of endpoints for a service. 163// We want to avoid passing around large objects - like full list of endpoints for a registry, 164// or the full list of endpoints for a service across registries, since it limits scalability. 165// 166// Future optimizations will include grouping the endpoints by labels, gateway or region to 167// reduce the time when subsetting or split-horizon is used. This design assumes pilot 168// tracks all endpoints in the mesh and they fit in RAM - so limit is few M endpoints. 169// It is possible to split the endpoint tracking in future. 170type XDSUpdater interface { 171 172 // EDSUpdate is called when the list of endpoints or labels in a ServiceEntry is 173 // changed. For each cluster and hostname, the full list of active endpoints (including empty list) 174 // must be sent. The shard name is used as a key - current implementation is using the registry 175 // name. 176 EDSUpdate(shard, hostname string, namespace string, entry []*IstioEndpoint) error 177 178 // SvcUpdate is called when a service definition is updated/deleted. 179 SvcUpdate(shard, hostname string, namespace string, event Event) 180 181 // ConfigUpdate is called to notify the XDS server of config updates and request a push. 182 // The requests may be collapsed and throttled. 183 // This replaces the 'cache invalidation' model. 184 ConfigUpdate(req *PushRequest) 185 186 // ProxyUpdate is called to notify the XDS server to send a push to the specified proxy. 187 // The requests may be collapsed and throttled. 188 ProxyUpdate(clusterID, ip string) 189} 190 191// PushRequest defines a request to push to proxies 192// It is used to send updates to the config update debouncer and pass to the PushQueue. 193type PushRequest struct { 194 // Full determines whether a full push is required or not. If set to false, only endpoints will be sent. 195 Full bool 196 197 // ConfigsUpdated keeps track of configs that have changed. 198 // This is used as an optimization to avoid unnecessary pushes to proxies that are scoped with a Sidecar. 199 // If this is empty, then all proxies will get an update. 200 // Otherwise only proxies depend on these configs will get an update. 201 // The kind of resources are defined in pkg/config/schemas. 202 ConfigsUpdated map[ConfigKey]struct{} 203 204 // Push stores the push context to use for the update. This may initially be nil, as we will 205 // debounce changes before a PushContext is eventually created. 206 Push *PushContext 207 208 // Start represents the time a push was started. This represents the time of adding to the PushQueue. 209 // Note that this does not include time spent debouncing. 210 Start time.Time 211 212 // Reason represents the reason for requesting a push. This should only be a fixed set of values, 213 // to avoid unbounded cardinality in metrics. If this is not set, it may be automatically filled in later. 214 // There should only be multiple reasons if the push request is the result of two distinct triggers, rather than 215 // classifying a single trigger as having multiple reasons. 216 Reason []TriggerReason 217} 218 219type TriggerReason string 220 221const ( 222 // Describes a push triggered by an Endpoint change 223 EndpointUpdate TriggerReason = "endpoint" 224 // Describes a push triggered by a config (generally and Istio CRD) change. 225 ConfigUpdate TriggerReason = "config" 226 // Describes a push triggered by a Service change 227 ServiceUpdate TriggerReason = "service" 228 // Describes a push triggered by a change to an individual proxy (such as label change) 229 ProxyUpdate TriggerReason = "proxy" 230 // Describes a push triggered by a change to global config, such as mesh config 231 GlobalUpdate TriggerReason = "global" 232 // Describes a push triggered by an unknown reason 233 UnknownTrigger TriggerReason = "unknown" 234 // Describes a push triggered for debugging 235 DebugTrigger TriggerReason = "debug" 236) 237 238var ( 239 ServiceEntryKind = collections.IstioNetworkingV1Alpha3Serviceentries.Resource().GroupVersionKind() 240 VirtualServiceKind = collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind() 241 DestinationRuleKind = collections.IstioNetworkingV1Alpha3Destinationrules.Resource().GroupVersionKind() 242) 243 244// Merge two update requests together 245func (first *PushRequest) Merge(other *PushRequest) *PushRequest { 246 if first == nil { 247 return other 248 } 249 if other == nil { 250 return first 251 } 252 253 merged := &PushRequest{ 254 // Keep the first (older) start time 255 Start: first.Start, 256 257 // If either is full we need a full push 258 Full: first.Full || other.Full, 259 260 // The other push context is presumed to be later and more up to date 261 Push: other.Push, 262 263 // Merge the two reasons. Note that we shouldn't deduplicate here, or we would under count 264 Reason: append(first.Reason, other.Reason...), 265 } 266 267 // Do not merge when any one is empty 268 if len(first.ConfigsUpdated) > 0 && len(other.ConfigsUpdated) > 0 { 269 merged.ConfigsUpdated = make(map[ConfigKey]struct{}, len(first.ConfigsUpdated)+len(other.ConfigsUpdated)) 270 for conf := range first.ConfigsUpdated { 271 merged.ConfigsUpdated[conf] = struct{}{} 272 } 273 for conf := range other.ConfigsUpdated { 274 merged.ConfigsUpdated[conf] = struct{}{} 275 } 276 } 277 278 return merged 279} 280 281// ProxyPushStatus represents an event captured during config push to proxies. 282// It may contain additional message and the affected proxy. 283type ProxyPushStatus struct { 284 Proxy string `json:"proxy,omitempty"` 285 Message string `json:"message,omitempty"` 286} 287 288// IsMixerEnabled returns true if mixer is enabled in the Mesh config. 289func (ps *PushContext) IsMixerEnabled() bool { 290 return ps != nil && ps.Mesh != nil && (ps.Mesh.MixerCheckServer != "" || ps.Mesh.MixerReportServer != "") 291} 292 293// AddMetric will add an case to the metric. 294func (ps *PushContext) AddMetric(metric monitoring.Metric, key string, proxy *Proxy, msg string) { 295 if ps == nil { 296 log.Infof("Metric without context %s %v %s", key, proxy, msg) 297 return 298 } 299 ps.proxyStatusMutex.Lock() 300 defer ps.proxyStatusMutex.Unlock() 301 302 metricMap, f := ps.ProxyStatus[metric.Name()] 303 if !f { 304 metricMap = map[string]ProxyPushStatus{} 305 ps.ProxyStatus[metric.Name()] = metricMap 306 } 307 ev := ProxyPushStatus{Message: msg} 308 if proxy != nil { 309 ev.Proxy = proxy.ID 310 } 311 metricMap[key] = ev 312} 313 314var ( 315 316 // EndpointNoPod tracks endpoints without an associated pod. This is an error condition, since 317 // we can't figure out the labels. It may be a transient problem, if endpoint is processed before 318 // pod. 319 EndpointNoPod = monitoring.NewGauge( 320 "endpoint_no_pod", 321 "Endpoints without an associated pod.", 322 ) 323 324 // ProxyStatusNoService represents proxies not selected by any service 325 // This can be normal - for workloads that act only as client, or are not covered by a Service. 326 // It can also be an error, for example in cases the Endpoint list of a service was not updated by the time 327 // the sidecar calls. 328 // Updated by GetProxyServiceInstances 329 ProxyStatusNoService = monitoring.NewGauge( 330 "pilot_no_ip", 331 "Pods not found in the endpoint table, possibly invalid.", 332 ) 333 334 // ProxyStatusEndpointNotReady represents proxies found not be ready. 335 // Updated by GetProxyServiceInstances. Normal condition when starting 336 // an app with readiness, error if it doesn't change to 0. 337 ProxyStatusEndpointNotReady = monitoring.NewGauge( 338 "pilot_endpoint_not_ready", 339 "Endpoint found in unready state.", 340 ) 341 342 // ProxyStatusConflictOutboundListenerTCPOverHTTP metric tracks number of 343 // wildcard TCP listeners that conflicted with existing wildcard HTTP listener on same port 344 ProxyStatusConflictOutboundListenerTCPOverHTTP = monitoring.NewGauge( 345 "pilot_conflict_outbound_listener_tcp_over_current_http", 346 "Number of conflicting wildcard tcp listeners with current wildcard http listener.", 347 ) 348 349 // ProxyStatusConflictOutboundListenerTCPOverTCP metric tracks number of 350 // TCP listeners that conflicted with existing TCP listeners on same port 351 ProxyStatusConflictOutboundListenerTCPOverTCP = monitoring.NewGauge( 352 "pilot_conflict_outbound_listener_tcp_over_current_tcp", 353 "Number of conflicting tcp listeners with current tcp listener.", 354 ) 355 356 // ProxyStatusConflictOutboundListenerTCPOverThrift metric tracks number of 357 // TCP listeners that conflicted with existing Thrift listeners on same port 358 ProxyStatusConflictOutboundListenerTCPOverThrift = monitoring.NewGauge( 359 "pilot_conflict_outbound_listener_tcp_over_current_thrift", 360 "Number of conflicting tcp listeners with current thrift listener.", 361 ) 362 363 // ProxyStatusConflictOutboundListenerHTTPOverTCP metric tracks number of 364 // wildcard HTTP listeners that conflicted with existing wildcard TCP listener on same port 365 ProxyStatusConflictOutboundListenerHTTPOverTCP = monitoring.NewGauge( 366 "pilot_conflict_outbound_listener_http_over_current_tcp", 367 "Number of conflicting wildcard http listeners with current wildcard tcp listener.", 368 ) 369 370 // ProxyStatusConflictInboundListener tracks cases of multiple inbound 371 // listeners - 2 services selecting the same port of the pod. 372 ProxyStatusConflictInboundListener = monitoring.NewGauge( 373 "pilot_conflict_inbound_listener", 374 "Number of conflicting inbound listeners.", 375 ) 376 377 // DuplicatedClusters tracks duplicate clusters seen while computing CDS 378 DuplicatedClusters = monitoring.NewGauge( 379 "pilot_duplicate_envoy_clusters", 380 "Duplicate envoy clusters caused by service entries with same hostname", 381 ) 382 383 // DNSNoEndpointClusters tracks dns clusters without endpoints 384 DNSNoEndpointClusters = monitoring.NewGauge( 385 "pilot_dns_cluster_without_endpoints", 386 "DNS clusters without endpoints caused by the endpoint field in "+ 387 "STRICT_DNS type cluster is not set or the corresponding subset cannot select any endpoint", 388 ) 389 390 // ProxyStatusClusterNoInstances tracks clusters (services) without workloads. 391 ProxyStatusClusterNoInstances = monitoring.NewGauge( 392 "pilot_eds_no_instances", 393 "Number of clusters without instances.", 394 ) 395 396 // DuplicatedDomains tracks rejected VirtualServices due to duplicated hostname. 397 DuplicatedDomains = monitoring.NewGauge( 398 "pilot_vservice_dup_domain", 399 "Virtual services with dup domains.", 400 ) 401 402 // DuplicatedSubsets tracks duplicate subsets that we rejected while merging multiple destination rules for same host 403 DuplicatedSubsets = monitoring.NewGauge( 404 "pilot_destrule_subsets", 405 "Duplicate subsets across destination rules for same host", 406 ) 407 408 // totalVirtualServices tracks the total number of virtual service 409 totalVirtualServices = monitoring.NewGauge( 410 "pilot_virt_services", 411 "Total virtual services known to pilot.", 412 ) 413 414 // LastPushStatus preserves the metrics and data collected during lasts global push. 415 // It can be used by debugging tools to inspect the push event. It will be reset after each push with the 416 // new version. 417 LastPushStatus *PushContext 418 // LastPushMutex will protect the LastPushStatus 419 LastPushMutex sync.Mutex 420 421 // All metrics we registered. 422 metrics = []monitoring.Metric{ 423 EndpointNoPod, 424 ProxyStatusNoService, 425 ProxyStatusEndpointNotReady, 426 ProxyStatusConflictOutboundListenerTCPOverHTTP, 427 ProxyStatusConflictOutboundListenerTCPOverTCP, 428 ProxyStatusConflictOutboundListenerHTTPOverTCP, 429 ProxyStatusConflictInboundListener, 430 DuplicatedClusters, 431 ProxyStatusClusterNoInstances, 432 DuplicatedDomains, 433 DuplicatedSubsets, 434 } 435) 436 437func init() { 438 for _, m := range metrics { 439 monitoring.MustRegister(m) 440 } 441 monitoring.MustRegister(totalVirtualServices) 442} 443 444// NewPushContext creates a new PushContext structure to track push status. 445func NewPushContext() *PushContext { 446 // TODO: detect push in progress, don't update status if set 447 return &PushContext{ 448 publicServices: []*Service{}, 449 privateServicesByNamespace: map[string][]*Service{}, 450 publicVirtualServicesByGateway: map[string][]Config{}, 451 privateVirtualServicesByNamespaceAndGateway: map[string]map[string][]Config{}, 452 namespaceLocalDestRules: map[string]*processedDestRules{}, 453 namespaceExportedDestRules: map[string]*processedDestRules{}, 454 sidecarsByNamespace: map[string][]*SidecarScope{}, 455 envoyFiltersByNamespace: map[string][]*EnvoyFilterWrapper{}, 456 gatewaysByNamespace: map[string][]Config{}, 457 allGateways: []Config{}, 458 ServiceByHostnameAndNamespace: map[host.Name]map[string]*Service{}, 459 ProxyStatus: map[string]map[string]ProxyPushStatus{}, 460 ServiceAccounts: map[host.Name]map[int][]string{}, 461 } 462} 463 464// JSON implements json.Marshaller, with a lock. 465func (ps *PushContext) StatusJSON() ([]byte, error) { 466 if ps == nil { 467 return []byte{'{', '}'}, nil 468 } 469 ps.proxyStatusMutex.RLock() 470 defer ps.proxyStatusMutex.RUnlock() 471 return json.MarshalIndent(ps.ProxyStatus, "", " ") 472} 473 474// OnConfigChange is called when a config change is detected. 475func (ps *PushContext) OnConfigChange() { 476 LastPushMutex.Lock() 477 LastPushStatus = ps 478 LastPushMutex.Unlock() 479 ps.UpdateMetrics() 480} 481 482// UpdateMetrics will update the prometheus metrics based on the 483// current status of the push. 484func (ps *PushContext) UpdateMetrics() { 485 ps.proxyStatusMutex.RLock() 486 defer ps.proxyStatusMutex.RUnlock() 487 488 for _, pm := range metrics { 489 mmap := ps.ProxyStatus[pm.Name()] 490 pm.Record(float64(len(mmap))) 491 } 492} 493 494func virtualServiceDestinations(v *networking.VirtualService) []*networking.Destination { 495 if v == nil { 496 return nil 497 } 498 499 var ds []*networking.Destination 500 501 for _, h := range v.Http { 502 for _, r := range h.Route { 503 if r.Destination != nil { 504 ds = append(ds, r.Destination) 505 } 506 } 507 if h.Mirror != nil { 508 ds = append(ds, h.Mirror) 509 } 510 } 511 for _, t := range v.Tcp { 512 for _, r := range t.Route { 513 if r.Destination != nil { 514 ds = append(ds, r.Destination) 515 } 516 } 517 } 518 for _, t := range v.Tls { 519 for _, r := range t.Route { 520 if r.Destination != nil { 521 ds = append(ds, r.Destination) 522 } 523 } 524 } 525 526 return ds 527} 528 529// GatewayServices returns the set of services which are referred from the proxy gateways. 530func (ps *PushContext) GatewayServices(proxy *Proxy) []*Service { 531 svcs := ps.Services(proxy) 532 // host set. 533 hostsFromGateways := map[string]struct{}{} 534 535 // MergedGateway will be nil when there are no configs in the 536 // system during initial installation. 537 if proxy.MergedGateway == nil { 538 return nil 539 } 540 541 for _, gw := range proxy.MergedGateway.GatewayNameForServer { 542 for _, vsConfig := range ps.VirtualServicesForGateway(proxy, gw) { 543 vs, ok := vsConfig.Spec.(*networking.VirtualService) 544 if !ok { // should never happen 545 log.Errorf("Failed in getting a virtual service: %v", vsConfig.Labels) 546 return svcs 547 } 548 549 for _, d := range virtualServiceDestinations(vs) { 550 hostsFromGateways[d.Host] = struct{}{} 551 } 552 } 553 } 554 555 log.Debugf("GatewayServices: gateway %v is exposing these hosts:%v", proxy.ID, hostsFromGateways) 556 557 gwSvcs := make([]*Service, 0, len(svcs)) 558 559 for _, s := range svcs { 560 svcHost := string(s.Hostname) 561 562 if _, ok := hostsFromGateways[svcHost]; ok { 563 gwSvcs = append(gwSvcs, s) 564 } 565 } 566 567 log.Debugf("GatewayServices:: gateways len(services)=%d, len(filtered)=%d", len(svcs), len(gwSvcs)) 568 569 return gwSvcs 570} 571 572// Services returns the list of services that are visible to a Proxy in a given config namespace 573func (ps *PushContext) Services(proxy *Proxy) []*Service { 574 // If proxy has a sidecar scope that is user supplied, then get the services from the sidecar scope 575 // sidecarScope.config is nil if there is no sidecar scope for the namespace 576 if proxy != nil && proxy.SidecarScope != nil && proxy.Type == SidecarProxy { 577 return proxy.SidecarScope.Services() 578 } 579 580 out := make([]*Service, 0) 581 582 // First add private services 583 if proxy == nil { 584 for _, privateServices := range ps.privateServicesByNamespace { 585 out = append(out, privateServices...) 586 } 587 } else { 588 out = append(out, ps.privateServicesByNamespace[proxy.ConfigNamespace]...) 589 } 590 591 // Second add public services 592 out = append(out, ps.publicServices...) 593 594 return out 595} 596 597func (ps *PushContext) VirtualServicesForGateway(proxy *Proxy, gateway string) []Config { 598 res := ps.privateVirtualServicesByNamespaceAndGateway[proxy.ConfigNamespace][gateway] 599 res = append(res, ps.publicVirtualServicesByGateway[gateway]...) 600 return res 601} 602 603// getSidecarScope returns a SidecarScope object associated with the 604// proxy. The SidecarScope object is a semi-processed view of the service 605// registry, and config state associated with the sidecar crd. The scope contains 606// a set of inbound and outbound listeners, services/configs per listener, 607// etc. The sidecar scopes are precomputed in the initSidecarContext 608// function based on the Sidecar API objects in each namespace. If there is 609// no sidecar api object, a default sidecarscope is assigned to the 610// namespace which enables connectivity to all services in the mesh. 611// 612// Callers can check if the sidecarScope is from user generated object or not 613// by checking the sidecarScope.Config field, that contains the user provided config 614func (ps *PushContext) getSidecarScope(proxy *Proxy, workloadLabels labels.Collection) *SidecarScope { 615 616 // Find the most specific matching sidecar config from the proxy's 617 // config namespace If none found, construct a sidecarConfig on the fly 618 // that allows the sidecar to talk to any namespace (the default 619 // behavior in the absence of sidecars). 620 if sidecars, ok := ps.sidecarsByNamespace[proxy.ConfigNamespace]; ok { 621 // TODO: logic to merge multiple sidecar resources 622 // Currently we assume that there will be only one sidecar config for a namespace. 623 var defaultSidecar *SidecarScope 624 for _, wrapper := range sidecars { 625 if wrapper.Config != nil { 626 sidecar := wrapper.Config.Spec.(*networking.Sidecar) 627 // if there is no workload selector, the config applies to all workloads 628 // if there is a workload selector, check for matching workload labels 629 if sidecar.GetWorkloadSelector() != nil { 630 workloadSelector := labels.Instance(sidecar.GetWorkloadSelector().GetLabels()) 631 if !workloadLabels.IsSupersetOf(workloadSelector) { 632 continue 633 } 634 return wrapper 635 } 636 defaultSidecar = wrapper 637 continue 638 } 639 // Not sure when this can happen (Config = nil ?) 640 if defaultSidecar != nil { 641 return defaultSidecar // still return the valid one 642 } 643 return wrapper 644 } 645 if defaultSidecar != nil { 646 return defaultSidecar // still return the valid one 647 } 648 } 649 650 return DefaultSidecarScopeForNamespace(ps, proxy.ConfigNamespace) 651} 652 653// GetAllSidecarScopes returns a map of namespace and the set of SidecarScope 654// object associated with the namespace. This will be used by the CDS code to 655// precompute CDS output for each sidecar scope. Since we have a default sidecarscope 656// for namespaces that do not explicitly have one, we are guaranteed to 657// have the CDS output cached for every namespace/sidecar scope combo. 658func (ps *PushContext) GetAllSidecarScopes() map[string][]*SidecarScope { 659 return ps.sidecarsByNamespace 660} 661 662// DestinationRule returns a destination rule for a service name in a given domain. 663func (ps *PushContext) DestinationRule(proxy *Proxy, service *Service) *Config { 664 // If proxy has a sidecar scope that is user supplied, then get the destination rules from the sidecar scope 665 // sidecarScope.config is nil if there is no sidecar scope for the namespace 666 if proxy.SidecarScope != nil && proxy.Type == SidecarProxy { 667 // If there is a sidecar scope for this proxy, return the destination rule 668 // from the sidecar scope. 669 return proxy.SidecarScope.DestinationRule(service.Hostname) 670 } 671 672 // If the proxy config namespace is same as the root config namespace 673 // look for dest rules in the service's namespace first. This hack is needed 674 // because sometimes, istio-system tends to become the root config namespace. 675 // Destination rules are defined here for global purposes. We do not want these 676 // catch all destination rules to be the only dest rule, when processing CDS for 677 // proxies like the istio-ingressgateway or istio-egressgateway. 678 // If there are no service specific dest rules, we will end up picking up the same 679 // rules anyway, later in the code 680 681 // 1. select destination rule from proxy config namespace 682 if proxy.ConfigNamespace != ps.Mesh.RootNamespace { 683 // search through the DestinationRules in proxy's namespace first 684 if ps.namespaceLocalDestRules[proxy.ConfigNamespace] != nil { 685 if hostname, ok := MostSpecificHostMatch(service.Hostname, 686 ps.namespaceLocalDestRules[proxy.ConfigNamespace].hosts); ok { 687 return ps.namespaceLocalDestRules[proxy.ConfigNamespace].destRule[hostname] 688 } 689 } 690 } 691 692 // 2. select destination rule from service namespace 693 svcNs := service.Attributes.Namespace 694 695 // This can happen when finding the subset labels for a proxy in root namespace. 696 // Because based on a pure cluster name, we do not know the service and 697 // construct a fake service without setting Attributes at all. 698 if svcNs == "" { 699 for _, svc := range ps.Services(proxy) { 700 if service.Hostname == svc.Hostname && svc.Attributes.Namespace != "" { 701 svcNs = svc.Attributes.Namespace 702 break 703 } 704 } 705 } 706 707 // 3. if no private/public rule matched in the calling proxy's namespace, 708 // check the target service's namespace for public rules 709 if svcNs != "" && ps.namespaceExportedDestRules[svcNs] != nil { 710 if hostname, ok := MostSpecificHostMatch(service.Hostname, 711 ps.namespaceExportedDestRules[svcNs].hosts); ok { 712 return ps.namespaceExportedDestRules[svcNs].destRule[hostname] 713 } 714 } 715 716 // 4. if no public/private rule in calling proxy's namespace matched, and no public rule in the 717 // target service's namespace matched, search for any public destination rule in the config root namespace 718 // NOTE: This does mean that we are effectively ignoring private dest rules in the config root namespace 719 if ps.namespaceExportedDestRules[ps.Mesh.RootNamespace] != nil { 720 if hostname, ok := MostSpecificHostMatch(service.Hostname, 721 ps.namespaceExportedDestRules[ps.Mesh.RootNamespace].hosts); ok { 722 return ps.namespaceExportedDestRules[ps.Mesh.RootNamespace].destRule[hostname] 723 } 724 } 725 726 return nil 727} 728 729// IsClusterLocal indicates whether the endpoints for the service should only be accessible to clients 730// within the cluster. 731func (ps *PushContext) IsClusterLocal(service *Service) bool { 732 _, ok := MostSpecificHostMatch(service.Hostname, ps.clusterLocalHosts) 733 return ok 734} 735 736// SubsetToLabels returns the labels associated with a subset of a given service. 737func (ps *PushContext) SubsetToLabels(proxy *Proxy, subsetName string, hostname host.Name) labels.Collection { 738 // empty subset 739 if subsetName == "" { 740 return nil 741 } 742 743 cfg := ps.DestinationRule(proxy, &Service{Hostname: hostname}) 744 if cfg == nil { 745 return nil 746 } 747 748 rule := cfg.Spec.(*networking.DestinationRule) 749 for _, subset := range rule.Subsets { 750 if subset.Name == subsetName { 751 if len(subset.Labels) == 0 { 752 return nil 753 } 754 return []labels.Instance{subset.Labels} 755 } 756 } 757 758 return nil 759} 760 761// InitContext will initialize the data structures used for code generation. 762// This should be called before starting the push, from the thread creating 763// the push context. 764func (ps *PushContext) InitContext(env *Environment, oldPushContext *PushContext, pushReq *PushRequest) error { 765 ps.Mutex.Lock() 766 defer ps.Mutex.Unlock() 767 if ps.initDone { 768 return nil 769 } 770 771 ps.Mesh = env.Mesh() 772 ps.Networks = env.Networks() 773 ps.ServiceDiscovery = env 774 ps.IstioConfigStore = env 775 ps.Version = env.Version() 776 777 // Must be initialized first 778 // as initServiceRegistry/VirtualServices/Destrules 779 // use the default export map 780 ps.initDefaultExportMaps() 781 782 // create new or incremental update 783 if pushReq == nil || oldPushContext == nil || !oldPushContext.initDone || len(pushReq.ConfigsUpdated) == 0 { 784 if err := ps.createNewContext(env); err != nil { 785 return err 786 } 787 } else { 788 if err := ps.updateContext(env, oldPushContext, pushReq); err != nil { 789 return err 790 } 791 } 792 793 // TODO: only do this when meshnetworks or gateway service changed 794 ps.initMeshNetworks() 795 796 ps.initClusterLocalHosts(env) 797 798 ps.initDone = true 799 return nil 800} 801 802func (ps *PushContext) createNewContext(env *Environment) error { 803 if err := ps.initServiceRegistry(env); err != nil { 804 return err 805 } 806 807 if err := ps.initVirtualServices(env); err != nil { 808 return err 809 } 810 811 if err := ps.initDestinationRules(env); err != nil { 812 return err 813 } 814 815 if err := ps.initAuthnPolicies(env); err != nil { 816 return err 817 } 818 819 if err := ps.initAuthorizationPolicies(env); err != nil { 820 authzLog.Errorf("failed to initialize authorization policies: %v", err) 821 return err 822 } 823 824 if err := ps.initEnvoyFilters(env); err != nil { 825 return err 826 } 827 828 if err := ps.initGateways(env); err != nil { 829 return err 830 } 831 832 if err := ps.initQuotaSpecs(env); err != nil { 833 return err 834 } 835 836 if err := ps.initQuotaSpecBindings(env); err != nil { 837 return err 838 } 839 840 // Must be initialized in the end 841 if err := ps.initSidecarScopes(env); err != nil { 842 return err 843 } 844 return nil 845} 846 847func (ps *PushContext) updateContext( 848 env *Environment, 849 oldPushContext *PushContext, 850 pushReq *PushRequest) error { 851 852 var servicesChanged, virtualServicesChanged, destinationRulesChanged, gatewayChanged, 853 authnChanged, authzChanged, envoyFiltersChanged, sidecarsChanged, quotasChanged bool 854 855 for conf := range pushReq.ConfigsUpdated { 856 switch conf.Kind { 857 case ServiceEntryKind: 858 servicesChanged = true 859 case DestinationRuleKind: 860 destinationRulesChanged = true 861 case VirtualServiceKind: 862 virtualServicesChanged = true 863 case collections.IstioNetworkingV1Alpha3Gateways.Resource().GroupVersionKind(): 864 gatewayChanged = true 865 case collections.IstioNetworkingV1Alpha3Sidecars.Resource().GroupVersionKind(): 866 sidecarsChanged = true 867 case collections.IstioNetworkingV1Alpha3Envoyfilters.Resource().GroupVersionKind(): 868 envoyFiltersChanged = true 869 case collections.IstioRbacV1Alpha1Servicerolebindings.Resource().GroupVersionKind(), 870 collections.IstioRbacV1Alpha1Serviceroles.Resource().GroupVersionKind(), 871 collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().GroupVersionKind(), 872 collections.IstioRbacV1Alpha1Rbacconfigs.Resource().GroupVersionKind(), 873 collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().GroupVersionKind(): 874 authzChanged = true 875 case collections.IstioSecurityV1Beta1Requestauthentications.Resource().GroupVersionKind(), 876 collections.IstioSecurityV1Beta1Peerauthentications.Resource().GroupVersionKind(): 877 authnChanged = true 878 case collections.IstioMixerV1ConfigClientQuotaspecbindings.Resource().GroupVersionKind(), 879 collections.IstioMixerV1ConfigClientQuotaspecs.Resource().GroupVersionKind(): 880 quotasChanged = true 881 case collections.K8SServiceApisV1Alpha1Trafficsplits.Resource().GroupVersionKind(), 882 collections.K8SServiceApisV1Alpha1Httproutes.Resource().GroupVersionKind(), 883 collections.K8SServiceApisV1Alpha1Tcproutes.Resource().GroupVersionKind(), 884 collections.K8SServiceApisV1Alpha1Gateways.Resource().GroupVersionKind(), 885 collections.K8SServiceApisV1Alpha1Gatewayclasses.Resource().GroupVersionKind(): 886 virtualServicesChanged = true 887 gatewayChanged = true 888 } 889 } 890 891 if servicesChanged { 892 // Services have changed. initialize service registry 893 if err := ps.initServiceRegistry(env); err != nil { 894 return err 895 } 896 } else { 897 ps.privateServicesByNamespace = oldPushContext.privateServicesByNamespace 898 ps.publicServices = oldPushContext.publicServices 899 ps.ServiceByHostnameAndNamespace = oldPushContext.ServiceByHostnameAndNamespace 900 ps.ServiceAccounts = oldPushContext.ServiceAccounts 901 } 902 903 if virtualServicesChanged { 904 if err := ps.initVirtualServices(env); err != nil { 905 return err 906 } 907 } else { 908 ps.privateVirtualServicesByNamespaceAndGateway = oldPushContext.privateVirtualServicesByNamespaceAndGateway 909 ps.publicVirtualServicesByGateway = oldPushContext.publicVirtualServicesByGateway 910 } 911 912 if destinationRulesChanged { 913 if err := ps.initDestinationRules(env); err != nil { 914 return err 915 } 916 } else { 917 ps.namespaceLocalDestRules = oldPushContext.namespaceLocalDestRules 918 ps.namespaceExportedDestRules = oldPushContext.namespaceExportedDestRules 919 } 920 921 if authnChanged { 922 if err := ps.initAuthnPolicies(env); err != nil { 923 return err 924 } 925 } else { 926 ps.AuthnBetaPolicies = oldPushContext.AuthnBetaPolicies 927 } 928 929 if authzChanged { 930 if err := ps.initAuthorizationPolicies(env); err != nil { 931 authzLog.Errorf("failed to initialize authorization policies: %v", err) 932 return err 933 } 934 } else { 935 ps.AuthzPolicies = oldPushContext.AuthzPolicies 936 } 937 938 if envoyFiltersChanged { 939 if err := ps.initEnvoyFilters(env); err != nil { 940 return err 941 } 942 } else { 943 ps.envoyFiltersByNamespace = oldPushContext.envoyFiltersByNamespace 944 } 945 946 if gatewayChanged { 947 if err := ps.initGateways(env); err != nil { 948 return err 949 } 950 } else { 951 ps.gatewaysByNamespace = oldPushContext.gatewaysByNamespace 952 ps.allGateways = oldPushContext.allGateways 953 } 954 955 if quotasChanged { 956 if err := ps.initQuotaSpecs(env); err != nil { 957 return err 958 } 959 if err := ps.initQuotaSpecBindings(env); err != nil { 960 return err 961 } 962 } else { 963 ps.QuotaSpec = oldPushContext.QuotaSpec 964 ps.QuotaSpecBinding = oldPushContext.QuotaSpecBinding 965 } 966 967 // Must be initialized in the end 968 // Sidecars need to be updated if services, virtual services, destination rules, or the sidecar configs change 969 if servicesChanged || virtualServicesChanged || destinationRulesChanged || sidecarsChanged { 970 if err := ps.initSidecarScopes(env); err != nil { 971 return err 972 } 973 } else { 974 ps.sidecarsByNamespace = oldPushContext.sidecarsByNamespace 975 } 976 977 return nil 978} 979 980// Caches list of services in the registry, and creates a map 981// of hostname to service 982func (ps *PushContext) initServiceRegistry(env *Environment) error { 983 services, err := env.Services() 984 if err != nil { 985 return err 986 } 987 // Sort the services in order of creation. 988 allServices := sortServicesByCreationTime(services) 989 for _, s := range allServices { 990 ns := s.Attributes.Namespace 991 if len(s.Attributes.ExportTo) == 0 { 992 if ps.defaultServiceExportTo[visibility.Private] { 993 ps.privateServicesByNamespace[ns] = append(ps.privateServicesByNamespace[ns], s) 994 } else if ps.defaultServiceExportTo[visibility.Public] { 995 ps.publicServices = append(ps.publicServices, s) 996 } 997 } else { 998 if s.Attributes.ExportTo[visibility.Private] { 999 ps.privateServicesByNamespace[ns] = append(ps.privateServicesByNamespace[ns], s) 1000 } else { 1001 ps.publicServices = append(ps.publicServices, s) 1002 } 1003 } 1004 if _, f := ps.ServiceByHostnameAndNamespace[s.Hostname]; !f { 1005 ps.ServiceByHostnameAndNamespace[s.Hostname] = map[string]*Service{} 1006 } 1007 ps.ServiceByHostnameAndNamespace[s.Hostname][s.Attributes.Namespace] = s 1008 } 1009 1010 ps.initServiceAccounts(env, allServices) 1011 1012 return nil 1013} 1014 1015// sortServicesByCreationTime sorts the list of services in ascending order by their creation time (if available). 1016func sortServicesByCreationTime(services []*Service) []*Service { 1017 sort.SliceStable(services, func(i, j int) bool { 1018 return services[i].CreationTime.Before(services[j].CreationTime) 1019 }) 1020 return services 1021} 1022 1023// Caches list of service accounts in the registry 1024func (ps *PushContext) initServiceAccounts(env *Environment, services []*Service) { 1025 for _, svc := range services { 1026 if ps.ServiceAccounts[svc.Hostname] == nil { 1027 ps.ServiceAccounts[svc.Hostname] = map[int][]string{} 1028 } 1029 for _, port := range svc.Ports { 1030 if port.Protocol == protocol.UDP { 1031 continue 1032 } 1033 ps.ServiceAccounts[svc.Hostname][port.Port] = env.GetIstioServiceAccounts(svc, []int{port.Port}) 1034 } 1035 } 1036} 1037 1038// Caches list of authentication policies 1039func (ps *PushContext) initAuthnPolicies(env *Environment) error { 1040 // Init beta policy. 1041 var initBetaPolicyErro error 1042 if ps.AuthnBetaPolicies, initBetaPolicyErro = initAuthenticationPolicies(env); initBetaPolicyErro != nil { 1043 return initBetaPolicyErro 1044 } 1045 1046 return nil 1047} 1048 1049// Caches list of virtual services 1050func (ps *PushContext) initVirtualServices(env *Environment) error { 1051 ps.privateVirtualServicesByNamespaceAndGateway = map[string]map[string][]Config{} 1052 ps.publicVirtualServicesByGateway = map[string][]Config{} 1053 1054 virtualServices, err := env.List(collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(), NamespaceAll) 1055 if err != nil { 1056 return err 1057 } 1058 1059 // values returned from ConfigStore.List are immutable. 1060 // Therefore, we make a copy 1061 vservices := make([]Config, len(virtualServices)) 1062 1063 for i := range vservices { 1064 vservices[i] = virtualServices[i].DeepCopy() 1065 } 1066 1067 totalVirtualServices.Record(float64(len(virtualServices))) 1068 1069 // TODO(rshriram): parse each virtual service and maintain a map of the 1070 // virtualservice name, the list of registry hosts in the VS and non 1071 // registry DNS names in the VS. This should cut down processing in 1072 // the RDS code. See separateVSHostsAndServices in route/route.go 1073 sortConfigByCreationTime(vservices) 1074 1075 vservices = mergeVirtualServicesIfNeeded(vservices) 1076 1077 // convert all shortnames in virtual services into FQDNs 1078 for _, r := range vservices { 1079 rule := r.Spec.(*networking.VirtualService) 1080 // resolve top level hosts 1081 for i, h := range rule.Hosts { 1082 rule.Hosts[i] = string(ResolveShortnameToFQDN(h, r.ConfigMeta)) 1083 } 1084 // resolve gateways to bind to 1085 for i, g := range rule.Gateways { 1086 if g != constants.IstioMeshGateway { 1087 rule.Gateways[i] = resolveGatewayName(g, r.ConfigMeta) 1088 } 1089 } 1090 // resolve host in http route.destination, route.mirror 1091 for _, d := range rule.Http { 1092 for _, m := range d.Match { 1093 for i, g := range m.Gateways { 1094 if g != constants.IstioMeshGateway { 1095 m.Gateways[i] = resolveGatewayName(g, r.ConfigMeta) 1096 } 1097 } 1098 } 1099 for _, w := range d.Route { 1100 if w.Destination != nil { 1101 w.Destination.Host = string(ResolveShortnameToFQDN(w.Destination.Host, r.ConfigMeta)) 1102 } 1103 } 1104 if d.Mirror != nil { 1105 d.Mirror.Host = string(ResolveShortnameToFQDN(d.Mirror.Host, r.ConfigMeta)) 1106 } 1107 } 1108 // resolve host in tcp route.destination 1109 for _, d := range rule.Tcp { 1110 for _, m := range d.Match { 1111 for i, g := range m.Gateways { 1112 if g != constants.IstioMeshGateway { 1113 m.Gateways[i] = resolveGatewayName(g, r.ConfigMeta) 1114 } 1115 } 1116 } 1117 for _, w := range d.Route { 1118 if w.Destination != nil { 1119 w.Destination.Host = string(ResolveShortnameToFQDN(w.Destination.Host, r.ConfigMeta)) 1120 } 1121 } 1122 } 1123 //resolve host in tls route.destination 1124 for _, tls := range rule.Tls { 1125 for _, m := range tls.Match { 1126 for i, g := range m.Gateways { 1127 if g != constants.IstioMeshGateway { 1128 m.Gateways[i] = resolveGatewayName(g, r.ConfigMeta) 1129 } 1130 } 1131 } 1132 for _, w := range tls.Route { 1133 if w.Destination != nil { 1134 w.Destination.Host = string(ResolveShortnameToFQDN(w.Destination.Host, r.ConfigMeta)) 1135 } 1136 } 1137 } 1138 } 1139 1140 for _, virtualService := range vservices { 1141 ns := virtualService.Namespace 1142 rule := virtualService.Spec.(*networking.VirtualService) 1143 gwNames := getGatewayNames(rule, virtualService.ConfigMeta) 1144 if len(rule.ExportTo) == 0 { 1145 // No exportTo in virtualService. Use the global default 1146 // TODO: We currently only honor ., * and ~ 1147 if ps.defaultVirtualServiceExportTo[visibility.Private] { 1148 if _, f := ps.privateVirtualServicesByNamespaceAndGateway[ns]; !f { 1149 ps.privateVirtualServicesByNamespaceAndGateway[ns] = map[string][]Config{} 1150 } 1151 // add to local namespace only 1152 for _, gw := range gwNames { 1153 ps.privateVirtualServicesByNamespaceAndGateway[ns][gw] = append(ps.privateVirtualServicesByNamespaceAndGateway[ns][gw], virtualService) 1154 } 1155 } else if ps.defaultVirtualServiceExportTo[visibility.Public] { 1156 for _, gw := range gwNames { 1157 ps.publicVirtualServicesByGateway[gw] = append(ps.publicVirtualServicesByGateway[gw], virtualService) 1158 } 1159 } 1160 } else { 1161 // TODO: we currently only process the first element in the array 1162 // and currently only consider . or * which maps to public/private 1163 if visibility.Instance(rule.ExportTo[0]) == visibility.Private { 1164 if _, f := ps.privateVirtualServicesByNamespaceAndGateway[ns]; !f { 1165 ps.privateVirtualServicesByNamespaceAndGateway[ns] = map[string][]Config{} 1166 } 1167 // add to local namespace only 1168 for _, gw := range gwNames { 1169 ps.privateVirtualServicesByNamespaceAndGateway[ns][gw] = append(ps.privateVirtualServicesByNamespaceAndGateway[ns][gw], virtualService) 1170 } 1171 } else { 1172 // ~ is not valid in the exportTo fields in virtualServices, services, destination rules 1173 // and we currently only allow . or *. So treat this as public export 1174 for _, gw := range gwNames { 1175 ps.publicVirtualServicesByGateway[gw] = append(ps.publicVirtualServicesByGateway[gw], virtualService) 1176 } 1177 } 1178 } 1179 } 1180 1181 return nil 1182} 1183 1184var meshGateways = []string{constants.IstioMeshGateway} 1185 1186func getGatewayNames(vs *networking.VirtualService, meta ConfigMeta) []string { 1187 if len(vs.Gateways) == 0 { 1188 return meshGateways 1189 } 1190 res := make([]string, 0, len(vs.Gateways)) 1191 for _, g := range vs.Gateways { 1192 if g == constants.IstioMeshGateway { 1193 res = append(res, constants.IstioMeshGateway) 1194 } else { 1195 name := resolveGatewayName(g, meta) 1196 res = append(res, name) 1197 } 1198 } 1199 return res 1200} 1201 1202func (ps *PushContext) initDefaultExportMaps() { 1203 ps.defaultDestinationRuleExportTo = make(map[visibility.Instance]bool) 1204 if ps.Mesh.DefaultDestinationRuleExportTo != nil { 1205 for _, e := range ps.Mesh.DefaultDestinationRuleExportTo { 1206 ps.defaultDestinationRuleExportTo[visibility.Instance(e)] = true 1207 } 1208 } else { 1209 // default to * 1210 ps.defaultDestinationRuleExportTo[visibility.Public] = true 1211 } 1212 1213 ps.defaultServiceExportTo = make(map[visibility.Instance]bool) 1214 if ps.Mesh.DefaultServiceExportTo != nil { 1215 for _, e := range ps.Mesh.DefaultServiceExportTo { 1216 ps.defaultServiceExportTo[visibility.Instance(e)] = true 1217 } 1218 } else { 1219 ps.defaultServiceExportTo[visibility.Public] = true 1220 } 1221 1222 ps.defaultVirtualServiceExportTo = make(map[visibility.Instance]bool) 1223 if ps.Mesh.DefaultVirtualServiceExportTo != nil { 1224 for _, e := range ps.Mesh.DefaultVirtualServiceExportTo { 1225 ps.defaultVirtualServiceExportTo[visibility.Instance(e)] = true 1226 } 1227 } else { 1228 ps.defaultVirtualServiceExportTo[visibility.Public] = true 1229 } 1230} 1231 1232// initSidecarScopes synthesizes Sidecar CRDs into objects called 1233// SidecarScope. The SidecarScope object is a semi-processed view of the 1234// service registry, and config state associated with the sidecar CRD. The 1235// scope contains a set of inbound and outbound listeners, services/configs 1236// per listener, etc. The sidecar scopes are precomputed based on the 1237// Sidecar API objects in each namespace. If there is no sidecar api object 1238// for a namespace, a default sidecarscope is assigned to the namespace 1239// which enables connectivity to all services in the mesh. 1240// 1241// When proxies connect to Pilot, we identify the sidecar scope associated 1242// with the proxy and derive listeners/routes/clusters based on the sidecar 1243// scope. 1244func (ps *PushContext) initSidecarScopes(env *Environment) error { 1245 sidecarConfigs, err := env.List(collections.IstioNetworkingV1Alpha3Sidecars.Resource().GroupVersionKind(), NamespaceAll) 1246 if err != nil { 1247 return err 1248 } 1249 1250 sortConfigByCreationTime(sidecarConfigs) 1251 1252 sidecarConfigWithSelector := make([]Config, 0) 1253 sidecarConfigWithoutSelector := make([]Config, 0) 1254 sidecarsWithoutSelectorByNamespace := make(map[string]struct{}) 1255 for _, sidecarConfig := range sidecarConfigs { 1256 sidecar := sidecarConfig.Spec.(*networking.Sidecar) 1257 if sidecar.WorkloadSelector != nil { 1258 sidecarConfigWithSelector = append(sidecarConfigWithSelector, sidecarConfig) 1259 } else { 1260 sidecarsWithoutSelectorByNamespace[sidecarConfig.Namespace] = struct{}{} 1261 sidecarConfigWithoutSelector = append(sidecarConfigWithoutSelector, sidecarConfig) 1262 } 1263 } 1264 1265 sidecarNum := len(sidecarConfigs) 1266 sidecarConfigs = make([]Config, 0, sidecarNum) 1267 sidecarConfigs = append(sidecarConfigs, sidecarConfigWithSelector...) 1268 sidecarConfigs = append(sidecarConfigs, sidecarConfigWithoutSelector...) 1269 1270 ps.sidecarsByNamespace = make(map[string][]*SidecarScope, sidecarNum) 1271 for _, sidecarConfig := range sidecarConfigs { 1272 sidecarConfig := sidecarConfig 1273 ps.sidecarsByNamespace[sidecarConfig.Namespace] = append(ps.sidecarsByNamespace[sidecarConfig.Namespace], 1274 ConvertToSidecarScope(ps, &sidecarConfig, sidecarConfig.Namespace)) 1275 } 1276 1277 // Hold reference root namespace's sidecar config 1278 // Root namespace can have only one sidecar config object 1279 // Currently we expect that it has no workloadSelectors 1280 var rootNSConfig *Config 1281 if ps.Mesh.RootNamespace != "" { 1282 for _, sidecarConfig := range sidecarConfigs { 1283 if sidecarConfig.Namespace == ps.Mesh.RootNamespace && 1284 sidecarConfig.Spec.(*networking.Sidecar).WorkloadSelector == nil { 1285 rootNSConfig = &sidecarConfig 1286 break 1287 } 1288 } 1289 } 1290 1291 // build sidecar scopes for namespaces that do not have a non-workloadSelector sidecar CRD object. 1292 // Derive the sidecar scope from the root namespace's sidecar object if present. Else fallback 1293 // to the default Istio behavior mimicked by the DefaultSidecarScopeForNamespace function. 1294 for _, nsMap := range ps.ServiceByHostnameAndNamespace { 1295 for ns := range nsMap { 1296 if _, exist := sidecarsWithoutSelectorByNamespace[ns]; !exist { 1297 ps.sidecarsByNamespace[ns] = append(ps.sidecarsByNamespace[ns], ConvertToSidecarScope(ps, rootNSConfig, ns)) 1298 } 1299 } 1300 } 1301 1302 return nil 1303} 1304 1305// Split out of DestinationRule expensive conversions - once per push. 1306func (ps *PushContext) initDestinationRules(env *Environment) error { 1307 configs, err := env.List(collections.IstioNetworkingV1Alpha3Destinationrules.Resource().GroupVersionKind(), NamespaceAll) 1308 if err != nil { 1309 return err 1310 } 1311 1312 // values returned from ConfigStore.List are immutable. 1313 // Therefore, we make a copy 1314 destRules := make([]Config, len(configs)) 1315 for i := range destRules { 1316 destRules[i] = configs[i].DeepCopy() 1317 } 1318 1319 ps.SetDestinationRules(destRules) 1320 return nil 1321} 1322 1323// SetDestinationRules is updates internal structures using a set of configs. 1324// Split out of DestinationRule expensive conversions, computed once per push. 1325// This also allows tests to inject a config without having the mock. 1326// This will not work properly for Sidecars, which will precompute their destination rules on init 1327func (ps *PushContext) SetDestinationRules(configs []Config) { 1328 // Sort by time first. So if two destination rule have top level traffic policies 1329 // we take the first one. 1330 sortConfigByCreationTime(configs) 1331 namespaceLocalDestRules := make(map[string]*processedDestRules) 1332 namespaceExportedDestRules := make(map[string]*processedDestRules) 1333 1334 for i := range configs { 1335 rule := configs[i].Spec.(*networking.DestinationRule) 1336 rule.Host = string(ResolveShortnameToFQDN(rule.Host, configs[i].ConfigMeta)) 1337 // Store in an index for the config's namespace 1338 // a proxy from this namespace will first look here for the destination rule for a given service 1339 // This pool consists of both public/private destination rules. 1340 // TODO: when exportTo is fully supported, only add the rule here if exportTo is '.' 1341 // The global exportTo doesn't matter here (its either . or * - both of which are applicable here) 1342 if _, exist := namespaceLocalDestRules[configs[i].Namespace]; !exist { 1343 namespaceLocalDestRules[configs[i].Namespace] = &processedDestRules{ 1344 hosts: make([]host.Name, 0), 1345 destRule: map[host.Name]*Config{}, 1346 } 1347 } 1348 // Merge this destination rule with any public/private dest rules for same host in the same namespace 1349 // If there are no duplicates, the dest rule will be added to the list 1350 ps.mergeDestinationRule(namespaceLocalDestRules[configs[i].Namespace], configs[i]) 1351 isPubliclyExported := false 1352 if len(rule.ExportTo) == 0 { 1353 // No exportTo in destinationRule. Use the global default 1354 // TODO: We currently only honor ., * and ~ 1355 if ps.defaultDestinationRuleExportTo[visibility.Public] { 1356 isPubliclyExported = true 1357 } 1358 } else { 1359 // TODO: we currently only process the first element in the array 1360 // and currently only consider . or * which maps to public/private 1361 if visibility.Instance(rule.ExportTo[0]) != visibility.Private { 1362 // ~ is not valid in the exportTo fields in virtualServices, services, destination rules 1363 // and we currently only allow . or *. So treat this as public export 1364 isPubliclyExported = true 1365 } 1366 } 1367 1368 if isPubliclyExported { 1369 if _, exist := namespaceExportedDestRules[configs[i].Namespace]; !exist { 1370 namespaceExportedDestRules[configs[i].Namespace] = &processedDestRules{ 1371 hosts: make([]host.Name, 0), 1372 destRule: map[host.Name]*Config{}, 1373 } 1374 } 1375 // Merge this destination rule with any public dest rule for the same host in the same namespace 1376 // If there are no duplicates, the dest rule will be added to the list 1377 ps.mergeDestinationRule(namespaceExportedDestRules[configs[i].Namespace], configs[i]) 1378 } 1379 } 1380 1381 // presort it so that we don't sort it for each DestinationRule call. 1382 // sort.Sort for Hostnames will automatically sort from the most specific to least specific 1383 for ns := range namespaceLocalDestRules { 1384 sort.Sort(host.Names(namespaceLocalDestRules[ns].hosts)) 1385 } 1386 for ns := range namespaceExportedDestRules { 1387 sort.Sort(host.Names(namespaceExportedDestRules[ns].hosts)) 1388 } 1389 1390 ps.namespaceLocalDestRules = namespaceLocalDestRules 1391 ps.namespaceExportedDestRules = namespaceExportedDestRules 1392} 1393 1394func (ps *PushContext) initAuthorizationPolicies(env *Environment) error { 1395 var err error 1396 if ps.AuthzPolicies, err = GetAuthorizationPolicies(env); err != nil { 1397 authzLog.Errorf("failed to initialize authorization policies: %v", err) 1398 return err 1399 } 1400 return nil 1401} 1402 1403// pre computes envoy filters per namespace 1404func (ps *PushContext) initEnvoyFilters(env *Environment) error { 1405 envoyFilterConfigs, err := env.List(collections.IstioNetworkingV1Alpha3Envoyfilters.Resource().GroupVersionKind(), NamespaceAll) 1406 if err != nil { 1407 return err 1408 } 1409 1410 sortConfigByCreationTime(envoyFilterConfigs) 1411 1412 ps.envoyFiltersByNamespace = make(map[string][]*EnvoyFilterWrapper) 1413 for _, envoyFilterConfig := range envoyFilterConfigs { 1414 efw := convertToEnvoyFilterWrapper(&envoyFilterConfig) 1415 if _, exists := ps.envoyFiltersByNamespace[envoyFilterConfig.Namespace]; !exists { 1416 ps.envoyFiltersByNamespace[envoyFilterConfig.Namespace] = make([]*EnvoyFilterWrapper, 0) 1417 } 1418 ps.envoyFiltersByNamespace[envoyFilterConfig.Namespace] = append(ps.envoyFiltersByNamespace[envoyFilterConfig.Namespace], efw) 1419 } 1420 return nil 1421} 1422 1423// EnvoyFilters return the merged EnvoyFilterWrapper of a proxy 1424func (ps *PushContext) EnvoyFilters(proxy *Proxy) *EnvoyFilterWrapper { 1425 // this should never happen 1426 if proxy == nil { 1427 return nil 1428 } 1429 matchedEnvoyFilters := make([]*EnvoyFilterWrapper, 0) 1430 // EnvoyFilters supports inheritance (global ones plus namespace local ones). 1431 // First get all the filter configs from the config root namespace 1432 // and then add the ones from proxy's own namespace 1433 if ps.Mesh.RootNamespace != "" { 1434 // if there is no workload selector, the config applies to all workloads 1435 // if there is a workload selector, check for matching workload labels 1436 for _, efw := range ps.envoyFiltersByNamespace[ps.Mesh.RootNamespace] { 1437 var workloadLabels labels.Collection 1438 // This should never happen except in tests. 1439 if proxy.Metadata != nil && len(proxy.Metadata.Labels) > 0 { 1440 workloadLabels = labels.Collection{proxy.Metadata.Labels} 1441 } 1442 if efw.workloadSelector == nil || workloadLabels.IsSupersetOf(efw.workloadSelector) { 1443 matchedEnvoyFilters = append(matchedEnvoyFilters, efw) 1444 } 1445 } 1446 } 1447 1448 // To prevent duplicate envoyfilters in case root namespace equals proxy's namespace 1449 if proxy.ConfigNamespace != ps.Mesh.RootNamespace { 1450 for _, efw := range ps.envoyFiltersByNamespace[proxy.ConfigNamespace] { 1451 var workloadLabels labels.Collection 1452 // This should never happen except in tests. 1453 if proxy.Metadata != nil && len(proxy.Metadata.Labels) > 0 { 1454 workloadLabels = labels.Collection{proxy.Metadata.Labels} 1455 } 1456 if efw.workloadSelector == nil || workloadLabels.IsSupersetOf(efw.workloadSelector) { 1457 matchedEnvoyFilters = append(matchedEnvoyFilters, efw) 1458 } 1459 } 1460 } 1461 1462 var out *EnvoyFilterWrapper 1463 if len(matchedEnvoyFilters) > 0 { 1464 out = &EnvoyFilterWrapper{ 1465 // no need populate workloadSelector, as it is not used later. 1466 Patches: make(map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper), 1467 } 1468 } 1469 // merge EnvoyFilterWrapper 1470 for _, efw := range matchedEnvoyFilters { 1471 for applyTo, cps := range efw.Patches { 1472 if out.Patches[applyTo] == nil { 1473 out.Patches[applyTo] = []*EnvoyFilterConfigPatchWrapper{} 1474 } 1475 for _, cp := range cps { 1476 if proxyMatch(proxy, cp) { 1477 out.Patches[applyTo] = append(out.Patches[applyTo], cp) 1478 } 1479 } 1480 } 1481 } 1482 1483 return out 1484} 1485 1486// pre computes gateways per namespace 1487func (ps *PushContext) initGateways(env *Environment) error { 1488 gatewayConfigs, err := env.List(collections.IstioNetworkingV1Alpha3Gateways.Resource().GroupVersionKind(), NamespaceAll) 1489 if err != nil { 1490 return err 1491 } 1492 1493 sortConfigByCreationTime(gatewayConfigs) 1494 1495 ps.allGateways = gatewayConfigs 1496 ps.gatewaysByNamespace = make(map[string][]Config) 1497 for _, gatewayConfig := range gatewayConfigs { 1498 if _, exists := ps.gatewaysByNamespace[gatewayConfig.Namespace]; !exists { 1499 ps.gatewaysByNamespace[gatewayConfig.Namespace] = make([]Config, 0) 1500 } 1501 ps.gatewaysByNamespace[gatewayConfig.Namespace] = append(ps.gatewaysByNamespace[gatewayConfig.Namespace], gatewayConfig) 1502 } 1503 return nil 1504} 1505 1506func (ps *PushContext) mergeGateways(proxy *Proxy) *MergedGateway { 1507 // this should never happen 1508 if proxy == nil { 1509 return nil 1510 } 1511 out := make([]Config, 0) 1512 1513 var configs []Config 1514 if features.ScopeGatewayToNamespace { 1515 configs = ps.gatewaysByNamespace[proxy.ConfigNamespace] 1516 } else { 1517 configs = ps.allGateways 1518 } 1519 1520 for _, cfg := range configs { 1521 gw := cfg.Spec.(*networking.Gateway) 1522 if gw.GetSelector() == nil { 1523 // no selector. Applies to all workloads asking for the gateway 1524 out = append(out, cfg) 1525 } else { 1526 gatewaySelector := labels.Instance(gw.GetSelector()) 1527 var workloadLabels labels.Collection 1528 // This should never happen except in tests. 1529 if proxy.Metadata != nil && len(proxy.Metadata.Labels) > 0 { 1530 workloadLabels = labels.Collection{proxy.Metadata.Labels} 1531 } 1532 if workloadLabels.IsSupersetOf(gatewaySelector) { 1533 out = append(out, cfg) 1534 } 1535 } 1536 } 1537 1538 if len(out) == 0 { 1539 return nil 1540 } 1541 return MergeGateways(out...) 1542} 1543 1544// pre computes gateways for each network 1545func (ps *PushContext) initMeshNetworks() { 1546 if ps.Networks == nil || len(ps.Networks.Networks) == 0 { 1547 return 1548 } 1549 1550 ps.networkGateways = map[string][]*Gateway{} 1551 for network, networkConf := range ps.Networks.Networks { 1552 gws := networkConf.Gateways 1553 if len(gws) == 0 { 1554 // all endpoints in this network are reachable directly from others. nothing to do. 1555 continue 1556 } 1557 1558 registryNames := getNetworkRegistries(networkConf) 1559 gateways := []*Gateway{} 1560 1561 for _, gw := range gws { 1562 gateways = append(gateways, getGatewayAddresses(gw, registryNames, ps.ServiceDiscovery)...) 1563 } 1564 1565 log.Debugf("Endpoints from registries %v on network %v reachable through %d gateways", 1566 registryNames, network, len(gateways)) 1567 1568 ps.networkGateways[network] = gateways 1569 } 1570} 1571 1572func (ps *PushContext) initClusterLocalHosts(e *Environment) { 1573 // Create the default list of cluster-local hosts. 1574 domainSuffix := e.GetDomainSuffix() 1575 defaultClusterLocalHosts := make([]host.Name, 0, len(defaultClusterLocalNamespaces)) 1576 for _, n := range defaultClusterLocalNamespaces { 1577 defaultClusterLocalHosts = append(defaultClusterLocalHosts, host.Name("*."+n+".svc."+domainSuffix)) 1578 } 1579 1580 if discoveryHost, err := e.GetDiscoveryHost(); err != nil { 1581 log.Errorf("failed to make discoveryAddress cluster-local: %v", err) 1582 } else { 1583 if !strings.HasSuffix(string(discoveryHost), domainSuffix) { 1584 discoveryHost += host.Name("." + domainSuffix) 1585 } 1586 defaultClusterLocalHosts = append(defaultClusterLocalHosts, discoveryHost) 1587 } 1588 1589 // Collect the cluster-local hosts. 1590 clusterLocalHosts := make([]host.Name, 0) 1591 for _, serviceSettings := range ps.Mesh.ServiceSettings { 1592 if serviceSettings.Settings.ClusterLocal { 1593 for _, h := range serviceSettings.Hosts { 1594 clusterLocalHosts = append(clusterLocalHosts, host.Name(h)) 1595 } 1596 } else { 1597 // Remove defaults if specified to be non-cluster-local. 1598 for _, h := range serviceSettings.Hosts { 1599 for i, defaultClusterLocalHost := range defaultClusterLocalHosts { 1600 if len(defaultClusterLocalHost) > 0 && strings.HasSuffix(h, string(defaultClusterLocalHost[1:])) { 1601 // This default was explicitly overridden, so remove it. 1602 defaultClusterLocalHosts[i] = "" 1603 } 1604 } 1605 } 1606 } 1607 } 1608 1609 // Add any remaining defaults to the end of the list. 1610 for _, defaultClusterLocalHost := range defaultClusterLocalHosts { 1611 if len(defaultClusterLocalHost) > 0 { 1612 clusterLocalHosts = append(clusterLocalHosts, defaultClusterLocalHost) 1613 } 1614 } 1615 1616 sort.Sort(host.Names(clusterLocalHosts)) 1617 ps.clusterLocalHosts = clusterLocalHosts 1618} 1619 1620func (ps *PushContext) initQuotaSpecs(env *Environment) error { 1621 var err error 1622 ps.QuotaSpec, err = env.List(collections.IstioMixerV1ConfigClientQuotaspecs.Resource().GroupVersionKind(), NamespaceAll) 1623 return err 1624} 1625 1626func (ps *PushContext) initQuotaSpecBindings(env *Environment) error { 1627 var err error 1628 ps.QuotaSpecBinding, err = env.List(collections.IstioMixerV1ConfigClientQuotaspecbindings.Resource().GroupVersionKind(), NamespaceAll) 1629 return err 1630} 1631 1632func getNetworkRegistries(network *meshconfig.Network) []string { 1633 var registryNames []string 1634 for _, eps := range network.Endpoints { 1635 if eps != nil && len(eps.GetFromRegistry()) > 0 { 1636 registryNames = append(registryNames, eps.GetFromRegistry()) 1637 } 1638 } 1639 return registryNames 1640} 1641 1642func getGatewayAddresses(gw *meshconfig.Network_IstioNetworkGateway, registryNames []string, discovery ServiceDiscovery) []*Gateway { 1643 // First, if a gateway address is provided in the configuration use it. If the gateway address 1644 // in the config was a hostname it got already resolved and replaced with an IP address 1645 // when loading the config 1646 if gwIP := net.ParseIP(gw.GetAddress()); gwIP != nil { 1647 return []*Gateway{{gw.GetAddress(), gw.Port}} 1648 } 1649 1650 // Second, try to find the gateway addresses by the provided service name 1651 if gwSvcName := gw.GetRegistryServiceName(); gwSvcName != "" { 1652 svc, _ := discovery.GetService(host.Name(gwSvcName)) 1653 if svc == nil { 1654 return nil 1655 } 1656 // No need lock here as the service returned is a new one 1657 if svc.Attributes.ClusterExternalAddresses != nil { 1658 var gateways []*Gateway 1659 for _, clusterName := range registryNames { 1660 remotePort := gw.Port 1661 // check if we have node port mappings 1662 if svc.Attributes.ClusterExternalPorts != nil { 1663 if nodePortMap, exists := svc.Attributes.ClusterExternalPorts[clusterName]; exists { 1664 // what we now have is a service port. If there is a mapping for cluster external ports, 1665 // look it up and get the node port for the remote port 1666 if nodePort, exists := nodePortMap[remotePort]; exists { 1667 remotePort = nodePort 1668 } 1669 } 1670 } 1671 ips := svc.Attributes.ClusterExternalAddresses[clusterName] 1672 for _, ip := range ips { 1673 gateways = append(gateways, &Gateway{ip, remotePort}) 1674 } 1675 } 1676 return gateways 1677 } 1678 } 1679 1680 return nil 1681} 1682 1683func (ps *PushContext) NetworkGateways() map[string][]*Gateway { 1684 return ps.networkGateways 1685} 1686 1687func (ps *PushContext) NetworkGatewaysByNetwork(network string) []*Gateway { 1688 if ps.networkGateways != nil { 1689 return ps.networkGateways[network] 1690 } 1691 1692 return nil 1693} 1694 1695func (ps *PushContext) QuotaSpecByDestination(hostname host.Name) []Config { 1696 return filterQuotaSpecsByDestination(hostname, ps.QuotaSpecBinding, ps.QuotaSpec) 1697} 1698 1699// BestEffortInferServiceMTLSMode infers the mTLS mode for the service + port from all authentication 1700// policies (both alpha and beta) in the system. The function always returns MTLSUnknown for external service. 1701// The resulst is a best effort. It is because the PeerAuthentication is workload-based, this function is unable 1702// to compute the correct service mTLS mode without knowing service to workload binding. For now, this 1703// function uses only mesh and namespace level PeerAuthentication and ignore workload & port level policies. 1704// This function is used to give a hint for auto-mTLS configuration on client side. 1705func (ps *PushContext) BestEffortInferServiceMTLSMode(service *Service, port *Port) MutualTLSMode { 1706 if service.MeshExternal { 1707 // Only need the authentication MTLS mode when service is not external. 1708 return MTLSUnknown 1709 } 1710 1711 // First , check mTLS settings from beta policy (i.e PeerAuthentication) at namespace / mesh level. 1712 // If the mode is not unknown, use it. 1713 if serviceMTLSMode := ps.AuthnBetaPolicies.GetNamespaceMutualTLSMode(service.Attributes.Namespace); serviceMTLSMode != MTLSUnknown { 1714 return serviceMTLSMode 1715 } 1716 1717 // When all are failed, default to permissive. 1718 return MTLSPermissive 1719} 1720