open-consul/agent/xds/version_compat.go
R.B. Boyer 97e57aedfb
connect: update supported envoy versions to 1.18.2, 1.17.2, 1.16.3, and 1.15.4 (#10101)
The only thing that needed fixing up pertained to this section of the 1.18.x release notes:

> grpc_stats: the default value for stats_for_all_methods is switched from true to false, in order to avoid possible memory exhaustion due to an untrusted downstream sending a large number of unique method names. The previous default value was deprecated in version 1.14.0. This only changes the behavior when the value is not set. The previous behavior can be used by setting the value to true. This behavior change by be overridden by setting runtime feature envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default.

For now to maintain status-quo I'm explicitly setting `stats_for_all_methods=true` in all versions to avoid relying upon the default.

Additionally the naming of the emitted metrics for these gRPC requests changed slightly so the integration test assertions for `case-grpc` needed adjusting.
2021-04-29 15:22:03 -05:00

586 lines
19 KiB
Go

package xds
import (
"errors"
"fmt"
envoy_api_v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
envoy_tls_v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
envoy_core_v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
envoy_listener_v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
envoy_route_v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
envoy_grpc_stats_v2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/http/grpc_stats/v2alpha"
envoy_http_rbac_v2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/http/rbac/v2"
envoy_tls_inspector_v2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/listener/tls_inspector/v2"
envoy_http_v2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
envoy_network_rbac_v2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/rbac/v2"
envoy_sni_v2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/sni_cluster/v2"
envoy_tcp_proxy_v2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/tcp_proxy/v2"
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoy_metrics_v2 "github.com/envoyproxy/go-control-plane/envoy/config/metrics/v2"
envoy_metrics_v3 "github.com/envoyproxy/go-control-plane/envoy/config/metrics/v3"
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_trace_v2 "github.com/envoyproxy/go-control-plane/envoy/config/trace/v2"
envoy_trace_v3 "github.com/envoyproxy/go-control-plane/envoy/config/trace/v3"
envoy_grpc_stats_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_stats/v3"
envoy_http_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3"
envoy_tls_inspector_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/tls_inspector/v3"
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
envoy_network_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/v3"
envoy_sni_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/sni_cluster/v3"
envoy_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_discovery_v2 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
envoy_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
envoy_type_v2 "github.com/envoyproxy/go-control-plane/envoy/type"
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
"google.golang.org/grpc"
)
// The plumbing in this file supports converting xDS v3 requests and responses
// to and from xDS v2 representations. This is primarily of use for envoy
// sidecars configured and launched by Consul versions prior to 1.10.
//
// Once the servers and client agents in a datacenter have been upgraded to
// 1.10+, and all of the sidecars have been restarted with a fresh bootstrap
// config file generated by Consul 1.10+ then none of these abstractions are
// used.
//
// At most we only need to retain this logic until our envoy support matrix
// looks like:
//
// - 1.20.x (v2 deleted)
// - 1.19.x (v2 deleted)
// - 1.18.x (v2 deleted)
// - 1.17.x (v2 opt-in)
type adsServerV2Shim struct {
srv *Server
}
// StreamAggregatedResources implements
// envoy_discovery_v2.AggregatedDiscoveryServiceServer. This is the ADS endpoint which is
// the only xDS API we directly support for now.
func (s *adsServerV2Shim) StreamAggregatedResources(stream ADSStream_v2) error {
shim := &adsStreamV3Shim{
stream: stream,
ServerStream: stream,
}
return s.srv.streamAggregatedResources(shim)
}
// DeltaAggregatedResources implements envoy_discovery_v2.AggregatedDiscoveryServiceServer
func (s *adsServerV2Shim) DeltaAggregatedResources(_ envoy_discovery_v2.AggregatedDiscoveryService_DeltaAggregatedResourcesServer) error {
return errors.New("not implemented")
}
type adsStreamV3Shim struct {
stream ADSStream_v2
grpc.ServerStream
}
var _ ADSStream = (*adsStreamV3Shim)(nil)
func (s *adsStreamV3Shim) Send(resp *envoy_discovery_v3.DiscoveryResponse) error {
respv2, err := convertDiscoveryResponseToV2(resp)
if err != nil {
return fmt.Errorf("Error converting a v3 DiscoveryResponse to v2: %w", err)
}
return s.stream.Send(respv2)
}
func (s *adsStreamV3Shim) Recv() (*envoy_discovery_v3.DiscoveryRequest, error) {
req, err := s.stream.Recv()
if err != nil {
return nil, err
}
reqv3, err := convertDiscoveryRequestToV3(req)
if err != nil {
return nil, fmt.Errorf("Error converting a v2 DiscoveryRequest to v3: %w", err)
}
return reqv3, nil
}
func convertDiscoveryRequestToV3(req *envoy_api_v2.DiscoveryRequest) (*envoy_discovery_v3.DiscoveryRequest, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(req); err != nil {
return nil, err
}
var reqV3 envoy_discovery_v3.DiscoveryRequest
if err := pbuf.Unmarshal(&reqV3); err != nil {
return nil, err
}
// only one field to munge
if err := convertTypeUrlsToV3(&reqV3.TypeUrl); err != nil {
return nil, err
}
return &reqV3, nil
}
// convertClusterToV2 is only used in tests.
func convertClusterToV2(resp *envoy_cluster_v3.Cluster) (*envoy_api_v2.Cluster, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(resp); err != nil {
return nil, err
}
var v2 envoy_api_v2.Cluster
if err := pbuf.Unmarshal(&v2); err != nil {
return nil, err
}
if err := convertTypedConfigsToV2(&v2); err != nil {
return nil, err
}
return &v2, nil
}
// convertClusterLoadAssignmentToV2 is only used in tests.
func convertClusterLoadAssignmentToV2(resp *envoy_endpoint_v3.ClusterLoadAssignment) (*envoy_api_v2.ClusterLoadAssignment, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(resp); err != nil {
return nil, err
}
var v2 envoy_api_v2.ClusterLoadAssignment
if err := pbuf.Unmarshal(&v2); err != nil {
return nil, err
}
if err := convertTypedConfigsToV2(&v2); err != nil {
return nil, err
}
return &v2, nil
}
// convertRouteConfigurationToV2 is only used in tests.
func convertRouteConfigurationToV2(resp *envoy_route_v3.RouteConfiguration) (*envoy_api_v2.RouteConfiguration, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(resp); err != nil {
return nil, err
}
var v2 envoy_api_v2.RouteConfiguration
if err := pbuf.Unmarshal(&v2); err != nil {
return nil, err
}
if err := convertTypedConfigsToV2(&v2); err != nil {
return nil, err
}
return &v2, nil
}
// convertListenerToV2 is only used in tests.
func convertListenerToV2(resp *envoy_listener_v3.Listener) (*envoy_api_v2.Listener, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(resp); err != nil {
return nil, err
}
var v2 envoy_api_v2.Listener
if err := pbuf.Unmarshal(&v2); err != nil {
return nil, err
}
if err := convertTypedConfigsToV2(&v2); err != nil {
return nil, err
}
return &v2, nil
}
func convertDiscoveryResponseToV2(resp *envoy_discovery_v3.DiscoveryResponse) (*envoy_api_v2.DiscoveryResponse, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(resp); err != nil {
return nil, err
}
var respV2 envoy_api_v2.DiscoveryResponse
if err := pbuf.Unmarshal(&respV2); err != nil {
return nil, err
}
if err := convertTypedConfigsToV2(&respV2); err != nil {
return nil, err
}
return &respV2, nil
}
// convertNetFilterToV2 is only used in tests.
func convertNetFilterToV2(filter *envoy_listener_v3.Filter) (*envoy_listener_v2.Filter, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(filter); err != nil {
return nil, err
}
var filterV2 envoy_listener_v2.Filter
if err := pbuf.Unmarshal(&filterV2); err != nil {
return nil, err
}
if err := convertTypedConfigsToV2(&filterV2); err != nil {
return nil, err
}
return &filterV2, nil
}
// convertHttpFilterToV2 is only used in tests.
func convertHttpFilterToV2(filter *envoy_http_v3.HttpFilter) (*envoy_http_v2.HttpFilter, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(filter); err != nil {
return nil, err
}
var filterV2 envoy_http_v2.HttpFilter
if err := pbuf.Unmarshal(&filterV2); err != nil {
return nil, err
}
if err := convertTypedConfigsToV2(&filterV2); err != nil {
return nil, err
}
return &filterV2, nil
}
func convertSemanticVersionToV2(version *envoy_type_v3.SemanticVersion) (*envoy_type_v2.SemanticVersion, error) {
var pbuf proto.Buffer
if err := pbuf.Marshal(version); err != nil {
return nil, err
}
var versionV2 envoy_type_v2.SemanticVersion
if err := pbuf.Unmarshal(&versionV2); err != nil {
return nil, err
}
return &versionV2, nil
}
// Responses
func convertTypedConfigsToV2(pb proto.Message) error {
switch x := pb.(type) {
case *envoy_api_v2.DiscoveryResponse:
if err := convertTypeUrlsToV2(&x.TypeUrl); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
for _, res := range x.Resources {
if err := convertTypedConfigsToV2(res); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
return nil
case *any.Any:
// first flip the any.Any to v2
if err := convertTypeUrlsToV2(&x.TypeUrl); err != nil {
return fmt.Errorf("%T(%s) convert type urls in envelope: %w", x, x.TypeUrl, err)
}
// now decode into a v2 type
var dynAny ptypes.DynamicAny
if err := ptypes.UnmarshalAny(x, &dynAny); err != nil {
return fmt.Errorf("%T(%s) dynamic unmarshal: %w", x, x.TypeUrl, err)
}
// handle the contents and then put them back in the any.Any
// handle contents first
if err := convertTypedConfigsToV2(dynAny.Message); err != nil {
return fmt.Errorf("%T(%s) convert type urls in body: %w", x, x.TypeUrl, err)
}
anyFixed, err := ptypes.MarshalAny(dynAny.Message)
if err != nil {
return fmt.Errorf("%T(%s) dynamic re-marshal: %w", x, x.TypeUrl, err)
}
x.Value = anyFixed.Value
return nil
case *envoy_api_v2.Listener:
for _, chain := range x.FilterChains {
if err := convertTypedConfigsToV2(chain); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
for _, filter := range x.ListenerFilters {
// We only ever plumb up the tls_inspector listener filter.
if err := convertTypedConfigsToV2(filter); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
return nil
case *envoy_listener_v2.ListenerFilter:
// This is really only here for when the tls inspector is for some
// random reason plumbed using the @type instead of the name.
if x.ConfigType != nil {
tc, ok := x.ConfigType.(*envoy_listener_v2.ListenerFilter_TypedConfig)
if !ok {
return fmt.Errorf("%T: ConfigType type %T not handled", x, x.ConfigType)
}
if tc.TypedConfig != nil {
if err := convertTypedConfigsToV2(tc.TypedConfig); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
}
return nil
case *envoy_listener_v2.FilterChain:
for _, filter := range x.Filters {
if err := convertTypedConfigsToV2(filter); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
if x.TransportSocket != nil {
if err := convertTypedConfigsToV2(x.TransportSocket); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
return nil
case *envoy_listener_v2.Filter:
// "envoy.filters.network.tcp_proxy"
// "envoy.filters.network.http_connection_manager"
// "envoy.filters.network.rbac"
// "envoy.filters.network.sni_cluster"
if x.ConfigType != nil {
tc, ok := x.ConfigType.(*envoy_listener_v2.Filter_TypedConfig)
if !ok {
return fmt.Errorf("%T: ConfigType type %T not handled", x, x.ConfigType)
}
if tc.TypedConfig != nil {
if err := convertTypedConfigsToV2(tc.TypedConfig); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
}
return nil
case *envoy_core_v2.TransportSocket:
if x.ConfigType != nil {
tc, ok := x.ConfigType.(*envoy_core_v2.TransportSocket_TypedConfig)
if !ok {
return fmt.Errorf("%T: ConfigType type %T not handled", x, x.ConfigType)
}
if tc.TypedConfig != nil {
if err := convertTypedConfigsToV2(tc.TypedConfig); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
}
return nil
case *envoy_api_v2.ClusterLoadAssignment:
return nil
case *envoy_api_v2.Cluster:
if x.TransportSocket != nil {
if err := convertTypedConfigsToV2(x.TransportSocket); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
for _, tsm := range x.TransportSocketMatches {
if tsm.TransportSocket != nil {
if err := convertTypedConfigsToV2(tsm.TransportSocket); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
}
if x.EdsClusterConfig != nil {
if x.EdsClusterConfig.EdsConfig != nil {
if err := convertTypedConfigsToV2(x.EdsClusterConfig.EdsConfig); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
}
return nil
case *envoy_api_v2.RouteConfiguration:
for _, vhost := range x.VirtualHosts {
if err := convertTypedConfigsToV2(vhost); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
if x.Vhds != nil && x.Vhds.ConfigSource != nil {
if err := convertTypedConfigsToV2(x.Vhds.ConfigSource); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
return nil
case *envoy_route_v2.VirtualHost:
if x.RetryPolicy != nil {
if err := convertTypedConfigsToV2(x.RetryPolicy); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
return nil
case *envoy_route_v2.RetryPolicy:
return nil
case *envoy_http_v2.HttpFilter:
if x.ConfigType != nil {
tc, ok := x.ConfigType.(*envoy_http_v2.HttpFilter_TypedConfig)
if !ok {
return fmt.Errorf("%T: ConfigType type %T not handled", x, x.ConfigType)
}
if tc.TypedConfig != nil {
if err := convertTypedConfigsToV2(tc.TypedConfig); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
}
return nil
case *envoy_core_v2.ConfigSource:
if x.ConfigSourceSpecifier != nil {
if _, ok := x.ConfigSourceSpecifier.(*envoy_core_v2.ConfigSource_Ads); !ok {
return fmt.Errorf("%T: ConfigSourceSpecifier type %T not handled", x, x.ConfigSourceSpecifier)
}
}
x.ResourceApiVersion = envoy_core_v2.ApiVersion_V2
return nil
case *envoy_http_v2.HttpConnectionManager: // "envoy.filters.network.http_connection_manager"
if x.RouteSpecifier != nil {
switch spec := x.RouteSpecifier.(type) {
case *envoy_http_v2.HttpConnectionManager_Rds:
if spec.Rds != nil && spec.Rds.ConfigSource != nil {
if err := convertTypedConfigsToV2(spec.Rds.ConfigSource); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
case *envoy_http_v2.HttpConnectionManager_RouteConfig:
if spec.RouteConfig != nil {
if err := convertTypedConfigsToV2(spec.RouteConfig); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
default:
return fmt.Errorf("%T: RouteSpecifier type %T not handled", x, spec)
}
}
for _, filter := range x.HttpFilters {
if err := convertTypedConfigsToV2(filter); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
if x.Tracing != nil && x.Tracing.Provider != nil && x.Tracing.Provider.ConfigType != nil {
tc, ok := x.Tracing.Provider.ConfigType.(*envoy_trace_v2.Tracing_Http_TypedConfig)
if !ok {
return fmt.Errorf("%T: Tracing.Provider.ConfigType type %T not handled", x, x.Tracing.Provider.ConfigType)
}
if err := convertTypedConfigsToV2(tc.TypedConfig); err != nil {
return fmt.Errorf("%T: %w", x, err)
}
}
return nil
case *envoy_tls_inspector_v2.TlsInspector: // "envoy.filters.listener.tls_inspector"
return nil
case *envoy_tcp_proxy_v2.TcpProxy: // "envoy.filters.network.tcp_proxy"
return nil
case *envoy_network_rbac_v2.RBAC: // "envoy.filters.network.rbac"
return nil
case *envoy_sni_v2.SniCluster: // "envoy.filters.network.sni_cluster"
return nil
case *envoy_http_rbac_v2.RBAC:
return nil
case *envoy_tls_v2.UpstreamTlsContext:
return nil
case *envoy_tls_v2.DownstreamTlsContext:
return nil
case *envoy_grpc_stats_v2.FilterConfig:
return nil
default:
return fmt.Errorf("could not convert unexpected type to v2: %T", pb)
}
}
func convertTypeUrlsToV2(typeUrl *string) error {
if _, ok := typeConvert2to3[*typeUrl]; ok {
return nil // already happened
}
converted, ok := typeConvert3to2[*typeUrl]
if !ok {
return fmt.Errorf("could not convert type url to v2: %s", *typeUrl)
}
*typeUrl = converted
return nil
}
func convertTypeUrlsToV3(typeUrl *string) error {
if _, ok := typeConvert3to2[*typeUrl]; ok {
return nil // already happened
}
converted, ok := typeConvert2to3[*typeUrl]
if !ok {
return fmt.Errorf("could not convert type url to v3: %s", *typeUrl)
}
*typeUrl = converted
return nil
}
var (
typeConvert2to3 map[string]string
typeConvert3to2 map[string]string
)
func init() {
typeConvert2to3 = make(map[string]string)
typeConvert3to2 = make(map[string]string)
reg := func(type2, type3 string) {
if type2 == "" {
panic("v2 type is empty")
}
if type3 == "" {
panic("v3 type is empty")
}
typeConvert2to3[type2] = type3
typeConvert3to2[type3] = type2
}
reg2 := func(pb2, pb3 proto.Message) {
any2, err := ptypes.MarshalAny(pb2)
if err != nil {
panic(err)
}
any3, err := ptypes.MarshalAny(pb3)
if err != nil {
panic(err)
}
reg(any2.TypeUrl, any3.TypeUrl)
}
// primary resources
reg2(&envoy_api_v2.Listener{}, &envoy_listener_v3.Listener{}) // LDS
reg2(&envoy_api_v2.Cluster{}, &envoy_cluster_v3.Cluster{}) // CDS
reg2(&envoy_api_v2.RouteConfiguration{}, &envoy_route_v3.RouteConfiguration{}) // RDS
reg2(&envoy_api_v2.ClusterLoadAssignment{}, &envoy_endpoint_v3.ClusterLoadAssignment{}) // EDS
// filters
reg2(&envoy_http_v2.HttpConnectionManager{}, &envoy_http_v3.HttpConnectionManager{}) // "envoy.filters.network.http_connection_manager"
reg2(&envoy_tcp_proxy_v2.TcpProxy{}, &envoy_tcp_proxy_v3.TcpProxy{}) // "envoy.filters.network.tcp_proxy"
reg2(&envoy_network_rbac_v2.RBAC{}, &envoy_network_rbac_v3.RBAC{}) // "envoy.filters.network.rbac"
reg2(&envoy_http_rbac_v2.RBAC{}, &envoy_http_rbac_v3.RBAC{}) // "envoy.filters.http.rbac
reg2(&envoy_tls_inspector_v2.TlsInspector{}, &envoy_tls_inspector_v3.TlsInspector{}) // "envoy.filters.listener.tls_inspector"
reg2(&envoy_sni_v2.SniCluster{}, &envoy_sni_v3.SniCluster{}) // "envoy.filters.network.sni_cluster"
// cluster tls
reg2(&envoy_tls_v2.UpstreamTlsContext{}, &envoy_tls_v3.UpstreamTlsContext{})
reg2(&envoy_tls_v2.DownstreamTlsContext{}, &envoy_tls_v3.DownstreamTlsContext{})
// extension elements
reg2(&envoy_metrics_v2.DogStatsdSink{}, &envoy_metrics_v3.DogStatsdSink{})
reg2(&envoy_metrics_v2.StatsdSink{}, &envoy_metrics_v3.StatsdSink{})
reg2(&envoy_trace_v2.ZipkinConfig{}, &envoy_trace_v3.ZipkinConfig{})
reg2(&envoy_grpc_stats_v2.FilterConfig{}, &envoy_grpc_stats_v3.FilterConfig{})
}