Skip to content

Commit

Permalink
Support custom HTTP filter ordering.
Browse files Browse the repository at this point in the history
Signed-off-by: huabing zhao <zhaohuabing@gmail.com>
  • Loading branch information
zhaohuabing committed Apr 25, 2024
1 parent dde05a9 commit 61586ce
Show file tree
Hide file tree
Showing 8 changed files with 466 additions and 34 deletions.
2 changes: 2 additions & 0 deletions api/v1alpha1/envoyextensionypolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ type EnvoyExtensionPolicySpec struct {
// Order matters, as the extensions will be loaded in the order they are
// defined in this list.
//
// +kubebuilder:validation:MaxItems=16
// +optional
Wasm []Wasm `json:"wasm,omitempty"`

// ExtProc is an ordered list of external processing filters
// that should added to the envoy filter chain
//
// +kubebuilder:validation:MaxItems=16
// +optional
ExtProc []ExtProc `json:"extProc,omitempty"`
}
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/envoyproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ type EnvoyProxySpec struct {
Shutdown *ShutdownConfig `json:"shutdown,omitempty"`

// FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.
// The FilterPosition in the list will be applied in the order they are defined.
// If unspecified, the default filter order is applied.
// Default filter order is:
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ spec:
the kind field or setting it to 'Service') is supported
rule: 'has(self.backendRef) ? (!has(self.backendRef.kind) || self.backendRef.kind
== ''Service'') : true'
maxItems: 16
type: array
targetRef:
description: |-
Expand Down Expand Up @@ -478,6 +479,7 @@ spec:
- code
- name
type: object
maxItems: 16
type: array
required:
- targetRef
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ spec:
filterOrder:
description: |-
FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.
The FilterPosition in the list will be applied in the order they are defined.
If unspecified, the default filter order is applied.
Default filter order is:

Expand Down
3 changes: 3 additions & 0 deletions internal/cmd/egctl/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ func translateGatewayAPIToXds(dnsDomain string, resourceType string, resources *
ServiceURL: ratelimit.GetServiceURL("envoy-gateway", dnsDomain),
},
}
if resources.EnvoyProxy != nil {
xTranslator.FilterOrder = resources.EnvoyProxy.Spec.FilterOrder
}
xRes, err := xTranslator.Translate(val)
if err != nil {
return nil, fmt.Errorf("failed to translate xds ir for key %s value %+v, error:%w", key, val, err)
Expand Down
108 changes: 86 additions & 22 deletions internal/xds/translator/httpfilters.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
package translator

import (
"container/list"
"fmt"
"sort"
"strconv"
"strings"

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"
"k8s.io/utils/ptr"

"github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/envoyproxy/gateway/internal/xds/filters"
"github.com/envoyproxy/gateway/internal/xds/types"

Expand Down Expand Up @@ -91,28 +95,28 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter {
// When the fault filter is configured to be at the first, the computation of
// the remaining filters is skipped when rejected early
switch {
case filter.Name == wellknown.Fault:
case isFilterType(filter.Name, wellknown.Fault):
order = 1
case filter.Name == wellknown.CORS:
case isFilterType(filter.Name, wellknown.CORS):
order = 2
case isFilterType(filter, extAuthFilter):
case isFilterType(filter.Name, extAuthFilter):
order = 3
case filter.Name == basicAuthFilter:
case isFilterType(filter.Name, basicAuthFilter):
order = 4
case isFilterType(filter, oauth2Filter):
case isFilterType(filter.Name, oauth2Filter):
order = 5
case filter.Name == jwtAuthn:
case isFilterType(filter.Name, jwtAuthn):
order = 6
case isFilterType(filter, extProcFilter):
order = 7
case isFilterType(filter, wasmFilter):
order = 8
case filter.Name == localRateLimitFilter:
order = 9
case filter.Name == wellknown.HTTPRateLimit:
order = 10
case filter.Name == wellknown.Router:
order = 100
case isFilterType(filter.Name, extProcFilter):
order = 100 + mustFilterIndex(filter.Name)
case isFilterType(filter.Name, wasmFilter):
order = 200 + mustFilterIndex(filter.Name)
case isFilterType(filter.Name, localRateLimitFilter):
order = 301
case isFilterType(filter.Name, wellknown.HTTPRateLimit):
order = 302
case isFilterType(filter.Name, wellknown.Router):
order = 303
}

return &OrderedHTTPFilter{
Expand Down Expand Up @@ -140,16 +144,66 @@ func (o OrderedHTTPFilters) Swap(i, j int) {
// 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 {
func sortHTTPFilters(filters []*hcmv3.HttpFilter, filterOrder []v1alpha1.FilterPosition) []*hcmv3.HttpFilter {
// Sort the filters in the default order.
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
l := list.New()
for i := 0; i < len(orderedFilters); i++ {
l.PushBack(orderedFilters[i])
}

// Sort the filters in the custom order.
for i := 0; i < len(filterOrder); i++ {
var currentFilters []*list.Element

for element := l.Front(); element != nil; element = element.Next() {
if isFilterType(element.Value.(*OrderedHTTPFilter).filter.Name, string(filterOrder[i].Name)) {
currentFilters = append(currentFilters, element)
}
}

// Skip if the filter is not found.
// This is just a sanity check. It should never happen since we validate
// the filter names in the API validation.
if len(currentFilters) == 0 {
continue
}

if filterOrder[i].Before != nil {
for element := l.Front(); element != nil; element = element.Next() {
if isFilterType(element.Value.(*OrderedHTTPFilter).filter.Name, string(*filterOrder[i].Before)) {
for _, filter := range currentFilters {
l.MoveBefore(filter, element)
}
break
}
}
} else {
var afterFilter *list.Element
for element := l.Front(); element != nil; element = element.Next() {
if isFilterType(element.Value.(*OrderedHTTPFilter).filter.Name, string(*filterOrder[i].After)) {
afterFilter = element
}
}
if afterFilter != nil {
for i := range currentFilters {
l.MoveAfter(currentFilters[len(currentFilters)-1-i], afterFilter)
}
}
}
}

i := 0
for element := l.Front(); element != nil; element = element.Next() {
filters[i] = element.Value.(*OrderedHTTPFilter).filter
i++
}

return filters
}

Expand Down Expand Up @@ -189,7 +243,7 @@ func (t *Translator) patchHCMWithFilters(
}

// Sort the filters in the correct order.
mgr.HttpFilters = sortHTTPFilters(mgr.HttpFilters)
mgr.HttpFilters = sortHTTPFilters(mgr.HttpFilters, t.FilterOrder)
return nil
}

Expand All @@ -216,11 +270,21 @@ func patchRouteWithPerRouteConfig(
}

// isFilterType returns true if the filter is the provided filter type.
func isFilterType(filter *hcmv3.HttpFilter, filterType string) bool {
func isFilterType(filterName string, filterType string) bool {
// Multiple filters of the same types are added to the HCM filter chain, one for each
// route. The filter name is prefixed with the filter type, for example:
// "envoy.filters.http.oauth2_first-route".
return strings.HasPrefix(filter.Name, filterType)
return strings.HasPrefix(filterName, filterType)
}

// mustFilterIndex returns the index of the filter in its filter type.
func mustFilterIndex(filterName string) int {
a := strings.Split(filterName, "/")
index, err := strconv.Atoi(a[len(a)-1])
if err != nil {
panic(fmt.Errorf("cannot get filter index from %s :%w", filterName, err))
}
return index
}

// patchResources adds all the other needed resources referenced by this
Expand Down
Loading

0 comments on commit 61586ce

Please sign in to comment.