1// Copyright 2017 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 "bytes" 19 "encoding/json" 20 "fmt" 21 "net" 22 "regexp" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 28 core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 29 gogojsonpb "github.com/gogo/protobuf/jsonpb" 30 "github.com/golang/protobuf/jsonpb" 31 "github.com/golang/protobuf/ptypes/any" 32 structpb "github.com/golang/protobuf/ptypes/struct" 33 34 meshconfig "istio.io/api/mesh/v1alpha1" 35 "istio.io/pkg/monitoring" 36 37 "istio.io/istio/pkg/config/host" 38 "istio.io/istio/pkg/config/labels" 39 "istio.io/istio/pkg/config/mesh" 40) 41 42const ( 43 defaultDomainSuffix = "cluster.local" 44) 45 46var _ mesh.Holder = &Environment{} 47var _ mesh.NetworksHolder = &Environment{} 48 49// Environment provides an aggregate environmental API for Pilot 50type Environment struct { 51 // Discovery interface for listing services and instances. 52 ServiceDiscovery 53 54 // Config interface for listing routing rules 55 IstioConfigStore 56 57 // Watcher is the watcher for the mesh config (to be merged into the config store) 58 mesh.Watcher 59 60 // NetworksWatcher (loaded from a config map) provides information about the 61 // set of networks inside a mesh and how to route to endpoints in each 62 // network. Each network provides information about the endpoints in a 63 // routable L3 network. A single routable L3 network can have one or more 64 // service registries. 65 mesh.NetworksWatcher 66 67 // PushContext holds informations during push generation. It is reset on config change, at the beginning 68 // of the pushAll. It will hold all errors and stats and possibly caches needed during the entire cache computation. 69 // DO NOT USE EXCEPT FOR TESTS AND HANDLING OF NEW CONNECTIONS. 70 // ALL USE DURING A PUSH SHOULD USE THE ONE CREATED AT THE 71 // START OF THE PUSH, THE GLOBAL ONE MAY CHANGE AND REFLECT A DIFFERENT 72 // CONFIG AND PUSH 73 PushContext *PushContext 74 75 // DomainSuffix provides a default domain for the Istio server. 76 DomainSuffix string 77} 78 79func (e *Environment) GetDomainSuffix() string { 80 if len(e.DomainSuffix) > 0 { 81 return e.DomainSuffix 82 } 83 return defaultDomainSuffix 84} 85 86func (e *Environment) Mesh() *meshconfig.MeshConfig { 87 if e != nil && e.Watcher != nil { 88 return e.Watcher.Mesh() 89 } 90 return nil 91} 92 93func (e *Environment) GetDiscoveryHost() (host.Name, error) { 94 proxyConfig := mesh.DefaultProxyConfig() 95 if e.Mesh().DefaultConfig != nil { 96 proxyConfig = *e.Mesh().DefaultConfig 97 } 98 hostname, _, err := net.SplitHostPort(proxyConfig.DiscoveryAddress) 99 if err != nil { 100 return "", err 101 } 102 return host.Name(hostname), nil 103} 104 105func (e *Environment) AddMeshHandler(h func()) { 106 if e != nil && e.Watcher != nil { 107 e.Watcher.AddMeshHandler(h) 108 } 109} 110 111func (e *Environment) Networks() *meshconfig.MeshNetworks { 112 if e != nil && e.NetworksWatcher != nil { 113 return e.NetworksWatcher.Networks() 114 } 115 return nil 116} 117 118func (e *Environment) AddNetworksHandler(h func()) { 119 if e != nil && e.NetworksWatcher != nil { 120 e.NetworksWatcher.AddNetworksHandler(h) 121 } 122} 123 124func (e *Environment) AddMetric(metric monitoring.Metric, key string, proxy *Proxy, msg string) { 125 if e != nil && e.PushContext != nil { 126 e.PushContext.AddMetric(metric, key, proxy, msg) 127 } 128} 129 130// Request is an alias for array of marshaled resources. 131type Resources = []*any.Any 132 133// XdsResourceGenerator creates the response for a typeURL DiscoveryRequest. If no generator is associated 134// with a Proxy, the default (a networking.core.ConfigGenerator instance) will be used. 135// The server may associate a different generator based on client metadata. Different 136// WatchedResources may use same or different Generator. 137type XdsResourceGenerator interface { 138 Generate(proxy *Proxy, push *PushContext, w *WatchedResource) Resources 139} 140 141// Proxy contains information about an specific instance of a proxy (envoy sidecar, gateway, 142// etc). The Proxy is initialized when a sidecar connects to Pilot, and populated from 143// 'node' info in the protocol as well as data extracted from registries. 144// 145// In current Istio implementation nodes use a 4-parts '~' delimited ID. 146// Type~IPAddress~ID~Domain 147type Proxy struct { 148 // ClusterID specifies the cluster where the proxy resides. 149 // TODO: clarify if this is needed in the new 'network' model, likely needs to 150 // be renamed to 'network' 151 ClusterID string 152 153 // Type specifies the node type. First part of the ID. 154 Type NodeType 155 156 // IPAddresses is the IP addresses of the proxy used to identify it and its 157 // co-located service instances. Example: "10.60.1.6". In some cases, the host 158 // where the poxy and service instances reside may have more than one IP address 159 IPAddresses []string 160 161 // ID is the unique platform-specific sidecar proxy ID. For k8s it is the pod ID and 162 // namespace. 163 ID string 164 165 // Locality is the location of where Envoy proxy runs. This is extracted from 166 // the registry where possible. If the registry doesn't provide a locality for the 167 // proxy it will use the one sent via ADS that can be configured in the Envoy bootstrap 168 Locality *core.Locality 169 170 // DNSDomain defines the DNS domain suffix for short hostnames (e.g. 171 // "default.svc.cluster.local") 172 DNSDomain string 173 174 // ConfigNamespace defines the namespace where this proxy resides 175 // for the purposes of network scoping. 176 // NOTE: DO NOT USE THIS FIELD TO CONSTRUCT DNS NAMES 177 ConfigNamespace string 178 179 // Metadata key-value pairs extending the Node identifier 180 Metadata *NodeMetadata 181 182 // the sidecarScope associated with the proxy 183 SidecarScope *SidecarScope 184 185 // the sidecarScope associated with the proxy previously 186 PrevSidecarScope *SidecarScope 187 188 // The merged gateways associated with the proxy if this is a Router 189 MergedGateway *MergedGateway 190 191 // service instances associated with the proxy 192 ServiceInstances []*ServiceInstance 193 194 // Istio version associated with the Proxy 195 IstioVersion *IstioVersion 196 197 // Indicates wheteher proxy supports IPv6 addresses 198 ipv6Support bool 199 200 // Indicates wheteher proxy supports IPv4 addresses 201 ipv4Support bool 202 203 // GlobalUnicastIP stores the globacl unicast IP if available, otherwise nil 204 GlobalUnicastIP string 205 206 // XdsResourceGenerator is used to generate resources for the node, based on the PushContext. 207 // If nil, the default networking/core v2 generator is used. This field can be set 208 // at connect time, based on node metadata, to trigger generation of a different style 209 // of configuration. 210 XdsResourceGenerator XdsResourceGenerator 211 212 // Active contains the list of watched resources for the proxy, keyed by the DiscoveryRequest type. 213 // It is nil if the Proxy uses the default generator 214 Active map[string]*WatchedResource 215} 216 217// WatchedResource tracks an active DiscoveryRequest type. 218type WatchedResource struct { 219 // TypeUrl is copied from the DiscoveryRequest.TypeUrl that initiated watching this resource. 220 // nolint 221 TypeUrl string 222 223 // ResourceNames tracks the list of resources that are actively watched. If empty, all resources of the 224 // TypeUrl type are watched. 225 ResourceNames []string 226 227 // VersionSent is the version of the resource included in the last sent response. 228 // It corresponds to the [Cluster/Route/Listener]VersionSent in the XDS package. 229 VersionSent string 230 231 // NonceSent is the nonce sent in the last sent response. If it is equal with NonceAcked, the 232 // last message has been processed. If empty: we never sent a message of this type. 233 NonceSent string 234 235 // VersionAcked represents the version that was applied successfully. It can be different from 236 // VersionSent: if NonceSent == NonceAcked and versions are different it means the client rejected 237 // the last version, and VersionAcked is the last accepted and active config. 238 // If empty it means the client has no accepted/valid version, and is not ready. 239 VersionAcked string 240 241 // NonceAcked is the last acked message. 242 NonceAcked string 243 244 // LastSent tracks the time of the generated push, to determine the time it takes the client to ack. 245 LastSent time.Time 246 247 // Updates count the number of generated updates for the resource 248 Updates int 249 250 // LastSize tracks the size of the last update 251 LastSize int 252} 253 254var ( 255 istioVersionRegexp = regexp.MustCompile(`^([1-9]+)\.([0-9]+)(\.([0-9]+))?`) 256) 257 258// StringList is a list that will be marshaled to a comma separate string in Json 259type StringList []string 260 261func (l StringList) MarshalJSON() ([]byte, error) { 262 if l == nil { 263 return nil, nil 264 } 265 return []byte(`"` + strings.Join(l, ",") + `"`), nil 266} 267 268func (l *StringList) UnmarshalJSON(data []byte) error { 269 if len(data) == 0 || string(data) == `""` { 270 *l = []string{} 271 } else { 272 *l = strings.Split(string(data[1:len(data)-1]), ",") 273 } 274 return nil 275} 276 277// PodPort describes a mapping of port name to port number. Generally, this is just the definition of 278// a port in Kubernetes, but without depending on Kubernetes api. 279type PodPort struct { 280 // If specified, this must be an IANA_SVC_NAME and unique within the pod. Each 281 // named port in a pod must have a unique name. Name for the port that can be 282 // referred to by services. 283 // +optional 284 Name string `json:"name,omitempty"` 285 // Number of port to expose on the pod's IP address. 286 // This must be a valid port number, 0 < x < 65536. 287 ContainerPort int `json:"containerPort"` 288 // Name of the protocol 289 Protocol string `json:"protocol"` 290} 291 292// PodPortList defines a list of PodPort's that is serialized as a string 293// This is for legacy reasons, where proper JSON was not supported and was written as a string 294type PodPortList []PodPort 295 296func (l PodPortList) MarshalJSON() ([]byte, error) { 297 if l == nil { 298 return nil, nil 299 } 300 b, err := json.Marshal([]PodPort(l)) 301 if err != nil { 302 return nil, err 303 } 304 b = bytes.ReplaceAll(b, []byte{'"'}, []byte{'\\', '"'}) 305 out := append([]byte{'"'}, b...) 306 out = append(out, '"') 307 return out, nil 308} 309 310func (l *PodPortList) UnmarshalJSON(data []byte) error { 311 var pl []PodPort 312 pls, err := strconv.Unquote(string(data)) 313 if err != nil { 314 return nil 315 } 316 if err := json.Unmarshal([]byte(pls), &pl); err != nil { 317 return err 318 } 319 *l = pl 320 return nil 321} 322 323// StringBool defines a boolean that is serialized as a string for legacy reasons 324type StringBool bool 325 326func (s StringBool) MarshalJSON() ([]byte, error) { 327 return []byte(fmt.Sprintf(`"%t"`, s)), nil 328} 329 330func (s *StringBool) UnmarshalJSON(data []byte) error { 331 pls, err := strconv.Unquote(string(data)) 332 if err != nil { 333 return err 334 } 335 b, err := strconv.ParseBool(pls) 336 if err != nil { 337 return err 338 } 339 *s = StringBool(b) 340 return nil 341} 342 343// ProxyConfig can only be marshaled using (gogo) jsonpb. However, the rest of node meta is not a proto 344// To allow marshaling, we need to define a custom type that calls out to the gogo marshaller 345type NodeMetaProxyConfig meshconfig.ProxyConfig 346 347func (s NodeMetaProxyConfig) MarshalJSON() ([]byte, error) { 348 var buf bytes.Buffer 349 pc := meshconfig.ProxyConfig(s) 350 if err := (&gogojsonpb.Marshaler{}).Marshal(&buf, &pc); err != nil { 351 return nil, err 352 } 353 return buf.Bytes(), nil 354} 355 356func (s *NodeMetaProxyConfig) UnmarshalJSON(data []byte) error { 357 pc := (*meshconfig.ProxyConfig)(s) 358 return gogojsonpb.Unmarshal(bytes.NewReader(data), pc) 359} 360 361// NodeMetadata defines the metadata associated with a proxy 362// Fields should not be assumed to exist on the proxy, especially newly added fields which will not exist 363// on older versions. 364// The JSON field names should never change, as they are needed for backward compatibility with older proxies 365// nolint: maligned 366type NodeMetadata struct { 367 // ProxyConfig defines the proxy config specified for a proxy. 368 // Note that this setting may be configured different for each proxy, due user overrides 369 // or from different versions of proxies connecting. While Pilot has access to the meshConfig.defaultConfig, 370 // this field should be preferred if it is present. 371 ProxyConfig *NodeMetaProxyConfig `json:"PROXY_CONFIG,omitempty"` 372 373 // IstioVersion specifies the Istio version associated with the proxy 374 IstioVersion string `json:"ISTIO_VERSION,omitempty"` 375 376 // Labels specifies the set of workload instance (ex: k8s pod) labels associated with this node. 377 Labels map[string]string `json:"LABELS,omitempty"` 378 379 // InstanceIPs is the set of IPs attached to this proxy 380 InstanceIPs StringList `json:"INSTANCE_IPS,omitempty"` 381 382 // ConfigNamespace is the name of the metadata variable that carries info about 383 // the config namespace associated with the proxy 384 ConfigNamespace string `json:"CONFIG_NAMESPACE,omitempty"` 385 386 // Namespace is the namespace in which the workload instance is running. 387 Namespace string `json:"NAMESPACE,omitempty"` // replaces CONFIG_NAMESPACE 388 389 // InterceptionMode is the name of the metadata variable that carries info about 390 // traffic interception mode at the proxy 391 InterceptionMode TrafficInterceptionMode `json:"INTERCEPTION_MODE,omitempty"` 392 393 // ServiceAccount specifies the service account which is running the workload. 394 ServiceAccount string `json:"SERVICE_ACCOUNT,omitempty"` 395 396 // RouterMode indicates whether the proxy is functioning as a SNI-DNAT router 397 // processing the AUTO_PASSTHROUGH gateway servers 398 RouterMode string `json:"ROUTER_MODE,omitempty"` 399 400 // MeshID specifies the mesh ID environment variable. 401 MeshID string `json:"MESH_ID,omitempty"` 402 403 // ClusterID defines the cluster the node belongs to. 404 ClusterID string `json:"CLUSTER_ID,omitempty"` 405 406 // Network defines the network the node belongs to. It is an optional metadata, 407 // set at injection time. When set, the Endpoints returned to a note and not on same network 408 // will be replaced with the gateway defined in the settings. 409 Network string `json:"NETWORK,omitempty"` 410 411 // RequestedNetworkView specifies the networks that the proxy wants to see 412 RequestedNetworkView StringList `json:"REQUESTED_NETWORK_VIEW,omitempty"` 413 414 // ExchangeKeys specifies a list of metadata keys that should be used for Node Metadata Exchange. 415 ExchangeKeys StringList `json:"EXCHANGE_KEYS,omitempty"` 416 417 // PlatformMetadata contains any platform specific metadata 418 PlatformMetadata map[string]string `json:"PLATFORM_METADATA,omitempty"` 419 420 // InstanceName is the short name for the workload instance (ex: pod name) 421 // replaces POD_NAME 422 InstanceName string `json:"NAME,omitempty"` 423 424 // WorkloadName specifies the name of the workload represented by this node. 425 WorkloadName string `json:"WORKLOAD_NAME,omitempty"` 426 427 // Owner specifies the workload owner (opaque string). Typically, this is the owning controller of 428 // of the workload instance (ex: k8s deployment for a k8s pod). 429 Owner string `json:"OWNER,omitempty"` 430 431 // PodPorts defines the ports on a pod. This is used to lookup named ports. 432 PodPorts PodPortList `json:"POD_PORTS,omitempty"` 433 434 // LocalityLabel defines the locality specified for the pod 435 LocalityLabel string `json:"istio-locality,omitempty"` 436 437 PolicyCheck string `json:"policy.istio.io/check,omitempty"` 438 PolicyCheckRetries string `json:"policy.istio.io/checkRetries,omitempty"` 439 PolicyCheckBaseRetryWaitTime string `json:"policy.istio.io/checkBaseRetryWaitTime,omitempty"` 440 PolicyCheckMaxRetryWaitTime string `json:"policy.istio.io/checkMaxRetryWaitTime,omitempty"` 441 442 StatsInclusionPrefixes string `json:"sidecar.istio.io/statsInclusionPrefixes,omitempty"` 443 StatsInclusionRegexps string `json:"sidecar.istio.io/statsInclusionRegexps,omitempty"` 444 StatsInclusionSuffixes string `json:"sidecar.istio.io/statsInclusionSuffixes,omitempty"` 445 ExtraStatTags string `json:"sidecar.istio.io/extraStatTags,omitempty"` 446 447 // TLSServerCertChain is the absolute path to server cert-chain file 448 TLSServerCertChain string `json:"TLS_SERVER_CERT_CHAIN,omitempty"` 449 // TLSServerKey is the absolute path to server private key file 450 TLSServerKey string `json:"TLS_SERVER_KEY,omitempty"` 451 // TLSServerRootCert is the absolute path to server root cert file 452 TLSServerRootCert string `json:"TLS_SERVER_ROOT_CERT,omitempty"` 453 // TLSClientCertChain is the absolute path to client cert-chain file 454 TLSClientCertChain string `json:"TLS_CLIENT_CERT_CHAIN,omitempty"` 455 // TLSClientKey is the absolute path to client private key file 456 TLSClientKey string `json:"TLS_CLIENT_KEY,omitempty"` 457 // TLSClientRootCert is the absolute path to client root cert file 458 TLSClientRootCert string `json:"TLS_CLIENT_ROOT_CERT,omitempty"` 459 460 // SdsTokenPath specifies the path of the SDS token used by the Envoy proxy. 461 // If not set, Pilot uses the default SDS token path. 462 SdsTokenPath string `json:"SDS_TOKEN_PATH,omitempty"` 463 SdsBase string `json:"BASE,omitempty"` 464 // SdsEnabled indicates if SDS is enabled or not. This is are set to "1" if true 465 SdsEnabled StringBool `json:"SDS,omitempty"` 466 // SdsTrustJwt indicates if SDS trust jwt is enabled or not. This is are set to "1" if true 467 SdsTrustJwt StringBool `json:"TRUSTJWT,omitempty"` 468 469 // StsPort specifies the port of security token exchange server (STS). 470 StsPort string `json:"STS_PORT,omitempty"` 471 472 InsecurePath string `json:"istio.io/insecurepath,omitempty"` 473 474 // IdleTimeout specifies the idle timeout for the proxy, in duration format (10s). 475 // If not set, no timeout is set. 476 IdleTimeout string `json:"IDLE_TIMEOUT,omitempty"` 477 478 // HTTP10 indicates the application behind the sidecar is making outbound http requests with HTTP/1.0 479 // protocol. It will enable the "AcceptHttp_10" option on the http options for outbound HTTP listeners. 480 // Alpha in 1.1, based on feedback may be turned into an API or change. Set to "1" to enable. 481 HTTP10 string `json:"HTTP10,omitempty"` 482 483 // Generator indicates the client wants to use a custom Generator plugin. 484 Generator string `json:"GENERATOR,omitempty"` 485 486 // Contains a copy of the raw metadata. This is needed to lookup arbitrary values. 487 // If a value is known ahead of time it should be added to the struct rather than reading from here, 488 Raw map[string]interface{} `json:"-"` 489} 490 491// ProxyConfigOrDefault is a helper function to get the ProxyConfig from metadata, or fallback to a default 492// This is useful as the logic should check for proxy config from proxy first and then defer to mesh wide defaults 493// if not present. 494func (m NodeMetadata) ProxyConfigOrDefault(def *meshconfig.ProxyConfig) *meshconfig.ProxyConfig { 495 if m.ProxyConfig != nil { 496 return (*meshconfig.ProxyConfig)(m.ProxyConfig) 497 } 498 return def 499} 500 501func (m *NodeMetadata) UnmarshalJSON(data []byte) error { 502 // Create a new type from the target type to avoid recursion. 503 type NodeMetadata2 NodeMetadata 504 505 t2 := &NodeMetadata2{} 506 if err := json.Unmarshal(data, t2); err != nil { 507 return err 508 } 509 var raw map[string]interface{} 510 if err := json.Unmarshal(data, &raw); err != nil { 511 return err 512 } 513 *m = NodeMetadata(*t2) 514 m.Raw = raw 515 516 return nil 517} 518 519// Converts this to a protobuf structure. This should be used only for debugging - performance is bad. 520func (m NodeMetadata) ToStruct() *structpb.Struct { 521 j, err := json.Marshal(m) 522 if err != nil { 523 return nil 524 } 525 526 pbs := &structpb.Struct{} 527 if err := jsonpb.Unmarshal(bytes.NewBuffer(j), pbs); err != nil { 528 return nil 529 } 530 531 return pbs 532} 533 534// IstioVersion encodes the Istio version of the proxy. This is a low key way to 535// do semver style comparisons and generate the appropriate envoy config 536type IstioVersion struct { 537 Major int 538 Minor int 539 Patch int 540} 541 542var ( 543 MaxIstioVersion = &IstioVersion{Major: 65535, Minor: 65535, Patch: 65535} 544) 545 546// Compare returns -1/0/1 if version is less than, equal or greater than inv 547// To compare only on major, call this function with { X, -1, -1}. 548// to compare only on major & minor, call this function with {X, Y, -1}. 549func (pversion *IstioVersion) Compare(inv *IstioVersion) int { 550 // check major 551 if r := compareVersion(pversion.Major, inv.Major); r != 0 { 552 return r 553 } 554 555 // check minor 556 if inv.Minor > -1 { 557 if r := compareVersion(pversion.Minor, inv.Minor); r != 0 { 558 return r 559 } 560 561 // check patch 562 if inv.Patch > -1 { 563 if r := compareVersion(pversion.Patch, inv.Patch); r != 0 { 564 return r 565 } 566 } 567 } 568 return 0 569} 570 571func compareVersion(ov, nv int) int { 572 if ov == nv { 573 return 0 574 } 575 if ov < nv { 576 return -1 577 } 578 return 1 579} 580 581// NodeType decides the responsibility of the proxy serves in the mesh 582type NodeType string 583 584const ( 585 // SidecarProxy type is used for sidecar proxies in the application containers 586 SidecarProxy NodeType = "sidecar" 587 588 // Router type is used for standalone proxies acting as L7/L4 routers 589 Router NodeType = "router" 590 591 // AllPortsLiteral is the string value indicating all ports 592 AllPortsLiteral = "*" 593) 594 595// IsApplicationNodeType verifies that the NodeType is one of the declared constants in the model 596func IsApplicationNodeType(nType NodeType) bool { 597 switch nType { 598 case SidecarProxy, Router: 599 return true 600 default: 601 return false 602 } 603} 604 605// ServiceNode encodes the proxy node attributes into a URI-acceptable string 606func (node *Proxy) ServiceNode() string { 607 ip := "" 608 if len(node.IPAddresses) > 0 { 609 ip = node.IPAddresses[0] 610 } 611 return strings.Join([]string{ 612 string(node.Type), ip, node.ID, node.DNSDomain, 613 }, serviceNodeSeparator) 614 615} 616 617// RouterMode decides the behavior of Istio Gateway (normal or sni-dnat) 618type RouterMode string 619 620const ( 621 // StandardRouter is the normal gateway mode 622 StandardRouter RouterMode = "standard" 623 624 // SniDnatRouter is used for bridging two networks 625 SniDnatRouter RouterMode = "sni-dnat" 626) 627 628// GetRouterMode returns the operating mode associated with the router. 629// Assumes that the proxy is of type Router 630func (node *Proxy) GetRouterMode() RouterMode { 631 if RouterMode(node.Metadata.RouterMode) == SniDnatRouter { 632 return SniDnatRouter 633 } 634 return StandardRouter 635} 636 637// SetSidecarScope identifies the sidecar scope object associated with this 638// proxy and updates the proxy Node. This is a convenience hack so that 639// callers can simply call push.Services(node) while the implementation of 640// push.Services can return the set of services from the proxyNode's 641// sidecar scope or from the push context's set of global services. Similar 642// logic applies to push.VirtualServices and push.DestinationRule. The 643// short cut here is useful only for CDS and parts of RDS generation code. 644// 645// Listener generation code will still use the SidecarScope object directly 646// as it needs the set of services for each listener port. 647func (node *Proxy) SetSidecarScope(ps *PushContext) { 648 sidecarScope := node.SidecarScope 649 650 if node.Type == SidecarProxy { 651 workloadLabels := labels.Collection{node.Metadata.Labels} 652 node.SidecarScope = ps.getSidecarScope(node, workloadLabels) 653 } else { 654 // Gateways should just have a default scope with egress: */* 655 node.SidecarScope = DefaultSidecarScopeForNamespace(ps, node.ConfigNamespace) 656 } 657 node.PrevSidecarScope = sidecarScope 658} 659 660// SetGatewaysForProxy merges the Gateway objects associated with this 661// proxy and caches the merged object in the proxy Node. This is a convenience hack so that 662// callers can simply call push.MergedGateways(node) instead of having to 663// fetch all the gateways and invoke the merge call in multiple places (lds/rds). 664func (node *Proxy) SetGatewaysForProxy(ps *PushContext) { 665 if node.Type != Router { 666 return 667 } 668 node.MergedGateway = ps.mergeGateways(node) 669} 670 671func (node *Proxy) SetServiceInstances(serviceDiscovery ServiceDiscovery) error { 672 instances, err := serviceDiscovery.GetProxyServiceInstances(node) 673 if err != nil { 674 log.Errorf("failed to get service proxy service instances: %v", err) 675 return err 676 } 677 678 // Keep service instances in order of creation/hostname. 679 sort.SliceStable(instances, func(i, j int) bool { 680 if instances[i].Service != nil && instances[j].Service != nil { 681 if !instances[i].Service.CreationTime.Equal(instances[j].Service.CreationTime) { 682 return instances[i].Service.CreationTime.Before(instances[j].Service.CreationTime) 683 } 684 // Additionally, sort by hostname just in case services created automatically at the same second. 685 return instances[i].Service.Hostname < instances[j].Service.Hostname 686 } 687 return true 688 }) 689 690 node.ServiceInstances = instances 691 return nil 692} 693 694// SetWorkloadLabels will set the node.Metadata.Labels only when it is nil. 695func (node *Proxy) SetWorkloadLabels(env *Environment) error { 696 // First get the workload labels from node meta 697 if len(node.Metadata.Labels) > 0 { 698 return nil 699 } 700 701 // Fallback to calling GetProxyWorkloadLabels 702 l, err := env.GetProxyWorkloadLabels(node) 703 if err != nil { 704 log.Errorf("failed to get service proxy labels: %v", err) 705 return err 706 } 707 if len(l) > 0 { 708 node.Metadata.Labels = l[0] 709 } 710 return nil 711} 712 713// DiscoverIPVersions discovers the IP Versions supported by Proxy based on its IP addresses. 714func (node *Proxy) DiscoverIPVersions() { 715 for i := 0; i < len(node.IPAddresses); i++ { 716 addr := net.ParseIP(node.IPAddresses[i]) 717 if addr == nil { 718 // Should not happen, invalid IP in proxy's IPAddresses slice should have been caught earlier, 719 // skip it to prevent a panic. 720 continue 721 } 722 if addr.IsGlobalUnicast() { 723 node.GlobalUnicastIP = addr.String() 724 } 725 if addr.To4() != nil { 726 node.ipv4Support = true 727 } else { 728 node.ipv6Support = true 729 } 730 } 731} 732 733// SupportsIPv4 returns true if proxy supports IPv4 addresses. 734func (node *Proxy) SupportsIPv4() bool { 735 return node.ipv4Support 736} 737 738// SupportsIPv6 returns true if proxy supports IPv6 addresses. 739func (node *Proxy) SupportsIPv6() bool { 740 return node.ipv6Support 741} 742 743// UnnamedNetwork is the default network that proxies in the mesh 744// get when they don't request a specific network view. 745const UnnamedNetwork = "" 746 747// GetNetworkView returns the networks that the proxy requested. 748// When sending EDS/CDS-with-dns-endpoints, Pilot will only send 749// endpoints corresponding to the networks that the proxy wants to see. 750// If not set, we assume that the proxy wants to see endpoints from the default 751// unnamed network. 752func GetNetworkView(node *Proxy) map[string]bool { 753 if node == nil { 754 return map[string]bool{UnnamedNetwork: true} 755 } 756 757 nmap := make(map[string]bool) 758 for _, n := range node.Metadata.RequestedNetworkView { 759 nmap[n] = true 760 } 761 762 if len(nmap) == 0 { 763 // Proxy sees endpoints from the default unnamed network only 764 nmap[UnnamedNetwork] = true 765 } 766 return nmap 767} 768 769// ParseMetadata parses the opaque Metadata from an Envoy Node into string key-value pairs. 770// Any non-string values are ignored. 771func ParseMetadata(metadata *structpb.Struct) (*NodeMetadata, error) { 772 if metadata == nil { 773 return &NodeMetadata{}, nil 774 } 775 776 buf := &bytes.Buffer{} 777 if err := (&jsonpb.Marshaler{OrigName: true}).Marshal(buf, metadata); err != nil { 778 return nil, fmt.Errorf("failed to read node metadata %v: %v", metadata, err) 779 } 780 meta := &NodeMetadata{} 781 if err := json.Unmarshal(buf.Bytes(), meta); err != nil { 782 return nil, fmt.Errorf("failed to unmarshal node metadata (%v): %v", buf.String(), err) 783 } 784 return meta, nil 785} 786 787// ParseServiceNodeWithMetadata parse the Envoy Node from the string generated by ServiceNode 788// function and the metadata. 789func ParseServiceNodeWithMetadata(s string, metadata *NodeMetadata) (*Proxy, error) { 790 parts := strings.Split(s, serviceNodeSeparator) 791 out := &Proxy{ 792 Metadata: metadata, 793 } 794 795 if len(parts) != 4 { 796 return out, fmt.Errorf("missing parts in the service node %q", s) 797 } 798 799 if !IsApplicationNodeType(NodeType(parts[0])) { 800 return out, fmt.Errorf("invalid node type (valid types: sidecar, router in the service node %q", s) 801 } 802 out.Type = NodeType(parts[0]) 803 804 // Get all IP Addresses from Metadata 805 if hasValidIPAddresses(metadata.InstanceIPs) { 806 out.IPAddresses = metadata.InstanceIPs 807 } else if isValidIPAddress(parts[1]) { 808 //Fall back, use IP from node id, it's only for backward-compatibility, IP should come from metadata 809 out.IPAddresses = append(out.IPAddresses, parts[1]) 810 } 811 812 // Does query from ingress or router have to carry valid IP address? 813 if len(out.IPAddresses) == 0 { 814 return out, fmt.Errorf("no valid IP address in the service node id or metadata") 815 } 816 817 out.ID = parts[2] 818 out.DNSDomain = parts[3] 819 if len(metadata.IstioVersion) == 0 { 820 log.Warnf("Istio Version is not found in metadata for %v, which may have undesirable side effects", out.ID) 821 } 822 out.IstioVersion = ParseIstioVersion(metadata.IstioVersion) 823 return out, nil 824} 825 826// ParseIstioVersion parses a version string and returns IstioVersion struct 827func ParseIstioVersion(ver string) *IstioVersion { 828 // strip the release- prefix if any and extract the version string 829 ver = istioVersionRegexp.FindString(strings.TrimPrefix(ver, "release-")) 830 831 if ver == "" { 832 // return very large values assuming latest version 833 return MaxIstioVersion 834 } 835 836 parts := strings.Split(ver, ".") 837 // we are guaranteed to have atleast major and minor based on the regex 838 major, _ := strconv.Atoi(parts[0]) 839 minor, _ := strconv.Atoi(parts[1]) 840 patch := 0 841 if len(parts) > 2 { 842 patch, _ = strconv.Atoi(parts[2]) 843 } 844 return &IstioVersion{Major: major, Minor: minor, Patch: patch} 845} 846 847// GetOrDefault returns either the value, or the default if the value is empty. Useful when retrieving node metadata fields. 848func GetOrDefault(s string, def string) string { 849 if len(s) > 0 { 850 return s 851 } 852 return def 853} 854 855// GetProxyConfigNamespace extracts the namespace associated with the proxy 856// from the proxy metadata or the proxy ID 857func GetProxyConfigNamespace(proxy *Proxy) string { 858 if proxy == nil { 859 return "" 860 } 861 862 // First look for ISTIO_META_CONFIG_NAMESPACE 863 // All newer proxies (from Istio 1.1 onwards) are supposed to supply this 864 if len(proxy.Metadata.ConfigNamespace) > 0 { 865 return proxy.Metadata.ConfigNamespace 866 } 867 868 // if not found, for backward compatibility, extract the namespace from 869 // the proxy domain. this is a k8s specific hack and should be enabled 870 parts := strings.Split(proxy.DNSDomain, ".") 871 if len(parts) > 1 { // k8s will have namespace.<domain> 872 return parts[0] 873 } 874 875 return "" 876} 877 878const ( 879 serviceNodeSeparator = "~" 880) 881 882// ParsePort extracts port number from a valid proxy address 883func ParsePort(addr string) int { 884 port, err := strconv.Atoi(addr[strings.Index(addr, ":")+1:]) 885 if err != nil { 886 log.Warna(err) 887 } 888 889 return port 890} 891 892// hasValidIPAddresses returns true if the input ips are all valid, otherwise returns false. 893func hasValidIPAddresses(ipAddresses []string) bool { 894 if len(ipAddresses) == 0 { 895 return false 896 } 897 for _, ipAddress := range ipAddresses { 898 if !isValidIPAddress(ipAddress) { 899 return false 900 } 901 } 902 return true 903} 904 905// Tell whether the given IP address is valid or not 906func isValidIPAddress(ip string) bool { 907 return net.ParseIP(ip) != nil 908} 909 910// Pile all node metadata constants here 911const ( 912 // NodeMetadataTLSServerCertChain is the absolute path to server cert-chain file 913 NodeMetadataTLSServerCertChain = "TLS_SERVER_CERT_CHAIN" 914 915 // NodeMetadataTLSServerKey is the absolute path to server private key file 916 NodeMetadataTLSServerKey = "TLS_SERVER_KEY" 917 918 // NodeMetadataTLSServerRootCert is the absolute path to server root cert file 919 NodeMetadataTLSServerRootCert = "TLS_SERVER_ROOT_CERT" 920 921 // NodeMetadataTLSClientCertChain is the absolute path to client cert-chain file 922 NodeMetadataTLSClientCertChain = "TLS_CLIENT_CERT_CHAIN" 923 924 // NodeMetadataTLSClientKey is the absolute path to client private key file 925 NodeMetadataTLSClientKey = "TLS_CLIENT_KEY" 926 927 // NodeMetadataTLSClientRootCert is the absolute path to client root cert file 928 NodeMetadataTLSClientRootCert = "TLS_CLIENT_ROOT_CERT" 929) 930 931// TrafficInterceptionMode indicates how traffic to/from the workload is captured and 932// sent to Envoy. This should not be confused with the CaptureMode in the API that indicates 933// how the user wants traffic to be intercepted for the listener. TrafficInterceptionMode is 934// always derived from the Proxy metadata 935type TrafficInterceptionMode string 936 937const ( 938 // InterceptionNone indicates that the workload is not using IPtables for traffic interception 939 InterceptionNone TrafficInterceptionMode = "NONE" 940 941 // InterceptionTproxy implies traffic intercepted by IPtables with TPROXY mode 942 InterceptionTproxy TrafficInterceptionMode = "TPROXY" 943 944 // InterceptionRedirect implies traffic intercepted by IPtables with REDIRECT mode 945 // This is our default mode 946 InterceptionRedirect TrafficInterceptionMode = "REDIRECT" 947) 948 949// GetInterceptionMode extracts the interception mode associated with the proxy 950// from the proxy metadata 951func (node *Proxy) GetInterceptionMode() TrafficInterceptionMode { 952 if node == nil { 953 return InterceptionRedirect 954 } 955 956 switch node.Metadata.InterceptionMode { 957 case "TPROXY": 958 return InterceptionTproxy 959 case "REDIRECT": 960 return InterceptionRedirect 961 case "NONE": 962 return InterceptionNone 963 } 964 965 return InterceptionRedirect 966} 967