Skip to content

Commit

Permalink
impl: httpfilter
Browse files Browse the repository at this point in the history
Signed-off-by: Guy Daich <[email protected]>
  • Loading branch information
guydc committed Sep 19, 2024
1 parent 1b56eda commit 6b98284
Show file tree
Hide file tree
Showing 41 changed files with 1,896 additions and 39 deletions.
7 changes: 6 additions & 1 deletion api/v1alpha1/httproutefilter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,18 @@ const (
type ReplaceRegexMatch struct {
// Pattern matches a regular expression against the value of the HTTP Path.The regex string must
// adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax.
// +kubebuilder:validation:MinLength=1
Pattern string `json:"pattern"`
// Substitution is an expression that replaces the matched portion.The expression may include numbered
// capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax.
// +kubebuilder:validation:MinLength=1
Substitution string `json:"substitution"`
}

// +kubebuilder:validation:XValidation:rule="self.type == 'ReplaceRegexMatch' ? has(self.replaceRegexMatch) : !has(self.replaceRegexMatch)",message="If HTTPPathModifier type is ReplaceRegexMatch, replaceRegexMatch field needs to be set."
type HTTPPathModifier struct {
// +kubebuilder:validation:Enum=RegexHTTPPathModifier
// +kubebuilder:validation:Enum=ReplaceRegexMatch
// +kubebuilder:validation:Required
Type HTTPPathModifierType `json:"type"`
// ReplaceRegexMatch defines a path regex rewrite. The path portions matched by the regex pattern are replaced by the defined substitution.
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-regex-rewrite
Expand All @@ -84,6 +88,7 @@ type HTTPPathModifier struct {
// pattern: (?i)/xxx/
// substitution: /yyy/
// Would transform path /aaa/XxX/bbb into /aaa/yyy/bbb (case-insensitive).
// +optional
ReplaceRegexMatch *ReplaceRegexMatch `json:"replaceRegexMatch,omitempty"`
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,13 @@ spec:
description: |-
Pattern matches a regular expression against the value of the HTTP Path.The regex string must
adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax.
minLength: 1
type: string
substitution:
description: |-
Substitution is an expression that replaces the matched portion.The expression may include numbered
capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax.
minLength: 1
type: string
required:
- pattern
Expand All @@ -96,11 +98,16 @@ spec:
description: HTTPPathModifierType defines the type of path
redirect or rewrite.
enum:
- RegexHTTPPathModifier
- ReplaceRegexMatch
type: string
required:
- type
type: object
x-kubernetes-validations:
- message: If HTTPPathModifier type is ReplaceRegexMatch, replaceRegexMatch
field needs to be set.
rule: 'self.type == ''ReplaceRegexMatch'' ? has(self.replaceRegexMatch)
: !has(self.replaceRegexMatch)'
type: object
type: object
required:
Expand Down
1 change: 1 addition & 0 deletions charts/gateway-helm/templates/_rbac.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ resources:
- securitypolicies
- envoyextensionpolicies
- backends
- httproutefilters
verbs:
- get
- list
Expand Down
117 changes: 103 additions & 14 deletions internal/gatewayapi/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ package gatewayapi

import (
"fmt"
"regexp"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
"github.com/envoyproxy/gateway/internal/gatewayapi/status"
"github.com/envoyproxy/gateway/internal/ir"
Expand Down Expand Up @@ -150,16 +152,20 @@ func (t *Translator) processURLRewriteFilter(
filterContext *HTTPFiltersContext,
) {
if filterContext.URLRewrite != nil {
routeStatus := GetRouteStatus(filterContext.Route)
status.SetRouteStatusCondition(routeStatus,
filterContext.ParentRef.routeParentStatusIdx,
filterContext.Route.GetGeneration(),
gwapiv1.RouteConditionAccepted,
metav1.ConditionFalse,
gwapiv1.RouteReasonUnsupportedValue,
"Cannot configure multiple urlRewrite filters for a single HTTPRouteRule",
)
return
if filterContext.URLRewrite.Hostname != nil ||
filterContext.URLRewrite.Path.FullReplace != nil ||
filterContext.URLRewrite.Path.PrefixMatchReplace != nil {
routeStatus := GetRouteStatus(filterContext.Route)
status.SetRouteStatusCondition(routeStatus,
filterContext.ParentRef.routeParentStatusIdx,
filterContext.Route.GetGeneration(),
gwapiv1.RouteConditionAccepted,
metav1.ConditionFalse,
gwapiv1.RouteReasonUnsupportedValue,
"Cannot configure multiple urlRewrite filters for a single HTTPRouteRule",
)
return
}
}

if rewrite == nil {
Expand Down Expand Up @@ -215,8 +221,10 @@ func (t *Translator) processURLRewriteFilter(
return
}
if rewrite.Path.ReplaceFullPath != nil {
newURLRewrite.Path = &ir.HTTPPathModifier{
FullReplace: rewrite.Path.ReplaceFullPath,
newURLRewrite.Path = &ir.ExtendedHTTPPathModifier{
HTTPPathModifier: ir.HTTPPathModifier{
FullReplace: rewrite.Path.ReplaceFullPath,
},
}
}
case gwapiv1.PrefixMatchHTTPPathModifier:
Expand Down Expand Up @@ -247,8 +255,10 @@ func (t *Translator) processURLRewriteFilter(
return
}
if rewrite.Path.ReplacePrefixMatch != nil {
newURLRewrite.Path = &ir.HTTPPathModifier{
PrefixMatchReplace: rewrite.Path.ReplacePrefixMatch,
newURLRewrite.Path = &ir.ExtendedHTTPPathModifier{
HTTPPathModifier: ir.HTTPPathModifier{
PrefixMatchReplace: rewrite.Path.ReplacePrefixMatch,
},
}
}
default:
Expand Down Expand Up @@ -738,6 +748,85 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec
}

filterNs := filterContext.Route.GetNamespace()

if string(extFilter.Kind) == egv1a1.KindHTTPRouteFilter {
for _, hrf := range resources.HTTPRouteFilters {
if hrf.Namespace == filterNs && hrf.Name == string(extFilter.Name) &&
hrf.Spec.URLRewrite.Path.Type == egv1a1.RegexHTTPPathModifier {

if hrf.Spec.URLRewrite.Path.ReplaceRegexMatch == nil ||
hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern == "" ||
hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Substitution == "" {
errMsg := "ReplaceRegexMatch Pattern and Substitution must be set when rewrite path type is \"ReplaceRegexMatch\""
routeStatus := GetRouteStatus(filterContext.Route)
status.SetRouteStatusCondition(routeStatus,
filterContext.ParentRef.routeParentStatusIdx,
filterContext.Route.GetGeneration(),
gwapiv1.RouteConditionAccepted,
metav1.ConditionFalse,
gwapiv1.RouteReasonUnsupportedValue,
errMsg,
)
return
} else if _, err := regexp.Compile(hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern); err != nil {
// Avoid envoy NACKs due to invalid regex.
// Golang's regexp is almost identical to RE2: https://pkg.go.dev/regexp/syntax
errMsg := "ReplaceRegexMatch must be a valid RE2 regular expression"
routeStatus := GetRouteStatus(filterContext.Route)
status.SetRouteStatusCondition(routeStatus,
filterContext.ParentRef.routeParentStatusIdx,
filterContext.Route.GetGeneration(),
gwapiv1.RouteConditionAccepted,
metav1.ConditionFalse,
gwapiv1.RouteReasonUnsupportedValue,
errMsg,
)
return
}

rmr := &ir.RegexMatchReplace{
Pattern: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern,
Substitution: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Substitution,
}

if filterContext.HTTPFilterIR.URLRewrite != nil {
// If path IR is already set - check for a conflict
if filterContext.HTTPFilterIR.URLRewrite.Path != nil {
path := filterContext.HTTPFilterIR.URLRewrite.Path
if path.RegexMatchReplace != nil || path.PrefixMatchReplace != nil || path.FullReplace != nil {
routeStatus := GetRouteStatus(filterContext.Route)
status.SetRouteStatusCondition(routeStatus,
filterContext.ParentRef.routeParentStatusIdx,
filterContext.Route.GetGeneration(),
gwapiv1.RouteConditionAccepted,
metav1.ConditionFalse,
gwapiv1.RouteReasonUnsupportedValue,
"Cannot configure multiple urlRewrite filters for a single HTTPRouteRule",
)
return
}
} else { // no path
filterContext.HTTPFilterIR.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{
RegexMatchReplace: rmr,
}
return
}
} else { // no url rewrite
filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{
Path: &ir.ExtendedHTTPPathModifier{
RegexMatchReplace: rmr,
},
}
return
}
}
}
errMsg := fmt.Sprintf("Unable to translate HTTPRouteFilter: %s/%s", filterNs,
extFilter.Name)
t.processUnresolvedHTTPFilter(errMsg, filterContext)
return
}

// This list of resources will be empty unless an extension is loaded (and introduces resources)
for _, res := range resources.ExtensionRefFilters {
if res.GetKind() == string(extFilter.Kind) && res.GetName() == string(extFilter.Name) && res.GetNamespace() == filterNs {
Expand Down
3 changes: 3 additions & 0 deletions internal/gatewayapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ func ValidateHTTPRouteFilter(filter *gwapiv1.HTTPRouteFilter, extGKs ...schema.G
switch {
case filter.ExtensionRef == nil:
return errors.New("extensionRef field must be specified for an extended filter")
case string(filter.ExtensionRef.Group) == egv1a1.GroupVersion.Group &&
string(filter.ExtensionRef.Kind) == egv1a1.KindHTTPRouteFilter:
return nil
default:
for _, gk := range extGKs {
if filter.ExtensionRef.Group == gwapiv1.Group(gk.Group) &&
Expand Down
14 changes: 14 additions & 0 deletions internal/gatewayapi/resource/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,20 @@ func kubernetesYAMLToResources(str string, addMissingResources bool) (*Resources
Spec: typedSpec.(egv1a1.SecurityPolicySpec),
}
resources.SecurityPolicies = append(resources.SecurityPolicies, securityPolicy)
case KindHTTPRouteFilter:
typedSpec := spec.Interface()
httpRouteFilter := &egv1a1.HTTPRouteFilter{
TypeMeta: metav1.TypeMeta{
Kind: KindHTTPRouteFilter,
APIVersion: egv1a1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
Spec: typedSpec.(egv1a1.HTTPRouteFilterSpec),
}
resources.HTTPRouteFilters = append(resources.HTTPRouteFilters, httpRouteFilter)
}
}

Expand Down
2 changes: 2 additions & 0 deletions internal/gatewayapi/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type Resources struct {
EnvoyExtensionPolicies []*egv1a1.EnvoyExtensionPolicy `json:"envoyExtensionPolicies,omitempty" yaml:"envoyExtensionPolicies,omitempty"`
ExtensionServerPolicies []unstructured.Unstructured `json:"extensionServerPolicies,omitempty" yaml:"extensionServerPolicies,omitempty"`
Backends []*egv1a1.Backend `json:"backends,omitempty" yaml:"backends,omitempty"`
HTTPRouteFilters []*egv1a1.HTTPRouteFilter `json:"httpFilters,omitempty" yaml:"httpFilters,omitempty"`
}

func NewResources() *Resources {
Expand All @@ -86,6 +87,7 @@ func NewResources() *Resources {
EnvoyExtensionPolicies: []*egv1a1.EnvoyExtensionPolicy{},
ExtensionServerPolicies: []unstructured.Unstructured{},
Backends: []*egv1a1.Backend{},
HTTPRouteFilters: []*egv1a1.HTTPRouteFilter{},
}
}

Expand Down
1 change: 1 addition & 0 deletions internal/gatewayapi/resource/supported_kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ const (
KindService = "Service"
KindServiceImport = "ServiceImport"
KindSecret = "Secret"
KindHTTPRouteFilter = "HTTPRouteFilter"
)
11 changes: 11 additions & 0 deletions internal/gatewayapi/resource/zz_generated.deepcopy.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,4 @@ xdsIR:
path:
fullReplace: /rewrite
prefixMatchReplace: null
regexMatchReplace: null
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,4 @@ xdsIR:
path:
fullReplace: null
prefixMatchReplace: /rewrite
regexMatchReplace: null
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,4 @@ xdsIR:
path:
fullReplace: null
prefixMatchReplace: /rewrite
regexMatchReplace: null
Loading

0 comments on commit 6b98284

Please sign in to comment.