istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/cluster_waypoint.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package core
    16  
    17  import (
    18  	"time"
    19  
    20  	cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    21  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    22  	endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    23  	tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
    24  	http "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3"
    25  	matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
    26  	metadata "github.com/envoyproxy/go-control-plane/envoy/type/metadata/v3"
    27  	"google.golang.org/protobuf/types/known/anypb"
    28  	"google.golang.org/protobuf/types/known/durationpb"
    29  	"google.golang.org/protobuf/types/known/structpb"
    30  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    31  
    32  	networking "istio.io/api/networking/v1alpha3"
    33  	"istio.io/istio/pilot/pkg/model"
    34  	"istio.io/istio/pilot/pkg/networking/util"
    35  	sec_model "istio.io/istio/pilot/pkg/security/model"
    36  	"istio.io/istio/pilot/pkg/util/protoconv"
    37  	"istio.io/istio/pilot/pkg/xds/endpoints"
    38  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    39  	"istio.io/istio/pkg/config/host"
    40  	"istio.io/istio/pkg/config/protocol"
    41  	"istio.io/istio/pkg/log"
    42  	"istio.io/istio/pkg/spiffe"
    43  )
    44  
    45  // buildInternalUpstreamCluster builds a single endpoint cluster to the internal listener.
    46  func buildInternalUpstreamCluster(name string, internalListener string) *cluster.Cluster {
    47  	return &cluster.Cluster{
    48  		Name:                 name,
    49  		ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STATIC},
    50  		LoadAssignment: &endpoint.ClusterLoadAssignment{
    51  			ClusterName: name,
    52  			Endpoints:   util.BuildInternalEndpoint(internalListener, nil),
    53  		},
    54  		TransportSocket: util.DefaultInternalUpstreamTransportSocket,
    55  		TypedExtensionProtocolOptions: map[string]*anypb.Any{
    56  			v3.HttpProtocolOptionsType: passthroughHttpProtocolOptions,
    57  		},
    58  	}
    59  }
    60  
    61  var (
    62  	MainInternalCluster = buildInternalUpstreamCluster(MainInternalName, MainInternalName)
    63  	EncapCluster        = buildInternalUpstreamCluster(EncapClusterName, ConnectOriginate)
    64  )
    65  
    66  func (configgen *ConfigGeneratorImpl) buildInboundHBONEClusters() *cluster.Cluster {
    67  	return MainInternalCluster
    68  }
    69  
    70  func (configgen *ConfigGeneratorImpl) buildWaypointInboundClusters(
    71  	cb *ClusterBuilder,
    72  	proxy *model.Proxy,
    73  	push *model.PushContext,
    74  	svcs map[host.Name]*model.Service,
    75  ) []*cluster.Cluster {
    76  	clusters := make([]*cluster.Cluster, 0)
    77  	// Creates "main_internal" cluster to route to the main internal listener.
    78  	// Creates "encap" cluster to route to the encap listener.
    79  	clusters = append(clusters, MainInternalCluster, EncapCluster)
    80  	// Creates per-VIP load balancing upstreams.
    81  	clusters = append(clusters, cb.buildWaypointInboundVIP(proxy, svcs)...)
    82  	// Upstream of the "encap" listener.
    83  	clusters = append(clusters, cb.buildWaypointConnectOriginate(proxy, push))
    84  
    85  	for _, c := range clusters {
    86  		if c.TransportSocket != nil && c.TransportSocketMatches != nil {
    87  			log.Errorf("invalid cluster, multiple matches: %v", c.Name)
    88  		}
    89  	}
    90  	return clusters
    91  }
    92  
    93  // `inbound-vip||hostname|port`. EDS routing to the internal listener for each pod in the VIP.
    94  func (cb *ClusterBuilder) buildWaypointInboundVIPCluster(proxy *model.Proxy, svc *model.Service, port model.Port, subset string) *clusterWrapper {
    95  	clusterName := model.BuildSubsetKey(model.TrafficDirectionInboundVIP, subset, svc.Hostname, port.Port)
    96  
    97  	discoveryType := convertResolution(cb.proxyType, svc)
    98  	var lbEndpoints []*endpoint.LocalityLbEndpoints
    99  	if discoveryType == cluster.Cluster_STRICT_DNS || discoveryType == cluster.Cluster_LOGICAL_DNS {
   100  		lbEndpoints = endpoints.NewCDSEndpointBuilder(
   101  			proxy,
   102  			cb.req.Push,
   103  			clusterName,
   104  			model.TrafficDirectionInboundVIP, subset, svc.Hostname, port.Port,
   105  			svc, nil,
   106  		).FromServiceEndpoints()
   107  	}
   108  	localCluster := cb.buildCluster(clusterName, discoveryType, lbEndpoints,
   109  		model.TrafficDirectionInboundVIP, &port, svc, nil, subset)
   110  
   111  	// Ensure VIP cluster has services metadata for stats filter usage
   112  	im := getOrCreateIstioMetadata(localCluster.cluster)
   113  	im.Fields["services"] = &structpb.Value{
   114  		Kind: &structpb.Value_ListValue{
   115  			ListValue: &structpb.ListValue{
   116  				Values: []*structpb.Value{},
   117  			},
   118  		},
   119  	}
   120  	svcMetaList := im.Fields["services"].GetListValue()
   121  	svcMetaList.Values = append(svcMetaList.Values, buildServiceMetadata(svc))
   122  
   123  	// no TLS, we are just going to internal address
   124  	localCluster.cluster.TransportSocketMatches = nil
   125  	localCluster.cluster.TransportSocket = util.TunnelHostInternalUpstreamTransportSocket
   126  	maybeApplyEdsConfig(localCluster.cluster)
   127  	return localCluster
   128  }
   129  
   130  // `inbound-vip|protocol|hostname|port`. EDS routing to the internal listener for each pod in the VIP.
   131  func (cb *ClusterBuilder) buildWaypointInboundVIP(proxy *model.Proxy, svcs map[host.Name]*model.Service) []*cluster.Cluster {
   132  	clusters := []*cluster.Cluster{}
   133  
   134  	for _, svc := range svcs {
   135  		for _, port := range svc.Ports {
   136  			if port.Protocol == protocol.UDP {
   137  				continue
   138  			}
   139  			if port.Protocol.IsUnsupported() || port.Protocol.IsTCP() {
   140  				clusters = append(clusters, cb.buildWaypointInboundVIPCluster(proxy, svc, *port, "tcp").build())
   141  			}
   142  			if port.Protocol.IsUnsupported() || port.Protocol.IsHTTP() {
   143  				clusters = append(clusters, cb.buildWaypointInboundVIPCluster(proxy, svc, *port, "http").build())
   144  			}
   145  			cfg := cb.sidecarScope.DestinationRule(model.TrafficDirectionInbound, proxy, svc.Hostname).GetRule()
   146  			if cfg != nil {
   147  				destinationRule := cfg.Spec.(*networking.DestinationRule)
   148  				for _, ss := range destinationRule.Subsets {
   149  					if port.Protocol.IsUnsupported() || port.Protocol.IsTCP() {
   150  						clusters = append(clusters, cb.buildWaypointInboundVIPCluster(proxy, svc, *port, "tcp/"+ss.Name).build())
   151  					}
   152  					if port.Protocol.IsUnsupported() || port.Protocol.IsHTTP() {
   153  						clusters = append(clusters, cb.buildWaypointInboundVIPCluster(proxy, svc, *port, "http/"+ss.Name).build())
   154  					}
   155  				}
   156  			}
   157  		}
   158  	}
   159  	return clusters
   160  }
   161  
   162  func (cb *ClusterBuilder) buildWaypointConnectOriginate(proxy *model.Proxy, push *model.PushContext) *cluster.Cluster {
   163  	m := &matcher.StringMatcher{}
   164  	m.MatchPattern = &matcher.StringMatcher_Prefix{
   165  		Prefix: spiffe.URIPrefix + spiffe.GetTrustDomain() + "/ns/" + proxy.Metadata.Namespace + "/sa/",
   166  	}
   167  	return cb.buildConnectOriginate(proxy, push, m)
   168  }
   169  
   170  func (cb *ClusterBuilder) buildConnectOriginate(proxy *model.Proxy, push *model.PushContext, uriSanMatchers ...*matcher.StringMatcher) *cluster.Cluster {
   171  	ctx := buildCommonConnectTLSContext(proxy, push)
   172  	validationCtx := ctx.GetCombinedValidationContext().DefaultValidationContext
   173  	for _, uriSanMatcher := range uriSanMatchers {
   174  		if uriSanMatcher != nil {
   175  			validationCtx.MatchTypedSubjectAltNames = append(validationCtx.MatchTypedSubjectAltNames, &tls.SubjectAltNameMatcher{
   176  				SanType: tls.SubjectAltNameMatcher_URI,
   177  				Matcher: uriSanMatcher,
   178  			})
   179  		}
   180  	}
   181  	// Compliance for Envoy tunnel upstreams.
   182  	sec_model.EnforceCompliance(ctx)
   183  	return &cluster.Cluster{
   184  		Name:                          ConnectOriginate,
   185  		ClusterDiscoveryType:          &cluster.Cluster_Type{Type: cluster.Cluster_ORIGINAL_DST},
   186  		LbPolicy:                      cluster.Cluster_CLUSTER_PROVIDED,
   187  		ConnectTimeout:                durationpb.New(2 * time.Second),
   188  		CleanupInterval:               durationpb.New(60 * time.Second),
   189  		TypedExtensionProtocolOptions: h2connectUpgrade(),
   190  		LbConfig: &cluster.Cluster_OriginalDstLbConfig_{
   191  			OriginalDstLbConfig: &cluster.Cluster_OriginalDstLbConfig{
   192  				UpstreamPortOverride: &wrappers.UInt32Value{
   193  					Value: model.HBoneInboundListenPort,
   194  				},
   195  				// Used to override destination pods with waypoints.
   196  				MetadataKey: &metadata.MetadataKey{
   197  					Key: util.OriginalDstMetadataKey,
   198  					Path: []*metadata.MetadataKey_PathSegment{{
   199  						Segment: &metadata.MetadataKey_PathSegment_Key{
   200  							Key: "waypoint",
   201  						},
   202  					}},
   203  				},
   204  			},
   205  		},
   206  		TransportSocket: &core.TransportSocket{
   207  			Name: "tls",
   208  			ConfigType: &core.TransportSocket_TypedConfig{TypedConfig: protoconv.MessageToAny(&tls.UpstreamTlsContext{
   209  				CommonTlsContext: ctx,
   210  			})},
   211  		},
   212  	}
   213  }
   214  
   215  func h2connectUpgrade() map[string]*anypb.Any {
   216  	return map[string]*anypb.Any{
   217  		v3.HttpProtocolOptionsType: protoconv.MessageToAny(&http.HttpProtocolOptions{
   218  			UpstreamProtocolOptions: &http.HttpProtocolOptions_ExplicitHttpConfig_{ExplicitHttpConfig: &http.HttpProtocolOptions_ExplicitHttpConfig{
   219  				ProtocolConfig: &http.HttpProtocolOptions_ExplicitHttpConfig_Http2ProtocolOptions{
   220  					Http2ProtocolOptions: &core.Http2ProtocolOptions{
   221  						AllowConnect: true,
   222  					},
   223  				},
   224  			}},
   225  		}),
   226  	}
   227  }