Skip to content

Commit

Permalink
cors ir and xds translation
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
zhaohuabing committed Oct 21, 2023
1 parent 930592c commit b8fc9d7
Show file tree
Hide file tree
Showing 13 changed files with 548 additions and 16 deletions.
22 changes: 22 additions & 0 deletions internal/ir/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ type HTTPRoute struct {
Timeout *metav1.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
// load balancer policy to use when routing to the backend endpoints.
LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty"`
// Cors policy for the route.
Cors *Cors `json:"cors,omitempty" yaml:"cors,omitempty"`
// ExtensionRefs holds unstructured resources that were introduced by an extension and used on the HTTPRoute as extensionRef filters
ExtensionRefs []*UnstructuredRef `json:"extensionRefs,omitempty" yaml:"extensionRefs,omitempty"`
}
Expand Down Expand Up @@ -312,6 +314,26 @@ type JwtRequestAuthentication struct {
Providers []egv1a1.JwtAuthenticationFilterProvider `json:"providers,omitempty" yaml:"providers,omitempty"`
}

// Cors holds the Cross-Origin Resource Sharing (CORS) policy for the route.
//
// +k8s:deepcopy-gen=true
type Cors struct {
// AllowOrigins defines the origins that are allowed to make requests.
AllowOrigins []*StringMatch `json:"allowOrigins,omitempty" yaml:"allowOrigins,omitempty"`
// AllowMethods defines the methods that are allowed to make requests.
AllowMethods []string `json:"allowMethods,omitempty" yaml:"allowMethods,omitempty"`
// AllowHeaders defines the headers that are allowed to be sent with requests.
AllowHeaders []string `json:"allowHeaders,omitempty" yaml:"allowHeaders,omitempty"`
// ExposeHeaders defines the headers that can be exposed in the responses.
ExposeHeaders []string `json:"exposeHeaders,omitempty" yaml:"exposeHeaders,omitempty"`
// MaxAge defines how long the results of a preflight request can be cached.
MaxAge *metav1.Duration `json:"maxAge,omitempty" yaml:"maxAge,omitempty"`
// AllowPrivateNetwork defines whether allow whose target server’s IP address
// is more private than that from which the request initiator was fetched.
// Defaults to false.
AllowPrivateNetworkAccess bool `json:"allowPrivateNetwork,omitempty" yaml:"allowPrivateNetwork,omitempty"`
}

// Validate the fields within the HTTPRoute structure
func (h HTTPRoute) Validate() error {
var errs error
Expand Down
51 changes: 51 additions & 0 deletions internal/ir/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

155 changes: 155 additions & 0 deletions internal/xds/translator/cors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// 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")
}

Check warning on line 30 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L29-L30

Added lines #L29 - L30 were not covered by tests

if irListener == nil {
return errors.New("ir listener is nil")
}

Check warning on line 34 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L33-L34

Added lines #L33 - L34 were not covered by tests

if !listenerContainsCors(irListener) {
return nil
}

// Return early if filter already exists.
for _, httpFilter := range mgr.HttpFilters {
if httpFilter.Name == wellknown.CORS {
return nil
}

Check warning on line 44 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L42-L44

Added lines #L42 - L44 were not covered by tests
}

corsFilter, err := buildHCMCorsFilter()
if err != nil {
return err
}

Check warning on line 50 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L49-L50

Added lines #L49 - L50 were not covered by tests

// 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
}

Check warning on line 65 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L64-L65

Added lines #L64 - L65 were not covered by tests

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
}

Check warning on line 80 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L79-L80

Added lines #L79 - L80 were not covered by tests

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")
}

Check warning on line 96 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L95-L96

Added lines #L95 - L96 were not covered by tests
if irRoute == nil {
return errors.New("ir route is nil")
}

Check warning on line 99 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L98-L99

Added lines #L98 - L99 were not covered by tests
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)
}

Check warning on line 109 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L106-L109

Added lines #L106 - L109 were not covered by tests

var (
allowOrigins []*matcherv3.StringMatcher
allowMethods string
allowHeaders string
exposeHeaders string
maxAge string
allowCredentials *wrappers.BoolValue
allowPrivateNetworkAccess *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()))
allowPrivateNetworkAccess = &wrappers.BoolValue{Value: irRoute.Cors.AllowPrivateNetworkAccess}

routeCfgProto := &corsv3.CorsPolicy{
AllowOriginStringMatch: allowOrigins,
AllowMethods: allowMethods,
AllowHeaders: allowHeaders,
ExposeHeaders: exposeHeaders,
MaxAge: maxAge,
AllowCredentials: allowCredentials,
AllowPrivateNetworkAccess: allowPrivateNetworkAccess,
}

routeCfgAny, err := anypb.New(routeCfgProto)
if err != nil {
return err
}

Check warning on line 146 in internal/xds/translator/cors.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/cors.go#L145-L146

Added lines #L145 - L146 were not covered by tests

if filterCfg == nil {
route.TypedPerFilterConfig = make(map[string]*anypb.Any)
}

route.TypedPerFilterConfig[wellknown.CORS] = routeCfgAny

return nil
}
142 changes: 142 additions & 0 deletions internal/xds/translator/httpfilters.go
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
}

Check warning on line 106 in internal/xds/translator/httpfilters.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/httpfilters.go#L105-L106

Added lines #L105 - L106 were not covered by tests

// Add the cors filter, if needed
if err := patchHCMWithCorsFilter(mgr, irListener); err != nil {
return err
}

Check warning on line 111 in internal/xds/translator/httpfilters.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/httpfilters.go#L110-L111

Added lines #L110 - L111 were not covered by tests

// 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
}

Check warning on line 130 in internal/xds/translator/httpfilters.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/httpfilters.go#L129-L130

Added lines #L129 - L130 were not covered by tests

// Add the jwt per route config to the route, if needed.
if err := patchRouteWithJwtConfig(route, irRoute); err != nil {
return nil
}

Check warning on line 135 in internal/xds/translator/httpfilters.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/httpfilters.go#L134-L135

Added lines #L134 - L135 were not covered by tests

// Add the cors per route config to the route, if needed.
if err := patchRouteWithCorsConfig(route, irRoute); err != nil {
return err
}

Check warning on line 140 in internal/xds/translator/httpfilters.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/httpfilters.go#L139-L140

Added lines #L139 - L140 were not covered by tests
return nil
}
Loading

0 comments on commit b8fc9d7

Please sign in to comment.