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