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"
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"
2021-02-22 21:00:15 +00:00
2020-06-23 20:19:56 +00:00
"github.com/golang/protobuf/proto"
2022-06-28 19:52:25 +00:00
"google.golang.org/protobuf/types/known/durationpb"
2021-02-22 21:00:15 +00:00
2020-08-28 20:27:40 +00:00
"github.com/hashicorp/consul/agent/connect"
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"
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 )
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 )
}
}
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
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
}
2022-01-20 16:12:04 +00:00
explicit := cfgSnap . ConnectProxy . UpstreamConfig [ uid ] . HasLocalPortOrSocket ( )
2022-07-13 20:12:01 +00:00
implicit := cfgSnap . ConnectProxy . IsImplicitUpstream ( uid )
if ! implicit && ! explicit {
2021-12-13 22:30:49 +00:00
// Discovery chain is not associated with a known explicit or implicit upstream so it is skipped.
continue
}
2022-06-28 19:52:25 +00:00
virtualHost , err := makeUpstreamRouteForDiscoveryChain ( uid . EnvoyID ( ) , chain , [ ] string { "*" } , "" )
2021-03-17 19:40:49 +00:00
if err != nil {
return nil , err
}
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 } ,
// ValidateClusters defaults to true when defined statically and false
2021-07-02 16:18:46 +00:00
// when done via RDS. Re-set the reasonable value of true to prevent
2021-03-17 19:40:49 +00:00
// null-routing traffic.
ValidateClusters : 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
}
// TODO(rb): make sure we don't generate an empty result
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 )
2022-07-14 18:45:51 +00:00
routes , err := s . makeRoutes ( cfgSnap , svc , clusterName , true )
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 ( ) )
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 ,
autoHostRewrite bool ) ( [ ] proto . Message , error ) {
resolver , hasResolver := cfgSnap . TerminatingGateway . ServiceResolvers [ svc ]
svcConfig := cfgSnap . TerminatingGateway . ServiceConfigs [ svc ]
cfg , err := ParseProxyConfig ( svcConfig . ProxyConfig )
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
return nil , nil
}
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
}
route , err := makeNamedDefaultRouteWithLB ( clusterName , lb , autoHostRewrite )
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 )
2020-10-23 09:53:29 +00:00
route , err := makeNamedDefaultRouteWithLB ( clusterName , lb , true )
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
}
if cfgSnap . MeshGateway . Leaf == nil {
continue // ignore; not ready
2022-06-28 19:52:25 +00:00
}
uid := proxycfg . NewUpstreamIDFromServiceName ( svc )
virtualHost , err := makeUpstreamRouteForDiscoveryChain (
uid . EnvoyID ( ) ,
chain ,
[ ] string { "*" } ,
meshGatewayExportedClusterNamePrefix ,
)
if err != nil {
return nil , err
}
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 : makeBoolValue ( true ) ,
}
resources = append ( resources , route )
}
return resources , nil
}
2021-04-06 09:05:26 +00:00
func makeNamedDefaultRouteWithLB ( clusterName string , lb * structs . LoadBalancer , autoHostRewrite 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 {
2020-10-23 09:53:29 +00:00
AutoHostRewrite : makeBoolValue ( true ) ,
}
}
2021-02-26 22:23:15 +00:00
return & 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 ,
} ,
} ,
} ,
} ,
// ValidateClusters defaults to true when defined statically and false
2021-07-02 16:18:46 +00:00
// when done via RDS. Re-set the reasonable value of true to prevent
2020-08-28 20:27:40 +00:00
// null-routing traffic.
ValidateClusters : makeBoolValue ( true ) ,
} , nil
}
// 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 ( ) ,
// ValidateClusters defaults to true when defined statically and false
2021-07-02 16:18:46 +00:00
// when done via RDS. Re-set the reasonable value of true to prevent
2020-04-16 23:24:11 +00:00
// null-routing traffic.
ValidateClusters : makeBoolValue ( true ) ,
}
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 {
continue
}
2020-07-07 15:43:04 +00:00
domains := generateUpstreamIngressDomains ( listenerKey , u )
2022-06-28 19:52:25 +00:00
virtualHost , err := makeUpstreamRouteForDiscoveryChain ( uid . EnvoyID ( ) , chain , domains , "" )
2020-04-23 15:06:19 +00:00
if err != nil {
return nil , err
2020-04-16 23:24:11 +00:00
}
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 {
Name : svcRouteName ,
ValidateClusters : makeBoolValue ( true ) ,
VirtualHosts : [ ] * envoy_route_v3 . VirtualHost { virtualHost } ,
}
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
}
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 ,
} ,
Append : makeBoolValue ( add ) ,
}
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!
wantSID := u . DestinationID ( )
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
}
2019-07-02 03:10:51 +00:00
func makeUpstreamRouteForDiscoveryChain (
2020-04-16 23:24:11 +00:00
routeName string ,
2019-07-02 03:10:51 +00:00
chain * structs . CompiledDiscoveryChain ,
2020-04-23 15:06:19 +00:00
serviceDomains [ ] string ,
2022-06-28 19:52:25 +00:00
clusterNamePrefix string ,
2021-02-26 22:23:15 +00:00
) ( * envoy_route_v3 . VirtualHost , error ) {
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
}
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 {
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-06-28 19:52:25 +00:00
routeAction , err = makeRouteActionForSplitter ( nextNode . Splits , chain , clusterNamePrefix )
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-06-28 19:52:25 +00:00
routeAction = makeRouteActionForChainCluster ( nextNode . Resolver . Target , chain , clusterNamePrefix )
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
}
if destination . HasRetryFeatures ( ) {
2021-02-26 22:23:15 +00:00
retryPolicy := & envoy_route_v3 . RetryPolicy { }
2019-07-12 19:16:21 +00:00
if destination . NumRetries > 0 {
retryPolicy . NumRetries = makeUint32Value ( int ( destination . NumRetries ) )
}
// 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
if destination . RetryOnConnectFailure {
retryPolicy . RetryOn = "connect-failure"
}
if len ( destination . RetryOnStatusCodes ) > 0 {
if retryPolicy . RetryOn != "" {
2020-02-18 15:25:47 +00:00
retryPolicy . RetryOn = retryPolicy . RetryOn + ",retriable-status-codes"
2019-07-12 19:16:21 +00:00
} else {
retryPolicy . RetryOn = "retriable-status-codes"
}
retryPolicy . RetriableStatusCodes = destination . RetryOnStatusCodes
}
routeAction . Route . RetryPolicy = retryPolicy
}
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
}
2021-07-13 15:15:04 +00:00
route . Match = routeMatch
route . Action = routeAction
routes = append ( routes , route )
2019-07-02 03:10:51 +00:00
}
case structs . DiscoveryGraphNodeTypeSplitter :
2022-06-28 19:52:25 +00:00
routeAction , err := makeRouteActionForSplitter ( startNode . Splits , chain , clusterNamePrefix )
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-06-28 19:52:25 +00:00
routeAction := makeRouteActionForChainCluster ( startNode . Resolver . Target , chain , clusterNamePrefix )
2020-08-28 20:27:40 +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 )
}
2019-07-02 03:10:51 +00:00
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
}
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 {
2020-07-31 20:52:49 +00:00
SafeRegex : 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
}
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 != "" :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_ExactMatch {
2019-07-02 03:10:51 +00:00
ExactMatch : hdr . Exact ,
}
case hdr . Regex != "" :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_SafeRegexMatch {
2020-07-31 20:52:49 +00:00
SafeRegexMatch : makeEnvoyRegexMatch ( hdr . Regex ) ,
2019-07-02 03:10:51 +00:00
}
case hdr . Prefix != "" :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_PrefixMatch {
2019-07-02 03:10:51 +00:00
PrefixMatch : hdr . Prefix ,
}
case hdr . Suffix != "" :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_SuffixMatch {
2019-07-02 03:10:51 +00:00
SuffixMatch : hdr . Suffix ,
}
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" ,
2021-02-26 22:23:15 +00:00
HeaderMatchSpecifier : & envoy_route_v3 . HeaderMatcher_SafeRegexMatch {
2020-07-09 22:04:51 +00:00
SafeRegexMatch : 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 {
2020-07-31 20:52:49 +00:00
SafeRegex : 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-06-28 19:52:25 +00:00
func makeRouteActionForChainCluster (
targetID string ,
chain * structs . CompiledDiscoveryChain ,
clusterNamePrefix string ,
) * envoy_route_v3 . Route_Route {
2019-08-02 20:34:54 +00:00
target := chain . Targets [ targetID ]
2022-06-28 19:52:25 +00:00
return makeRouteActionFromName ( clusterNamePrefix + CustomizeClusterName ( target . Name , chain ) )
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-06-28 19:52:25 +00:00
func makeRouteActionForSplitter (
splits [ ] * structs . DiscoverySplit ,
chain * structs . CompiledDiscoveryChain ,
clusterNamePrefix string ,
) ( * 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
target := chain . Targets [ targetID ]
2019-08-02 03:03:34 +00:00
2022-06-28 19:52:25 +00:00
clusterName := clusterNamePrefix + CustomizeClusterName ( target . Name , chain )
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.
2021-02-26 22:23:15 +00:00
cw := & envoy_route_v3 . WeightedCluster_ClusterWeight {
2019-07-12 19:16:21 +00:00
Weight : makeUint32Value ( int ( split . Weight * 100 ) ) ,
2019-07-02 03:10:51 +00:00
Name : clusterName ,
}
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 )
}
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 {
2019-07-02 03:10:51 +00:00
Clusters : clusters ,
2019-07-12 19:16:21 +00:00
TotalWeight : makeUint32Value ( 10000 ) , // scaled up 100%
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 {
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
}