blob: 2f63c5c3fa13bb5b0c085461d6af80d00b86f288 [file] [log] [blame]
// 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
}