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 }