Skip to content

Commit

Permalink
Backport of fix: use Envoy's default for validate_clusters to fix bre…
Browse files Browse the repository at this point in the history
…aking routes when some backend clusters don't exist into release/1.19.x (#21621)

---------

Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
  • Loading branch information
hc-github-team-consul-core and ndhanushkodi authored Aug 20, 2024
1 parent abefc02 commit b996f99
Show file tree
Hide file tree
Showing 94 changed files with 2,248 additions and 1,671 deletions.
3 changes: 3 additions & 0 deletions .changelog/21587.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
Use Envoy's default for a route's validate_clusters option, which is false. This fixes a case where non-existent clusters could cause a route to no longer route to any of its backends, including existing ones.
```
8 changes: 8 additions & 0 deletions agent/structs/config_entry_mesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ type MeshConfigEntry struct {
// MutualTLSMode=permissive in either service-defaults or proxy-defaults.
AllowEnablingPermissiveMutualTLS bool `json:",omitempty" alias:"allow_enabling_permissive_mutual_tls"`

// ValidateClusters controls whether the clusters the route table refers to are validated. The default value is
// false. When set to false and a route refers to a cluster that does not exist, the route table loads and routing
// to a non-existent cluster results in a 404. When set to true and the route is set to a cluster that do not exist,
// the route table will not load. For more information, refer to
// [HTTP route configuration in the Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route.proto#envoy-v3-api-field-config-route-v3-routeconfiguration-validate-clusters)
// for more details.
ValidateClusters bool `json:",omitempty" alias:"validate_clusters"`

TLS *MeshTLSConfig `json:",omitempty"`

HTTP *MeshHTTPConfig `json:",omitempty"`
Expand Down
10 changes: 10 additions & 0 deletions agent/xds/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,16 @@ func getConnectProxyDiscoChainTests(enterprise bool) []goldenTestCase {
},
alsoRunTestForV2: true,
},
{
name: "connect-proxy-with-chain-and-splitter-and-mesh-validate-clusters",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
cfgSnap := proxycfg.TestConfigSnapshotDiscoveryChain(t, "chain-and-splitter", enterprise, nil, nil)
cfgSnap.ConnectProxy.MeshConfig = &structs.MeshConfigEntry{
ValidateClusters: true,
}
return cfgSnap
},
},
{
name: "connect-proxy-with-grpc-router",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
Expand Down
81 changes: 45 additions & 36 deletions agent/xds/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,19 @@ func (s *ResourceGenerator) routesFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot)
}
}

func meshValidateClusters(cfgSnap *proxycfg.ConfigSnapshot) bool {
validate := false
if mesh := cfgSnap.MeshConfig(); mesh != nil {
validate = mesh.ValidateClusters
}
return validate
}

// routesFromSnapshotConnectProxy returns the xDS API representation of the
// "routes" in the snapshot.
func (s *ResourceGenerator) routesForConnectProxy(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) {
var resources []proto.Message
validateClusters := meshValidateClusters(cfgSnap)
for uid, chain := range cfgSnap.ConnectProxy.DiscoveryChain {
if chain.Default {
continue
Expand All @@ -76,10 +85,9 @@ func (s *ResourceGenerator) routesForConnectProxy(cfgSnap *proxycfg.ConfigSnapsh
route := &envoy_route_v3.RouteConfiguration{
Name: uid.EnvoyID(),
VirtualHosts: []*envoy_route_v3.VirtualHost{virtualHost},
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the reasonable value of true to prevent
// null-routing traffic.
ValidateClusters: response.MakeBoolValue(true),
}
if validateClusters {
route.ValidateClusters = response.MakeBoolValue(true)
}
resources = append(resources, route)
}
Expand Down Expand Up @@ -112,7 +120,7 @@ func (s *ResourceGenerator) routesForConnectProxy(cfgSnap *proxycfg.ConfigSnapsh
}

for routeName, clusters := range addressesMap {
routes, err := s.makeRoutesForAddresses(routeName, clusters)
routes, err := s.makeRoutesForAddresses(routeName, clusters, validateClusters)
if err != nil {
return nil, err
}
Expand All @@ -125,10 +133,10 @@ func (s *ResourceGenerator) routesForConnectProxy(cfgSnap *proxycfg.ConfigSnapsh
return resources, nil
}

func (s *ResourceGenerator) makeRoutesForAddresses(routeName string, addresses map[string]string) ([]proto.Message, error) {
func (s *ResourceGenerator) makeRoutesForAddresses(routeName string, addresses map[string]string, validateClusters bool) ([]proto.Message, error) {
var resources []proto.Message

route, err := makeNamedAddressesRoute(routeName, addresses)
route, err := makeNamedAddressesRoute(routeName, addresses, validateClusters)
if err != nil {
s.Logger.Error("failed to make route", "cluster", "error", err)
return nil, err
Expand Down Expand Up @@ -223,7 +231,8 @@ func (s *ResourceGenerator) makeRoutes(
if resolver.LoadBalancer != nil {
lb = resolver.LoadBalancer
}
route, err := makeNamedDefaultRouteWithLB(clusterName, lb, resolver.RequestTimeout, autoHostRewrite)
validateClusters := meshValidateClusters(cfgSnap)
route, err := makeNamedDefaultRouteWithLB(clusterName, lb, resolver.RequestTimeout, autoHostRewrite, validateClusters)
if err != nil {
s.Logger.Error("failed to make route", "cluster", clusterName, "error", err)
return nil, err
Expand All @@ -233,7 +242,7 @@ func (s *ResourceGenerator) makeRoutes(
// If there is a service-resolver for this service then also setup routes for each subset
for name := range resolver.Subsets {
clusterName = connect.ServiceSNI(svc.Name, name, svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
route, err := makeNamedDefaultRouteWithLB(clusterName, lb, resolver.RequestTimeout, autoHostRewrite)
route, err := makeNamedDefaultRouteWithLB(clusterName, lb, resolver.RequestTimeout, autoHostRewrite, validateClusters)
if err != nil {
s.Logger.Error("failed to make route", "cluster", clusterName, "error", err)
return nil, err
Expand Down Expand Up @@ -276,18 +285,17 @@ func (s *ResourceGenerator) routesForMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
route := &envoy_route_v3.RouteConfiguration{
Name: uid.EnvoyID(),
VirtualHosts: []*envoy_route_v3.VirtualHost{virtualHost},
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the reasonable value of true to prevent
// null-routing traffic.
ValidateClusters: response.MakeBoolValue(true),
}
if meshValidateClusters(cfgSnap) {
route.ValidateClusters = response.MakeBoolValue(true)
}
resources = append(resources, route)
}

return resources, nil
}

func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, timeout time.Duration, autoHostRewrite bool) (*envoy_route_v3.RouteConfiguration, error) {
func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, timeout time.Duration, autoHostRewrite bool, validateClusters bool) (*envoy_route_v3.RouteConfiguration, error) {
action := makeRouteActionFromName(clusterName)

if err := injectLBToRouteAction(lb, action.Route); err != nil {
Expand All @@ -305,7 +313,7 @@ func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, t
action.Route.Timeout = durationpb.New(timeout)
}

return &envoy_route_v3.RouteConfiguration{
route := &envoy_route_v3.RouteConfiguration{
Name: clusterName,
VirtualHosts: []*envoy_route_v3.VirtualHost{
{
Expand All @@ -319,20 +327,19 @@ func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, t
},
},
},
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the reasonable value of true to prevent
// null-routing traffic.
ValidateClusters: response.MakeBoolValue(true),
}, nil
}
if validateClusters {
route.ValidateClusters = response.MakeBoolValue(true)
}
return route, nil
}

func makeNamedAddressesRoute(routeName string, addresses map[string]string) (*envoy_route_v3.RouteConfiguration, error) {
func makeNamedAddressesRoute(routeName string, addresses map[string]string, validateClusters bool) (*envoy_route_v3.RouteConfiguration, error) {
route := &envoy_route_v3.RouteConfiguration{
Name: routeName,
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the reasonable value of true to prevent
// null-routing traffic.
ValidateClusters: response.MakeBoolValue(true),
}
if validateClusters {
route.ValidateClusters = response.MakeBoolValue(true)
}
for clusterName, address := range addresses {
action := makeRouteActionFromName(clusterName)
Expand Down Expand Up @@ -371,10 +378,10 @@ func (s *ResourceGenerator) routesForIngressGateway(cfgSnap *proxycfg.ConfigSnap
// don't have custom filter chains and routes to this.
defaultRoute := &envoy_route_v3.RouteConfiguration{
Name: listenerKey.RouteName(),
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the reasonable value of true to prevent
// null-routing traffic.
ValidateClusters: response.MakeBoolValue(true),
}
validateClusters := meshValidateClusters(cfgSnap)
if validateClusters {
defaultRoute.ValidateClusters = response.MakeBoolValue(true)
}

for _, u := range upstreams {
Expand Down Expand Up @@ -422,9 +429,11 @@ func (s *ResourceGenerator) routesForIngressGateway(cfgSnap *proxycfg.ConfigSnap
defaultRoute.VirtualHosts = append(defaultRoute.VirtualHosts, virtualHost)
} else {
svcRoute := &envoy_route_v3.RouteConfiguration{
Name: svcRouteName,
ValidateClusters: response.MakeBoolValue(true),
VirtualHosts: []*envoy_route_v3.VirtualHost{virtualHost},
Name: svcRouteName,
VirtualHosts: []*envoy_route_v3.VirtualHost{virtualHost},
}
if validateClusters {
svcRoute.ValidateClusters = response.MakeBoolValue(true)
}
result = append(result, svcRoute)
}
Expand Down Expand Up @@ -460,10 +469,10 @@ func (s *ResourceGenerator) routesForAPIGateway(cfgSnap *proxycfg.ConfigSnapshot

listenerRoute := &envoy_route_v3.RouteConfiguration{
Name: readyListener.listenerKey.RouteName(),
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the reasonable value of true to prevent
// null-routing traffic.
ValidateClusters: response.MakeBoolValue(true),
}
validateClusters := meshValidateClusters(cfgSnap)
if validateClusters {
listenerRoute.ValidateClusters = response.MakeBoolValue(true)
}

// Consolidate all routes for this listener into the minimum possible set based on hostname matching.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "canary1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"validateClusters": true,
"virtualHosts": [
{
"domains": [
Expand All @@ -27,7 +26,6 @@
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "canary2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"validateClusters": true,
"virtualHosts": [
{
"domains": [
Expand All @@ -50,7 +48,6 @@
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"validateClusters": true,
"virtualHosts": [
{
"domains": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"validateClusters": true,
"virtualHosts": [
{
"domains": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "destination.443.~http.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"validateClusters": true,
"virtualHosts": [
{
"domains": [
Expand All @@ -27,7 +26,6 @@
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "destination.9093.~http.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"validateClusters": true,
"virtualHosts": [
{
"domains": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"mostSpecificHeaderMutationsWins": true,
"name": "db",
"validateClusters": true,
"virtualHosts": [
{
"domains": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
}
]
}
],
"validateClusters": true
]
}
],
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
Expand Down
Loading

0 comments on commit b996f99

Please sign in to comment.