/
context.go
1110 lines (917 loc) · 38.4 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 (
"bytes"
"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"
"github.com/golang/protobuf/jsonpb"
any "google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/structpb"
meshconfig "istio.io/api/mesh/v1alpha1"
istionetworking "istio.io/istio/pilot/pkg/networking"
"istio.io/istio/pilot/pkg/trustbundle"
networkutil "istio.io/istio/pilot/pkg/util/network"
"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/network"
"istio.io/istio/pkg/spiffe"
"istio.io/istio/pkg/util/identifier"
"istio.io/istio/pkg/util/protomarshal"
"istio.io/pkg/ledger"
"istio.io/pkg/monitoring"
)
var _ mesh.Holder = &Environment{}
// 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
// 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
GatewayAPIController GatewayController
}
func (e *Environment) Mesh() *meshconfig.MeshConfig {
if e != nil && e.Watcher != nil {
return e.Watcher.Mesh()
}
return nil
}
// 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 != 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.DefaultKubernetesDomain
}
// 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
}
// 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 []*any.Any) Resources {
a := make(Resources, 0, len(r))
for _, rr := range r {
a = append(a, &discovery.Resource{Resource: rr})
}
return a
}
func ResourcesToAny(r Resources) []*any.Any {
a := make([]*any.Any, 0, len(r))
for _, rr := range r {
a = append(a, rr.Resource)
}
return a
}
// XdsUpdates include information about the subset of updated resources.
// See for example EDS incremental updates.
type XdsUpdates = map[ConfigKey]struct{}
// 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
// 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
// service instances associated with the proxy
ServiceInstances []*ServiceInstance
// 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
AutoregisteredWorkloadEntryName string
// 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
}
// WatchedResource tracks an active DiscoveryRequest subscription.
type WatchedResource struct {
// TypeUrl is copied from the DiscoveryRequest.TypeUrl that initiated watching this resource.
// nolint
TypeUrl string
// ResourceNames tracks the list of resources that are actively watched.
// For LDS and CDS, all resources of the TypeUrl type are watched if it is empty.
// For endpoints the resource names will have list of clusters and for clusters it is empty.
// For Delta Xds, all resources of the TypeUrl that a client has subscribed to.
ResourceNames []string
// VersionSent is the version of the resource included in the last sent response.
// It corresponds to the [Cluster/Route/Listener]VersionSent in the XDS package.
VersionSent string
// NonceSent is the nonce sent in the last sent response. If it is equal with NonceAcked, the
// last message has been processed. If empty: we never sent a message of this type.
NonceSent string
// NonceAcked is the last acked message.
NonceAcked string
// NonceNacked is the last nacked message. This is reset following a successful ACK
NonceNacked string
// LastSent tracks the time of the generated push, to determine the time it takes the client to ack.
LastSent time.Time
// LastResources tracks the contents of the last push.
// This field is extremely expensive to maintain and is typically disabled
LastResources Resources
}
var istioVersionRegexp = regexp.MustCompile(`^([1-9]+)\.([0-9]+)(\.([0-9]+))?`)
// StringList is a list that will be marshaled to a comma separate string in Json
type StringList []string
func (l StringList) MarshalJSON() ([]byte, error) {
if l == nil {
return nil, nil
}
return []byte(`"` + strings.Join(l, ",") + `"`), nil
}
func (l *StringList) UnmarshalJSON(data []byte) error {
if len(data) < 2 || string(data) == `""` {
*l = []string{}
} else {
*l = strings.Split(string(data[1:len(data)-1]), ",")
}
return nil
}
// PodPort describes a mapping of port name to port number. Generally, this is just the definition of
// a port in Kubernetes, but without depending on Kubernetes api.
type PodPort struct {
// If specified, this must be an IANA_SVC_NAME and unique within the pod. Each
// named port in a pod must have a unique name. Name for the port that can be
// referred to by services.
// +optional
Name string `json:"name,omitempty"`
// Number of port to expose on the pod's IP address.
// This must be a valid port number, 0 < x < 65536.
ContainerPort int `json:"containerPort"`
// Name of the protocol
Protocol string `json:"protocol"`
}
// PodPortList defines a list of PodPort's that is serialized as a string
// This is for legacy reasons, where proper JSON was not supported and was written as a string
type PodPortList []PodPort
func (l PodPortList) MarshalJSON() ([]byte, error) {
if l == nil {
return nil, nil
}
b, err := json.Marshal([]PodPort(l))
if err != nil {
return nil, err
}
b = bytes.ReplaceAll(b, []byte{'"'}, []byte{'\\', '"'})
out := append([]byte{'"'}, b...)
out = append(out, '"')
return out, nil
}
func (l *PodPortList) UnmarshalJSON(data []byte) error {
var pl []PodPort
pls, err := strconv.Unquote(string(data))
if err != nil {
return err
}
if err := json.Unmarshal([]byte(pls), &pl); err != nil {
return err
}
*l = pl
return nil
}
// StringBool defines a boolean that is serialized as a string for legacy reasons
type StringBool bool
func (s StringBool) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%t"`, s)), nil
}
func (s *StringBool) UnmarshalJSON(data []byte) error {
pls, err := strconv.Unquote(string(data))
if err != nil {
return err
}
b, err := strconv.ParseBool(pls)
if err != nil {
return err
}
*s = StringBool(b)
return nil
}
// ProxyConfig can only be marshaled using (gogo) jsonpb. However, the rest of node meta is not a proto
// To allow marshaling, we need to define a custom type that calls out to the gogo marshaller
type NodeMetaProxyConfig meshconfig.ProxyConfig
func (s *NodeMetaProxyConfig) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
pc := (*meshconfig.ProxyConfig)(s)
if err := (&jsonpb.Marshaler{}).Marshal(&buf, pc); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *NodeMetaProxyConfig) UnmarshalJSON(data []byte) error {
pc := (*meshconfig.ProxyConfig)(s)
return jsonpb.Unmarshal(bytes.NewReader(data), pc)
}
// Node is a typed version of Envoy node with metadata.
type Node struct {
// ID of the Envoy node
ID string
// Metadata is the typed node metadata
Metadata *BootstrapNodeMetadata
// RawMetadata is the untyped node metadata
RawMetadata map[string]interface{}
// Locality from Envoy bootstrap
Locality *core.Locality
}
// BootstrapNodeMetadata is a superset of NodeMetadata, intended to model the entirety of the node metadata
// we configure in the Envoy bootstrap. This is split out from NodeMetadata to explicitly segment the parameters
// that are consumed by Pilot from the parameters used only as part of the bootstrap. Fields used by bootstrap only
// are consumed by Envoy itself, such as the telemetry filters.
type BootstrapNodeMetadata struct {
NodeMetadata
// InstanceName is the short name for the workload instance (ex: pod name)
// replaces POD_NAME
InstanceName string `json:"NAME,omitempty"`
// WorkloadName specifies the name of the workload represented by this node.
WorkloadName string `json:"WORKLOAD_NAME,omitempty"`
// Owner specifies the workload owner (opaque string). Typically, this is the owning controller of
// of the workload instance (ex: k8s deployment for a k8s pod).
Owner string `json:"OWNER,omitempty"`
// PilotSAN is the list of subject alternate names for the xDS server.
PilotSubjectAltName []string `json:"PILOT_SAN,omitempty"`
// XDSRootCert defines the root cert to use for XDS connections
XDSRootCert string `json:"-"`
// OutlierLogPath is the cluster manager outlier event log path.
OutlierLogPath string `json:"OUTLIER_LOG_PATH,omitempty"`
// ProvCertDir is the directory containing pre-provisioned certs.
ProvCert string `json:"PROV_CERT,omitempty"`
// AppContainers is the list of containers in the pod.
AppContainers string `json:"APP_CONTAINERS,omitempty"`
// IstioProxySHA is the SHA of the proxy version.
IstioProxySHA string `json:"ISTIO_PROXY_SHA,omitempty"`
}
// NodeMetadata defines the metadata associated with a proxy
// Fields should not be assumed to exist on the proxy, especially newly added fields which will not exist
// on older versions.
// The JSON field names should never change, as they are needed for backward compatibility with older proxies
// nolint: maligned
type NodeMetadata struct {
// ProxyConfig defines the proxy config specified for a proxy.
// Note that this setting may be configured different for each proxy, due user overrides
// or from different versions of proxies connecting. While Pilot has access to the meshConfig.defaultConfig,
// this field should be preferred if it is present.
ProxyConfig *NodeMetaProxyConfig `json:"PROXY_CONFIG,omitempty"`
// IstioVersion specifies the Istio version associated with the proxy
IstioVersion string `json:"ISTIO_VERSION,omitempty"`
// IstioRevision specifies the Istio revision associated with the proxy.
// Mostly used when istiod requests the upstream.
IstioRevision string `json:"ISTIO_REVISION,omitempty"`
// Labels specifies the set of workload instance (ex: k8s pod) labels associated with this node.
Labels map[string]string `json:"LABELS,omitempty"`
// Labels specifies the set of workload instance (ex: k8s pod) annotations associated with this node.
Annotations map[string]string `json:"ANNOTATIONS,omitempty"`
// InstanceIPs is the set of IPs attached to this proxy
InstanceIPs StringList `json:"INSTANCE_IPS,omitempty"`
// Namespace is the namespace in which the workload instance is running.
Namespace string `json:"NAMESPACE,omitempty"`
// InterceptionMode is the name of the metadata variable that carries info about
// traffic interception mode at the proxy
InterceptionMode TrafficInterceptionMode `json:"INTERCEPTION_MODE,omitempty"`
// ServiceAccount specifies the service account which is running the workload.
ServiceAccount string `json:"SERVICE_ACCOUNT,omitempty"`
// HTTPProxyPort enables http proxy on the port for the current sidecar.
// Same as MeshConfig.HttpProxyPort, but with per/sidecar scope.
HTTPProxyPort string `json:"HTTP_PROXY_PORT,omitempty"`
// MeshID specifies the mesh ID environment variable.
MeshID string `json:"MESH_ID,omitempty"`
// ClusterID defines the cluster the node belongs to.
ClusterID cluster.ID `json:"CLUSTER_ID,omitempty"`
// Network defines the network the node belongs to. It is an optional metadata,
// set at injection time. When set, the Endpoints returned to a node and not on same network
// will be replaced with the gateway defined in the settings.
Network network.ID `json:"NETWORK,omitempty"`
// RequestedNetworkView specifies the networks that the proxy wants to see
RequestedNetworkView StringList `json:"REQUESTED_NETWORK_VIEW,omitempty"`
// PodPorts defines the ports on a pod. This is used to lookup named ports.
PodPorts PodPortList `json:"POD_PORTS,omitempty"`
// TLSServerCertChain is the absolute path to server cert-chain file
TLSServerCertChain string `json:"TLS_SERVER_CERT_CHAIN,omitempty"`
// TLSServerKey is the absolute path to server private key file
TLSServerKey string `json:"TLS_SERVER_KEY,omitempty"`
// TLSServerRootCert is the absolute path to server root cert file
TLSServerRootCert string `json:"TLS_SERVER_ROOT_CERT,omitempty"`
// TLSClientCertChain is the absolute path to client cert-chain file
TLSClientCertChain string `json:"TLS_CLIENT_CERT_CHAIN,omitempty"`
// TLSClientKey is the absolute path to client private key file
TLSClientKey string `json:"TLS_CLIENT_KEY,omitempty"`
// TLSClientRootCert is the absolute path to client root cert file
TLSClientRootCert string `json:"TLS_CLIENT_ROOT_CERT,omitempty"`
CertBaseDir string `json:"BASE,omitempty"`
// IdleTimeout specifies the idle timeout for the proxy, in duration format (10s).
// If not set, default timeout is 1 hour.
IdleTimeout string `json:"IDLE_TIMEOUT,omitempty"`
// HTTP10 indicates the application behind the sidecar is making outbound http requests with HTTP/1.0
// protocol. It will enable the "AcceptHttp_10" option on the http options for outbound HTTP listeners.
// Alpha in 1.1, based on feedback may be turned into an API or change. Set to "1" to enable.
HTTP10 string `json:"HTTP10,omitempty"`
// Generator indicates the client wants to use a custom Generator plugin.
Generator string `json:"GENERATOR,omitempty"`
// DNSCapture indicates whether the workload has enabled dns capture
DNSCapture StringBool `json:"DNS_CAPTURE,omitempty"`
// DNSAutoAllocate indicates whether the workload should have auto allocated addresses for ServiceEntry
// This allows resolving ServiceEntries, which is especially useful for distinguishing TCP traffic
// This depends on DNSCapture.
DNSAutoAllocate StringBool `json:"DNS_AUTO_ALLOCATE,omitempty"`
// AutoRegister will enable auto registration of the connected endpoint to the service registry using the given WorkloadGroup name
AutoRegisterGroup string `json:"AUTO_REGISTER_GROUP,omitempty"`
// UnprivilegedPod is used to determine whether a Gateway Pod can open ports < 1024
UnprivilegedPod string `json:"UNPRIVILEGED_POD,omitempty"`
// PlatformMetadata contains any platform specific metadata
PlatformMetadata map[string]string `json:"PLATFORM_METADATA,omitempty"`
// StsPort specifies the port of security token exchange server (STS).
// Used by envoy filters
StsPort string `json:"STS_PORT,omitempty"`
// Envoy status port redirecting to agent status port.
EnvoyStatusPort int `json:"ENVOY_STATUS_PORT,omitempty"`
// Envoy prometheus port redirecting to admin port prometheus endpoint.
EnvoyPrometheusPort int `json:"ENVOY_PROMETHEUS_PORT,omitempty"`
// ExitOnZeroActiveConnections terminates Envoy if there are no active connections if set.
ExitOnZeroActiveConnections StringBool `json:"EXIT_ON_ZERO_ACTIVE_CONNECTIONS,omitempty"`
// InboundListenerExactBalance sets connection balance config to use exact_balance for virtualInbound,
// as long as QUIC, since it uses UDP, isn't also used.
InboundListenerExactBalance StringBool `json:"INBOUND_LISTENER_EXACT_BALANCE,omitempty"`
// OutboundListenerExactBalance sets connection balance config to use exact_balance for outbound
// redirected tcp listeners. This does not change the virtualOutbound listener.
OutboundListenerExactBalance StringBool `json:"OUTBOUND_LISTENER_EXACT_BALANCE,omitempty"`
// Contains a copy of the raw metadata. This is needed to lookup arbitrary values.
// If a value is known ahead of time it should be added to the struct rather than reading from here,
Raw map[string]interface{} `json:"-"`
}
// ProxyConfigOrDefault is a helper function to get the ProxyConfig from metadata, or fallback to a default
// This is useful as the logic should check for proxy config from proxy first and then defer to mesh wide defaults
// if not present.
func (m NodeMetadata) ProxyConfigOrDefault(def *meshconfig.ProxyConfig) *meshconfig.ProxyConfig {
if m.ProxyConfig != nil {
return (*meshconfig.ProxyConfig)(m.ProxyConfig)
}
return def
}
// 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())
}
func (m *BootstrapNodeMetadata) UnmarshalJSON(data []byte) error {
// Create a new type from the target type to avoid recursion.
type BootstrapNodeMetadata2 BootstrapNodeMetadata
t2 := &BootstrapNodeMetadata2{}
if err := json.Unmarshal(data, t2); err != nil {
return err
}
var raw map[string]interface{}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
*m = BootstrapNodeMetadata(*t2)
m.Raw = raw
return nil
}
// ToStruct converts NodeMetadata to a protobuf structure. This should be used only for debugging - performance is bad.
func (m NodeMetadata) ToStruct() *structpb.Struct {
j, err := json.Marshal(m)
if err != nil {
return nil
}
pbs := &structpb.Struct{}
if err := protomarshal.Unmarshal(j, pbs); err != nil {
return nil
}
return pbs
}
// 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
}
// NodeType decides the responsibility of the proxy serves in the mesh
type NodeType string
const (
// SidecarProxy type is used for sidecar proxies in the application containers
SidecarProxy NodeType = "sidecar"
// Router type is used for standalone proxies acting as L7/L4 routers
Router NodeType = "router"
)
var NodeTypes = [...]NodeType{SidecarProxy, Router}
// IPMode represents the IP mode of proxy.
type IPMode int
// IPMode constants starting with index 1.
const (
IPv4 IPMode = iota + 1
IPv6
Dual
)
// IsApplicationNodeType verifies that the NodeType is one of the declared constants in the model
func IsApplicationNodeType(nType NodeType) bool {
switch nType {
case SidecarProxy, Router:
return true
default:
return false
}
}
// ServiceNode encodes the proxy node attributes into a URI-acceptable string
func (node *Proxy) ServiceNode() string {
ip := ""
if len(node.IPAddresses) > 0 {
ip = node.IPAddresses[0]
}
return strings.Join([]string{
string(node.Type), ip, node.ID, node.DNSDomain,
}, serviceNodeSeparator)
}
// 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
if node.Type == SidecarProxy {
node.SidecarScope = ps.getSidecarScope(node, node.Metadata.Labels)
} else {
// Gateways should just have a default scope with egress: */*
node.SidecarScope = ps.getSidecarScope(node, nil)
}
node.PrevSidecarScope = sidecarScope
}
// 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 ServiceInstances are set
func (node *Proxy) SetGatewaysForProxy(ps *PushContext) {
if node.Type != Router {
return
}
node.MergedGateway = ps.mergeGateways(node)
}
func (node *Proxy) SetServiceInstances(serviceDiscovery ServiceDiscovery) {
instances := serviceDiscovery.GetProxyServiceInstances(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.ServiceInstances = instances
}
// SetWorkloadLabels will set the node.Metadata.Labels only when it is nil.
func (node *Proxy) SetWorkloadLabels(env *Environment) {
// First get the workload labels from node meta
if len(node.Metadata.Labels) > 0 {
return
}
// Fallback to calling GetProxyWorkloadLabels
node.Metadata.Labels = env.GetProxyWorkloadLabels(node)
}
// DiscoverIPMode discovers the IP Versions supported by Proxy based on its IP addresses.
func (node *Proxy) DiscoverIPMode() {
if networkutil.AllIPv4(node.IPAddresses) {
node.ipMode = IPv4
} else if networkutil.AllIPv6(node.IPAddresses) {
node.ipMode = IPv6
} else {
node.ipMode = Dual
}
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
}
// 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
}
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.NodeMetadata, 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 !IsApplicationNodeType(NodeType(parts[0])) {
return out, fmt.Errorf("invalid node type (valid types: sidecar, router in the service node %q", nodeID)
}
out.Type = NodeType(parts[0])
// Get all IP Addresses from Metadata
if hasValidIPAddresses(metadata.InstanceIPs) {
out.IPAddresses = metadata.InstanceIPs
} else if 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 atleast 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 {
if len(s) > 0 {
return s
}
return 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
}