mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
7.4 KiB
235 lines
7.4 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package troubleshoot |
|
|
|
import ( |
|
"github.com/hashicorp/consul/api" |
|
"github.com/hashicorp/consul/envoyextensions/extensioncommon" |
|
"github.com/hashicorp/consul/envoyextensions/xdscommon" |
|
"github.com/hashicorp/consul/troubleshoot/validate" |
|
|
|
envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" |
|
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_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" |
|
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" |
|
|
|
"google.golang.org/protobuf/encoding/protojson" |
|
"google.golang.org/protobuf/proto" |
|
) |
|
|
|
const ( |
|
listenersType string = "type.googleapis.com/envoy.admin.v3.ListenersConfigDump" |
|
clustersType string = "type.googleapis.com/envoy.admin.v3.ClustersConfigDump" |
|
routesType string = "type.googleapis.com/envoy.admin.v3.RoutesConfigDump" |
|
endpointsType string = "type.googleapis.com/envoy.admin.v3.EndpointsConfigDump" |
|
) |
|
|
|
func ParseConfigDump(rawConfig []byte) (*xdscommon.IndexedResources, error) { |
|
config := &envoy_admin_v3.ConfigDump{} |
|
|
|
unmarshal := &protojson.UnmarshalOptions{ |
|
DiscardUnknown: true, |
|
} |
|
err := unmarshal.Unmarshal(rawConfig, config) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return ProxyConfigDumpToIndexedResources(config) |
|
} |
|
|
|
func ParseClusters(rawClusters []byte) (*envoy_admin_v3.Clusters, error) { |
|
clusters := &envoy_admin_v3.Clusters{} |
|
unmarshal := &protojson.UnmarshalOptions{ |
|
DiscardUnknown: true, |
|
} |
|
err := unmarshal.Unmarshal(rawClusters, clusters) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return clusters, nil |
|
} |
|
|
|
// Validate validates the Envoy resources (indexedResources) for a given upstream service, peer, and vip. The peer |
|
// should be "" for an upstream not on a remote peer. The vip is required for a transparent proxy upstream. |
|
func Validate(indexedResources *xdscommon.IndexedResources, envoyID string, vip string, validateEndpoints bool, clusters *envoy_admin_v3.Clusters) validate.Messages { |
|
// Get all SNIs from the clusters in the configuration. Not all SNIs will need to be validated, but this ensures we |
|
// capture SNIs which aren't directly identical to the upstream service name, but are still used for that upstream |
|
// service. For example, in the case of having a splitter/redirect or another L7 config entry, the upstream service |
|
// name could be "db" but due to a redirect SNI would be something like |
|
// "redis.default.dc1.internal.<trustdomain>.consul". The envoyID will be used to limit which SNIs we actually |
|
// validate. |
|
snis := map[string]struct{}{} |
|
for s := range indexedResources.Index[xdscommon.ClusterType] { |
|
snis[s] = struct{}{} |
|
} |
|
|
|
// For this extension runtime configuration, we are only validating one upstream service, so the map key doesn't |
|
// need the full service name. |
|
emptyServiceKey := api.CompoundServiceName{} |
|
|
|
// Build an ExtensionConfiguration for Validate plugin. |
|
extConfig := extensioncommon.RuntimeConfig{ |
|
EnvoyExtension: api.EnvoyExtension{ |
|
Name: "builtin/proxy/validate", |
|
Arguments: map[string]interface{}{ |
|
"envoyID": envoyID, |
|
}, |
|
}, |
|
ServiceName: emptyServiceKey, |
|
IsSourcedFromUpstream: true, |
|
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ |
|
emptyServiceKey: { |
|
VIP: vip, |
|
// Even though snis are under the upstream service name we're validating, it actually contains all |
|
// the cluster SNIs configured on this proxy, not just the upstream being validated. This means the |
|
// PatchCluster function in the Validate plugin will be run on all clusters, but errors will only |
|
// surface for clusters related to the upstream being validated. |
|
SNIs: snis, |
|
EnvoyID: envoyID, |
|
}, |
|
}, |
|
Kind: api.ServiceKindConnectProxy, |
|
} |
|
ext, err := validate.MakeValidate(extConfig) |
|
if err != nil { |
|
return []validate.Message{{Message: err.Error()}} |
|
} |
|
extender := extensioncommon.UpstreamEnvoyExtender{ |
|
Extension: ext, |
|
} |
|
err = extender.Validate(&extConfig) |
|
if err != nil { |
|
return []validate.Message{{Message: err.Error()}} |
|
} |
|
|
|
_, err = extender.Extend(indexedResources, &extConfig) |
|
if err != nil { |
|
return []validate.Message{{Message: err.Error()}} |
|
} |
|
|
|
v, ok := extender.Extension.(*validate.Validate) |
|
if !ok { |
|
panic("validate plugin was not correctly created") |
|
} |
|
|
|
return v.GetMessages(validateEndpoints, validate.DoEndpointValidation, clusters) |
|
} |
|
|
|
func ProxyConfigDumpToIndexedResources(config *envoy_admin_v3.ConfigDump) (*xdscommon.IndexedResources, error) { |
|
indexedResources := xdscommon.EmptyIndexedResources() |
|
unmarshal := &proto.UnmarshalOptions{ |
|
DiscardUnknown: true, |
|
} |
|
|
|
for _, cfg := range config.Configs { |
|
switch cfg.TypeUrl { |
|
case listenersType: |
|
lcd := &envoy_admin_v3.ListenersConfigDump{} |
|
|
|
err := unmarshal.Unmarshal(cfg.GetValue(), lcd) |
|
if err != nil { |
|
return indexedResources, err |
|
} |
|
|
|
for _, listener := range lcd.GetDynamicListeners() { |
|
// TODO We should care about these: |
|
// listener.GetErrorState() |
|
// listener.GetDrainingState() |
|
// listener.GetWarmingState() |
|
|
|
r := indexedResources.Index[xdscommon.ListenerType] |
|
if r == nil { |
|
r = make(map[string]proto.Message) |
|
} |
|
as := listener.GetActiveState() |
|
if as == nil { |
|
continue |
|
} |
|
|
|
l := &envoy_listener_v3.Listener{} |
|
unmarshal.Unmarshal(as.Listener.GetValue(), l) |
|
if err != nil { |
|
return indexedResources, err |
|
} |
|
|
|
r[listener.Name] = l |
|
indexedResources.Index[xdscommon.ListenerType] = r |
|
} |
|
case clustersType: |
|
ccd := &envoy_admin_v3.ClustersConfigDump{} |
|
|
|
err := unmarshal.Unmarshal(cfg.GetValue(), ccd) |
|
if err != nil { |
|
return indexedResources, err |
|
} |
|
|
|
// TODO we should care about ccd.GetDynamicWarmingClusters() |
|
for _, cluster := range ccd.GetDynamicActiveClusters() { |
|
r := indexedResources.Index[xdscommon.ClusterType] |
|
if r == nil { |
|
r = make(map[string]proto.Message) |
|
} |
|
|
|
c := &envoy_cluster_v3.Cluster{} |
|
unmarshal.Unmarshal(cluster.GetCluster().Value, c) |
|
if err != nil { |
|
return indexedResources, err |
|
} |
|
|
|
r[c.Name] = c |
|
indexedResources.Index[xdscommon.ClusterType] = r |
|
} |
|
case routesType: |
|
rcd := &envoy_admin_v3.RoutesConfigDump{} |
|
|
|
err := unmarshal.Unmarshal(cfg.GetValue(), rcd) |
|
if err != nil { |
|
return indexedResources, err |
|
} |
|
|
|
for _, route := range rcd.GetDynamicRouteConfigs() { |
|
r := indexedResources.Index[xdscommon.RouteType] |
|
if r == nil { |
|
r = make(map[string]proto.Message) |
|
} |
|
|
|
rc := &envoy_route_v3.RouteConfiguration{} |
|
unmarshal.Unmarshal(route.GetRouteConfig().Value, rc) |
|
if err != nil { |
|
return indexedResources, err |
|
} |
|
|
|
r[rc.Name] = rc |
|
indexedResources.Index[xdscommon.RouteType] = r |
|
} |
|
case endpointsType: |
|
ecd := &envoy_admin_v3.EndpointsConfigDump{} |
|
|
|
err := unmarshal.Unmarshal(cfg.GetValue(), ecd) |
|
if err != nil { |
|
return indexedResources, err |
|
} |
|
|
|
for _, route := range ecd.GetDynamicEndpointConfigs() { |
|
r := indexedResources.Index[xdscommon.EndpointType] |
|
if r == nil { |
|
r = make(map[string]proto.Message) |
|
} |
|
|
|
rc := &envoy_endpoint_v3.ClusterLoadAssignment{} |
|
err := unmarshal.Unmarshal(route.EndpointConfig.GetValue(), rc) |
|
if err != nil { |
|
return indexedResources, err |
|
} |
|
|
|
r[rc.ClusterName] = rc |
|
indexedResources.Index[xdscommon.EndpointType] = r |
|
} |
|
} |
|
} |
|
|
|
return indexedResources, nil |
|
}
|
|
|