github.com/outbrain/consul@v1.4.5/agent/xds/clusters.go (about)

     1  package xds
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	envoy "github.com/envoyproxy/go-control-plane/envoy/api/v2"
    10  	envoyauth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
    11  	envoycore "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
    12  	"github.com/gogo/protobuf/jsonpb"
    13  	"github.com/gogo/protobuf/proto"
    14  	"github.com/gogo/protobuf/types"
    15  
    16  	"github.com/hashicorp/consul/agent/proxycfg"
    17  	"github.com/hashicorp/consul/agent/structs"
    18  )
    19  
    20  // clustersFromSnapshot returns the xDS API representation of the "clusters"
    21  // (upstreams) in the snapshot.
    22  func clustersFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot, token string) ([]proto.Message, error) {
    23  	if cfgSnap == nil {
    24  		return nil, errors.New("nil config given")
    25  	}
    26  	// Include the "app" cluster for the public listener
    27  	clusters := make([]proto.Message, len(cfgSnap.Proxy.Upstreams)+1)
    28  
    29  	var err error
    30  	clusters[0], err = makeAppCluster(cfgSnap)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	for idx, upstream := range cfgSnap.Proxy.Upstreams {
    36  		clusters[idx+1], err = makeUpstreamCluster(upstream, cfgSnap)
    37  		if err != nil {
    38  			return nil, err
    39  		}
    40  	}
    41  
    42  	return clusters, nil
    43  }
    44  
    45  func makeAppCluster(cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Cluster, error) {
    46  	var c *envoy.Cluster
    47  	var err error
    48  
    49  	// If we have overridden local cluster config try to parse it into an Envoy cluster
    50  	if clusterJSONRaw, ok := cfgSnap.Proxy.Config["envoy_local_cluster_json"]; ok {
    51  		if clusterJSON, ok := clusterJSONRaw.(string); ok {
    52  			c, err = makeClusterFromUserConfig(clusterJSON)
    53  			if err != nil {
    54  				return c, err
    55  			}
    56  		}
    57  	}
    58  
    59  	if c == nil {
    60  		addr := cfgSnap.Proxy.LocalServiceAddress
    61  		if addr == "" {
    62  			addr = "127.0.0.1"
    63  		}
    64  		c = &envoy.Cluster{
    65  			Name:           LocalAppClusterName,
    66  			ConnectTimeout: 5 * time.Second,
    67  			Type:           envoy.Cluster_STATIC,
    68  			// API v2 docs say hosts is deprecated and should use LoadAssignment as
    69  			// below.. but it doesn't work for tcp_proxy target for some reason.
    70  			Hosts: []*envoycore.Address{makeAddressPtr(addr, cfgSnap.Proxy.LocalServicePort)},
    71  			// LoadAssignment: &envoy.ClusterLoadAssignment{
    72  			//  ClusterName: LocalAppClusterName,
    73  			//  Endpoints: []endpoint.LocalityLbEndpoints{
    74  			//    {
    75  			//      LbEndpoints: []endpoint.LbEndpoint{
    76  			//        makeEndpoint(LocalAppClusterName,
    77  			//          addr,
    78  			//          cfgSnap.Proxy.LocalServicePort),
    79  			//      },
    80  			//    },
    81  			//  },
    82  			// },
    83  		}
    84  	}
    85  
    86  	return c, err
    87  }
    88  
    89  func makeUpstreamCluster(upstream structs.Upstream, cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Cluster, error) {
    90  	var c *envoy.Cluster
    91  	var err error
    92  
    93  	// If we have overridden cluster config attempt to parse it into an Envoy cluster
    94  	if clusterJSONRaw, ok := upstream.Config["envoy_cluster_json"]; ok {
    95  		if clusterJSON, ok := clusterJSONRaw.(string); ok {
    96  			c, err = makeClusterFromUserConfig(clusterJSON)
    97  			if err != nil {
    98  				return c, err
    99  			}
   100  		}
   101  	}
   102  
   103  	if c == nil {
   104  		c = &envoy.Cluster{
   105  			Name:           upstream.Identifier(),
   106  			ConnectTimeout: 5 * time.Second,
   107  			Type:           envoy.Cluster_EDS,
   108  			EdsClusterConfig: &envoy.Cluster_EdsClusterConfig{
   109  				EdsConfig: &envoycore.ConfigSource{
   110  					ConfigSourceSpecifier: &envoycore.ConfigSource_Ads{
   111  						Ads: &envoycore.AggregatedConfigSource{},
   112  					},
   113  				},
   114  			},
   115  		}
   116  	}
   117  
   118  	// Enable TLS upstream with the configured client certificate.
   119  	c.TlsContext = &envoyauth.UpstreamTlsContext{
   120  		CommonTlsContext: makeCommonTLSContext(cfgSnap),
   121  	}
   122  
   123  	return c, nil
   124  }
   125  
   126  // makeClusterFromUserConfig returns the listener config decoded from an
   127  // arbitrary proto3 json format string or an error if it's invalid.
   128  //
   129  // For now we only support embedding in JSON strings because of the hcl parsing
   130  // pain (see config.go comment above call to patchSliceOfMaps). Until we
   131  // refactor config parser a _lot_ user's opaque config that contains arrays will
   132  // be mangled. We could actually fix that up in mapstructure which knows the
   133  // type of the target so could resolve the slices to singletons unambiguously
   134  // and it would work for us here... but we still have the problem that the
   135  // config would render incorrectly in general in our HTTP API responses so we
   136  // really need to fix it "properly".
   137  //
   138  // When we do that we can support just nesting the config directly into the
   139  // JSON/hcl naturally but this is a stop-gap that gets us an escape hatch
   140  // immediately. It's also probably not a bad thing to support long-term since
   141  // any config generated by other systems will likely be in canonical protobuf
   142  // from rather than our slight variant in JSON/hcl.
   143  func makeClusterFromUserConfig(configJSON string) (*envoy.Cluster, error) {
   144  	var jsonFields map[string]*json.RawMessage
   145  	if err := json.Unmarshal([]byte(configJSON), &jsonFields); err != nil {
   146  		fmt.Println("Custom error", err, configJSON)
   147  		return nil, err
   148  	}
   149  
   150  	var c envoy.Cluster
   151  
   152  	if _, ok := jsonFields["@type"]; ok {
   153  		// Type field is present so decode it as a types.Any
   154  		var any types.Any
   155  		err := jsonpb.UnmarshalString(configJSON, &any)
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  		// And then unmarshal the listener again...
   160  		err = proto.Unmarshal(any.Value, &c)
   161  		if err != nil {
   162  			panic(err)
   163  			//return nil, err
   164  		}
   165  		return &c, err
   166  	}
   167  
   168  	// No @type so try decoding as a straight listener.
   169  	err := jsonpb.UnmarshalString(configJSON, &c)
   170  	return &c, err
   171  }