2023-03-28 18:39:22 +00:00
// Copyright (c) HashiCorp, Inc.
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
2023-03-28 18:39:22 +00:00
2018-10-03 18:18:55 +00:00
package xds
import (
"errors"
2019-07-02 03:10:51 +00:00
"fmt"
2020-07-07 15:43:04 +00:00
"net"
2022-08-01 18:12:43 +00:00
"sort"
2019-07-24 01:56:39 +00:00
"strings"
2020-09-12 00:34:03 +00:00
"time"
2018-10-03 18:18:55 +00:00
2021-07-13 12:53:59 +00:00
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
2021-02-26 22:23:15 +00:00
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
2023-10-10 20:25:36 +00:00
"golang.org/x/exp/maps"
2023-10-11 18:11:29 +00:00
"golang.org/x/exp/slices"
2023-10-10 20:25:36 +00:00
2020-08-28 20:27:40 +00:00
"github.com/hashicorp/consul/agent/connect"
2023-05-25 14:54:55 +00:00
"github.com/hashicorp/consul/agent/consul/discoverychain"
2018-10-03 18:18:55 +00:00
"github.com/hashicorp/consul/agent/proxycfg"
2019-07-02 03:10:51 +00:00
"github.com/hashicorp/consul/agent/structs"
2023-08-15 18:57:07 +00:00
"github.com/hashicorp/consul/agent/xds/config"
2023-08-17 18:43:21 +00:00
"github.com/hashicorp/consul/agent/xds/response"
2023-12-22 19:49:44 +00:00
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
2024-01-22 15:23:24 +00:00
"google.golang.org/protobuf/types/known/wrapperspb"
2018-10-03 18:18:55 +00:00
)
2019-07-02 03:10:51 +00:00
// routesFromSnapshot returns the xDS API representation of the "routes" in the
// snapshot.
2021-04-29 18:54:05 +00:00
func ( s * ResourceGenerator ) routesFromSnapshot ( cfgSnap * proxycfg . ConfigSnapshot ) ( [ ] proto . Message , error ) {
2018-10-03 18:18:55 +00:00
if cfgSnap == nil {
return nil , errors . New ( "nil config given" )
}
2019-07-02 03:10:51 +00:00
switch cfgSnap . Kind {
case structs . ServiceKindConnectProxy :
2021-12-13 22:30:49 +00:00
return s . routesForConnectProxy ( cfgSnap )
2020-04-03 19:41:10 +00:00
case structs . ServiceKindIngressGateway :
2022-03-07 17:47:14 +00:00
return s . routesForIngressGateway ( cfgSnap )
2023-02-08 21:52:12 +00:00
case structs . ServiceKindAPIGateway :
2023-05-25 14:54:55 +00:00
return s . routesForAPIGateway ( cfgSnap )
2020-08-28 20:27:40 +00:00
case structs . ServiceKindTerminatingGateway :
2022-06-28 19:52:25 +00:00
return s . routesForTerminatingGateway ( cfgSnap )
2021-04-29 18:54:05 +00:00
case structs . ServiceKindMeshGateway :
2022-06-28 19:52:25 +00:00
return s . routesForMeshGateway ( cfgSnap )
2019-07-02 03:10:51 +00:00
default :
return nil , fmt . Errorf ( "Invalid service kind: %v" , cfgSnap . Kind )
}
}
2024-08-20 05:39:28 +00:00
func meshValidateClusters ( cfgSnap * proxycfg . ConfigSnapshot ) bool {
validate := false
if mesh := cfgSnap . MeshConfig ( ) ; mesh != nil {
validate = mesh . ValidateClusters
}
return validate
}
2020-09-11 16:49:26 +00:00
// routesFromSnapshotConnectProxy returns the xDS API representation of the
// "routes" in the snapshot.
2021-12-13 22:30:49 +00:00
func ( s * ResourceGenerator ) routesForConnectProxy ( cfgSnap * proxycfg . ConfigSnapshot ) ( [ ] proto . Message , error ) {
2020-09-11 16:49:26 +00:00
var resources [ ] proto . Message
2024-08-20 05:39:28 +00:00
validateClusters := meshValidateClusters ( cfgSnap )
2022-01-20 16:12:04 +00:00
for uid , chain := range cfgSnap . ConnectProxy . DiscoveryChain {
2022-03-30 15:04:18 +00:00
if chain . Default {
2021-03-17 19:40:49 +00:00
continue
2020-09-11 16:49:26 +00:00
}
2023-11-03 20:53:17 +00:00
if ! structs . IsProtocolHTTPLike ( chain . Protocol ) {
// Routes can only be defined for HTTP services
continue
}
2023-08-31 16:23:59 +00:00
virtualHost , err := s . makeUpstreamRouteForDiscoveryChain ( cfgSnap , uid , chain , [ ] string { "*" } , false , perRouteFilterBuilder { } )
2021-03-17 19:40:49 +00:00
if err != nil {
return nil , err
}
2023-08-03 19:03:02 +00:00
if virtualHost == nil {
continue
}
2020-09-11 16:49:26 +00:00
2021-03-17 19:40:49 +00:00
route := & envoy_route_v3 . RouteConfiguration {
2022-01-20 16:12:04 +00:00
Name : uid . EnvoyID ( ) ,
2021-03-17 19:40:49 +00:00
VirtualHosts : [ ] * envoy_route_v3 . VirtualHost { virtualHost } ,
2024-08-20 05:39:28 +00:00
}
if validateClusters {
route . ValidateClusters = response . MakeBoolValue ( true )
2020-09-11 16:49:26 +00:00
}
2021-03-17 19:40:49 +00:00
resources = append ( resources , route )
2020-09-11 16:49:26 +00:00
}
2022-08-01 18:12:43 +00:00
addressesMap := make ( map [ string ] map [ string ] string )
err := cfgSnap . ConnectProxy . DestinationsUpstream . ForEachKeyE ( func ( uid proxycfg . UpstreamID ) error {
svcConfig , ok := cfgSnap . ConnectProxy . DestinationsUpstream . Get ( uid )
if ! ok || svcConfig == nil {
return nil
}
if ! structs . IsProtocolHTTPLike ( svcConfig . Protocol ) {
// Routes can only be defined for HTTP services
return nil
}
for _ , address := range svcConfig . Destination . Addresses {
routeName := clusterNameForDestination ( cfgSnap , "~http" , fmt . Sprintf ( "%d" , svcConfig . Destination . Port ) , svcConfig . NamespaceOrDefault ( ) , svcConfig . PartitionOrDefault ( ) )
if _ , ok := addressesMap [ routeName ] ; ! ok {
addressesMap [ routeName ] = make ( map [ string ] string )
}
// cluster name is unique per address/port so we should not be doing any override here
2023-08-31 16:23:59 +00:00
2022-08-01 18:12:43 +00:00
clusterName := clusterNameForDestination ( cfgSnap , svcConfig . Name , address , svcConfig . NamespaceOrDefault ( ) , svcConfig . PartitionOrDefault ( ) )
addressesMap [ routeName ] [ clusterName ] = address
}
return nil
} )
if err != nil {
return nil , err
}
for routeName , clusters := range addressesMap {
2024-08-20 05:39:28 +00:00
routes , err := s . makeRoutesForAddresses ( routeName , clusters , validateClusters )
2022-08-01 18:12:43 +00:00
if err != nil {
return nil , err
}
if routes != nil {
resources = append ( resources , routes ... )
}
}
2020-09-11 16:49:26 +00:00
// TODO(rb): make sure we don't generate an empty result
return resources , nil
}
2024-08-20 05:39:28 +00:00
func ( s * ResourceGenerator ) makeRoutesForAddresses ( routeName string , addresses map [ string ] string , validateClusters bool ) ( [ ] proto . Message , error ) {
2022-08-01 18:12:43 +00:00
var resources [ ] proto . Message
2024-08-20 05:39:28 +00:00
route , err := makeNamedAddressesRoute ( routeName , addresses , validateClusters )
2022-08-01 18:12:43 +00:00
if err != nil {
s . Logger . Error ( "failed to make route" , "cluster" , "error" , err )
return nil , err
}
resources = append ( resources , route )
return resources , nil
}
2020-08-28 20:27:40 +00:00
// routesFromSnapshotTerminatingGateway returns the xDS API representation of the "routes" in the snapshot.
// For any HTTP service we will return a default route.
2022-06-28 19:52:25 +00:00
func ( s * ResourceGenerator ) routesForTerminatingGateway ( cfgSnap * proxycfg . ConfigSnapshot ) ( [ ] proto . Message , error ) {
2019-07-02 03:10:51 +00:00
if cfgSnap == nil {
return nil , errors . New ( "nil config given" )
}
var resources [ ] proto . Message
2020-09-02 15:10:50 +00:00
for _ , svc := range cfgSnap . TerminatingGateway . ValidServices ( ) {
2021-09-01 14:35:39 +00:00
clusterName := connect . ServiceSNI ( svc . Name , "" , svc . NamespaceOrDefault ( ) , svc . PartitionOrDefault ( ) , cfgSnap . Datacenter , cfgSnap . Roots . TrustDomain )
2023-08-15 18:57:07 +00:00
cfg , err := config . ParseProxyConfig ( cfgSnap . TerminatingGateway . ServiceConfigs [ svc ] . ProxyConfig )
2022-08-01 18:12:43 +00:00
if err != nil {
// Don't hard fail on a config typo, just warn. The parse func returns
// default config if there is an error so it's safe to continue.
s . Logger . Warn (
"failed to parse Proxy.Config" ,
"service" , svc . String ( ) ,
"error" , err ,
)
}
2024-03-12 20:37:20 +00:00
service := cfgSnap . TerminatingGateway . GatewayServices [ svc ]
autoHostRewrite := service . AutoHostRewrite
2022-08-01 18:12:43 +00:00
if ! structs . IsProtocolHTTPLike ( cfg . Protocol ) {
// Routes can only be defined for HTTP services
continue
}
2024-03-12 20:37:20 +00:00
routes , err := s . makeRoutes ( cfgSnap , svc , clusterName , autoHostRewrite )
2020-09-03 16:21:20 +00:00
if err != nil {
2022-07-14 18:45:51 +00:00
return nil , err
2020-09-03 16:21:20 +00:00
}
2022-07-14 18:45:51 +00:00
if routes != nil {
resources = append ( resources , routes ... )
2020-08-28 20:27:40 +00:00
}
2022-07-14 18:45:51 +00:00
}
2020-08-28 20:27:40 +00:00
2022-07-14 18:45:51 +00:00
for _ , svc := range cfgSnap . TerminatingGateway . ValidDestinations ( ) {
2022-07-18 21:10:06 +00:00
svcConfig := cfgSnap . TerminatingGateway . ServiceConfigs [ svc ]
for _ , address := range svcConfig . Destination . Addresses {
clusterName := clusterNameForDestination ( cfgSnap , svc . Name , address , svc . NamespaceOrDefault ( ) , svc . PartitionOrDefault ( ) )
2023-08-15 18:57:07 +00:00
cfg , err := config . ParseProxyConfig ( cfgSnap . TerminatingGateway . ServiceConfigs [ svc ] . ProxyConfig )
2022-08-01 18:12:43 +00:00
if err != nil {
// Don't hard fail on a config typo, just warn. The parse func returns
// default config if there is an error so it's safe to continue.
s . Logger . Warn (
"failed to parse Proxy.Config" ,
"service" , svc . String ( ) ,
"error" , err ,
)
}
if ! structs . IsProtocolHTTPLike ( cfg . Protocol ) {
// Routes can only be defined for HTTP services
continue
}
2022-07-18 21:10:06 +00:00
routes , err := s . makeRoutes ( cfgSnap , svc , clusterName , false )
if err != nil {
return nil , err
}
if routes != nil {
resources = append ( resources , routes ... )
}
2020-09-02 15:10:50 +00:00
}
2022-07-14 18:45:51 +00:00
}
return resources , nil
}
func ( s * ResourceGenerator ) makeRoutes (
cfgSnap * proxycfg . ConfigSnapshot ,
svc structs . ServiceName ,
clusterName string ,
2023-08-31 16:23:59 +00:00
autoHostRewrite bool ,
) ( [ ] proto . Message , error ) {
2022-07-14 18:45:51 +00:00
resolver , hasResolver := cfgSnap . TerminatingGateway . ServiceResolvers [ svc ]
if ! hasResolver {
// Use a zero value resolver with no timeout and no subsets
resolver = & structs . ServiceResolverConfigEntry { }
}
var resources [ ] proto . Message
var lb * structs . LoadBalancer
if resolver . LoadBalancer != nil {
lb = resolver . LoadBalancer
}
2024-08-20 05:39:28 +00:00
validateClusters := meshValidateClusters ( cfgSnap )
route , err := makeNamedDefaultRouteWithLB ( clusterName , lb , resolver . RequestTimeout , autoHostRewrite , validateClusters )
2022-07-14 18:45:51 +00:00
if err != nil {
s . Logger . Error ( "failed to make route" , "cluster" , clusterName , "error" , err )
return nil , err
}
resources = append ( resources , route )
// 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 )
2024-08-20 05:39:28 +00:00
route , err := makeNamedDefaultRouteWithLB ( clusterName , lb , resolver . RequestTimeout , autoHostRewrite , validateClusters )
2020-08-28 20:27:40 +00:00
if err != nil {
2021-04-29 18:54:05 +00:00
s . Logger . Error ( "failed to make route" , "cluster" , clusterName , "error" , err )
2022-07-14 18:45:51 +00:00
return nil , err
2020-08-28 20:27:40 +00:00
}
resources = append ( resources , route )
}
return resources , nil
}
2022-06-28 19:52:25 +00:00
func ( s * ResourceGenerator ) routesForMeshGateway ( cfgSnap * proxycfg . ConfigSnapshot ) ( [ ] proto . Message , error ) {
if cfgSnap == nil {
return nil , errors . New ( "nil config given" )
}
var resources [ ] proto . Message
for _ , svc := range cfgSnap . MeshGatewayValidExportedServices ( ) {
chain := cfgSnap . MeshGateway . DiscoveryChain [ svc ]
if ! structs . IsProtocolHTTPLike ( chain . Protocol ) {
2022-06-29 15:29:54 +00:00
continue // ignore; not relevant
}
2022-06-28 19:52:25 +00:00
uid := proxycfg . NewUpstreamIDFromServiceName ( svc )
2022-09-09 17:58:28 +00:00
virtualHost , err := s . makeUpstreamRouteForDiscoveryChain (
cfgSnap ,
uid ,
2022-06-28 19:52:25 +00:00
chain ,
[ ] string { "*" } ,
2022-09-09 17:58:28 +00:00
true ,
2023-08-31 16:23:59 +00:00
perRouteFilterBuilder { } ,
2022-06-28 19:52:25 +00:00
)
if err != nil {
return nil , err
}
2023-08-03 19:03:02 +00:00
if virtualHost == nil {
continue
}
2022-06-28 19:52:25 +00:00
route := & envoy_route_v3 . RouteConfiguration {
Name : uid . EnvoyID ( ) ,
VirtualHosts : [ ] * envoy_route_v3 . VirtualHost { virtualHost } ,
2024-08-20 05:39:28 +00:00
}
if meshValidateClusters ( cfgSnap ) {
route . ValidateClusters = response . MakeBoolValue ( true )
2022-06-28 19:52:25 +00:00
}
resources = append ( resources , route )
}
return resources , nil
}
2024-08-20 05:39:28 +00:00
func makeNamedDefaultRouteWithLB ( clusterName string , lb * structs . LoadBalancer , timeout time . Duration , autoHostRewrite bool , validateClusters bool ) ( * envoy_route_v3 . RouteConfiguration , error ) {
2020-08-28 20:27:40 +00:00
action := makeRouteActionFromName ( clusterName )
2020-09-02 15:10:50 +00:00
2020-09-02 21:13:50 +00:00
if err := injectLBToRouteAction ( lb , action . Route ) ; err != nil {
2020-08-28 20:27:40 +00:00
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2020-10-23 09:53:29 +00:00
// Configure Envoy to rewrite Host header
if autoHostRewrite {
2021-04-06 09:05:26 +00:00
action . Route . HostRewriteSpecifier = & envoy_route_v3 . RouteAction_AutoHostRewrite {
2023-08-17 18:43:21 +00:00
AutoHostRewrite : response . MakeBoolValue ( true ) ,
2020-10-23 09:53:29 +00:00
}
}
2023-03-03 14:37:12 +00:00
if timeout != 0 {
action . Route . Timeout = durationpb . New ( timeout )
}
2024-08-20 05:39:28 +00:00
route := & envoy_route_v3 . RouteConfiguration {
2020-08-28 20:27:40 +00:00
Name : clusterName ,
2021-02-26 22:23:15 +00:00
VirtualHosts : [ ] * envoy_route_v3 . VirtualHost {
2020-08-28 20:27:40 +00:00
{
Name : clusterName ,
Domains : [ ] string { "*" } ,
2021-02-26 22:23:15 +00:00
Routes : [ ] * envoy_route_v3 . Route {
2020-08-28 20:27:40 +00:00
{
Match : makeDefaultRouteMatch ( ) ,
Action : action ,
} ,
} ,
} ,
} ,
2024-08-20 05:39:28 +00:00
}
if validateClusters {
route . ValidateClusters = response . MakeBoolValue ( true )
}
return route , nil
2020-08-28 20:27:40 +00:00
}
2024-08-20 05:39:28 +00:00
func makeNamedAddressesRoute ( routeName string , addresses map [ string ] string , validateClusters bool ) ( * envoy_route_v3 . RouteConfiguration , error ) {
2022-08-01 18:12:43 +00:00
route := & envoy_route_v3 . RouteConfiguration {
Name : routeName ,
2024-08-20 05:39:28 +00:00
}
if validateClusters {
route . ValidateClusters = response . MakeBoolValue ( true )
2022-08-01 18:12:43 +00:00
}
for clusterName , address := range addresses {
action := makeRouteActionFromName ( clusterName )
virtualHost := & envoy_route_v3 . VirtualHost {
Name : clusterName ,
Domains : [ ] string { address } ,
Routes : [ ] * envoy_route_v3 . Route {
{
Match : makeDefaultRouteMatch ( ) ,
Action : action ,
} ,
} ,
}
route . VirtualHosts = append ( route . VirtualHosts , virtualHost )
}
// sort virtual hosts to have a stable order
sort . SliceStable ( route . VirtualHosts , func ( i , j int ) bool {
return route . VirtualHosts [ i ] . Name > route . VirtualHosts [ j ] . Name
} )
return route , nil
}
2020-08-28 20:27:40 +00:00
// routesForIngressGateway returns the xDS API representation of the
2020-04-16 23:24:11 +00:00
// "routes" in the snapshot.
2022-03-07 17:47:14 +00:00
func ( s * ResourceGenerator ) routesForIngressGateway ( cfgSnap * proxycfg . ConfigSnapshot ) ( [ ] proto . Message , error ) {
2020-04-16 23:24:11 +00:00
var result [ ] proto . Message
2022-03-07 17:47:14 +00:00
for listenerKey , upstreams := range cfgSnap . IngressGateway . Upstreams {
2020-04-16 23:24:11 +00:00
// Do not create any route configuration for TCP listeners
if listenerKey . Protocol == "tcp" {
continue
}
2021-08-24 14:25:22 +00:00
// Depending on their TLS config, upstreams are either attached to the
// default route or have their own routes. We'll add any upstreams that
// don't have custom filter chains and routes to this.
defaultRoute := & envoy_route_v3 . RouteConfiguration {
2020-04-16 23:24:11 +00:00
Name : listenerKey . RouteName ( ) ,
2024-08-20 05:39:28 +00:00
}
validateClusters := meshValidateClusters ( cfgSnap )
if validateClusters {
defaultRoute . ValidateClusters = response . MakeBoolValue ( true )
2020-04-16 23:24:11 +00:00
}
2021-08-24 14:25:22 +00:00
2020-04-16 23:24:11 +00:00
for _ , u := range upstreams {
2022-01-20 16:12:04 +00:00
uid := proxycfg . NewUpstreamID ( & u )
2022-03-07 17:47:14 +00:00
chain := cfgSnap . IngressGateway . DiscoveryChain [ uid ]
2020-04-23 15:06:19 +00:00
if chain == nil {
2023-08-03 19:03:02 +00:00
// Note that if we continue here we must also do this in the cluster generation
s . Logger . Warn ( "could not find discovery chain for ingress upstream" ,
"listener" , listenerKey , "upstream" , uid )
2020-04-23 15:06:19 +00:00
continue
}
2020-07-07 15:43:04 +00:00
domains := generateUpstreamIngressDomains ( listenerKey , u )
2023-08-31 16:23:59 +00:00
virtualHost , err := s . makeUpstreamRouteForDiscoveryChain ( cfgSnap , uid , chain , domains , false , perRouteFilterBuilder { } )
2020-04-23 15:06:19 +00:00
if err != nil {
return nil , err
2020-04-16 23:24:11 +00:00
}
2023-08-03 19:03:02 +00:00
if virtualHost == nil {
continue
}
2021-07-13 12:53:59 +00:00
2021-08-24 14:25:22 +00:00
// Lookup listener and service config details from ingress gateway
// definition.
2022-03-07 17:47:14 +00:00
lCfg , ok := cfgSnap . IngressGateway . Listeners [ listenerKey ]
2021-08-24 14:25:22 +00:00
if ! ok {
2022-03-07 17:47:14 +00:00
return nil , fmt . Errorf ( "missing ingress listener config (service %q listener on proto/port %s/%d)" ,
u . DestinationID ( ) , listenerKey . Protocol , listenerKey . Port )
2021-08-24 14:25:22 +00:00
}
svc := findIngressServiceMatchingUpstream ( lCfg , u )
if svc == nil {
2022-03-07 17:47:14 +00:00
return nil , fmt . Errorf ( "missing service in listener config (service %q listener on proto/port %s/%d)" ,
u . DestinationID ( ) , listenerKey . Protocol , listenerKey . Port )
2021-08-24 14:25:22 +00:00
}
if err := injectHeaderManipToVirtualHost ( svc , virtualHost ) ; err != nil {
return nil , err
}
2021-09-13 12:03:16 +00:00
// See if this upstream has its own route/filter chain
svcRouteName := routeNameForUpstream ( lCfg , * svc )
2021-07-13 12:53:59 +00:00
2021-08-24 14:25:22 +00:00
// If the routeName is the same as the default one, merge the virtual host
// to the default route
if svcRouteName == defaultRoute . Name {
defaultRoute . VirtualHosts = append ( defaultRoute . VirtualHosts , virtualHost )
} else {
svcRoute := & envoy_route_v3 . RouteConfiguration {
2024-08-20 05:39:28 +00:00
Name : svcRouteName ,
VirtualHosts : [ ] * envoy_route_v3 . VirtualHost { virtualHost } ,
}
if validateClusters {
svcRoute . ValidateClusters = response . MakeBoolValue ( true )
2021-08-24 14:25:22 +00:00
}
result = append ( result , svcRoute )
}
2020-04-16 23:24:11 +00:00
}
2021-08-24 14:25:22 +00:00
if len ( defaultRoute . VirtualHosts ) > 0 {
result = append ( result , defaultRoute )
}
2020-04-16 23:24:11 +00:00
}
return result , nil
}
2023-05-25 15:16:37 +00:00
// routesForAPIGateway returns the xDS API representation of the "routes" in the snapshot.
2023-05-25 14:54:55 +00:00
func ( s * ResourceGenerator ) routesForAPIGateway ( cfgSnap * proxycfg . ConfigSnapshot ) ( [ ] proto . Message , error ) {
var result [ ] proto . Message
2023-10-11 18:11:29 +00:00
// Build up the routes in a deterministic way
2023-10-10 20:25:36 +00:00
readyListeners := getReadyListeners ( cfgSnap )
2023-10-11 18:11:29 +00:00
listenerNames := maps . Keys ( readyListeners )
sort . Strings ( listenerNames )
2023-12-07 22:56:14 +00:00
// Iterate over all listeners that are ready and configure their routes.
2023-10-11 18:11:29 +00:00
for _ , listenerName := range listenerNames {
readyListener , ok := readyListeners [ listenerName ]
if ! ok {
continue
}
2023-05-25 14:54:55 +00:00
// Do not create any route configuration for TCP listeners
2023-10-10 20:25:36 +00:00
if readyListener . listenerCfg . Protocol != structs . ListenerProtocolHTTP {
2023-05-25 14:54:55 +00:00
continue
}
2023-10-10 20:25:36 +00:00
listenerRoute := & envoy_route_v3 . RouteConfiguration {
Name : readyListener . listenerKey . RouteName ( ) ,
2024-08-20 05:39:28 +00:00
}
validateClusters := meshValidateClusters ( cfgSnap )
if validateClusters {
listenerRoute . ValidateClusters = response . MakeBoolValue ( true )
2023-05-25 14:54:55 +00:00
}
2023-10-10 20:25:36 +00:00
// Consolidate all routes for this listener into the minimum possible set based on hostname matching.
allRoutesForListener := [ ] * structs . HTTPRouteConfigEntry { }
for _ , routeRef := range maps . Keys ( readyListener . routeReferences ) {
route , ok := cfgSnap . APIGateway . HTTPRoutes . Get ( routeRef )
if ! ok {
return nil , fmt . Errorf ( "missing route for route routeRef %s:%s" , routeRef . Name , routeRef . Kind )
}
allRoutesForListener = append ( allRoutesForListener , route )
2023-05-25 14:54:55 +00:00
}
2023-10-10 20:25:36 +00:00
consolidatedRoutes := discoverychain . ConsolidateHTTPRoutes ( cfgSnap . APIGateway . GatewayConfig , & readyListener . listenerCfg , allRoutesForListener ... )
2023-05-25 14:54:55 +00:00
2023-10-10 20:25:36 +00:00
// Produce one virtual host per hostname. If no hostname is specified for a set of
// Gateway + HTTPRoutes, then the virtual host will be "*".
for _ , consolidatedRoute := range consolidatedRoutes {
upstream := buildHTTPRouteUpstream ( consolidatedRoute , readyListener . listenerCfg )
2023-10-11 18:11:29 +00:00
// Consolidate all routes for this listener into the minimum possible set based on hostname matching.
2023-05-25 14:54:55 +00:00
uid := proxycfg . NewUpstreamID ( & upstream )
chain := cfgSnap . APIGateway . DiscoveryChain [ uid ]
if chain == nil {
s . Logger . Debug ( "Discovery chain not found for flattened route" , "discovery chain ID" , uid )
continue
}
2023-12-07 22:56:14 +00:00
consolidatedRoute := consolidatedRoute // Reassignment to avoid closure issues with the loop variable.
2023-10-10 20:25:36 +00:00
domains := generateUpstreamAPIsDomains ( readyListener . listenerKey , upstream , consolidatedRoute . Hostnames )
2023-05-25 14:54:55 +00:00
2023-10-10 20:25:36 +00:00
filterBuilder := perRouteFilterBuilder { providerMap : cfgSnap . JWTProviders , listener : & readyListener . listenerCfg , route : & consolidatedRoute }
2023-08-31 16:23:59 +00:00
virtualHost , err := s . makeUpstreamRouteForDiscoveryChain ( cfgSnap , uid , chain , domains , false , filterBuilder )
2023-05-25 14:54:55 +00:00
if err != nil {
return nil , err
}
2023-10-10 20:25:36 +00:00
listenerRoute . VirtualHosts = append ( listenerRoute . VirtualHosts , virtualHost )
2023-05-25 14:54:55 +00:00
}
2023-10-10 20:25:36 +00:00
if len ( listenerRoute . VirtualHosts ) > 0 {
2023-10-11 18:11:29 +00:00
// Build up the virtual hosts in a deterministic way
slices . SortStableFunc ( listenerRoute . VirtualHosts , func ( a , b * envoy_route_v3 . VirtualHost ) int {
if a . Name < b . Name {
return - 1
}
return 1
} )
2023-10-10 20:25:36 +00:00
result = append ( result , listenerRoute )
2023-05-25 14:54:55 +00:00
}
}
return result , nil
}
func buildHTTPRouteUpstream ( route structs . HTTPRouteConfigEntry , listener structs . APIGatewayListener ) structs . Upstream {
return structs . Upstream {
DestinationName : route . GetName ( ) ,
DestinationNamespace : route . NamespaceOrDefault ( ) ,
DestinationPartition : route . PartitionOrDefault ( ) ,
IngressHosts : route . Hostnames ,
LocalBindPort : listener . Port ,
Config : map [ string ] interface { } {
"protocol" : string ( listener . Protocol ) ,
} ,
}
}
2021-07-13 12:53:59 +00:00
func makeHeadersValueOptions ( vals map [ string ] string , add bool ) [ ] * envoy_core_v3 . HeaderValueOption {
opts := make ( [ ] * envoy_core_v3 . HeaderValueOption , 0 , len ( vals ) )
for k , v := range vals {
o := & envoy_core_v3 . HeaderValueOption {
Header : & envoy_core_v3 . HeaderValue {
Key : k ,
Value : v ,
} ,
2024-01-04 00:36:25 +00:00
}
if ! add {
// default is APPEND_IF_EXISTS_OR_ADD
o . AppendAction = envoy_core_v3 . HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD
2021-07-13 12:53:59 +00:00
}
opts = append ( opts , o )
}
return opts
}
func findIngressServiceMatchingUpstream ( l structs . IngressListener , u structs . Upstream ) * structs . IngressService {
// Hunt through for the matching service. We validate now that there is
// only one IngressService for each unique name although originally that
// wasn't checked as it didn't matter. Assume there is only one now
// though!
2023-02-03 15:51:53 +00:00
wantSID := u . DestinationID ( ) . ServiceName . ToServiceID ( )
2021-08-24 14:25:22 +00:00
var foundSameNSWildcard * structs . IngressService
2021-07-13 12:53:59 +00:00
for _ , s := range l . Services {
sid := structs . NewServiceID ( s . Name , & s . EnterpriseMeta )
if wantSID . Matches ( sid ) {
return & s
}
2021-08-24 14:25:22 +00:00
if s . Name == structs . WildcardSpecifier &&
2021-09-22 15:05:11 +00:00
s . NamespaceOrDefault ( ) == wantSID . NamespaceOrDefault ( ) &&
s . PartitionOrDefault ( ) == wantSID . PartitionOrDefault ( ) {
// Make a copy so we don't take a reference to the loop variable
found := s
foundSameNSWildcard = & found
2021-08-24 14:25:22 +00:00
}
2021-07-13 12:53:59 +00:00
}
2021-08-24 14:25:22 +00:00
// Didn't find an exact match. Return the wildcard from same service if we
// found one.
return foundSameNSWildcard
2021-07-13 12:53:59 +00:00
}
2020-07-07 15:43:04 +00:00
func generateUpstreamIngressDomains ( listenerKey proxycfg . IngressListenerKey , u structs . Upstream ) [ ] string {
var domains [ ] string
domainsSet := make ( map [ string ] bool )
namespace := u . GetEnterpriseMeta ( ) . NamespaceOrDefault ( )
switch {
case len ( u . IngressHosts ) > 0 :
// If a user has specified hosts, do not add the default
// "<service-name>.ingress.*" prefixes
domains = u . IngressHosts
case namespace != structs . IntentionDefaultNamespace :
domains = [ ] string { fmt . Sprintf ( "%s.ingress.%s.*" , u . DestinationName , namespace ) }
default :
domains = [ ] string { fmt . Sprintf ( "%s.ingress.*" , u . DestinationName ) }
}
for _ , h := range domains {
domainsSet [ h ] = true
}
// Host headers may contain port numbers in them, so we need to make sure
// we match on the host with and without the port number. Well-known
// ports like HTTP/HTTPS are stripped from Host headers, but other ports
// will be in the header.
for _ , h := range domains {
_ , _ , err := net . SplitHostPort ( h )
// Error message from Go's net/ipsock.go
// We check to see if a port is not missing, and ignore the
// error from SplitHostPort otherwise, since we have previously
// validated the Host values and should trust the user's input.
if err == nil || ! strings . Contains ( err . Error ( ) , "missing port in address" ) {
continue
}
domainWithPort := fmt . Sprintf ( "%s:%d" , h , listenerKey . Port )
// Do not add a duplicate domain if a hostname with port is already in the
// set
if ! domainsSet [ domainWithPort ] {
domains = append ( domains , domainWithPort )
}
}
return domains
}
2023-05-25 14:54:55 +00:00
func generateUpstreamAPIsDomains ( listenerKey proxycfg . APIGatewayListenerKey , u structs . Upstream , hosts [ ] string ) [ ] string {
u . IngressHosts = hosts
return generateUpstreamIngressDomains ( listenerKey , u )
}
2022-09-09 17:58:28 +00:00
func ( s * ResourceGenerator ) makeUpstreamRouteForDiscoveryChain (
cfgSnap * proxycfg . ConfigSnapshot ,
uid proxycfg . UpstreamID ,
2019-07-02 03:10:51 +00:00
chain * structs . CompiledDiscoveryChain ,
2020-04-23 15:06:19 +00:00
serviceDomains [ ] string ,
2022-09-09 17:58:28 +00:00
forMeshGateway bool ,
2023-08-31 16:23:59 +00:00
filterBuilder perRouteFilterBuilder ,
2021-02-26 22:23:15 +00:00
) ( * envoy_route_v3 . VirtualHost , error ) {
2022-09-09 17:58:28 +00:00
routeName := uid . EnvoyID ( )
2021-02-26 22:23:15 +00:00
var routes [ ] * envoy_route_v3 . Route
2019-07-02 03:10:51 +00:00
2019-08-02 03:44:05 +00:00
startNode := chain . Nodes [ chain . StartNode ]
if startNode == nil {
2020-08-12 16:19:20 +00:00
return nil , fmt . Errorf ( "missing first node in compiled discovery chain for: %s" , chain . ServiceName )
2019-08-02 03:44:05 +00:00
}
2022-09-09 17:58:28 +00:00
upstreamsSnapshot , err := cfgSnap . ToConfigSnapshotUpstreams ( )
if err != nil && ! forMeshGateway {
return nil , err
}
2019-08-02 03:44:05 +00:00
switch startNode . Type {
2019-07-02 03:10:51 +00:00
case structs . DiscoveryGraphNodeTypeRouter :
2021-02-26 22:23:15 +00:00
routes = make ( [ ] * envoy_route_v3 . Route , 0 , len ( startNode . Routes ) )
2019-07-02 03:10:51 +00:00
2019-08-02 03:44:05 +00:00
for _ , discoveryRoute := range startNode . Routes {
2023-08-31 16:23:59 +00:00
discoveryRoute := discoveryRoute
2021-04-29 18:54:05 +00:00
routeMatch := makeRouteMatchForDiscoveryRoute ( discoveryRoute )
2019-07-02 03:10:51 +00:00
var (
2021-02-26 22:23:15 +00:00
routeAction * envoy_route_v3 . Route_Route
2019-07-02 03:10:51 +00:00
err error
)
2019-08-02 03:44:05 +00:00
nextNode := chain . Nodes [ discoveryRoute . NextNode ]
2020-09-11 16:49:26 +00:00
var lb * structs . LoadBalancer
2020-09-02 15:10:50 +00:00
if nextNode . LoadBalancer != nil {
2020-09-11 15:21:43 +00:00
lb = nextNode . LoadBalancer
2020-09-02 15:10:50 +00:00
}
2019-08-02 03:44:05 +00:00
switch nextNode . Type {
case structs . DiscoveryGraphNodeTypeSplitter :
2022-09-09 17:58:28 +00:00
routeAction , err = s . makeRouteActionForSplitter ( upstreamsSnapshot , nextNode . Splits , chain , forMeshGateway )
2019-07-02 03:10:51 +00:00
if err != nil {
2020-06-23 20:19:56 +00:00
return nil , err
2019-07-02 03:10:51 +00:00
}
2019-08-02 03:44:05 +00:00
case structs . DiscoveryGraphNodeTypeResolver :
2022-09-09 17:58:28 +00:00
ra , ok := s . makeRouteActionForChainCluster ( upstreamsSnapshot , nextNode . Resolver . Target , chain , forMeshGateway )
if ! ok {
continue
}
routeAction = ra
2020-08-28 20:27:40 +00:00
2019-08-02 03:44:05 +00:00
default :
2020-06-23 20:19:56 +00:00
return nil , fmt . Errorf ( "unexpected graph node after route %q" , nextNode . Type )
2019-07-02 03:10:51 +00:00
}
2021-07-13 15:15:04 +00:00
if err := injectLBToRouteAction ( lb , routeAction . Route ) ; err != nil {
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2019-07-12 19:16:21 +00:00
// TODO(rb): Better help handle the envoy case where you need (prefix=/foo/,rewrite=/) and (exact=/foo,rewrite=/) to do a full rewrite
destination := discoveryRoute . Definition . Destination
2021-07-13 15:15:04 +00:00
route := & envoy_route_v3 . Route { }
2019-07-12 19:16:21 +00:00
if destination != nil {
if destination . PrefixRewrite != "" {
routeAction . Route . PrefixRewrite = destination . PrefixRewrite
}
if destination . RequestTimeout > 0 {
2022-05-25 01:44:54 +00:00
routeAction . Route . Timeout = durationpb . New ( destination . RequestTimeout )
2019-07-12 19:16:21 +00:00
}
2023-12-19 23:36:07 +00:00
// Disable the timeout if user specifies negative value. Setting 0 disables the timeout in Envoy.
if destination . RequestTimeout < 0 {
routeAction . Route . Timeout = durationpb . New ( 0 * time . Second )
}
2019-07-12 19:16:21 +00:00
2022-11-29 22:43:15 +00:00
if destination . IdleTimeout > 0 {
routeAction . Route . IdleTimeout = durationpb . New ( destination . IdleTimeout )
}
2023-12-19 23:36:07 +00:00
// Disable the timeout if user specifies negative value. Setting 0 disables the timeout in Envoy.
if destination . IdleTimeout < 0 {
routeAction . Route . IdleTimeout = durationpb . New ( 0 * time . Second )
}
2022-11-29 22:43:15 +00:00
2019-07-12 19:16:21 +00:00
if destination . HasRetryFeatures ( ) {
2022-10-05 17:06:44 +00:00
routeAction . Route . RetryPolicy = getRetryPolicyForDestination ( destination )
2019-07-12 19:16:21 +00:00
}
2021-07-13 15:15:04 +00:00
if err := injectHeaderManipToRoute ( destination , route ) ; err != nil {
return nil , fmt . Errorf ( "failed to apply header manipulation configuration to route: %v" , err )
}
2019-07-12 19:16:21 +00:00
}
2023-08-31 16:23:59 +00:00
filter , err := filterBuilder . buildTypedPerFilterConfig ( routeMatch , routeAction )
if err != nil {
return nil , err
}
2021-07-13 15:15:04 +00:00
route . Match = routeMatch
route . Action = routeAction
2023-08-31 16:23:59 +00:00
route . TypedPerFilterConfig = filter
2021-07-13 15:15:04 +00:00
routes = append ( routes , route )
2019-07-02 03:10:51 +00:00
}
case structs . DiscoveryGraphNodeTypeSplitter :
2022-09-09 17:58:28 +00:00
routeAction , err := s . makeRouteActionForSplitter ( upstreamsSnapshot , startNode . Splits , chain , forMeshGateway )
2019-07-02 03:10:51 +00:00
if err != nil {
2020-06-23 20:19:56 +00:00
return nil , err
2019-07-02 03:10:51 +00:00
}
2020-09-11 16:49:26 +00:00
var lb * structs . LoadBalancer
2020-09-02 15:10:50 +00:00
if startNode . LoadBalancer != nil {
2020-09-11 15:21:43 +00:00
lb = startNode . LoadBalancer
2020-09-02 15:10:50 +00:00
}
2020-09-02 21:13:50 +00:00
if err := injectLBToRouteAction ( lb , routeAction . Route ) ; err != nil {
2020-08-28 20:27:40 +00:00
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2021-02-26 22:23:15 +00:00
defaultRoute := & envoy_route_v3 . Route {
2019-07-02 03:10:51 +00:00
Match : makeDefaultRouteMatch ( ) ,
Action : routeAction ,
}
2021-02-26 22:23:15 +00:00
routes = [ ] * envoy_route_v3 . Route { defaultRoute }
2019-07-02 03:10:51 +00:00
2019-08-02 03:44:05 +00:00
case structs . DiscoveryGraphNodeTypeResolver :
2022-09-09 17:58:28 +00:00
routeAction , ok := s . makeRouteActionForChainCluster ( upstreamsSnapshot , startNode . Resolver . Target , chain , forMeshGateway )
if ! ok {
break
}
2020-09-11 16:49:26 +00:00
var lb * structs . LoadBalancer
2020-09-02 15:10:50 +00:00
if startNode . LoadBalancer != nil {
2020-09-11 15:21:43 +00:00
lb = startNode . LoadBalancer
2020-09-02 15:10:50 +00:00
}
2020-09-02 21:13:50 +00:00
if err := injectLBToRouteAction ( lb , routeAction . Route ) ; err != nil {
2020-08-28 20:27:40 +00:00
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2019-07-02 03:10:51 +00:00
2023-12-19 23:36:07 +00:00
// A request timeout can be configured on a resolver or router. If configured on a resolver, the timeout will
// only apply if the start node is a resolver. This is because the timeout is attached to an (Envoy
// RouteAction)[https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-routeaction]
// If there is a splitter before this resolver, the branches of the split are configured within the same
// RouteAction, and the timeout cannot be shared between branches of a split.
2023-03-03 14:37:12 +00:00
if startNode . Resolver . RequestTimeout > 0 {
routeAction . Route . Timeout = durationpb . New ( startNode . Resolver . RequestTimeout )
}
2023-12-19 23:36:07 +00:00
// Disable the timeout if user specifies negative value. Setting 0 disables the timeout in Envoy.
if startNode . Resolver . RequestTimeout < 0 {
routeAction . Route . Timeout = durationpb . New ( 0 * time . Second )
}
2021-02-26 22:23:15 +00:00
defaultRoute := & envoy_route_v3 . Route {
2019-07-02 03:10:51 +00:00
Match : makeDefaultRouteMatch ( ) ,
Action : routeAction ,
}
2021-02-26 22:23:15 +00:00
routes = [ ] * envoy_route_v3 . Route { defaultRoute }
2019-07-02 03:10:51 +00:00
default :
2020-08-12 16:19:20 +00:00
return nil , fmt . Errorf ( "unknown first node in discovery chain of type: %s" , startNode . Type )
2019-07-02 03:10:51 +00:00
}
2021-02-26 22:23:15 +00:00
host := & envoy_route_v3 . VirtualHost {
2020-04-16 23:24:11 +00:00
Name : routeName ,
2020-04-23 15:06:19 +00:00
Domains : serviceDomains ,
2020-04-16 23:24:11 +00:00
Routes : routes ,
}
return host , nil
2019-07-02 03:10:51 +00:00
}
2022-10-05 17:06:44 +00:00
func getRetryPolicyForDestination ( destination * structs . ServiceRouteDestination ) * envoy_route_v3 . RetryPolicy {
retryPolicy := & envoy_route_v3 . RetryPolicy { }
if destination . NumRetries > 0 {
2023-08-17 18:43:21 +00:00
retryPolicy . NumRetries = response . MakeUint32Value ( int ( destination . NumRetries ) )
2022-10-05 17:06:44 +00:00
}
// The RetryOn magic values come from: https://www.envoyproxy.io/docs/envoy/v1.10.0/configuration/http_filters/router_filter#config-http-filters-router-x-envoy-retry-on
var retryStrings [ ] string
if len ( destination . RetryOn ) > 0 {
retryStrings = append ( retryStrings , destination . RetryOn ... )
}
if destination . RetryOnConnectFailure {
// connect-failure can be enabled by either adding connect-failure to the RetryOn list or by using the legacy RetryOnConnectFailure option
// Check that it's not already in the RetryOn list, so we don't set it twice
connectFailureExists := false
for _ , r := range retryStrings {
if r == "connect-failure" {
connectFailureExists = true
}
}
if ! connectFailureExists {
retryStrings = append ( retryStrings , "connect-failure" )
}
}
if len ( destination . RetryOnStatusCodes ) > 0 {
retryStrings = append ( retryStrings , "retriable-status-codes" )
retryPolicy . RetriableStatusCodes = destination . RetryOnStatusCodes
}
retryPolicy . RetryOn = strings . Join ( retryStrings , "," )
return retryPolicy
}
2021-04-29 18:54:05 +00:00
func makeRouteMatchForDiscoveryRoute ( discoveryRoute * structs . DiscoveryRoute ) * envoy_route_v3 . RouteMatch {
2019-07-02 03:10:51 +00:00
match := discoveryRoute . Definition . Match
if match == nil || match . IsEmpty ( ) {
return makeDefaultRouteMatch ( )
}
2021-02-26 22:23:15 +00:00
em := & envoy_route_v3 . RouteMatch { }
2019-07-02 03:10:51 +00:00
switch {
case match . HTTP . PathExact != "" :
2021-02-26 22:23:15 +00:00
em . PathSpecifier = & envoy_route_v3 . RouteMatch_Path {
2019-07-12 19:16:21 +00:00
Path : match . HTTP . PathExact ,
}
2019-07-02 03:10:51 +00:00
case match . HTTP . PathPrefix != "" :
2021-02-26 22:23:15 +00:00
em . PathSpecifier = & envoy_route_v3 . RouteMatch_Prefix {
2019-07-12 19:16:21 +00:00
Prefix : match . HTTP . PathPrefix ,
}
2019-07-02 03:10:51 +00:00
case match . HTTP . PathRegex != "" :
2021-02-26 22:23:15 +00:00
em . PathSpecifier = & envoy_route_v3 . RouteMatch_SafeRegex {
2023-08-17 18:43:21 +00:00
SafeRegex : response . MakeEnvoyRegexMatch ( match . HTTP . PathRegex ) ,
2019-07-12 19:16:21 +00:00
}
2019-07-02 03:10:51 +00:00
default :
2021-02-26 22:23:15 +00:00
em . PathSpecifier = & envoy_route_v3 . RouteMatch_Prefix {
2019-07-12 19:16:21 +00:00
Prefix : "/" ,
}
2019-07-02 03:10:51 +00:00
}
2024-01-22 15:23:24 +00:00
if match . HTTP . CaseInsensitive {
em . CaseSensitive = wrapperspb . Bool ( false )
}
2019-07-02 03:10:51 +00:00
if len ( match . HTTP . Header ) > 0 {
2021-02-26 22:23:15 +00:00
em . Headers = make ( [ ] * envoy_route_v3 . HeaderMatcher , 0 , len ( match . HTTP . Header ) )
2019-07-02 03:10:51 +00:00
for _ , hdr := range match . HTTP . Header {
2021-02-26 22:23:15 +00:00
eh := & envoy_route_v3 . HeaderMatcher {
2019-07-02 03:10:51 +00:00
Name : hdr . Name ,
}
switch {
case hdr . Exact != "" :
2023-09-07 19:03:09 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_Exact {
Exact : hdr . Exact ,
} ,
IgnoreCase : false ,
} ,
2019-07-02 03:10:51 +00:00
}
case hdr . Regex != "" :
2023-09-07 19:03:09 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_SafeRegex {
SafeRegex : response . MakeEnvoyRegexMatch ( hdr . Regex ) ,
} ,
IgnoreCase : false ,
} ,
2019-07-02 03:10:51 +00:00
}
2023-09-07 19:03:09 +00:00
2019-07-02 03:10:51 +00:00
case hdr . Prefix != "" :
2023-09-07 19:03:09 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_Prefix {
Prefix : hdr . Prefix ,
} ,
IgnoreCase : false ,
} ,
2019-07-02 03:10:51 +00:00
}
2023-09-07 19:03:09 +00:00
2019-07-02 03:10:51 +00:00
case hdr . Suffix != "" :
2023-09-07 19:03:09 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_Suffix {
Suffix : hdr . Suffix ,
} ,
IgnoreCase : false ,
} ,
2019-07-02 03:10:51 +00:00
}
2023-09-07 19:03:09 +00:00
2019-07-02 03:10:51 +00:00
case hdr . Present :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_PresentMatch {
2019-07-02 03:10:51 +00:00
PresentMatch : true ,
}
default :
continue // skip this impossible situation
}
if hdr . Invert {
eh . InvertMatch = true
}
em . Headers = append ( em . Headers , eh )
}
}
2019-07-24 01:56:39 +00:00
if len ( match . HTTP . Methods ) > 0 {
methodHeaderRegex := strings . Join ( match . HTTP . Methods , "|" )
2021-02-26 22:23:15 +00:00
eh := & envoy_route_v3 . HeaderMatcher {
2019-07-24 01:56:39 +00:00
Name : ":method" ,
2024-01-03 16:53:39 +00:00
HeaderMatchSpecifier : & envoy_route_v3 . HeaderMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_SafeRegex {
SafeRegex : response . MakeEnvoyRegexMatch ( methodHeaderRegex ) ,
} ,
} ,
2020-07-31 20:52:49 +00:00
} ,
2019-07-24 01:56:39 +00:00
}
2020-07-09 22:04:51 +00:00
2019-07-24 01:56:39 +00:00
em . Headers = append ( em . Headers , eh )
}
2019-07-02 03:10:51 +00:00
if len ( match . HTTP . QueryParam ) > 0 {
2021-02-26 22:23:15 +00:00
em . QueryParameters = make ( [ ] * envoy_route_v3 . QueryParameterMatcher , 0 , len ( match . HTTP . QueryParam ) )
2019-07-02 03:10:51 +00:00
for _ , qm := range match . HTTP . QueryParam {
2021-02-26 22:23:15 +00:00
eq := & envoy_route_v3 . QueryParameterMatcher {
2019-07-24 01:55:26 +00:00
Name : qm . Name ,
}
switch {
case qm . Exact != "" :
2021-02-26 22:23:15 +00:00
eq . QueryParameterMatchSpecifier = & envoy_route_v3 . QueryParameterMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_Exact {
2020-07-31 20:52:49 +00:00
Exact : qm . Exact ,
2020-07-09 22:04:51 +00:00
} ,
2020-07-31 20:52:49 +00:00
} ,
2020-07-09 22:04:51 +00:00
}
2019-07-24 01:55:26 +00:00
case qm . Regex != "" :
2021-02-26 22:23:15 +00:00
eq . QueryParameterMatchSpecifier = & envoy_route_v3 . QueryParameterMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_SafeRegex {
2023-08-17 18:43:21 +00:00
SafeRegex : response . MakeEnvoyRegexMatch ( qm . Regex ) ,
2020-07-09 22:04:51 +00:00
} ,
2020-07-31 20:52:49 +00:00
} ,
2020-07-09 22:04:51 +00:00
}
2019-07-24 01:55:26 +00:00
case qm . Present :
2021-02-26 22:23:15 +00:00
eq . QueryParameterMatchSpecifier = & envoy_route_v3 . QueryParameterMatcher_PresentMatch {
2020-07-31 20:52:49 +00:00
PresentMatch : true ,
2020-07-09 22:04:51 +00:00
}
2019-07-24 01:55:26 +00:00
default :
continue // skip this impossible situation
2019-07-02 03:10:51 +00:00
}
em . QueryParameters = append ( em . QueryParameters , eq )
}
}
return em
}
2021-02-26 22:23:15 +00:00
func makeDefaultRouteMatch ( ) * envoy_route_v3 . RouteMatch {
return & envoy_route_v3 . RouteMatch {
PathSpecifier : & envoy_route_v3 . RouteMatch_Prefix {
2019-07-02 03:10:51 +00:00
Prefix : "/" ,
} ,
// TODO(banks) Envoy supports matching only valid GRPC
// requests which might be nice to add here for gRPC services
// but it's not supported in our current envoy SDK version
// although docs say it was supported by 1.8.0. Going to defer
// that until we've updated the deps.
}
}
2022-09-09 17:58:28 +00:00
func ( s * ResourceGenerator ) makeRouteActionForChainCluster (
upstreamsSnapshot * proxycfg . ConfigSnapshotUpstreams ,
2022-06-28 19:52:25 +00:00
targetID string ,
chain * structs . CompiledDiscoveryChain ,
2022-09-09 17:58:28 +00:00
forMeshGateway bool ,
) ( * envoy_route_v3 . Route_Route , bool ) {
2023-07-20 22:02:21 +00:00
clusterName := s . getTargetClusterName ( upstreamsSnapshot , chain , targetID , forMeshGateway )
2023-02-23 16:32:32 +00:00
if clusterName == "" {
2022-09-09 17:58:28 +00:00
return nil , false
}
2023-02-23 16:32:32 +00:00
return makeRouteActionFromName ( clusterName ) , true
2020-08-28 20:27:40 +00:00
}
2019-08-02 20:34:54 +00:00
2021-02-26 22:23:15 +00:00
func makeRouteActionFromName ( clusterName string ) * envoy_route_v3 . Route_Route {
return & envoy_route_v3 . Route_Route {
Route : & envoy_route_v3 . RouteAction {
ClusterSpecifier : & envoy_route_v3 . RouteAction_Cluster {
2019-07-02 03:10:51 +00:00
Cluster : clusterName ,
} ,
} ,
}
}
2022-09-09 17:58:28 +00:00
func ( s * ResourceGenerator ) makeRouteActionForSplitter (
upstreamsSnapshot * proxycfg . ConfigSnapshotUpstreams ,
2022-06-28 19:52:25 +00:00
splits [ ] * structs . DiscoverySplit ,
chain * structs . CompiledDiscoveryChain ,
2022-09-09 17:58:28 +00:00
forMeshGateway bool ,
2022-06-28 19:52:25 +00:00
) ( * envoy_route_v3 . Route_Route , error ) {
2021-02-26 22:23:15 +00:00
clusters := make ( [ ] * envoy_route_v3 . WeightedCluster_ClusterWeight , 0 , len ( splits ) )
2019-07-02 03:10:51 +00:00
for _ , split := range splits {
2019-08-02 03:44:05 +00:00
nextNode := chain . Nodes [ split . NextNode ]
if nextNode . Type != structs . DiscoveryGraphNodeTypeResolver {
return nil , fmt . Errorf ( "unexpected splitter destination node type: %s" , nextNode . Type )
2019-07-02 03:10:51 +00:00
}
2019-08-02 20:34:54 +00:00
targetID := nextNode . Resolver . Target
2023-07-20 22:02:21 +00:00
clusterName := s . getTargetClusterName ( upstreamsSnapshot , chain , targetID , forMeshGateway )
2023-02-23 16:32:32 +00:00
if clusterName == "" {
2022-09-09 17:58:28 +00:00
continue
}
2019-07-02 03:10:51 +00:00
2019-07-12 19:16:21 +00:00
// The smallest representable weight is 1/10000 or .01% but envoy
// deals with integers so scale everything up by 100x.
2023-03-06 13:41:57 +00:00
weight := int ( split . Weight * 100 )
2021-02-26 22:23:15 +00:00
cw := & envoy_route_v3 . WeightedCluster_ClusterWeight {
2023-08-17 18:43:21 +00:00
Weight : response . MakeUint32Value ( weight ) ,
2023-02-23 16:32:32 +00:00
Name : clusterName ,
2019-07-02 03:10:51 +00:00
}
2021-07-13 18:49:14 +00:00
if err := injectHeaderManipToWeightedCluster ( split . Definition , cw ) ; err != nil {
return nil , err
}
2019-07-02 03:10:51 +00:00
clusters = append ( clusters , cw )
}
2022-12-08 01:19:34 +00:00
if len ( clusters ) <= 0 {
return nil , fmt . Errorf ( "number of clusters in splitter must be > 0; got %d" , len ( clusters ) )
}
2021-02-26 22:23:15 +00:00
return & envoy_route_v3 . Route_Route {
Route : & envoy_route_v3 . RouteAction {
ClusterSpecifier : & envoy_route_v3 . RouteAction_WeightedClusters {
WeightedClusters : & envoy_route_v3 . WeightedCluster {
2023-08-03 19:03:02 +00:00
Clusters : clusters ,
2019-07-02 03:10:51 +00:00
} ,
} ,
} ,
} , nil
2018-10-03 18:18:55 +00:00
}
2020-09-02 21:13:50 +00:00
2021-02-26 22:23:15 +00:00
func injectLBToRouteAction ( lb * structs . LoadBalancer , action * envoy_route_v3 . RouteAction ) error {
2020-09-11 15:21:43 +00:00
if lb == nil || ! lb . IsHashBased ( ) {
2020-09-02 21:13:50 +00:00
return nil
}
2021-02-26 22:23:15 +00:00
result := make ( [ ] * envoy_route_v3 . RouteAction_HashPolicy , 0 , len ( lb . HashPolicies ) )
2020-09-11 15:21:43 +00:00
for _ , policy := range lb . HashPolicies {
2020-09-02 21:13:50 +00:00
if policy . SourceIP {
2021-02-26 22:23:15 +00:00
result = append ( result , & envoy_route_v3 . RouteAction_HashPolicy {
PolicySpecifier : & envoy_route_v3 . RouteAction_HashPolicy_ConnectionProperties_ {
ConnectionProperties : & envoy_route_v3 . RouteAction_HashPolicy_ConnectionProperties {
2020-09-02 21:13:50 +00:00
SourceIp : true ,
} ,
} ,
Terminal : policy . Terminal ,
} )
continue
}
switch policy . Field {
case structs . HashPolicyHeader :
2021-02-26 22:23:15 +00:00
result = append ( result , & envoy_route_v3 . RouteAction_HashPolicy {
PolicySpecifier : & envoy_route_v3 . RouteAction_HashPolicy_Header_ {
Header : & envoy_route_v3 . RouteAction_HashPolicy_Header {
2020-09-02 21:13:50 +00:00
HeaderName : policy . FieldValue ,
} ,
} ,
Terminal : policy . Terminal ,
} )
case structs . HashPolicyCookie :
2021-02-26 22:23:15 +00:00
cookie := envoy_route_v3 . RouteAction_HashPolicy_Cookie {
2020-09-02 21:13:50 +00:00
Name : policy . FieldValue ,
}
if policy . CookieConfig != nil {
cookie . Path = policy . CookieConfig . Path
2020-09-12 00:34:03 +00:00
if policy . CookieConfig . TTL != 0 * time . Second {
2022-05-25 01:44:54 +00:00
cookie . Ttl = durationpb . New ( policy . CookieConfig . TTL )
2020-09-12 00:34:03 +00:00
}
// Envoy will generate a session cookie if the ttl is present and zero.
if policy . CookieConfig . Session {
2022-05-25 01:44:54 +00:00
cookie . Ttl = durationpb . New ( 0 * time . Second )
2020-09-12 00:34:03 +00:00
}
2020-09-02 21:13:50 +00:00
}
2021-02-26 22:23:15 +00:00
result = append ( result , & envoy_route_v3 . RouteAction_HashPolicy {
PolicySpecifier : & envoy_route_v3 . RouteAction_HashPolicy_Cookie_ {
2020-09-02 21:13:50 +00:00
Cookie : & cookie ,
} ,
Terminal : policy . Terminal ,
} )
case structs . HashPolicyQueryParam :
2021-02-26 22:23:15 +00:00
result = append ( result , & envoy_route_v3 . RouteAction_HashPolicy {
PolicySpecifier : & envoy_route_v3 . RouteAction_HashPolicy_QueryParameter_ {
QueryParameter : & envoy_route_v3 . RouteAction_HashPolicy_QueryParameter {
2020-09-02 21:13:50 +00:00
Name : policy . FieldValue ,
} ,
} ,
Terminal : policy . Terminal ,
} )
default :
return fmt . Errorf ( "unsupported load balancer hash policy field: %v" , policy . Field )
}
}
action . HashPolicy = result
return nil
}
2021-07-13 15:15:04 +00:00
func injectHeaderManipToRoute ( dest * structs . ServiceRouteDestination , r * envoy_route_v3 . Route ) error {
2024-01-04 00:36:25 +00:00
2021-07-13 18:49:14 +00:00
if ! dest . RequestHeaders . IsZero ( ) {
2021-07-13 15:15:04 +00:00
r . RequestHeadersToAdd = append (
r . RequestHeadersToAdd ,
makeHeadersValueOptions ( dest . RequestHeaders . Add , true ) ... ,
)
r . RequestHeadersToAdd = append (
r . RequestHeadersToAdd ,
makeHeadersValueOptions ( dest . RequestHeaders . Set , false ) ... ,
)
r . RequestHeadersToRemove = append (
r . RequestHeadersToRemove ,
dest . RequestHeaders . Remove ... ,
)
}
2021-07-13 18:49:14 +00:00
if ! dest . ResponseHeaders . IsZero ( ) {
2021-07-13 15:15:04 +00:00
r . ResponseHeadersToAdd = append (
r . ResponseHeadersToAdd ,
makeHeadersValueOptions ( dest . ResponseHeaders . Add , true ) ... ,
)
r . ResponseHeadersToAdd = append (
r . ResponseHeadersToAdd ,
makeHeadersValueOptions ( dest . ResponseHeaders . Set , false ) ... ,
)
r . ResponseHeadersToRemove = append (
r . ResponseHeadersToRemove ,
2021-08-24 14:25:22 +00:00
dest . ResponseHeaders . Remove ... ,
)
}
return nil
}
func injectHeaderManipToVirtualHost ( dest * structs . IngressService , vh * envoy_route_v3 . VirtualHost ) error {
if ! dest . RequestHeaders . IsZero ( ) {
vh . RequestHeadersToAdd = append (
vh . RequestHeadersToAdd ,
makeHeadersValueOptions ( dest . RequestHeaders . Add , true ) ... ,
)
vh . RequestHeadersToAdd = append (
vh . RequestHeadersToAdd ,
makeHeadersValueOptions ( dest . RequestHeaders . Set , false ) ... ,
)
vh . RequestHeadersToRemove = append (
vh . RequestHeadersToRemove ,
dest . RequestHeaders . Remove ... ,
)
}
if ! dest . ResponseHeaders . IsZero ( ) {
vh . ResponseHeadersToAdd = append (
vh . ResponseHeadersToAdd ,
makeHeadersValueOptions ( dest . ResponseHeaders . Add , true ) ... ,
)
vh . ResponseHeadersToAdd = append (
vh . ResponseHeadersToAdd ,
makeHeadersValueOptions ( dest . ResponseHeaders . Set , false ) ... ,
)
vh . ResponseHeadersToRemove = append (
vh . ResponseHeadersToRemove ,
2021-07-13 15:15:04 +00:00
dest . ResponseHeaders . Remove ... ,
)
}
return nil
}
2021-07-13 18:49:14 +00:00
func injectHeaderManipToWeightedCluster ( split * structs . ServiceSplit , c * envoy_route_v3 . WeightedCluster_ClusterWeight ) error {
if ! split . RequestHeaders . IsZero ( ) {
c . RequestHeadersToAdd = append (
c . RequestHeadersToAdd ,
makeHeadersValueOptions ( split . RequestHeaders . Add , true ) ... ,
)
c . RequestHeadersToAdd = append (
c . RequestHeadersToAdd ,
makeHeadersValueOptions ( split . RequestHeaders . Set , false ) ... ,
)
c . RequestHeadersToRemove = append (
c . RequestHeadersToRemove ,
split . RequestHeaders . Remove ... ,
)
}
if ! split . ResponseHeaders . IsZero ( ) {
c . ResponseHeadersToAdd = append (
c . ResponseHeadersToAdd ,
makeHeadersValueOptions ( split . ResponseHeaders . Add , true ) ... ,
)
c . ResponseHeadersToAdd = append (
c . ResponseHeadersToAdd ,
makeHeadersValueOptions ( split . ResponseHeaders . Set , false ) ... ,
)
c . ResponseHeadersToRemove = append (
c . ResponseHeadersToRemove ,
split . ResponseHeaders . Remove ... ,
)
}
return nil
}