forked from envoyproxy/gateway
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: CORS ir and xds translation (envoyproxy#2016)
* cors ir and xds translation Signed-off-by: huabing zhao <[email protected]> sort HTTP filters Signed-off-by: huabing zhao <[email protected]> address comments Signed-off-by: huabing zhao <[email protected]> refactor route filter config Signed-off-by: huabing zhao <[email protected]> address comments Signed-off-by: huabing zhao <[email protected]> * Remove AllowPrivateNetworkAccess since it's not a common knob Signed-off-by: huabing zhao <[email protected]> --------- Signed-off-by: huabing zhao <[email protected]>
- Loading branch information
1 parent
c12c14c
commit c1d03de
Showing
13 changed files
with
540 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
// Copyright Envoy Gateway Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// The full text of the Apache license is available in the LICENSE file at | ||
// the root of the repo. | ||
|
||
package translator | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" | ||
corsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cors/v3" | ||
hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" | ||
matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" | ||
"github.com/envoyproxy/go-control-plane/pkg/wellknown" | ||
"github.com/golang/protobuf/ptypes/wrappers" | ||
"google.golang.org/protobuf/types/known/anypb" | ||
|
||
"github.com/envoyproxy/gateway/internal/ir" | ||
) | ||
|
||
// patchHCMWithCorsFilter builds and appends the Cors Filter to the HTTP | ||
// Connection Manager if applicable. | ||
func patchHCMWithCorsFilter(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error { | ||
if mgr == nil { | ||
return errors.New("hcm is nil") | ||
} | ||
|
||
if irListener == nil { | ||
return errors.New("ir listener is nil") | ||
} | ||
|
||
if !listenerContainsCors(irListener) { | ||
return nil | ||
} | ||
|
||
// Return early if filter already exists. | ||
for _, httpFilter := range mgr.HttpFilters { | ||
if httpFilter.Name == wellknown.CORS { | ||
return nil | ||
} | ||
} | ||
|
||
corsFilter, err := buildHCMCorsFilter() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Ensure the cors filter is the first one in the filter chain. | ||
mgr.HttpFilters = append([]*hcmv3.HttpFilter{corsFilter}, mgr.HttpFilters...) | ||
|
||
return nil | ||
} | ||
|
||
// buildHCMCorsFilter returns a Cors filter from the provided IR listener. | ||
func buildHCMCorsFilter() (*hcmv3.HttpFilter, error) { | ||
corsProto := &corsv3.Cors{} | ||
|
||
corsAny, err := anypb.New(corsProto) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &hcmv3.HttpFilter{ | ||
Name: wellknown.CORS, | ||
ConfigType: &hcmv3.HttpFilter_TypedConfig{ | ||
TypedConfig: corsAny, | ||
}, | ||
}, nil | ||
} | ||
|
||
// listenerContainsCors returns true if the provided listener has Cors | ||
// policies attached to its routes. | ||
func listenerContainsCors(irListener *ir.HTTPListener) bool { | ||
if irListener == nil { | ||
return false | ||
} | ||
|
||
for _, route := range irListener.Routes { | ||
if route.Cors != nil { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
// patchRouteWithCorsConfig patches the provided route with the Cors config if | ||
// applicable. | ||
func patchRouteWithCorsConfig(route *routev3.Route, irRoute *ir.HTTPRoute) error { | ||
if route == nil { | ||
return errors.New("xds route is nil") | ||
} | ||
if irRoute == nil { | ||
return errors.New("ir route is nil") | ||
} | ||
if irRoute.Cors == nil { | ||
return nil | ||
} | ||
|
||
filterCfg := route.GetTypedPerFilterConfig() | ||
if _, ok := filterCfg[wellknown.CORS]; ok { | ||
// This should not happen since this is the only place where the cors | ||
// filter is added in a route. | ||
return fmt.Errorf("route already contains cors config: %+v", route) | ||
} | ||
|
||
var ( | ||
allowOrigins []*matcherv3.StringMatcher | ||
allowMethods string | ||
allowHeaders string | ||
exposeHeaders string | ||
maxAge string | ||
allowCredentials *wrappers.BoolValue | ||
) | ||
|
||
//nolint:gocritic | ||
|
||
for _, origin := range irRoute.Cors.AllowOrigins { | ||
allowOrigins = append(allowOrigins, buildXdsStringMatcher(origin)) | ||
} | ||
|
||
allowMethods = strings.Join(irRoute.Cors.AllowMethods, ", ") | ||
allowHeaders = strings.Join(irRoute.Cors.AllowHeaders, ", ") | ||
exposeHeaders = strings.Join(irRoute.Cors.ExposeHeaders, ", ") | ||
maxAge = strconv.Itoa(int(irRoute.Cors.MaxAge.Seconds())) | ||
|
||
routeCfgProto := &corsv3.CorsPolicy{ | ||
AllowOriginStringMatch: allowOrigins, | ||
AllowMethods: allowMethods, | ||
AllowHeaders: allowHeaders, | ||
ExposeHeaders: exposeHeaders, | ||
MaxAge: maxAge, | ||
AllowCredentials: allowCredentials, | ||
} | ||
|
||
routeCfgAny, err := anypb.New(routeCfgProto) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if filterCfg == nil { | ||
route.TypedPerFilterConfig = make(map[string]*anypb.Any) | ||
} | ||
|
||
route.TypedPerFilterConfig[wellknown.CORS] = routeCfgAny | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Copyright Envoy Gateway Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// The full text of the Apache license is available in the LICENSE file at | ||
// the root of the repo. | ||
|
||
package translator | ||
|
||
import ( | ||
"sort" | ||
|
||
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" | ||
hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" | ||
"github.com/envoyproxy/go-control-plane/pkg/wellknown" | ||
|
||
"github.com/envoyproxy/gateway/internal/ir" | ||
xdsfilters "github.com/envoyproxy/gateway/internal/xds/filters" | ||
) | ||
|
||
type OrderedHTTPFilter struct { | ||
filter *hcmv3.HttpFilter | ||
order int | ||
} | ||
|
||
type OrderedHTTPFilters []*OrderedHTTPFilter | ||
|
||
// newOrderedHTTPFilter gives each HTTP filter a rational order. | ||
// This is needed because the order of the filters is important. | ||
// For example, the cors filter should be put at the first to avoid unnecessary | ||
// processing of other filters for unauthorized cross-region access. | ||
// The router filter must be the last one since it's a terminal filter. | ||
// | ||
// Important: please modify this method and set the order for the new filter | ||
// when adding a new filter in the HCM filter chain. | ||
// If the order is not explicitly specified in this method, a filter will be set | ||
// a default order 50. | ||
func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter { | ||
order := 50 | ||
|
||
// Set a rational order for all the filters. | ||
switch filter.Name { | ||
case wellknown.CORS: | ||
order = 1 | ||
case jwtAuthenFilter: | ||
order = 2 | ||
case wellknown.HTTPRateLimit: | ||
order = 3 | ||
case wellknown.Router: | ||
order = 100 | ||
} | ||
|
||
return &OrderedHTTPFilter{ | ||
filter: filter, | ||
order: order, | ||
} | ||
} | ||
|
||
// sort.Interface implementation. | ||
func (o OrderedHTTPFilters) Len() int { | ||
return len(o) | ||
} | ||
|
||
func (o OrderedHTTPFilters) Less(i, j int) bool { | ||
return o[i].order < o[j].order | ||
} | ||
|
||
func (o OrderedHTTPFilters) Swap(i, j int) { | ||
o[i], o[j] = o[j], o[i] | ||
} | ||
|
||
// sortHTTPFilters sorts the HTTP filters in the correct order. | ||
// This is needed because the order of the filters is important. | ||
// For example, the cors filter should be put at the first to avoid unnecessary | ||
// processing of other filters for unauthorized cross-region access. | ||
// The router filter must be the last one since it's a terminal filter. | ||
func sortHTTPFilters(filters []*hcmv3.HttpFilter) []*hcmv3.HttpFilter { | ||
orderedFilters := make(OrderedHTTPFilters, len(filters)) | ||
for i := 0; i < len(filters); i++ { | ||
orderedFilters[i] = newOrderedHTTPFilter(filters[i]) | ||
} | ||
sort.Sort(orderedFilters) | ||
|
||
for i := 0; i < len(filters); i++ { | ||
filters[i] = orderedFilters[i].filter | ||
} | ||
return filters | ||
} | ||
|
||
// patchHCMWithFilters builds and appends HTTP Filters to the HTTP connection | ||
// manager. | ||
// Important: don't forget to set the order for newly added filters in the | ||
// newOrderedHTTPFilter method. | ||
func (t *Translator) patchHCMWithFilters( | ||
mgr *hcmv3.HttpConnectionManager, | ||
irListener *ir.HTTPListener) error { | ||
// The order of filter patching is not relevant here. | ||
// All the filters will be sorted in correct order after the patching is done. | ||
// Important: don't forget to set the order for new filters in the | ||
// newOrderedHTTPFilter method. | ||
// TODO: Make this a generic interface for all API Gateway features. | ||
// https://github.com/envoyproxy/gateway/issues/882 | ||
t.patchHCMWithRateLimit(mgr, irListener) | ||
|
||
// Add the jwt authn filter, if needed. | ||
if err := patchHCMWithJwtAuthnFilter(mgr, irListener); err != nil { | ||
return err | ||
} | ||
|
||
// Add the cors filter, if needed | ||
if err := patchHCMWithCorsFilter(mgr, irListener); err != nil { | ||
return err | ||
} | ||
|
||
// Add the router filter | ||
mgr.HttpFilters = append(mgr.HttpFilters, xdsfilters.HTTPRouter) | ||
|
||
// Sort the filters in the correct order. | ||
mgr.HttpFilters = sortHTTPFilters(mgr.HttpFilters) | ||
return nil | ||
} | ||
|
||
// patchRouteWithFilters appends per-route filter configurations to the route. | ||
func patchRouteWithFilters( | ||
route *routev3.Route, | ||
irRoute *ir.HTTPRoute) error { | ||
// TODO: Convert this into a generic interface for API Gateway features. | ||
// https://github.com/envoyproxy/gateway/issues/882 | ||
if err := | ||
patchRouteWithRateLimit(route.GetRoute(), irRoute); err != nil { | ||
return nil | ||
} | ||
|
||
// Add the jwt per route config to the route, if needed. | ||
if err := patchRouteWithJwtConfig(route, irRoute); err != nil { | ||
return nil | ||
} | ||
|
||
// Add the cors per route config to the route, if needed. | ||
if err := patchRouteWithCorsConfig(route, irRoute); err != nil { | ||
return err | ||
} | ||
return nil | ||
} |
Oops, something went wrong.