| // 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" |
| ) |
| |
| import ( |
| 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" // nolint: staticcheck |
| any "google.golang.org/protobuf/types/known/anypb" |
| "google.golang.org/protobuf/types/known/structpb" |
| meshconfig "istio.io/api/mesh/v1alpha1" |
| "istio.io/pkg/ledger" |
| "istio.io/pkg/monitoring" |
| ) |
| |
| import ( |
| istionetworking "github.com/apache/dubbo-go-pixiu/pilot/pkg/networking" |
| "github.com/apache/dubbo-go-pixiu/pilot/pkg/trustbundle" |
| networkutil "github.com/apache/dubbo-go-pixiu/pilot/pkg/util/network" |
| "github.com/apache/dubbo-go-pixiu/pkg/cluster" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/constants" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/host" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/mesh" |
| "github.com/apache/dubbo-go-pixiu/pkg/network" |
| "github.com/apache/dubbo-go-pixiu/pkg/spiffe" |
| "github.com/apache/dubbo-go-pixiu/pkg/util/identifier" |
| "github.com/apache/dubbo-go-pixiu/pkg/util/protomarshal" |
| ) |
| |
| 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"` |
| |
| // The istiod address when running ASM Managed Control Plane. |
| CloudrunAddr string `json:"CLOUDRUN_ADDR,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 |
| } |
| |
| // 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 !isValidIPAddress(ipAddress) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // Tell whether the given IP address is valid or not |
| func isValidIPAddress(ip string) bool { |
| return net.ParseIP(ip) != nil |
| } |
| |
| // TrafficInterceptionMode indicates how traffic to/from the workload is captured and |
| // sent to Envoy. This should not be confused with the CaptureMode in the API that indicates |
| // how the user wants traffic to be intercepted for the listener. TrafficInterceptionMode is |
| // always derived from the Proxy metadata |
| type TrafficInterceptionMode string |
| |
| 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 && IsPrivilegedPort(port) && node.IsUnprivileged() { |
| 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 intstead of the TestVMLabel |
| return node.Metadata != nil && node.Metadata.Labels[constants.TestVMLabel] != "" |
| } |
| |
| func (node *Proxy) IsProxylessGrpc() bool { |
| return node.Metadata != nil && node.Metadata.Generator == "grpc" |
| } |
| |
| type GatewayController interface { |
| ConfigStoreController |
| // Recompute updates the internal state of the gateway controller for a given input. This should be |
| // called before any List/Get calls if the state has changed |
| Recompute(GatewayContext) error |
| // SecretAllowed determines if a SDS credential is accessible to a given namespace. |
| // For example, for resourceName of `kubernetes-gateway://ns-name/secret-name` and namespace of `ingress-ns`, |
| // this would return true only if there was a policy allowing `ingress-ns` to access Secrets in the `ns-name` namespace. |
| SecretAllowed(resourceName string, namespace string) bool |
| } |
| |
| // OutboundListenerClass is a helper to turn a NodeType for outbound to a ListenerClass. |
| func OutboundListenerClass(t NodeType) istionetworking.ListenerClass { |
| if t == Router { |
| return istionetworking.ListenerClassGateway |
| } |
| return istionetworking.ListenerClassSidecarOutbound |
| } |