github.com/cilium/cilium@v1.16.2/operator/pkg/model/translation/envoy_virtual_host.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package translation
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"regexp"
    10  	"sort"
    11  	"strings"
    12  	"time"
    13  
    14  	envoy_config_core_v3 "github.com/cilium/proxy/go/envoy/config/core/v3"
    15  	envoy_config_route_v3 "github.com/cilium/proxy/go/envoy/config/route/v3"
    16  	envoy_type_matcher_v3 "github.com/cilium/proxy/go/envoy/type/matcher/v3"
    17  	envoy_type_v3 "github.com/cilium/proxy/go/envoy/type/v3"
    18  	"google.golang.org/protobuf/types/known/durationpb"
    19  	"google.golang.org/protobuf/types/known/wrapperspb"
    20  
    21  	"github.com/cilium/cilium/operator/pkg/model"
    22  	"github.com/cilium/cilium/pkg/math"
    23  )
    24  
    25  const (
    26  	wildCard       = "*"
    27  	envoyAuthority = ":authority"
    28  	slash          = "/"
    29  	dot            = "."
    30  	starDot        = "*."
    31  	dotRegex       = "[.]"
    32  	notDotRegex    = "[^.]"
    33  )
    34  
    35  type VirtualHostMutator func(*envoy_config_route_v3.VirtualHost) *envoy_config_route_v3.VirtualHost
    36  
    37  // SortableRoute is a slice of envoy Route, which can be sorted based on
    38  // matching order as per Ingress requirement.
    39  //
    40  // The sorting order is as follows, continuing on ties, and also noting that
    41  // when Exact, Regex, or Prefix matches are unset, their length is zero:
    42  //   - Exact Match length
    43  //   - Regex Match length
    44  //   - Prefix match length
    45  //   - Method match
    46  //   - Number of header matches
    47  //   - Number of query parameter matches
    48  //
    49  // As Envoy route matching logic is done sequentially, we need to enforce
    50  // such sorting order.
    51  type SortableRoute []*envoy_config_route_v3.Route
    52  
    53  func (s SortableRoute) Len() int {
    54  	return len(s)
    55  }
    56  
    57  func (s SortableRoute) Less(i, j int) bool {
    58  	// Make sure Exact Match always comes first
    59  	exactMatch1 := len(s[i].Match.GetPath())
    60  	exactMatch2 := len(s[j].Match.GetPath())
    61  	if exactMatch1 != exactMatch2 {
    62  		return exactMatch1 > exactMatch2
    63  	}
    64  
    65  	// Make sure longest Regex match always after Exact
    66  	regexMatch1 := len(s[i].Match.GetSafeRegex().GetRegex())
    67  	regexMatch2 := len(s[j].Match.GetSafeRegex().GetRegex())
    68  	if regexMatch1 != regexMatch2 {
    69  		return regexMatch1 > regexMatch2
    70  	}
    71  
    72  	// There are two types of prefix match, so get whichever one is bigger
    73  	prefixMatch1 := math.IntMax(len(s[i].Match.GetPathSeparatedPrefix()), len(s[i].Match.GetPrefix()))
    74  	prefixMatch2 := math.IntMax(len(s[j].Match.GetPathSeparatedPrefix()), len(s[j].Match.GetPrefix()))
    75  	headerMatch1 := len(s[i].Match.GetHeaders())
    76  	headerMatch2 := len(s[j].Match.GetHeaders())
    77  	queryMatch1 := len(s[i].Match.GetQueryParameters())
    78  	queryMatch2 := len(s[j].Match.GetQueryParameters())
    79  
    80  	// Next up, sort by prefix match length
    81  	if prefixMatch1 != prefixMatch2 {
    82  		return prefixMatch1 > prefixMatch2
    83  	}
    84  
    85  	// Next up, sort by method based on :method header
    86  	// Give higher priority for the route having method specified
    87  	method1 := getMethod(s[i].Match.GetHeaders())
    88  	method2 := getMethod(s[j].Match.GetHeaders())
    89  	if method1 == nil && method2 != nil {
    90  		return false
    91  	}
    92  	if method1 != nil && method2 == nil {
    93  		return true
    94  	}
    95  	if method1 != nil && *method1 != *method2 {
    96  		return *method1 < *method2
    97  	}
    98  
    99  	// If that's the same, then sort by header length
   100  	if headerMatch1 != headerMatch2 {
   101  		return headerMatch1 > headerMatch2
   102  	}
   103  
   104  	// lastly, sort by query match length
   105  	return queryMatch1 > queryMatch2
   106  }
   107  
   108  func getMethod(headers []*envoy_config_route_v3.HeaderMatcher) *string {
   109  	for _, h := range headers {
   110  		if h.Name == ":method" {
   111  			return model.AddressOf(h.GetStringMatch().GetExact())
   112  		}
   113  	}
   114  	return nil
   115  }
   116  
   117  func (s SortableRoute) Swap(i, j int) {
   118  	s[i], s[j] = s[j], s[i]
   119  }
   120  
   121  // VirtualHostParameter is the parameter for NewVirtualHost
   122  type VirtualHostParameter struct {
   123  	HostNames           []string
   124  	HTTPSRedirect       bool
   125  	HostNameSuffixMatch bool
   126  	ListenerPort        uint32
   127  }
   128  
   129  // NewVirtualHostWithDefaults is same as NewVirtualHost but with a few
   130  // default mutator function. If there are multiple http routes having
   131  // the same path matching (e.g. exact, prefix or regex), the incoming
   132  // request will be load-balanced to multiple backends equally.
   133  func NewVirtualHostWithDefaults(httpRoutes []model.HTTPRoute, param VirtualHostParameter, mutators ...VirtualHostMutator) (*envoy_config_route_v3.VirtualHost, error) {
   134  	return NewVirtualHost(httpRoutes, param, mutators...)
   135  }
   136  
   137  // NewVirtualHost creates a new VirtualHost with the given host and routes.
   138  func NewVirtualHost(httpRoutes []model.HTTPRoute, param VirtualHostParameter, mutators ...VirtualHostMutator) (*envoy_config_route_v3.VirtualHost, error) {
   139  	var routes SortableRoute
   140  	if param.HTTPSRedirect {
   141  		routes = envoyHTTPSRoutes(httpRoutes, param.HostNames, param.HostNameSuffixMatch)
   142  	} else {
   143  		routes = envoyHTTPRoutes(httpRoutes, param.HostNames, param.HostNameSuffixMatch, param.ListenerPort)
   144  	}
   145  
   146  	// This is to make sure that the Exact match is always having higher priority.
   147  	// Each route entry in the virtual host is checked, in order. If there is a
   148  	// match, the route is used and no further route checks are made.
   149  	// Related docs https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/route_matching
   150  	sort.Stable(routes)
   151  
   152  	var domains []string
   153  	for _, host := range param.HostNames {
   154  		if host == wildCard {
   155  			domains = []string{wildCard}
   156  			break
   157  		}
   158  		domains = append(domains,
   159  			host,
   160  			// match authority header with port (e.g. "example.com:80")
   161  			net.JoinHostPort(host, wildCard),
   162  		)
   163  	}
   164  
   165  	res := &envoy_config_route_v3.VirtualHost{
   166  		Name:    domains[0],
   167  		Domains: domains,
   168  		Routes:  routes,
   169  	}
   170  
   171  	for _, fn := range mutators {
   172  		res = fn(res)
   173  	}
   174  
   175  	return res, nil
   176  }
   177  
   178  func envoyHTTPSRoutes(httpRoutes []model.HTTPRoute, hostnames []string, hostNameSuffixMatch bool) []*envoy_config_route_v3.Route {
   179  	matchBackendMap := make(map[string][]model.HTTPRoute)
   180  	for _, r := range httpRoutes {
   181  		matchBackendMap[r.GetMatchKey()] = append(matchBackendMap[r.GetMatchKey()], r)
   182  	}
   183  
   184  	routes := make([]*envoy_config_route_v3.Route, 0, len(matchBackendMap))
   185  	for _, r := range httpRoutes {
   186  		hRoutes, exists := matchBackendMap[r.GetMatchKey()]
   187  		// if not exists, it means this route is already added to the routes
   188  		if !exists {
   189  			continue
   190  		}
   191  		rRedirect := &envoy_config_route_v3.Route_Redirect{
   192  			Redirect: &envoy_config_route_v3.RedirectAction{
   193  				SchemeRewriteSpecifier: &envoy_config_route_v3.RedirectAction_HttpsRedirect{
   194  					HttpsRedirect: true,
   195  				},
   196  			},
   197  		}
   198  		route := envoy_config_route_v3.Route{
   199  			Match: getRouteMatch(hostnames,
   200  				hostNameSuffixMatch,
   201  				hRoutes[0].PathMatch,
   202  				hRoutes[0].QueryParamsMatch,
   203  				hRoutes[0].HeadersMatch,
   204  				hRoutes[0].Method),
   205  			Action: rRedirect,
   206  		}
   207  		routes = append(routes, &route)
   208  		delete(matchBackendMap, r.GetMatchKey())
   209  	}
   210  	return routes
   211  }
   212  
   213  func envoyHTTPRoutes(httpRoutes []model.HTTPRoute, hostnames []string, hostNameSuffixMatch bool, listenerPort uint32) []*envoy_config_route_v3.Route {
   214  	matchBackendMap := make(map[string][]model.HTTPRoute)
   215  	for _, r := range httpRoutes {
   216  		matchBackendMap[r.GetMatchKey()] = append(matchBackendMap[r.GetMatchKey()], r)
   217  	}
   218  
   219  	routes := make([]*envoy_config_route_v3.Route, 0, len(matchBackendMap))
   220  	for _, r := range httpRoutes {
   221  		hRoutes, exists := matchBackendMap[r.GetMatchKey()]
   222  		if !exists {
   223  			continue
   224  		}
   225  		var backends []model.Backend
   226  		for _, r := range hRoutes {
   227  			backends = append(backends, r.Backends...)
   228  		}
   229  
   230  		if len(backends) == 0 && hRoutes[0].RequestRedirect == nil {
   231  			routes = append(routes, envoyHTTPRouteNoBackend(hRoutes[0], hostnames, hostNameSuffixMatch))
   232  			continue
   233  		}
   234  
   235  		route := envoy_config_route_v3.Route{
   236  			Match: getRouteMatch(hostnames,
   237  				hostNameSuffixMatch,
   238  				hRoutes[0].PathMatch,
   239  				hRoutes[0].HeadersMatch,
   240  				hRoutes[0].QueryParamsMatch,
   241  				hRoutes[0].Method),
   242  			RequestHeadersToAdd:     getHeadersToAdd(hRoutes[0].RequestHeaderFilter),
   243  			RequestHeadersToRemove:  getHeadersToRemove(hRoutes[0].RequestHeaderFilter),
   244  			ResponseHeadersToAdd:    getHeadersToAdd(hRoutes[0].ResponseHeaderModifier),
   245  			ResponseHeadersToRemove: getHeadersToRemove(hRoutes[0].ResponseHeaderModifier),
   246  		}
   247  
   248  		if hRoutes[0].RequestRedirect != nil {
   249  			route.Action = getRouteRedirect(hRoutes[0].RequestRedirect, listenerPort)
   250  		} else {
   251  			route.Action = getRouteAction(&r, backends, r.BackendHTTPFilters, r.Rewrite, r.RequestMirrors)
   252  		}
   253  		// If there is only one backend, we can add the header filter to the route
   254  		if len(backends) == 1 {
   255  			for _, fn := range hRoutes[0].BackendHTTPFilters {
   256  				route.RequestHeadersToAdd = append(route.RequestHeadersToAdd, getHeadersToAdd(fn.RequestHeaderFilter)...)
   257  				route.RequestHeadersToRemove = append(route.RequestHeadersToRemove, getHeadersToRemove(fn.RequestHeaderFilter)...)
   258  				route.ResponseHeadersToAdd = append(route.ResponseHeadersToAdd, getHeadersToAdd(fn.ResponseHeaderModifier)...)
   259  				route.ResponseHeadersToRemove = append(route.ResponseHeadersToRemove, getHeadersToRemove(fn.ResponseHeaderModifier)...)
   260  			}
   261  		}
   262  		routes = append(routes, &route)
   263  		delete(matchBackendMap, r.GetMatchKey())
   264  	}
   265  	return routes
   266  }
   267  
   268  type routeActionMutation func(*envoy_config_route_v3.Route_Route) *envoy_config_route_v3.Route_Route
   269  
   270  func hostRewriteMutation(rewrite *model.HTTPURLRewriteFilter) routeActionMutation {
   271  	return func(route *envoy_config_route_v3.Route_Route) *envoy_config_route_v3.Route_Route {
   272  		if rewrite == nil || rewrite.HostName == nil || route.Route == nil {
   273  			return route
   274  		}
   275  		route.Route.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_HostRewriteLiteral{
   276  			HostRewriteLiteral: *rewrite.HostName,
   277  		}
   278  		return route
   279  	}
   280  }
   281  
   282  func pathPrefixMutation(rewrite *model.HTTPURLRewriteFilter, httpRoute *model.HTTPRoute) routeActionMutation {
   283  	return func(route *envoy_config_route_v3.Route_Route) *envoy_config_route_v3.Route_Route {
   284  		if rewrite == nil || rewrite.Path == nil || httpRoute == nil || len(rewrite.Path.Exact) != 0 || len(rewrite.Path.Regex) != 0 {
   285  			return route
   286  		}
   287  
   288  		// Refer to: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPPathModifier
   289  		// ReplacePrefix is allowed to be empty.
   290  		if len(rewrite.Path.Prefix) == 0 || rewrite.Path.Prefix == "/" {
   291  			route.Route.RegexRewrite = &envoy_type_matcher_v3.RegexMatchAndSubstitute{
   292  				Pattern: &envoy_type_matcher_v3.RegexMatcher{
   293  					Regex: fmt.Sprintf(`^%s(/?)(.*)`, regexp.QuoteMeta(httpRoute.PathMatch.Prefix)),
   294  				},
   295  				// hold `/` in case the entire path is removed
   296  				Substitution: `/\2`,
   297  			}
   298  		} else {
   299  			route.Route.PrefixRewrite = rewrite.Path.Prefix
   300  		}
   301  		return route
   302  	}
   303  }
   304  
   305  func pathFullReplaceMutation(rewrite *model.HTTPURLRewriteFilter) routeActionMutation {
   306  	return func(route *envoy_config_route_v3.Route_Route) *envoy_config_route_v3.Route_Route {
   307  		if rewrite == nil || rewrite.Path == nil || len(rewrite.Path.Exact) == 0 {
   308  			return route
   309  		}
   310  		route.Route.RegexRewrite = &envoy_type_matcher_v3.RegexMatchAndSubstitute{
   311  			Pattern: &envoy_type_matcher_v3.RegexMatcher{
   312  				Regex: "^/.*$",
   313  			},
   314  			Substitution: rewrite.Path.Exact,
   315  		}
   316  		return route
   317  	}
   318  }
   319  
   320  func requestMirrorMutation(mirrors []*model.HTTPRequestMirror) routeActionMutation {
   321  	return func(route *envoy_config_route_v3.Route_Route) *envoy_config_route_v3.Route_Route {
   322  		if len(mirrors) == 0 {
   323  			return route
   324  		}
   325  		var action []*envoy_config_route_v3.RouteAction_RequestMirrorPolicy
   326  		for _, m := range mirrors {
   327  			if m.Backend == nil {
   328  				continue
   329  			}
   330  			action = append(action, &envoy_config_route_v3.RouteAction_RequestMirrorPolicy{
   331  				Cluster: fmt.Sprintf("%s:%s:%s", m.Backend.Namespace, m.Backend.Name, m.Backend.Port.GetPort()),
   332  				RuntimeFraction: &envoy_config_core_v3.RuntimeFractionalPercent{
   333  					DefaultValue: &envoy_type_v3.FractionalPercent{
   334  						Numerator: 100,
   335  					},
   336  				},
   337  			})
   338  		}
   339  		route.Route.RequestMirrorPolicies = action
   340  		return route
   341  	}
   342  }
   343  
   344  func timeoutMutation(backend *time.Duration, request *time.Duration) routeActionMutation {
   345  	return func(route *envoy_config_route_v3.Route_Route) *envoy_config_route_v3.Route_Route {
   346  		if backend == nil && request == nil {
   347  			route.Route.MaxStreamDuration = &envoy_config_route_v3.RouteAction_MaxStreamDuration{
   348  				MaxStreamDuration: &durationpb.Duration{
   349  					Seconds: 0,
   350  				},
   351  			}
   352  			return route
   353  		}
   354  		minTimeout := backend
   355  		if request != nil && (minTimeout == nil || *request < *minTimeout) {
   356  			minTimeout = request
   357  		}
   358  		route.Route.Timeout = durationpb.New(*minTimeout)
   359  		return route
   360  	}
   361  }
   362  
   363  func getRouteAction(route *model.HTTPRoute, backends []model.Backend, backendHTTPFilter []*model.BackendHTTPFilter, rewrite *model.HTTPURLRewriteFilter, mirrors []*model.HTTPRequestMirror) *envoy_config_route_v3.Route_Route {
   364  	var routeAction *envoy_config_route_v3.Route_Route
   365  
   366  	mutators := []routeActionMutation{
   367  		hostRewriteMutation(rewrite),
   368  		pathPrefixMutation(rewrite, route),
   369  		pathFullReplaceMutation(rewrite),
   370  		requestMirrorMutation(mirrors),
   371  		timeoutMutation(route.Timeout.Backend, route.Timeout.Request),
   372  	}
   373  
   374  	if len(backends) == 1 {
   375  		r := &envoy_config_route_v3.Route_Route{
   376  			Route: &envoy_config_route_v3.RouteAction{
   377  				ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
   378  					Cluster: getClusterName(backends[0].Namespace, backends[0].Name, backends[0].Port.GetPort()),
   379  				},
   380  			},
   381  		}
   382  
   383  		for _, mutator := range mutators {
   384  			r = mutator(r)
   385  		}
   386  		return r
   387  	}
   388  	backendFilter := make(map[string]*model.BackendHTTPFilter)
   389  	for _, f := range backendHTTPFilter {
   390  		backendFilter[f.Name] = f
   391  	}
   392  	weightedClusters := make([]*envoy_config_route_v3.WeightedCluster_ClusterWeight, 0, len(backends))
   393  	for _, be := range backends {
   394  		var weight int32 = 1
   395  		if be.Weight != nil {
   396  			weight = *be.Weight
   397  		}
   398  		clusterWeight := &envoy_config_route_v3.WeightedCluster_ClusterWeight{
   399  			Name:   getClusterName(be.Namespace, be.Name, be.Port.GetPort()),
   400  			Weight: wrapperspb.UInt32(uint32(weight)),
   401  		}
   402  		// If their two or more backends, we need to add the header filter to the clusterWeight level.
   403  		if fn, ok := backendFilter[getClusterName(be.Namespace, be.Name, be.Port.GetPort())]; ok {
   404  			clusterWeight.RequestHeadersToAdd = append(clusterWeight.RequestHeadersToAdd, getHeadersToAdd(fn.RequestHeaderFilter)...)
   405  			clusterWeight.RequestHeadersToRemove = append(clusterWeight.RequestHeadersToRemove, getHeadersToRemove(fn.RequestHeaderFilter)...)
   406  			clusterWeight.ResponseHeadersToAdd = append(clusterWeight.ResponseHeadersToAdd, getHeadersToAdd(fn.ResponseHeaderModifier)...)
   407  			clusterWeight.ResponseHeadersToRemove = append(clusterWeight.ResponseHeadersToRemove, getHeadersToRemove(fn.ResponseHeaderModifier)...)
   408  		}
   409  		weightedClusters = append(weightedClusters, clusterWeight)
   410  	}
   411  	routeAction = &envoy_config_route_v3.Route_Route{
   412  		Route: &envoy_config_route_v3.RouteAction{
   413  			ClusterSpecifier: &envoy_config_route_v3.RouteAction_WeightedClusters{
   414  				WeightedClusters: &envoy_config_route_v3.WeightedCluster{
   415  					Clusters: weightedClusters,
   416  				},
   417  			},
   418  		},
   419  	}
   420  	for _, mutator := range mutators {
   421  		routeAction = mutator(routeAction)
   422  	}
   423  	return routeAction
   424  }
   425  
   426  func getRouteRedirect(redirect *model.HTTPRequestRedirectFilter, listenerPort uint32) *envoy_config_route_v3.Route_Redirect {
   427  	redirectAction := &envoy_config_route_v3.RedirectAction{}
   428  
   429  	if redirect.Scheme != nil {
   430  		redirectAction.SchemeRewriteSpecifier = &envoy_config_route_v3.RedirectAction_SchemeRedirect{
   431  			SchemeRedirect: *redirect.Scheme,
   432  		}
   433  	}
   434  
   435  	if redirect.Hostname != nil {
   436  		redirectAction.HostRedirect = *redirect.Hostname
   437  	}
   438  
   439  	if redirect.Port != nil {
   440  		redirectAction.PortRedirect = uint32(*redirect.Port)
   441  	} else {
   442  		if redirect.Scheme != nil {
   443  			if *redirect.Scheme == "https" {
   444  				redirectAction.PortRedirect = 443
   445  			} else if *redirect.Scheme == "http" {
   446  				redirectAction.PortRedirect = 80
   447  			}
   448  		} else {
   449  			redirectAction.PortRedirect = listenerPort
   450  		}
   451  	}
   452  
   453  	if redirect.StatusCode != nil {
   454  		redirectAction.ResponseCode = toRedirectResponseCode(*redirect.StatusCode)
   455  	}
   456  
   457  	if redirect.Path != nil {
   458  		if len(redirect.Path.Prefix) != 0 {
   459  			redirectAction.PathRewriteSpecifier = &envoy_config_route_v3.RedirectAction_PrefixRewrite{
   460  				PrefixRewrite: redirect.Path.Prefix,
   461  			}
   462  		}
   463  		if len(redirect.Path.Exact) != 0 {
   464  			redirectAction.PathRewriteSpecifier = &envoy_config_route_v3.RedirectAction_PathRedirect{
   465  				PathRedirect: redirect.Path.Exact,
   466  			}
   467  		}
   468  	}
   469  
   470  	return &envoy_config_route_v3.Route_Redirect{
   471  		Redirect: redirectAction,
   472  	}
   473  }
   474  
   475  func envoyHTTPRouteNoBackend(route model.HTTPRoute, hostnames []string, hostNameSuffixMatch bool) *envoy_config_route_v3.Route {
   476  	if route.DirectResponse == nil {
   477  		return nil
   478  	}
   479  
   480  	return &envoy_config_route_v3.Route{
   481  		Match: getRouteMatch(hostnames,
   482  			hostNameSuffixMatch,
   483  			route.PathMatch,
   484  			route.HeadersMatch,
   485  			route.QueryParamsMatch,
   486  			route.Method),
   487  		Action: &envoy_config_route_v3.Route_DirectResponse{
   488  			DirectResponse: &envoy_config_route_v3.DirectResponseAction{
   489  				Status: uint32(route.DirectResponse.StatusCode),
   490  				Body: &envoy_config_core_v3.DataSource{
   491  					Specifier: &envoy_config_core_v3.DataSource_InlineString{
   492  						InlineString: route.DirectResponse.Body,
   493  					},
   494  				},
   495  			},
   496  		},
   497  	}
   498  }
   499  
   500  func getRouteMatch(hostnames []string, hostNameSuffixMatch bool, pathMatch model.StringMatch, headers []model.KeyValueMatch, query []model.KeyValueMatch, method *string) *envoy_config_route_v3.RouteMatch {
   501  	headerMatchers := getHeaderMatchers(hostnames, hostNameSuffixMatch, headers, method)
   502  	queryMatchers := getQueryMatchers(query)
   503  
   504  	switch {
   505  	case pathMatch.Exact != "":
   506  		return &envoy_config_route_v3.RouteMatch{
   507  			PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{
   508  				Path: pathMatch.Exact,
   509  			},
   510  			Headers:         headerMatchers,
   511  			QueryParameters: queryMatchers,
   512  		}
   513  	case pathMatch.Prefix == "/":
   514  		return &envoy_config_route_v3.RouteMatch{
   515  			PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{
   516  				Prefix: pathMatch.Prefix,
   517  			},
   518  			Headers:         headerMatchers,
   519  			QueryParameters: queryMatchers,
   520  		}
   521  	case pathMatch.Prefix != "":
   522  		return &envoy_config_route_v3.RouteMatch{
   523  			PathSpecifier: &envoy_config_route_v3.RouteMatch_PathSeparatedPrefix{
   524  				PathSeparatedPrefix: strings.TrimSuffix(pathMatch.Prefix, "/"),
   525  			},
   526  			Headers:         headerMatchers,
   527  			QueryParameters: queryMatchers,
   528  		}
   529  	case pathMatch.Regex != "":
   530  		return &envoy_config_route_v3.RouteMatch{
   531  			PathSpecifier: &envoy_config_route_v3.RouteMatch_SafeRegex{
   532  				SafeRegex: &envoy_type_matcher_v3.RegexMatcher{
   533  					Regex: pathMatch.Regex,
   534  				},
   535  			},
   536  			Headers:         headerMatchers,
   537  			QueryParameters: queryMatchers,
   538  		}
   539  	default:
   540  		return &envoy_config_route_v3.RouteMatch{
   541  			PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{
   542  				Prefix: "/",
   543  			},
   544  			Headers:         headerMatchers,
   545  			QueryParameters: queryMatchers,
   546  		}
   547  	}
   548  }
   549  
   550  func getQueryMatchers(query []model.KeyValueMatch) []*envoy_config_route_v3.QueryParameterMatcher {
   551  	res := make([]*envoy_config_route_v3.QueryParameterMatcher, 0, len(query))
   552  	for _, q := range query {
   553  		res = append(res, &envoy_config_route_v3.QueryParameterMatcher{
   554  			Name: q.Key,
   555  			QueryParameterMatchSpecifier: &envoy_config_route_v3.QueryParameterMatcher_StringMatch{
   556  				StringMatch: getEnvoyStringMatcher(q.Match),
   557  			},
   558  		})
   559  	}
   560  	return res
   561  }
   562  
   563  func getHeaderMatchers(hostnames []string, hostNameSuffixMatch bool, headers []model.KeyValueMatch, method *string) []*envoy_config_route_v3.HeaderMatcher {
   564  	var result []*envoy_config_route_v3.HeaderMatcher
   565  
   566  	if !hostNameSuffixMatch {
   567  		for _, host := range hostnames {
   568  			if len(host) != 0 && host != wildCard && strings.Contains(host, wildCard) {
   569  				// Make sure that wildcard character only match one single dns domain.
   570  				// For example, if host is *.foo.com, baz.bar.foo.com should not match
   571  				result = append(result, &envoy_config_route_v3.HeaderMatcher{
   572  					Name: envoyAuthority,
   573  					HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{
   574  						StringMatch: &envoy_type_matcher_v3.StringMatcher{
   575  							MatchPattern: &envoy_type_matcher_v3.StringMatcher_SafeRegex{
   576  								SafeRegex: &envoy_type_matcher_v3.RegexMatcher{
   577  									Regex: getMatchingHeaderRegex(host),
   578  								},
   579  							},
   580  						},
   581  					},
   582  				})
   583  			}
   584  		}
   585  	}
   586  
   587  	for _, h := range headers {
   588  		result = append(result, &envoy_config_route_v3.HeaderMatcher{
   589  			Name: h.Key,
   590  			HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{
   591  				StringMatch: getEnvoyStringMatcher(h.Match),
   592  			},
   593  		})
   594  	}
   595  
   596  	if method != nil {
   597  		result = append(result, &envoy_config_route_v3.HeaderMatcher{
   598  			Name: ":method",
   599  			HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{
   600  				StringMatch: &envoy_type_matcher_v3.StringMatcher{
   601  					MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{
   602  						Exact: strings.ToUpper(*method),
   603  					},
   604  				},
   605  			},
   606  		})
   607  	}
   608  
   609  	return result
   610  }
   611  
   612  // getMatchingHeaderRegex is to make sure that one and only one single
   613  // subdomain is matched e.g. For example, *.foo.com should only match
   614  // bar.foo.com but not baz.bar.foo.com
   615  func getMatchingHeaderRegex(host string) string {
   616  	if strings.HasPrefix(host, starDot) {
   617  		return fmt.Sprintf("^%s+%s%s$", notDotRegex, dotRegex, strings.ReplaceAll(host[2:], dot, dotRegex))
   618  	}
   619  	return fmt.Sprintf("^%s$", strings.ReplaceAll(host, dot, dotRegex))
   620  }
   621  
   622  func getEnvoyStringMatcher(s model.StringMatch) *envoy_type_matcher_v3.StringMatcher {
   623  	if s.Exact != "" {
   624  		return &envoy_type_matcher_v3.StringMatcher{
   625  			MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{
   626  				Exact: s.Exact,
   627  			},
   628  		}
   629  	}
   630  	if s.Prefix != "" {
   631  		return &envoy_type_matcher_v3.StringMatcher{
   632  			MatchPattern: &envoy_type_matcher_v3.StringMatcher_Prefix{
   633  				Prefix: s.Prefix,
   634  			},
   635  		}
   636  	}
   637  	if s.Regex != "" {
   638  		return &envoy_type_matcher_v3.StringMatcher{
   639  			MatchPattern: &envoy_type_matcher_v3.StringMatcher_SafeRegex{
   640  				SafeRegex: &envoy_type_matcher_v3.RegexMatcher{
   641  					Regex: s.Regex,
   642  				},
   643  			},
   644  		}
   645  	}
   646  	return nil
   647  }
   648  
   649  func getHeadersToAdd(filter *model.HTTPHeaderFilter) []*envoy_config_core_v3.HeaderValueOption {
   650  	if filter == nil {
   651  		return nil
   652  	}
   653  	result := make(
   654  		[]*envoy_config_core_v3.HeaderValueOption,
   655  		0,
   656  		len(filter.HeadersToAdd)+len(filter.HeadersToSet),
   657  	)
   658  	for _, h := range filter.HeadersToAdd {
   659  		result = append(result, &envoy_config_core_v3.HeaderValueOption{
   660  			Header: &envoy_config_core_v3.HeaderValue{
   661  				Key:   h.Name,
   662  				Value: h.Value,
   663  			},
   664  			AppendAction: envoy_config_core_v3.HeaderValueOption_APPEND_IF_EXISTS_OR_ADD,
   665  		})
   666  	}
   667  
   668  	for _, h := range filter.HeadersToSet {
   669  		result = append(result, &envoy_config_core_v3.HeaderValueOption{
   670  			Header: &envoy_config_core_v3.HeaderValue{
   671  				Key:   h.Name,
   672  				Value: h.Value,
   673  			},
   674  			AppendAction: envoy_config_core_v3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
   675  		})
   676  	}
   677  	return result
   678  }
   679  
   680  func getHeadersToRemove(filter *model.HTTPHeaderFilter) []string {
   681  	if filter == nil {
   682  		return nil
   683  	}
   684  	return filter.HeadersToRemove
   685  }
   686  
   687  func toRedirectResponseCode(statusCode int) envoy_config_route_v3.RedirectAction_RedirectResponseCode {
   688  	switch statusCode {
   689  	case 301:
   690  		return envoy_config_route_v3.RedirectAction_MOVED_PERMANENTLY
   691  	case 302:
   692  		return envoy_config_route_v3.RedirectAction_FOUND
   693  	case 303:
   694  		return envoy_config_route_v3.RedirectAction_SEE_OTHER
   695  	case 307:
   696  		return envoy_config_route_v3.RedirectAction_TEMPORARY_REDIRECT
   697  	case 308:
   698  		return envoy_config_route_v3.RedirectAction_PERMANENT_REDIRECT
   699  	default:
   700  		return envoy_config_route_v3.RedirectAction_MOVED_PERMANENTLY
   701  	}
   702  }