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"
"github.com/golang/protobuf/ptypes"
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 :
2021-07-13 12:53:59 +00:00
return s . routesForIngressGateway (
cfgSnap . IngressGateway . Listeners ,
cfgSnap . IngressGateway . Upstreams ,
cfgSnap . IngressGateway . DiscoveryChain ,
)
2020-08-28 20:27:40 +00:00
case structs . ServiceKindTerminatingGateway :
2021-04-29 18:54:05 +00:00
return s . routesFromSnapshotTerminatingGateway ( cfgSnap )
case structs . ServiceKindMeshGateway :
return nil , nil // mesh gateways will never have routes
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
2021-12-13 22:30:49 +00:00
for id , chain := range cfgSnap . ConnectProxy . DiscoveryChain {
2021-03-17 19:40:49 +00:00
if chain . IsDefault ( ) {
continue
2020-09-11 16:49:26 +00:00
}
2021-12-13 22:30:49 +00:00
explicit := cfgSnap . ConnectProxy . UpstreamConfig [ id ] . HasLocalPortOrSocket ( )
if _ , implicit := cfgSnap . ConnectProxy . IntentionUpstreams [ id ] ; ! implicit && ! explicit {
// Discovery chain is not associated with a known explicit or implicit upstream so it is skipped.
continue
}
2021-04-29 18:54:05 +00:00
virtualHost , err := makeUpstreamRouteForDiscoveryChain ( id , 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 {
Name : id ,
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.
2021-04-29 18:54:05 +00:00
func ( s * ResourceGenerator ) routesFromSnapshotTerminatingGateway ( 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 )
2020-08-28 20:27:40 +00:00
resolver , hasResolver := cfgSnap . TerminatingGateway . ServiceResolvers [ svc ]
svcConfig := cfgSnap . TerminatingGateway . ServiceConfigs [ svc ]
cfg , err := ParseProxyConfig ( svcConfig . ProxyConfig )
2020-09-03 16:21:20 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to parse upstream config: %v" , err )
}
if ! structs . IsProtocolHTTPLike ( cfg . Protocol ) {
2020-08-28 20:27:40 +00:00
// Routes can only be defined for HTTP services
continue
}
if ! hasResolver {
// Use a zero value resolver with no timeout and no subsets
resolver = & structs . ServiceResolverConfigEntry { }
}
2020-09-02 15:10:50 +00:00
2020-09-11 15:21:43 +00:00
var lb * structs . LoadBalancer
2020-09-02 15:10:50 +00:00
if resolver . LoadBalancer != nil {
2020-09-11 15:21:43 +00:00
lb = resolver . LoadBalancer
2020-09-02 15:10:50 +00:00
}
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 )
2020-08-28 20:27:40 +00:00
continue
}
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 {
2021-09-01 14:35:39 +00:00
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 )
2020-08-28 20:27:40 +00:00
continue
}
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.
2021-04-29 18:54:05 +00:00
func ( s * ResourceGenerator ) routesForIngressGateway (
2021-07-13 12:53:59 +00:00
listeners map [ proxycfg . IngressListenerKey ] structs . IngressListener ,
2020-08-28 20:27:40 +00:00
upstreams map [ proxycfg . IngressListenerKey ] structs . Upstreams ,
chains map [ string ] * structs . CompiledDiscoveryChain ,
) ( [ ] proto . Message , error ) {
2020-04-16 23:24:11 +00:00
var result [ ] proto . Message
2020-08-28 20:27:40 +00:00
for listenerKey , upstreams := range 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 {
upstreamID := u . Identifier ( )
2020-08-28 20:27:40 +00:00
chain := chains [ upstreamID ]
2020-04-23 15:06:19 +00:00
if chain == nil {
continue
}
2020-07-07 15:43:04 +00:00
domains := generateUpstreamIngressDomains ( listenerKey , u )
2021-04-29 18:54:05 +00:00
virtualHost , err := makeUpstreamRouteForDiscoveryChain ( upstreamID , 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.
lCfg , ok := listeners [ listenerKey ]
if ! ok {
return nil , fmt . Errorf ( "missing ingress listener config (listener on port %d)" , listenerKey . Port )
}
svc := findIngressServiceMatchingUpstream ( lCfg , u )
if svc == nil {
return nil , fmt . Errorf ( "missing service in listener config (service %q listener on port %d)" ,
u . DestinationID ( ) , listenerKey . Port )
}
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 ,
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 :
2020-04-03 19:41:10 +00:00
routeAction , err = makeRouteActionForSplitter ( nextNode . Splits , chain )
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 :
2020-08-28 20:27:40 +00:00
routeAction = makeRouteActionForChainCluster ( nextNode . Resolver . Target , chain )
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 {
2020-06-23 20:19:56 +00:00
routeAction . Route . Timeout = ptypes . DurationProto ( 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 :
2020-04-03 19:41:10 +00:00
routeAction , err := makeRouteActionForSplitter ( startNode . Splits , chain )
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 :
2020-08-28 20:27:40 +00:00
routeAction := makeRouteActionForChainCluster ( startNode . Resolver . Target , chain )
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.
}
}
2021-02-26 22:23:15 +00:00
func makeRouteActionForChainCluster ( targetID string , chain * structs . CompiledDiscoveryChain ) * envoy_route_v3 . Route_Route {
2019-08-02 20:34:54 +00:00
target := chain . Targets [ targetID ]
2020-08-28 20:27:40 +00:00
return makeRouteActionFromName ( CustomizeClusterName ( target . Name , chain ) )
}
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 ,
} ,
} ,
}
}
2021-02-26 22:23:15 +00:00
func makeRouteActionForSplitter ( splits [ ] * structs . DiscoverySplit , chain * structs . CompiledDiscoveryChain ) ( * envoy_route_v3 . Route_Route , error ) {
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
2019-08-19 18:03:03 +00:00
clusterName := 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 {
cookie . Ttl = ptypes . DurationProto ( policy . CookieConfig . TTL )
}
// Envoy will generate a session cookie if the ttl is present and zero.
if policy . CookieConfig . Session {
cookie . Ttl = ptypes . DurationProto ( 0 * time . Second )
}
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
}