-
Notifications
You must be signed in to change notification settings - Fork 7.6k
/
context.go
1029 lines (873 loc) · 33.1 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"encoding/json"
"fmt"
"net"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
anypb "google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/structpb"
meshconfig "istio.io/api/mesh/v1alpha1"
"istio.io/istio/pilot/pkg/credentials"
"istio.io/istio/pilot/pkg/features"
istionetworking "istio.io/istio/pilot/pkg/networking"
"istio.io/istio/pilot/pkg/serviceregistry/util/label"
"istio.io/istio/pilot/pkg/trustbundle"
networkutil "istio.io/istio/pilot/pkg/util/network"
v3 "istio.io/istio/pilot/pkg/xds/v3"
"istio.io/istio/pkg/cluster"
"istio.io/istio/pkg/config/constants"
"istio.io/istio/pkg/config/host"
"istio.io/istio/pkg/config/mesh"
"istio.io/istio/pkg/ledger"
"istio.io/istio/pkg/maps"
pm "istio.io/istio/pkg/model"
"istio.io/istio/pkg/monitoring"
"istio.io/istio/pkg/network"
"istio.io/istio/pkg/spiffe"
"istio.io/istio/pkg/util/identifier"
netutil "istio.io/istio/pkg/util/net"
"istio.io/istio/pkg/util/protomarshal"
"istio.io/istio/pkg/util/sets"
"istio.io/istio/pkg/xds"
)
type (
Node = pm.Node
NodeMetadata = pm.NodeMetadata
NodeMetaProxyConfig = pm.NodeMetaProxyConfig
NodeType = pm.NodeType
BootstrapNodeMetadata = pm.BootstrapNodeMetadata
TrafficInterceptionMode = pm.TrafficInterceptionMode
PodPort = pm.PodPort
StringBool = pm.StringBool
IPMode = pm.IPMode
)
const (
SidecarProxy = pm.SidecarProxy
Router = pm.Router
Waypoint = pm.Waypoint
Ztunnel = pm.Ztunnel
IPv4 = pm.IPv4
IPv6 = pm.IPv6
Dual = pm.Dual
)
var _ mesh.Holder = &Environment{}
func NewEnvironment() *Environment {
var cache XdsCache
if features.EnableXDSCaching {
cache = NewXdsCache()
} else {
cache = DisabledCache{}
}
return &Environment{
pushContext: NewPushContext(),
Cache: cache,
EndpointIndex: NewEndpointIndex(cache),
}
}
// Environment provides an aggregate environmental API for Pilot
type Environment struct {
// Discovery interface for listing services and instances.
ServiceDiscovery
// Config interface for listing routing rules
ConfigStore
// Watcher is the watcher for the mesh config (to be merged into the config store)
mesh.Watcher
// NetworksWatcher (loaded from a config map) provides information about the
// set of networks inside a mesh and how to route to endpoints in each
// network. Each network provides information about the endpoints in a
// routable L3 network. A single routable L3 network can have one or more
// service registries.
NetworksWatcher mesh.NetworksWatcher
NetworkManager *NetworkManager
// mutex used for protecting Environment.pushContext
mutex sync.RWMutex
// pushContext holds information during push generation. It is reset on config change, at the beginning
// of the pushAll. It will hold all errors and stats and possibly caches needed during the entire cache computation.
// DO NOT USE EXCEPT FOR TESTS AND HANDLING OF NEW CONNECTIONS.
// ALL USE DURING A PUSH SHOULD USE THE ONE CREATED AT THE
// START OF THE PUSH, THE GLOBAL ONE MAY CHANGE AND REFLECT A DIFFERENT
// CONFIG AND PUSH
pushContext *PushContext
// DomainSuffix provides a default domain for the Istio server.
DomainSuffix string
ledger ledger.Ledger
// TrustBundle: List of Mesh TrustAnchors
TrustBundle *trustbundle.TrustBundle
clusterLocalServices ClusterLocalProvider
CredentialsController credentials.MulticlusterController
GatewayAPIController GatewayController
// EndpointShards for a service. This is a global (per-server) list, built from
// incremental updates. This is keyed by service and namespace
EndpointIndex *EndpointIndex
// Cache for XDS resources.
Cache XdsCache
}
func (e *Environment) Mesh() *meshconfig.MeshConfig {
if e != nil && e.Watcher != nil {
return e.Watcher.Mesh()
}
return nil
}
func (e *Environment) MeshNetworks() *meshconfig.MeshNetworks {
if e != nil && e.NetworksWatcher != nil {
return e.NetworksWatcher.Networks()
}
return nil
}
// SetPushContext sets the push context with lock protected
func (e *Environment) SetPushContext(pc *PushContext) {
e.mutex.Lock()
defer e.mutex.Unlock()
e.pushContext = pc
}
// PushContext returns the push context with lock protected
func (e *Environment) PushContext() *PushContext {
e.mutex.RLock()
defer e.mutex.RUnlock()
return e.pushContext
}
// GetDiscoveryAddress parses the DiscoveryAddress specified via MeshConfig.
func (e *Environment) GetDiscoveryAddress() (host.Name, string, error) {
proxyConfig := mesh.DefaultProxyConfig()
if e.Mesh().DefaultConfig != nil {
proxyConfig = e.Mesh().DefaultConfig
}
hostname, port, err := net.SplitHostPort(proxyConfig.DiscoveryAddress)
if err != nil {
return "", "", fmt.Errorf("invalid Istiod Address: %s, %v", proxyConfig.DiscoveryAddress, err)
}
if _, err := strconv.Atoi(port); err != nil {
return "", "", fmt.Errorf("invalid Istiod Port: %s, %s, %v", port, proxyConfig.DiscoveryAddress, err)
}
return host.Name(hostname), port, nil
}
func (e *Environment) AddMeshHandler(h func()) {
if e != nil && e.Watcher != nil {
e.Watcher.AddMeshHandler(h)
}
}
func (e *Environment) AddNetworksHandler(h func()) {
if e != nil && e.NetworksWatcher != nil {
e.NetworksWatcher.AddNetworksHandler(h)
}
}
func (e *Environment) AddMetric(metric monitoring.Metric, key string, proxyID, msg string) {
if e != nil {
e.PushContext().AddMetric(metric, key, proxyID, msg)
}
}
func (e *Environment) Version() string {
if x := e.GetLedger(); x != nil {
return x.RootHash()
}
return ""
}
// Init initializes the Environment for use.
func (e *Environment) Init() {
// Use a default DomainSuffix, if none was provided.
if len(e.DomainSuffix) == 0 {
e.DomainSuffix = constants.DefaultClusterLocalDomain
}
// Create the cluster-local service registry.
e.clusterLocalServices = NewClusterLocalProvider(e)
}
func (e *Environment) InitNetworksManager(updater XDSUpdater) (err error) {
e.NetworkManager, err = NewNetworkManager(e, updater)
return
}
func (e *Environment) ClusterLocal() ClusterLocalProvider {
return e.clusterLocalServices
}
func (e *Environment) GetLedger() ledger.Ledger {
return e.ledger
}
func (e *Environment) SetLedger(l ledger.Ledger) {
e.ledger = l
}
func (e *Environment) GetProxyConfigOrDefault(ns string, labels, annotations map[string]string, meshConfig *meshconfig.MeshConfig) *meshconfig.ProxyConfig {
push := e.PushContext()
if push != nil && push.ProxyConfigs != nil {
if generatedProxyConfig := push.ProxyConfigs.EffectiveProxyConfig(
&NodeMetadata{
Namespace: ns,
Labels: labels,
Annotations: annotations,
}, meshConfig); generatedProxyConfig != nil {
return generatedProxyConfig
}
}
return mesh.DefaultProxyConfig()
}
// Resources is an alias for array of marshaled resources.
type Resources = []*discovery.Resource
// DeletedResources is an alias for array of strings that represent removed resources in delta.
type DeletedResources = []string
func AnyToUnnamedResources(r []*anypb.Any) Resources {
a := make(Resources, 0, len(r))
for _, rr := range r {
a = append(a, &discovery.Resource{Resource: rr})
}
return a
}
// XdsUpdates include information about the subset of updated resources.
// See for example EDS incremental updates.
type XdsUpdates = sets.Set[ConfigKey]
// XdsLogDetails contains additional metadata that is captured by Generators and used by xds processors
// like Ads and Delta to uniformly log.
type XdsLogDetails struct {
Incremental bool
AdditionalInfo string
}
var DefaultXdsLogDetails = XdsLogDetails{}
// XdsResourceGenerator creates the response for a typeURL DiscoveryRequest or DeltaDiscoveryRequest. If no generator
// is associated with a Proxy, the default (a networking.core.ConfigGenerator instance) will be used.
// The server may associate a different generator based on client metadata. Different
// WatchedResources may use same or different Generator.
// Note: any errors returned will completely close the XDS stream. Use with caution; typically and empty
// or no response is preferred.
type XdsResourceGenerator interface {
// Generate generates the Sotw resources for Xds.
Generate(proxy *Proxy, w *WatchedResource, req *PushRequest) (Resources, XdsLogDetails, error)
}
// XdsDeltaResourceGenerator generates Sotw and delta resources.
type XdsDeltaResourceGenerator interface {
XdsResourceGenerator
// GenerateDeltas returns the changed and removed resources, along with whether or not delta was actually used.
GenerateDeltas(proxy *Proxy, req *PushRequest, w *WatchedResource) (Resources, DeletedResources, XdsLogDetails, bool, error)
}
// Proxy contains information about an specific instance of a proxy (envoy sidecar, gateway,
// etc). The Proxy is initialized when a sidecar connects to Pilot, and populated from
// 'node' info in the protocol as well as data extracted from registries.
//
// In current Istio implementation nodes use a 4-parts '~' delimited ID.
// Type~IPAddress~ID~Domain
type Proxy struct {
sync.RWMutex
// Type specifies the node type. First part of the ID.
Type NodeType
// IPAddresses is the IP addresses of the proxy used to identify it and its
// co-located service instances. Example: "10.60.1.6". In some cases, the host
// where the proxy and service instances reside may have more than one IP address
IPAddresses []string
// ID is the unique platform-specific sidecar proxy ID. For k8s it is the pod ID and
// namespace <podName.namespace>.
ID string
// Locality is the location of where Envoy proxy runs. This is extracted from
// the registry where possible. If the registry doesn't provide a locality for the
// proxy it will use the one sent via ADS that can be configured in the Envoy bootstrap
Locality *core.Locality
// DNSDomain defines the DNS domain suffix for short hostnames (e.g.
// "default.svc.cluster.local")
DNSDomain string
// ConfigNamespace defines the namespace where this proxy resides
// for the purposes of network scoping.
// NOTE: DO NOT USE THIS FIELD TO CONSTRUCT DNS NAMES
ConfigNamespace string
// Labels specifies the set of workload instance (ex: k8s pod) labels associated with this node.
// Labels can be different from that in Metadata because of pod labels update after startup,
// while NodeMetadata.Labels are set during bootstrap.
Labels map[string]string
// Metadata key-value pairs extending the Node identifier
Metadata *NodeMetadata
// the sidecarScope associated with the proxy
SidecarScope *SidecarScope
// the sidecarScope associated with the proxy previously
PrevSidecarScope *SidecarScope
// The merged gateways associated with the proxy if this is a Router
MergedGateway *MergedGateway
// PrevMergedGateway contains information about merged gateway associated with the proxy previously
PrevMergedGateway *PrevMergedGateway
// ServiceTargets contains a list of all Services associated with the proxy, contextualized for this particular proxy.
// These are unique to this proxy, as the port information is specific to it - while a ServicePort is shared with the
// service, the target port may be distinct per-endpoint. So this maintains a view specific to this proxy.
// ServiceTargets will maintain a list entry for each Service-port, so if we have 2 services each with 3 ports, we
// would have 6 entries.
ServiceTargets []ServiceTarget
// Istio version associated with the Proxy
IstioVersion *IstioVersion
// VerifiedIdentity determines whether a proxy had its identity verified. This
// generally occurs by JWT or mTLS authentication. This can be false when
// connecting over plaintext. If this is set to true, we can verify the proxy has
// access to ConfigNamespace namespace. However, other options such as node type
// are not part of an Istio identity and thus are not verified.
VerifiedIdentity *spiffe.Identity
// IPMode of proxy.
ipMode IPMode
// GlobalUnicastIP stores the global unicast IP if available, otherwise nil
GlobalUnicastIP string
// XdsResourceGenerator is used to generate resources for the node, based on the PushContext.
// If nil, the default networking/core v2 generator is used. This field can be set
// at connect time, based on node metadata, to trigger generation of a different style
// of configuration.
XdsResourceGenerator XdsResourceGenerator
// WatchedResources contains the list of watched resources for the proxy, keyed by the DiscoveryRequest TypeUrl.
WatchedResources map[string]*WatchedResource
// XdsNode is the xDS node identifier
XdsNode *core.Node
workloadEntryName string
workloadEntryAutoCreated bool
// LastPushContext stores the most recent push context for this proxy. This will be monotonically
// increasing in version. Requests should send config based on this context; not the global latest.
// Historically, the latest was used which can cause problems when computing whether a push is
// required, as the computed sidecar scope version would not monotonically increase.
LastPushContext *PushContext
// LastPushTime records the time of the last push. This is used in conjunction with
// LastPushContext; the XDS cache depends on knowing the time of the PushContext to determine if a
// key is stale or not.
LastPushTime time.Time
}
type WatchedResource = xds.WatchedResource
var istioVersionRegexp = regexp.MustCompile(`^([1-9]+)\.([0-9]+)(\.([0-9]+))?`)
// GetView returns a restricted view of the mesh for this proxy. The view can be
// restricted by network (via ISTIO_META_REQUESTED_NETWORK_VIEW).
// If not set, we assume that the proxy wants to see endpoints in any network.
func (node *Proxy) GetView() ProxyView {
return newProxyView(node)
}
// InNetwork returns true if the proxy is on the given network, or if either
// the proxy's network or the given network is unspecified ("").
func (node *Proxy) InNetwork(network network.ID) bool {
return node == nil || identifier.IsSameOrEmpty(network.String(), node.Metadata.Network.String())
}
// InCluster returns true if the proxy is in the given cluster, or if either
// the proxy's cluster id or the given cluster id is unspecified ("").
func (node *Proxy) InCluster(cluster cluster.ID) bool {
return node == nil || identifier.IsSameOrEmpty(cluster.String(), node.Metadata.ClusterID.String())
}
// IsWaypointProxy returns true if the proxy is acting as a waypoint proxy in an ambient mesh.
func (node *Proxy) IsWaypointProxy() bool {
return node.Type == Waypoint
}
// IsZTunnel returns true if the proxy is acting as a ztunnel in an ambient mesh.
func (node *Proxy) IsZTunnel() bool {
return node.Type == Ztunnel
}
// IsAmbient returns true if the proxy is acting as either a ztunnel or a waypoint proxy in an ambient mesh.
func (node *Proxy) IsAmbient() bool {
return node.IsWaypointProxy() || node.IsZTunnel()
}
// IstioVersion encodes the Istio version of the proxy. This is a low key way to
// do semver style comparisons and generate the appropriate envoy config
type IstioVersion struct {
Major int
Minor int
Patch int
}
var MaxIstioVersion = &IstioVersion{Major: 65535, Minor: 65535, Patch: 65535}
// Compare returns -1/0/1 if version is less than, equal or greater than inv
// To compare only on major, call this function with { X, -1, -1}.
// to compare only on major & minor, call this function with {X, Y, -1}.
func (pversion *IstioVersion) Compare(inv *IstioVersion) int {
// check major
if r := compareVersion(pversion.Major, inv.Major); r != 0 {
return r
}
// check minor
if inv.Minor > -1 {
if r := compareVersion(pversion.Minor, inv.Minor); r != 0 {
return r
}
// check patch
if inv.Patch > -1 {
if r := compareVersion(pversion.Patch, inv.Patch); r != 0 {
return r
}
}
}
return 0
}
func compareVersion(ov, nv int) int {
if ov == nv {
return 0
}
if ov < nv {
return -1
}
return 1
}
var NodeTypes = [...]NodeType{SidecarProxy, Router, Waypoint, Ztunnel}
// SetSidecarScope identifies the sidecar scope object associated with this
// proxy and updates the proxy Node. This is a convenience hack so that
// callers can simply call push.Services(node) while the implementation of
// push.Services can return the set of services from the proxyNode's
// sidecar scope or from the push context's set of global services. Similar
// logic applies to push.VirtualServices and push.DestinationRule. The
// short cut here is useful only for CDS and parts of RDS generation code.
//
// Listener generation code will still use the SidecarScope object directly
// as it needs the set of services for each listener port.
func (node *Proxy) SetSidecarScope(ps *PushContext) {
sidecarScope := node.SidecarScope
switch node.Type {
case SidecarProxy:
node.SidecarScope = ps.getSidecarScope(node, node.Labels)
case Router, Waypoint:
// Gateways should just have a default scope with egress: */*
node.SidecarScope = ps.getSidecarScope(node, nil)
}
node.PrevSidecarScope = sidecarScope
}
func (node *Proxy) VersionGreaterAndEqual(inv *IstioVersion) bool {
if inv == nil {
return true
}
return node.IstioVersion.Compare(inv) >= 0
}
// SetGatewaysForProxy merges the Gateway objects associated with this
// proxy and caches the merged object in the proxy Node. This is a convenience hack so that
// callers can simply call push.MergedGateways(node) instead of having to
// fetch all the gateways and invoke the merge call in multiple places (lds/rds).
// Must be called after ServiceTargets are set
func (node *Proxy) SetGatewaysForProxy(ps *PushContext) {
if node.Type != Router {
return
}
var prevMergedGateway MergedGateway
if node.MergedGateway != nil {
prevMergedGateway = *node.MergedGateway
}
node.MergedGateway = ps.mergeGateways(node)
node.PrevMergedGateway = &PrevMergedGateway{
ContainsAutoPassthroughGateways: prevMergedGateway.ContainsAutoPassthroughGateways,
AutoPassthroughSNIHosts: prevMergedGateway.GetAutoPassthrughGatewaySNIHosts(),
}
}
func (node *Proxy) SetServiceTargets(serviceDiscovery ServiceDiscovery) {
instances := serviceDiscovery.GetProxyServiceTargets(node)
// Keep service instances in order of creation/hostname.
sort.SliceStable(instances, func(i, j int) bool {
if instances[i].Service != nil && instances[j].Service != nil {
if !instances[i].Service.CreationTime.Equal(instances[j].Service.CreationTime) {
return instances[i].Service.CreationTime.Before(instances[j].Service.CreationTime)
}
// Additionally, sort by hostname just in case services created automatically at the same second.
return instances[i].Service.Hostname < instances[j].Service.Hostname
}
return true
})
node.ServiceTargets = instances
}
// SetWorkloadLabels will set the node.Labels.
// It merges both node meta labels and workload labels and give preference to workload labels.
func (node *Proxy) SetWorkloadLabels(env *Environment) {
// If this is VM proxy, do not override labels at all, because in istio test we use pod to simulate VM.
if node.IsVM() {
node.Labels = node.Metadata.Labels
return
}
labels := env.GetProxyWorkloadLabels(node)
if labels != nil {
node.Labels = make(map[string]string, len(labels)+len(node.Metadata.StaticLabels))
// we can't just equate proxy workload labels to node meta labels as it may be customized by user
// with `ISTIO_METAJSON_LABELS` env (pkg/bootstrap/config.go extractAttributesMetadata).
// so, we fill the `ISTIO_METAJSON_LABELS` as well.
for k, v := range node.Metadata.StaticLabels {
node.Labels[k] = v
}
for k, v := range labels {
node.Labels[k] = v
}
} else {
// If could not find pod labels, fallback to use the node metadata labels.
node.Labels = node.Metadata.Labels
}
}
// DiscoverIPMode discovers the IP Versions supported by Proxy based on its IP addresses.
func (node *Proxy) DiscoverIPMode() {
node.ipMode = pm.DiscoverIPMode(node.IPAddresses)
node.GlobalUnicastIP = networkutil.GlobalUnicastIP(node.IPAddresses)
}
// SupportsIPv4 returns true if proxy supports IPv4 addresses.
func (node *Proxy) SupportsIPv4() bool {
return node.ipMode == IPv4 || node.ipMode == Dual
}
// SupportsIPv6 returns true if proxy supports IPv6 addresses.
func (node *Proxy) SupportsIPv6() bool {
return node.ipMode == IPv6 || node.ipMode == Dual
}
// IsIPv6 returns true if proxy only supports IPv6 addresses.
func (node *Proxy) IsIPv6() bool {
return node.ipMode == IPv6
}
func (node *Proxy) IsDualStack() bool {
return node.ipMode == Dual
}
// GetIPMode returns proxy's ipMode
func (node *Proxy) GetIPMode() IPMode {
return node.ipMode
}
// ParseMetadata parses the opaque Metadata from an Envoy Node into string key-value pairs.
// Any non-string values are ignored.
func ParseMetadata(metadata *structpb.Struct) (*NodeMetadata, error) {
if metadata == nil {
return &NodeMetadata{}, nil
}
bootstrapNodeMeta, err := ParseBootstrapNodeMetadata(metadata)
if err != nil {
return nil, err
}
return &bootstrapNodeMeta.NodeMetadata, nil
}
// ParseBootstrapNodeMetadata parses the opaque Metadata from an Envoy Node into string key-value pairs.
func ParseBootstrapNodeMetadata(metadata *structpb.Struct) (*BootstrapNodeMetadata, error) {
if metadata == nil {
return &BootstrapNodeMetadata{}, nil
}
b, err := protomarshal.MarshalProtoNames(metadata)
if err != nil {
return nil, fmt.Errorf("failed to read node metadata %v: %v", metadata, err)
}
meta := &BootstrapNodeMetadata{}
if err := json.Unmarshal(b, meta); err != nil {
return nil, fmt.Errorf("failed to unmarshal node metadata (%v): %v", string(b), err)
}
return meta, nil
}
// ParseServiceNodeWithMetadata parse the Envoy Node from the string generated by ServiceNode
// function and the metadata.
func ParseServiceNodeWithMetadata(nodeID string, metadata *NodeMetadata) (*Proxy, error) {
parts := strings.Split(nodeID, serviceNodeSeparator)
out := &Proxy{
Metadata: metadata,
}
if len(parts) != 4 {
return out, fmt.Errorf("missing parts in the service node %q", nodeID)
}
if !pm.IsApplicationNodeType(NodeType(parts[0])) {
return out, fmt.Errorf("invalid node type (valid types: %v) in the service node %q", NodeTypes, nodeID)
}
out.Type = NodeType(parts[0])
// Get all IP Addresses from Metadata
if hasValidIPAddresses(metadata.InstanceIPs) {
out.IPAddresses = metadata.InstanceIPs
} else if netutil.IsValidIPAddress(parts[1]) {
// Fall back, use IP from node id, it's only for backward-compatibility, IP should come from metadata
out.IPAddresses = append(out.IPAddresses, parts[1])
}
// Does query from ingress or router have to carry valid IP address?
if len(out.IPAddresses) == 0 {
return out, fmt.Errorf("no valid IP address in the service node id or metadata")
}
out.ID = parts[2]
out.DNSDomain = parts[3]
if len(metadata.IstioVersion) == 0 {
log.Warnf("Istio Version is not found in metadata for %v, which may have undesirable side effects", out.ID)
}
out.IstioVersion = ParseIstioVersion(metadata.IstioVersion)
return out, nil
}
// ParseIstioVersion parses a version string and returns IstioVersion struct
func ParseIstioVersion(ver string) *IstioVersion {
// strip the release- prefix if any and extract the version string
ver = istioVersionRegexp.FindString(strings.TrimPrefix(ver, "release-"))
if ver == "" {
// return very large values assuming latest version
return MaxIstioVersion
}
parts := strings.Split(ver, ".")
// we are guaranteed to have at least major and minor based on the regex
major, _ := strconv.Atoi(parts[0])
minor, _ := strconv.Atoi(parts[1])
// Assume very large patch release if not set
patch := 65535
if len(parts) > 2 {
patch, _ = strconv.Atoi(parts[2])
}
return &IstioVersion{Major: major, Minor: minor, Patch: patch}
}
// GetOrDefault returns either the value, or the default if the value is empty. Useful when retrieving node metadata fields.
func GetOrDefault(s string, def string) string {
return pm.GetOrDefault(s, def)
}
// GetProxyConfigNamespace extracts the namespace associated with the proxy
// from the proxy metadata or the proxy ID
func GetProxyConfigNamespace(proxy *Proxy) string {
if proxy == nil {
return ""
}
// First look for ISTIO_META_CONFIG_NAMESPACE
// All newer proxies (from Istio 1.1 onwards) are supposed to supply this
if len(proxy.Metadata.Namespace) > 0 {
return proxy.Metadata.Namespace
}
// if not found, for backward compatibility, extract the namespace from
// the proxy domain. this is a k8s specific hack and should be enabled
parts := strings.Split(proxy.DNSDomain, ".")
if len(parts) > 1 { // k8s will have namespace.<domain>
return parts[0]
}
return ""
}
const (
serviceNodeSeparator = "~"
)
// ParsePort extracts port number from a valid proxy address
func ParsePort(addr string) int {
_, sPort, err := net.SplitHostPort(addr)
if sPort == "" {
return 0
}
if err != nil {
log.Warn(err)
}
port, pErr := strconv.Atoi(sPort)
if pErr != nil {
log.Warn(pErr)
}
return port
}
// hasValidIPAddresses returns true if the input ips are all valid, otherwise returns false.
func hasValidIPAddresses(ipAddresses []string) bool {
if len(ipAddresses) == 0 {
return false
}
for _, ipAddress := range ipAddresses {
if !netutil.IsValidIPAddress(ipAddress) {
return false
}
}
return true
}
const (
// InterceptionNone indicates that the workload is not using IPtables for traffic interception
InterceptionNone TrafficInterceptionMode = "NONE"
// InterceptionTproxy implies traffic intercepted by IPtables with TPROXY mode
InterceptionTproxy TrafficInterceptionMode = "TPROXY"
// InterceptionRedirect implies traffic intercepted by IPtables with REDIRECT mode
// This is our default mode
InterceptionRedirect TrafficInterceptionMode = "REDIRECT"
)
// GetInterceptionMode extracts the interception mode associated with the proxy
// from the proxy metadata
func (node *Proxy) GetInterceptionMode() TrafficInterceptionMode {
if node == nil {
return InterceptionRedirect
}
switch node.Metadata.InterceptionMode {
case "TPROXY":
return InterceptionTproxy
case "REDIRECT":
return InterceptionRedirect
case "NONE":
return InterceptionNone
}
return InterceptionRedirect
}
// IsUnprivileged returns true if the proxy has explicitly indicated that it is
// unprivileged, i.e. it cannot bind to the privileged ports 1-1023.
func (node *Proxy) IsUnprivileged() bool {
if node == nil || node.Metadata == nil {
return false
}
// expect explicit "true" value
unprivileged, _ := strconv.ParseBool(node.Metadata.UnprivilegedPod)
return unprivileged
}
// CanBindToPort returns true if the proxy can bind to a given port.
func (node *Proxy) CanBindToPort(bindTo bool, port uint32) bool {
if bindTo {
if IsPrivilegedPort(port) && node.IsUnprivileged() {
return false
}
if node.Metadata != nil &&
(node.Metadata.EnvoyPrometheusPort == int(port) || node.Metadata.EnvoyStatusPort == int(port)) {
// can not bind to port that already bound by proxy static listener
return false
}
}
return true
}
// IsPrivilegedPort returns true if a given port is in the range 1-1023.
func IsPrivilegedPort(port uint32) bool {
// check for 0 is important because:
// 1) technically, 0 is not a privileged port; any process can ask to bind to 0
// 2) this function will be receiving 0 on input in the case of UDS listeners
return 0 < port && port < 1024
}
func (node *Proxy) IsVM() bool {
// TODO use node metadata to indicate that this is a VM instead of the TestVMLabel
return node.Metadata.Labels[constants.TestVMLabel] != ""
}
func (node *Proxy) IsProxylessGrpc() bool {
return node.Metadata != nil && node.Metadata.Generator == "grpc"
}
func (node *Proxy) GetNodeName() string {
if node.Metadata != nil && len(node.Metadata.NodeName) > 0 {
return node.Metadata.NodeName
}
// fall back to get the node name from labels
// this can happen for an "old" proxy with no `Metadata.NodeName` set
// TODO: remove this when 1.16 is EOL?
return node.Labels[label.LabelHostname]
}
func (node *Proxy) GetClusterID() cluster.ID {
if node == nil || node.Metadata == nil {
return ""
}
return node.Metadata.ClusterID
}
func (node *Proxy) GetNamespace() string {
if node == nil || node.Metadata == nil {
return ""
}
return node.Metadata.Namespace
}
func (node *Proxy) GetIstioVersion() string {
if node == nil || node.Metadata == nil {
return ""
}
return node.Metadata.IstioVersion
}
func (node *Proxy) GetID() string {
if node == nil {
return ""
}
return node.ID
}
func (node *Proxy) FuzzValidate() bool {
if node.Metadata == nil {
return false
}
found := false
for _, t := range NodeTypes {
if node.Type == t {
found = true
break
}
}
if !found {
return false
}
return len(node.IPAddresses) != 0
}
func (node *Proxy) EnableHBONEListen() bool {
return node.IsAmbient() || (features.EnableSidecarHBONEListening && bool(node.Metadata.EnableHBONE))
}
func (node *Proxy) SetWorkloadEntry(name string, create bool) {
node.Lock()
defer node.Unlock()
node.workloadEntryName = name
node.workloadEntryAutoCreated = create
}
func (node *Proxy) WorkloadEntry() (string, bool) {
node.RLock()
defer node.RUnlock()
return node.workloadEntryName, node.workloadEntryAutoCreated
}
// CloneWatchedResources clones the watched resources, both the keys and values are shallow copy.
func (node *Proxy) CloneWatchedResources() map[string]*WatchedResource {
node.RLock()
defer node.RUnlock()
return maps.Clone(node.WatchedResources)
}
func (node *Proxy) GetWatchedResourceTypes() sets.String {
node.RLock()
defer node.RUnlock()
ret := sets.NewWithLength[string](len(node.WatchedResources))
for typeURL := range node.WatchedResources {
ret.Insert(typeURL)
}
return ret
}
func (node *Proxy) GetWatchedResource(typeURL string) *WatchedResource {
node.RLock()
defer node.RUnlock()
return node.WatchedResources[typeURL]
}
func (node *Proxy) NewWatchedResource(typeURL string, names []string) {
node.Lock()
defer node.Unlock()
node.WatchedResources[typeURL] = &WatchedResource{TypeUrl: typeURL, ResourceNames: names}
// For all EDS requests that we have already responded with in the same stream let us
// force the response. It is important to respond to those requests for Envoy to finish
// warming of those resources(Clusters).
// This can happen with the following sequence
// 1. Envoy disconnects and reconnects to Istiod.
// 2. Envoy sends EDS request and we respond with it.
// 3. Envoy sends CDS request and we respond with clusters.
// 4. Envoy detects a change in cluster state and tries to warm those clusters and send EDS request for them.
// 5. We should respond to the EDS request with Endpoints to let Envoy finish cluster warming.
// Refer to https://github.com/envoyproxy/envoy/issues/13009 for more details.
for _, dependent := range WarmingDependencies(typeURL) {
if dwr, exists := node.WatchedResources[dependent]; exists {
dwr.AlwaysRespond = true
}
}
}
// WarmingDependencies returns the dependent typeURLs that need to be responded with
// for warming of this typeURL.
func WarmingDependencies(typeURL string) []string {
switch typeURL {
case v3.ClusterType:
return []string{v3.EndpointType}
default:
return nil
}
}
func (node *Proxy) AddOrUpdateWatchedResource(r *WatchedResource) {
if r == nil {
return
}
node.Lock()
defer node.Unlock()
node.WatchedResources[r.TypeUrl] = r
}
func (node *Proxy) UpdateWatchedResource(typeURL string, updateFn func(*WatchedResource) *WatchedResource) {
node.Lock()
defer node.Unlock()
r := node.WatchedResources[typeURL]
r = updateFn(r)
if r != nil {
node.WatchedResources[typeURL] = r
} else {
delete(node.WatchedResources, typeURL)
}
}
func (node *Proxy) DeleteWatchedResource(typeURL string) {
node.Lock()
defer node.Unlock()