istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/filters/filters.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 filters
    16  
    17  import (
    18  	cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    19  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    20  	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    21  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    22  	sfsvalue "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/common/set_filter_state/v3"
    23  	cors "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cors/v3"
    24  	fault "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3"
    25  	grpcstats "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_stats/v3"
    26  	grpcweb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_web/v3"
    27  	router "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
    28  	sfs "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/set_filter_state/v3"
    29  	statefulsession "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/stateful_session/v3"
    30  	httpinspector "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/http_inspector/v3"
    31  	originaldst "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/original_dst/v3"
    32  	originalsrc "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/original_src/v3"
    33  	proxy_proto "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/proxy_protocol/v3"
    34  	tlsinspector "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/tls_inspector/v3"
    35  	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    36  	sfsnetwork "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/set_filter_state/v3"
    37  	previoushost "github.com/envoyproxy/go-control-plane/envoy/extensions/retry/host/previous_hosts/v3"
    38  	resourcedetectors "github.com/envoyproxy/go-control-plane/envoy/extensions/tracers/opentelemetry/resource_detectors/v3"
    39  	rawbuffer "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/raw_buffer/v3"
    40  	"google.golang.org/protobuf/types/known/wrapperspb"
    41  
    42  	alpn "istio.io/api/envoy/config/filter/http/alpn/v2alpha1"
    43  	"istio.io/istio/pilot/pkg/networking/util"
    44  	"istio.io/istio/pilot/pkg/util/protoconv"
    45  	"istio.io/istio/pkg/wellknown"
    46  )
    47  
    48  const (
    49  	TLSTransportProtocol       = "tls"
    50  	RawBufferTransportProtocol = "raw_buffer"
    51  
    52  	// Alpn HTTP filter name which will override the ALPN for upstream TLS connection.
    53  	AlpnFilterName = "istio.alpn"
    54  
    55  	MxFilterName = "istio.metadata_exchange"
    56  
    57  	// AuthnFilterName is the name for the Istio AuthN filter. This should be the same
    58  	// as the name defined in
    59  	// https://github.com/istio/proxy/blob/master/src/envoy/http/authn/http_filter_factory.cc#L30
    60  	AuthnFilterName = "istio_authn"
    61  
    62  	// EnvoyJwtFilterName is the name of the Envoy JWT filter.
    63  	EnvoyJwtFilterName = "envoy.filters.http.jwt_authn"
    64  
    65  	// EnvoyJwtFilterPayload is the struct field for the payload in dynamic metadata in Envoy JWT filter.
    66  	EnvoyJwtFilterPayload = "payload"
    67  )
    68  
    69  // Define static filters to be reused across the codebase. This avoids duplicate marshaling/unmarshaling
    70  // This should not be used for filters that will be mutated
    71  var (
    72  	RetryPreviousHosts = &route.RetryPolicy_RetryHostPredicate{
    73  		Name: "envoy.retry_host_predicates.previous_hosts",
    74  		ConfigType: &route.RetryPolicy_RetryHostPredicate_TypedConfig{
    75  			TypedConfig: protoconv.MessageToAny(&previoushost.PreviousHostsPredicate{}),
    76  		},
    77  	}
    78  	RawBufferTransportSocket = &core.TransportSocket{
    79  		Name: wellknown.TransportSocketRawBuffer,
    80  		ConfigType: &core.TransportSocket_TypedConfig{
    81  			TypedConfig: protoconv.MessageToAny(&rawbuffer.RawBuffer{}),
    82  		},
    83  	}
    84  	Cors = &hcm.HttpFilter{
    85  		Name: wellknown.CORS,
    86  		ConfigType: &hcm.HttpFilter_TypedConfig{
    87  			TypedConfig: protoconv.MessageToAny(&cors.Cors{}),
    88  		},
    89  	}
    90  	Fault = &hcm.HttpFilter{
    91  		Name: wellknown.Fault,
    92  		ConfigType: &hcm.HttpFilter_TypedConfig{
    93  			TypedConfig: protoconv.MessageToAny(&fault.HTTPFault{}),
    94  		},
    95  	}
    96  	GrpcWeb = &hcm.HttpFilter{
    97  		Name: wellknown.GRPCWeb,
    98  		ConfigType: &hcm.HttpFilter_TypedConfig{
    99  			TypedConfig: protoconv.MessageToAny(&grpcweb.GrpcWeb{}),
   100  		},
   101  	}
   102  	GrpcStats = &hcm.HttpFilter{
   103  		Name: wellknown.HTTPGRPCStats,
   104  		ConfigType: &hcm.HttpFilter_TypedConfig{
   105  			TypedConfig: protoconv.MessageToAny(&grpcstats.FilterConfig{
   106  				EmitFilterState: true,
   107  				PerMethodStatSpecifier: &grpcstats.FilterConfig_StatsForAllMethods{
   108  					StatsForAllMethods: &wrapperspb.BoolValue{Value: false},
   109  				},
   110  			}),
   111  		},
   112  	}
   113  	TLSInspector = &listener.ListenerFilter{
   114  		Name: wellknown.TLSInspector,
   115  		ConfigType: &listener.ListenerFilter_TypedConfig{
   116  			TypedConfig: protoconv.MessageToAny(&tlsinspector.TlsInspector{}),
   117  		},
   118  	}
   119  	HTTPInspector = &listener.ListenerFilter{
   120  		Name: wellknown.HTTPInspector,
   121  		ConfigType: &listener.ListenerFilter_TypedConfig{
   122  			TypedConfig: protoconv.MessageToAny(&httpinspector.HttpInspector{}),
   123  		},
   124  	}
   125  	OriginalDestination = &listener.ListenerFilter{
   126  		Name: wellknown.OriginalDestination,
   127  		ConfigType: &listener.ListenerFilter_TypedConfig{
   128  			TypedConfig: protoconv.MessageToAny(&originaldst.OriginalDst{}),
   129  		},
   130  	}
   131  	OriginalSrc = &listener.ListenerFilter{
   132  		Name: wellknown.OriginalSource,
   133  		ConfigType: &listener.ListenerFilter_TypedConfig{
   134  			TypedConfig: protoconv.MessageToAny(&originalsrc.OriginalSrc{
   135  				Mark: 1337,
   136  			}),
   137  		},
   138  	}
   139  	ProxyProtocol = &listener.ListenerFilter{
   140  		Name: wellknown.ProxyProtocol,
   141  		ConfigType: &listener.ListenerFilter_TypedConfig{
   142  			TypedConfig: protoconv.MessageToAny(&proxy_proto.ProxyProtocol{}),
   143  		},
   144  	}
   145  	EmptySessionFilter = &hcm.HttpFilter{
   146  		Name: util.StatefulSessionFilter,
   147  		ConfigType: &hcm.HttpFilter_TypedConfig{
   148  			TypedConfig: protoconv.MessageToAny(&statefulsession.StatefulSession{}),
   149  		},
   150  	}
   151  	Alpn = &hcm.HttpFilter{
   152  		Name: AlpnFilterName,
   153  		ConfigType: &hcm.HttpFilter_TypedConfig{
   154  			TypedConfig: protoconv.MessageToAny(&alpn.FilterConfig{
   155  				AlpnOverride: []*alpn.FilterConfig_AlpnOverride{
   156  					{
   157  						UpstreamProtocol: alpn.FilterConfig_HTTP10,
   158  						AlpnOverride:     mtlsHTTP10ALPN,
   159  					},
   160  					{
   161  						UpstreamProtocol: alpn.FilterConfig_HTTP11,
   162  						AlpnOverride:     mtlsHTTP11ALPN,
   163  					},
   164  					{
   165  						UpstreamProtocol: alpn.FilterConfig_HTTP2,
   166  						AlpnOverride:     mtlsHTTP2ALPN,
   167  					},
   168  				},
   169  			}),
   170  		},
   171  	}
   172  
   173  	// TCP MX is an Istio filter defined in https://github.com/istio/proxy/tree/master/source/extensions/filters/network/metadata_exchange.
   174  	tcpMx = protoconv.TypedStructWithFields("type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange",
   175  		map[string]any{
   176  			"protocol":         "istio-peer-exchange",
   177  			"enable_discovery": true,
   178  		})
   179  
   180  	TCPListenerMx = &listener.Filter{
   181  		Name:       MxFilterName,
   182  		ConfigType: &listener.Filter_TypedConfig{TypedConfig: tcpMx},
   183  	}
   184  
   185  	TCPClusterMx = &cluster.Filter{
   186  		Name:        MxFilterName,
   187  		TypedConfig: tcpMx,
   188  	}
   189  
   190  	WaypointDownstreamMetadataFilter = &hcm.HttpFilter{
   191  		Name: "waypoint_downstream_peer_metadata",
   192  		ConfigType: &hcm.HttpFilter_TypedConfig{
   193  			TypedConfig: protoconv.TypedStructWithFields("type.googleapis.com/io.istio.http.peer_metadata.Config",
   194  				map[string]any{
   195  					"downstream_discovery": []any{
   196  						map[string]any{
   197  							"workload_discovery": map[string]any{},
   198  						},
   199  					},
   200  					"shared_with_upstream": true,
   201  				}),
   202  		},
   203  	}
   204  
   205  	WaypointUpstreamMetadataFilter = &hcm.HttpFilter{
   206  		Name: "waypoint_upstream_peer_metadata",
   207  		ConfigType: &hcm.HttpFilter_TypedConfig{
   208  			TypedConfig: protoconv.TypedStructWithFields("type.googleapis.com/io.istio.http.peer_metadata.Config",
   209  				map[string]any{
   210  					"upstream_discovery": []any{
   211  						map[string]any{
   212  							"workload_discovery": map[string]any{},
   213  						},
   214  					},
   215  				}),
   216  		},
   217  	}
   218  
   219  	SidecarInboundMetadataFilter = &hcm.HttpFilter{
   220  		Name: MxFilterName,
   221  		ConfigType: &hcm.HttpFilter_TypedConfig{
   222  			TypedConfig: protoconv.TypedStructWithFields("type.googleapis.com/io.istio.http.peer_metadata.Config",
   223  				map[string]any{
   224  					"downstream_discovery": []any{
   225  						map[string]any{
   226  							"istio_headers": map[string]any{},
   227  						},
   228  						map[string]any{
   229  							"workload_discovery": map[string]any{},
   230  						},
   231  					},
   232  					"downstream_propagation": []any{
   233  						map[string]any{
   234  							"istio_headers": map[string]any{},
   235  						},
   236  					},
   237  				}),
   238  		},
   239  	}
   240  
   241  	SidecarOutboundMetadataFilter = &hcm.HttpFilter{
   242  		Name: MxFilterName,
   243  		ConfigType: &hcm.HttpFilter_TypedConfig{
   244  			TypedConfig: protoconv.TypedStructWithFields("type.googleapis.com/io.istio.http.peer_metadata.Config",
   245  				map[string]any{
   246  					"upstream_discovery": []any{
   247  						map[string]any{
   248  							"istio_headers": map[string]any{},
   249  						},
   250  						map[string]any{
   251  							"workload_discovery": map[string]any{},
   252  						},
   253  					},
   254  					"upstream_propagation": []any{
   255  						map[string]any{
   256  							"istio_headers": map[string]any{},
   257  						},
   258  					},
   259  				}),
   260  		},
   261  	}
   262  	// TODO https://github.com/istio/istio/issues/46740
   263  	// false values can be omitted in protobuf, results in diff JSON values between control plane and envoy config dumps
   264  	// long term fix will be to add the metadata config to istio/api and use that over TypedStruct
   265  	SidecarOutboundMetadataFilterSkipHeaders = &hcm.HttpFilter{
   266  		Name: MxFilterName,
   267  		ConfigType: &hcm.HttpFilter_TypedConfig{
   268  			TypedConfig: protoconv.TypedStructWithFields("type.googleapis.com/io.istio.http.peer_metadata.Config",
   269  				map[string]any{
   270  					"upstream_discovery": []any{
   271  						map[string]any{
   272  							"istio_headers": map[string]any{},
   273  						},
   274  						map[string]any{
   275  							"workload_discovery": map[string]any{},
   276  						},
   277  					},
   278  					"upstream_propagation": []any{
   279  						map[string]any{
   280  							"istio_headers": map[string]any{
   281  								"skip_external_clusters": true,
   282  							},
   283  						},
   284  					},
   285  				}),
   286  		},
   287  	}
   288  
   289  	ConnectAuthorityFilter = &hcm.HttpFilter{
   290  		Name: "connect_authority",
   291  		ConfigType: &hcm.HttpFilter_TypedConfig{
   292  			TypedConfig: protoconv.MessageToAny(&sfs.Config{
   293  				OnRequestHeaders: []*sfsvalue.FilterStateValue{
   294  					{
   295  						Key: &sfsvalue.FilterStateValue_ObjectKey{
   296  							ObjectKey: "envoy.filters.listener.original_dst.local_ip",
   297  						},
   298  						Value: &sfsvalue.FilterStateValue_FormatString{
   299  							FormatString: &core.SubstitutionFormatString{
   300  								Format: &core.SubstitutionFormatString_TextFormatSource{
   301  									TextFormatSource: &core.DataSource{
   302  										Specifier: &core.DataSource_InlineString{
   303  											InlineString: "%REQ(:AUTHORITY)%",
   304  										},
   305  									},
   306  								},
   307  							},
   308  						},
   309  						SharedWithUpstream: sfsvalue.FilterStateValue_ONCE,
   310  					}, {
   311  						Key: &sfsvalue.FilterStateValue_ObjectKey{
   312  							ObjectKey: "envoy.filters.listener.original_dst.remote_ip",
   313  						},
   314  						Value: &sfsvalue.FilterStateValue_FormatString{
   315  							FormatString: &core.SubstitutionFormatString{
   316  								Format: &core.SubstitutionFormatString_TextFormatSource{
   317  									TextFormatSource: &core.DataSource{
   318  										Specifier: &core.DataSource_InlineString{
   319  											InlineString: "%DOWNSTREAM_REMOTE_ADDRESS%",
   320  										},
   321  									},
   322  								},
   323  							},
   324  						},
   325  						SharedWithUpstream: sfsvalue.FilterStateValue_ONCE,
   326  					}, {
   327  						Key: &sfsvalue.FilterStateValue_ObjectKey{
   328  							ObjectKey: "io.istio.peer_principal",
   329  						},
   330  						FactoryKey: "envoy.string",
   331  						Value: &sfsvalue.FilterStateValue_FormatString{
   332  							FormatString: &core.SubstitutionFormatString{
   333  								Format: &core.SubstitutionFormatString_TextFormatSource{
   334  									TextFormatSource: &core.DataSource{
   335  										Specifier: &core.DataSource_InlineString{
   336  											InlineString: "%DOWNSTREAM_PEER_URI_SAN%",
   337  										},
   338  									},
   339  								},
   340  							},
   341  						},
   342  						SharedWithUpstream: sfsvalue.FilterStateValue_ONCE,
   343  					}, {
   344  						Key: &sfsvalue.FilterStateValue_ObjectKey{
   345  							ObjectKey: "io.istio.local_principal",
   346  						},
   347  						FactoryKey: "envoy.string",
   348  						Value: &sfsvalue.FilterStateValue_FormatString{
   349  							FormatString: &core.SubstitutionFormatString{
   350  								Format: &core.SubstitutionFormatString_TextFormatSource{
   351  									TextFormatSource: &core.DataSource{
   352  										Specifier: &core.DataSource_InlineString{
   353  											InlineString: "%DOWNSTREAM_LOCAL_URI_SAN%",
   354  										},
   355  									},
   356  								},
   357  							},
   358  						},
   359  						SharedWithUpstream: sfsvalue.FilterStateValue_ONCE,
   360  					},
   361  				},
   362  			}),
   363  		},
   364  	}
   365  
   366  	ConnectAuthorityNetworkFilter = &listener.Filter{
   367  		Name: "connect_authority",
   368  		ConfigType: &listener.Filter_TypedConfig{
   369  			TypedConfig: protoconv.MessageToAny(&sfsnetwork.Config{
   370  				OnNewConnection: []*sfsvalue.FilterStateValue{{
   371  					Key: &sfsvalue.FilterStateValue_ObjectKey{
   372  						ObjectKey: "envoy.filters.listener.original_dst.local_ip",
   373  					},
   374  					Value: &sfsvalue.FilterStateValue_FormatString{
   375  						FormatString: &core.SubstitutionFormatString{
   376  							Format: &core.SubstitutionFormatString_TextFormatSource{
   377  								TextFormatSource: &core.DataSource{
   378  									Specifier: &core.DataSource_InlineString{
   379  										InlineString: "%FILTER_STATE(envoy.filters.listener.original_dst.local_ip:PLAIN)%",
   380  									},
   381  								},
   382  							},
   383  						},
   384  					},
   385  					SharedWithUpstream: sfsvalue.FilterStateValue_ONCE,
   386  				}},
   387  			}),
   388  		},
   389  	}
   390  )
   391  
   392  // Router is used a bunch, so its worth precomputing even though we have a few options.
   393  // Since there are only 4 possible options, just precompute them all
   394  var routers = func() map[RouterFilterContext]*hcm.HttpFilter {
   395  	res := map[RouterFilterContext]*hcm.HttpFilter{}
   396  	for _, startSpan := range []bool{true, false} {
   397  		for _, suppressHeaders := range []bool{true, false} {
   398  			res[RouterFilterContext{
   399  				StartChildSpan:       startSpan,
   400  				SuppressDebugHeaders: suppressHeaders,
   401  			}] = &hcm.HttpFilter{
   402  				Name: wellknown.Router,
   403  				ConfigType: &hcm.HttpFilter_TypedConfig{
   404  					TypedConfig: protoconv.MessageToAny(&router.Router{
   405  						StartChildSpan:       startSpan,
   406  						SuppressEnvoyHeaders: suppressHeaders,
   407  					}),
   408  				},
   409  			}
   410  		}
   411  	}
   412  	return res
   413  }()
   414  
   415  func BuildRouterFilter(ctx RouterFilterContext) *hcm.HttpFilter {
   416  	return routers[ctx]
   417  }
   418  
   419  var (
   420  	// These ALPNs are injected in the client side by the ALPN filter.
   421  	// "istio" is added for each upstream protocol in order to make it
   422  	// backward compatible. e.g., 1.4 proxy -> 1.3 proxy.
   423  	// Non istio-* variants are added to ensure that traffic sent out of the mesh has a valid ALPN;
   424  	// ideally this would not be added, but because the override filter is in the HCM, rather than cluster,
   425  	// we do not yet know the upstream so we cannot determine if its in or out of the mesh
   426  	mtlsHTTP10ALPN = []string{"istio-http/1.0", "istio", "http/1.0"}
   427  	mtlsHTTP11ALPN = []string{"istio-http/1.1", "istio", "http/1.1"}
   428  	mtlsHTTP2ALPN  = []string{"istio-h2", "istio", "h2"}
   429  )
   430  
   431  // OpenTelemetry Resource Detectors
   432  var (
   433  	EnvironmentResourceDetector = &core.TypedExtensionConfig{
   434  		Name:        "envoy.tracers.opentelemetry.resource_detectors.environment",
   435  		TypedConfig: protoconv.MessageToAny(&resourcedetectors.EnvironmentResourceDetectorConfig{}),
   436  	}
   437  	DynatraceResourceDetector = &core.TypedExtensionConfig{
   438  		Name:        "envoy.tracers.opentelemetry.resource_detectors.dynatrace",
   439  		TypedConfig: protoconv.MessageToAny(&resourcedetectors.DynatraceResourceDetectorConfig{}),
   440  	}
   441  )