From 9353be2e7cfe017389bcf21c468f1472ac7c51f8 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Wed, 23 Oct 2024 23:02:38 -0700 Subject: [PATCH] feat: direct response (#4508) * feat: direct response Relates to https://github.com/envoyproxy/gateway/issues/2714 Signed-off-by: Arko Dasgupta * provider logic Signed-off-by: Arko Dasgupta * default status code is 200 Signed-off-by: Arko Dasgupta --------- Signed-off-by: Arko Dasgupta --- internal/gatewayapi/backendtrafficpolicy.go | 87 ++++++++++--------- internal/gatewayapi/envoyextensionpolicy.go | 8 +- internal/gatewayapi/filters.go | 53 ++++++++--- internal/gatewayapi/route.go | 8 +- internal/gatewayapi/securitypolicy.go | 8 +- internal/gatewayapi/translator.go | 2 +- internal/ir/xds.go | 34 ++++---- internal/ir/xds_test.go | 8 +- internal/ir/zz_generated.deepcopy.go | 29 +++---- internal/provider/kubernetes/controller.go | 4 + internal/provider/kubernetes/filters.go | 38 ++++++++ internal/provider/kubernetes/indexers.go | 30 +++++++ internal/provider/kubernetes/predicates.go | 12 +++ internal/provider/kubernetes/routes.go | 2 + internal/xds/translator/custom_response.go | 9 +- internal/xds/translator/route.go | 16 +++- .../testdata/in/xds-ir/accesslog-cel.yaml | 3 - .../in/xds-ir/accesslog-endpoint-stats.yaml | 3 - .../in/xds-ir/accesslog-formatters.yaml | 3 - .../in/xds-ir/accesslog-multi-cel.yaml | 3 - .../testdata/in/xds-ir/accesslog-types.yaml | 3 - .../in/xds-ir/accesslog-without-format.yaml | 3 - .../testdata/in/xds-ir/accesslog.yaml | 3 - .../testdata/in/xds-ir/tracing-datadog.yaml | 3 - .../in/xds-ir/tracing-endpoint-stats.yaml | 3 - .../testdata/in/xds-ir/tracing-zipkin.yaml | 3 - .../testdata/in/xds-ir/tracing.yaml | 3 - .../out/xds-ir/accesslog-cel.routes.yaml | 8 +- .../accesslog-endpoint-stats.routes.yaml | 8 +- .../xds-ir/accesslog-formatters.routes.yaml | 8 +- .../xds-ir/accesslog-multi-cel.routes.yaml | 8 +- .../out/xds-ir/accesslog-types.routes.yaml | 8 +- .../accesslog-without-format.routes.yaml | 8 +- .../testdata/out/xds-ir/accesslog.routes.yaml | 8 +- .../http-route-direct-response.routes.yaml | 2 + .../out/xds-ir/tracing-datadog.routes.yaml | 8 +- .../xds-ir/tracing-endpoint-stats.routes.yaml | 8 +- .../out/xds-ir/tracing-zipkin.routes.yaml | 8 +- .../testdata/out/xds-ir/tracing.routes.yaml | 8 +- 39 files changed, 292 insertions(+), 179 deletions(-) diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 89b6804a2ba..3069ef5cc50 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -14,10 +14,10 @@ import ( "strings" perr "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/ptr" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -32,14 +32,14 @@ const ( MaxConsistentHashTableSize = 5000011 // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-maglevlbconfig ) -func (t *Translator) ProcessBackendTrafficPolicies(backendTrafficPolicies []*egv1a1.BackendTrafficPolicy, +func (t *Translator) ProcessBackendTrafficPolicies(resources *resource.Resources, gateways []*GatewayContext, routes []RouteContext, xdsIR resource.XdsIRMap, - configMaps []*corev1.ConfigMap, ) []*egv1a1.BackendTrafficPolicy { res := []*egv1a1.BackendTrafficPolicy{} + backendTrafficPolicies := resources.BackendTrafficPolicies // Sort based on timestamp sort.Slice(backendTrafficPolicies, func(i, j int) bool { return backendTrafficPolicies[i].CreationTimestamp.Before(&(backendTrafficPolicies[j].CreationTimestamp)) @@ -130,7 +130,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(backendTrafficPolicies []*egv } // Set conditions for translation error if it got any - if err := t.translateBackendTrafficPolicyForRoute(policy, route, xdsIR, configMaps); err != nil { + if err := t.translateBackendTrafficPolicyForRoute(policy, route, xdsIR, resources); err != nil { status.SetTranslationErrorForPolicyAncestors(&policy.Status, ancestorRefs, t.GatewayControllerName, @@ -184,7 +184,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(backendTrafficPolicies []*egv } // Set conditions for translation error if it got any - if err := t.translateBackendTrafficPolicyForGateway(policy, currTarget, gateway, xdsIR, configMaps); err != nil { + if err := t.translateBackendTrafficPolicyForGateway(policy, currTarget, gateway, xdsIR, resources); err != nil { status.SetTranslationErrorForPolicyAncestors(&policy.Status, ancestorRefs, t.GatewayControllerName, @@ -288,7 +288,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute( policy *egv1a1.BackendTrafficPolicy, route RouteContext, xdsIR resource.XdsIRMap, - configMaps []*corev1.ConfigMap, + resources *resource.Resources, ) error { var ( rl *ir.RateLimit @@ -349,7 +349,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute( errs = errors.Join(errs, err) } - if ro, err = buildResponseOverride(policy, configMaps); err != nil { + if ro, err = buildResponseOverride(policy, resources); err != nil { err = perr.WithMessage(err, "ResponseOverride") errs = errors.Join(errs, err) } @@ -392,8 +392,8 @@ func (t *Translator) translateBackendTrafficPolicyForRoute( if strings.HasPrefix(r.Name, prefix) { if errs != nil { // Return a 500 direct response - r.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + r.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } continue } @@ -438,7 +438,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway( target gwapiv1a2.LocalPolicyTargetReferenceWithSectionName, gateway *GatewayContext, xdsIR resource.XdsIRMap, - configMaps []*corev1.ConfigMap, + resources *resource.Resources, ) error { var ( rl *ir.RateLimit @@ -491,7 +491,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway( err = perr.WithMessage(err, "HTTP2") errs = errors.Join(errs, err) } - if ro, err = buildResponseOverride(policy, configMaps); err != nil { + if ro, err = buildResponseOverride(policy, resources); err != nil { err = perr.WithMessage(err, "ResponseOverride") errs = errors.Join(errs, err) } @@ -561,8 +561,8 @@ func (t *Translator) translateBackendTrafficPolicyForGateway( if errs != nil { // Return a 500 direct response - r.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + r.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } continue } @@ -864,7 +864,7 @@ func makeIrTriggerSet(in []egv1a1.TriggerEnum) []ir.TriggerEnum { return irTriggers } -func buildResponseOverride(policy *egv1a1.BackendTrafficPolicy, configMaps []*corev1.ConfigMap) (*ir.ResponseOverride, error) { +func buildResponseOverride(policy *egv1a1.BackendTrafficPolicy, resources *resource.Resources) (*ir.ResponseOverride, error) { if len(policy.Spec.ResponseOverride) == 0 { return nil, nil } @@ -894,33 +894,10 @@ func buildResponseOverride(policy *egv1a1.BackendTrafficPolicy, configMaps []*co ContentType: ro.Response.ContentType, } - if ro.Response.Body.Type != nil && *ro.Response.Body.Type == egv1a1.ResponseValueTypeValueRef { - foundCM := false - for _, cm := range configMaps { - if cm.Namespace == policy.Namespace && cm.Name == string(ro.Response.Body.ValueRef.Name) { - body, dataOk := cm.Data["response.body"] - switch { - case dataOk: - response.Body = body - case len(cm.Data) > 0: // Fallback to the first key if response.body is not found - for _, value := range cm.Data { - body = value - break - } - response.Body = body - default: - return nil, fmt.Errorf("can't find the key response.body in the referenced configmap %s", ro.Response.Body.ValueRef.Name) - } - - foundCM = true - break - } - } - if !foundCM { - return nil, fmt.Errorf("can't find the referenced configmap %s", ro.Response.Body.ValueRef.Name) - } - } else { - response.Body = *ro.Response.Body.Inline + var err error + response.Body, err = getCustomResponseBody(ro.Response.Body, resources, policy.Namespace) + if err != nil { + return nil, err } rules = append(rules, ir.ResponseOverrideRule{ @@ -935,6 +912,34 @@ func buildResponseOverride(policy *egv1a1.BackendTrafficPolicy, configMaps []*co }, nil } +func getCustomResponseBody(body egv1a1.CustomResponseBody, resources *resource.Resources, policyNs string) (*string, error) { + if body.Type != nil && *body.Type == egv1a1.ResponseValueTypeValueRef { + cm := resources.GetConfigMap(policyNs, string(body.ValueRef.Name)) + if cm != nil { + b, dataOk := cm.Data["response.body"] + switch { + case dataOk: + return &b, nil + case len(cm.Data) > 0: // Fallback to the first key if response.body is not found + for _, value := range cm.Data { + b = value + break + } + return &b, nil + default: + return nil, fmt.Errorf("can't find the key response.body in the referenced configmap %s", body.ValueRef.Name) + } + + } else { + return nil, fmt.Errorf("can't find the referenced configmap %s", body.ValueRef.Name) + } + } else if body.Inline != nil { + return body.Inline, nil + } + + return nil, nil +} + func defaultResponseOverrideRuleName(policy *egv1a1.BackendTrafficPolicy, index int) string { return fmt.Sprintf( "%s/responseoverride/rule/%s", diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index 5e61f2eb3aa..4abc9a69046 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -324,8 +324,8 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute( if strings.HasPrefix(r.Name, prefix) { // return 500 and do not configure EnvoyExtensions in this case if errs != nil { - r.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + r.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } continue } @@ -386,8 +386,8 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway( // return 500 and do not configure EnvoyExtensions in this case if errs != nil { - r.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + r.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } continue } diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index e969b7365fc..68bf84b33c2 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -47,7 +47,7 @@ type HTTPFiltersContext struct { // HTTPFilterIR contains the ir processing results. type HTTPFilterIR struct { - DirectResponse *ir.DirectResponse + DirectResponse *ir.CustomResponse RedirectResponse *ir.Redirect URLRewrite *ir.URLRewrite @@ -785,8 +785,10 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec filterNs := filterContext.Route.GetNamespace() if string(extFilter.Kind) == egv1a1.KindHTTPRouteFilter { + found := false for _, hrf := range resources.HTTPRouteFilters { if hrf.Namespace == filterNs && hrf.Name == string(extFilter.Name) { + found = true if hrf.Spec.URLRewrite != nil { if filterContext.URLRewrite != nil { @@ -846,7 +848,6 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec filterContext.HTTPFilterIR.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{ RegexMatchReplace: rmr, } - return } } else { // no url rewrite filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ @@ -854,7 +855,6 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec RegexMatchReplace: rmr, }, } - return } } } @@ -887,22 +887,49 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec if filterContext.HTTPFilterIR.URLRewrite != nil { if filterContext.HTTPFilterIR.URLRewrite.Host == nil { filterContext.HTTPFilterIR.URLRewrite.Host = hm - return } } else { // no url rewrite filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ Host: hm, } + } + } + + } + + if hrf.Spec.DirectResponse != nil { + dr := &ir.CustomResponse{} + if hrf.Spec.DirectResponse.Body != nil { + var err error + if dr.Body, err = getCustomResponseBody(*hrf.Spec.DirectResponse.Body, resources, filterNs); err != nil { + t.processInvalidHTTPFilter(string(extFilter.Kind), filterContext, err) return } } + if hrf.Spec.DirectResponse.StatusCode != nil { + dr.StatusCode = ptr.To(uint32(*hrf.Spec.DirectResponse.StatusCode)) + } else { + dr.StatusCode = ptr.To(uint32(200)) + } + + if hrf.Spec.DirectResponse.ContentType != nil { + newHeader := ir.AddHeader{ + Name: "Content-Type", + Value: []string{*hrf.Spec.DirectResponse.ContentType}, + } + filterContext.AddResponseHeaders = append(filterContext.AddResponseHeaders, newHeader) + } + + filterContext.HTTPFilterIR.DirectResponse = dr } } } - errMsg := fmt.Sprintf("Unable to translate HTTPRouteFilter: %s/%s", filterNs, - extFilter.Name) - t.processUnresolvedHTTPFilter(errMsg, filterContext) + if !found { + errMsg := fmt.Sprintf("Unable to translate HTTPRouteFilter: %s/%s", filterNs, + extFilter.Name) + t.processUnresolvedHTTPFilter(errMsg, filterContext) + } return } @@ -993,8 +1020,8 @@ func (t *Translator) processUnresolvedHTTPFilter(errMsg string, filterContext *H gwapiv1.RouteReasonUnsupportedValue, errMsg, ) - filterContext.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + filterContext.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } } @@ -1009,8 +1036,8 @@ func (t *Translator) processUnsupportedHTTPFilter(filterType string, filterConte gwapiv1.RouteReasonUnsupportedValue, errMsg, ) - filterContext.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + filterContext.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } } @@ -1025,7 +1052,7 @@ func (t *Translator) processInvalidHTTPFilter(filterType string, filterContext * gwapiv1.RouteReasonUnsupportedValue, errMsg, ) - filterContext.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + filterContext.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } } diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index f82158715a5..648aebaeb5c 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -238,8 +238,8 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe for _, ruleRoute := range ruleRoutes { noValidBackends := ruleRoute.Destination == nil || ruleRoute.Destination.ToBackendWeights().Valid == 0 if noValidBackends && ruleRoute.Redirect == nil { - ruleRoute.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + ruleRoute.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } } ruleRoute.IsHTTP2 = false @@ -570,8 +570,8 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe for _, ruleRoute := range ruleRoutes { noValidBackends := ruleRoute.Destination == nil || ruleRoute.Destination.ToBackendWeights().Valid == 0 if noValidBackends && ruleRoute.Redirect == nil { - ruleRoute.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + ruleRoute.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } } ruleRoute.IsHTTP2 = true diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index c9289bbcfb3..302d5054507 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -406,8 +406,8 @@ func (t *Translator) translateSecurityPolicyForRoute( } if errs != nil { // Return a 500 direct response to avoid unauthorized access - r.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + r.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } } } @@ -514,8 +514,8 @@ func (t *Translator) translateSecurityPolicyForGateway( } if errs != nil { // Return a 500 direct response to avoid unauthorized access - r.DirectResponse = &ir.DirectResponse{ - StatusCode: 500, + r.DirectResponse = &ir.CustomResponse{ + StatusCode: ptr.To(uint32(500)), } } } diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 0f518b71033..23e651b6c69 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -211,7 +211,7 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult, // Process BackendTrafficPolicies backendTrafficPolicies := t.ProcessBackendTrafficPolicies( - resources.BackendTrafficPolicies, gateways, routes, xdsIR, resources.ConfigMaps) + resources, gateways, routes, xdsIR) // Process SecurityPolicies securityPolicies := t.ProcessSecurityPolicies( diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 00edaf21b57..e10a1ec2987 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -532,7 +532,20 @@ type CustomResponse struct { ContentType *string `json:"contentType,omitempty"` // Body of the Custom Response - Body string `json:"body"` + Body *string `json:"body,omitempty"` + + // StatusCode will be used for the response's status code. + StatusCode *uint32 `json:"statusCode,omitempty"` +} + +// Validate the fields within the CustomResponse structure +func (r *CustomResponse) Validate() error { + var errs error + if status := r.StatusCode; status != nil && (*status > 599 || *status < 100) { + errs = errors.Join(errs, ErrDirectResponseStatusInvalid) + } + + return errs } // HealthCheckSettings provides HealthCheck configuration on the HTTP/HTTPS listener. @@ -625,7 +638,7 @@ type HTTPRoute struct { // RemoveResponseHeaders defines a list of headers to be removed from response. RemoveResponseHeaders []string `json:"removeResponseHeaders,omitempty" yaml:"removeResponseHeaders,omitempty"` // Direct responses to be returned for this route. Takes precedence over Destinations and Redirect. - DirectResponse *DirectResponse `json:"directResponse,omitempty" yaml:"directResponse,omitempty"` + DirectResponse *CustomResponse `json:"directResponse,omitempty" yaml:"directResponse,omitempty"` // Redirections to be returned for this route. Takes precedence over Destinations. Redirect *Redirect `json:"redirect,omitempty" yaml:"redirect,omitempty"` // Destination that requests to this HTTPRoute will be mirrored to @@ -1361,23 +1374,6 @@ func (h AddHeader) Validate() error { return errs } -// DirectResponse holds the details for returning a body and status code for a route. -// +k8s:deepcopy-gen=true -type DirectResponse struct { - // StatusCode will be used for the direct response's status code. - StatusCode uint32 `json:"statusCode" yaml:"statusCode"` -} - -// Validate the fields within the DirectResponse structure -func (r DirectResponse) Validate() error { - var errs error - if status := r.StatusCode; status > 599 || status < 100 { - errs = errors.Join(errs, ErrDirectResponseStatusInvalid) - } - - return errs -} - // URLRewrite holds the details for how to rewrite a request // +k8s:deepcopy-gen=true type URLRewrite struct { diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index b4593152593..7a81491417d 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -254,8 +254,8 @@ var ( PathMatch: &StringMatch{ Exact: ptr.To("filter-error"), }, - DirectResponse: &DirectResponse{ - StatusCode: uint32(500), + DirectResponse: &CustomResponse{ + StatusCode: ptr.To(uint32(500)), }, } @@ -296,8 +296,8 @@ var ( PathMatch: &StringMatch{ Exact: ptr.To("redirect"), }, - DirectResponse: &DirectResponse{ - StatusCode: uint32(799), + DirectResponse: &CustomResponse{ + StatusCode: ptr.To(uint32(799)), }, } diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 111f2661377..9054f99854a 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -610,6 +610,16 @@ func (in *CustomResponse) DeepCopyInto(out *CustomResponse) { *out = new(string) **out = **in } + if in.Body != nil { + in, out := &in.Body, &out.Body + *out = new(string) + **out = **in + } + if in.StatusCode != nil { + in, out := &in.StatusCode, &out.StatusCode + *out = new(uint32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResponse. @@ -779,21 +789,6 @@ func (in *DestinationSetting) DeepCopy() *DestinationSetting { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DirectResponse) DeepCopyInto(out *DirectResponse) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DirectResponse. -func (in *DirectResponse) DeepCopy() *DirectResponse { - if in == nil { - return nil - } - out := new(DirectResponse) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvoyExtensionFeatures) DeepCopyInto(out *EnvoyExtensionFeatures) { *out = *in @@ -1464,8 +1459,8 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { } if in.DirectResponse != nil { in, out := &in.DirectResponse, &out.DirectResponse - *out = new(DirectResponse) - **out = **in + *out = new(CustomResponse) + (*in).DeepCopyInto(*out) } if in.Redirect != nil { in, out := &in.Redirect, &out.Redirect diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index ac106a69f7e..d4681406454 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -1660,6 +1660,10 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M return err } + if err := addRouteFilterIndexers(ctx, mgr); err != nil { + return err + } + return nil } diff --git a/internal/provider/kubernetes/filters.go b/internal/provider/kubernetes/filters.go index 109c0314dd0..b322950cced 100644 --- a/internal/provider/kubernetes/filters.go +++ b/internal/provider/kubernetes/filters.go @@ -9,9 +9,13 @@ import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/gatewayapi/resource" + "github.com/envoyproxy/gateway/internal/utils" ) func (r *gatewayAPIReconciler) getExtensionRefFilters(ctx context.Context) ([]unstructured.Unstructured, error) { @@ -54,3 +58,37 @@ func (r *gatewayAPIReconciler) getHTTPRouteFilters(ctx context.Context) ([]egv1a return httpFilterList.Items, nil } + +// processRouteFilterConfigMapRef adds the referenced ConfigMap in a HTTPRouteFilter +// to the resourceTree +func (r *gatewayAPIReconciler) processRouteFilterConfigMapRef( + ctx context.Context, filter *egv1a1.HTTPRouteFilter, + resourceMap *resourceMappings, resourceTree *resource.Resources, +) { + if filter.Spec.DirectResponse != nil && + filter.Spec.DirectResponse.Body.ValueRef != nil && + string(filter.Spec.DirectResponse.Body.ValueRef.Kind) == resource.KindConfigMap { + configMap := new(corev1.ConfigMap) + err := r.client.Get(ctx, + types.NamespacedName{Namespace: filter.Namespace, Name: string(filter.Spec.DirectResponse.Body.ValueRef.Name)}, + configMap) + // we don't return an error here, because we want to continue + // reconciling the rest of the HTTPRouteFilter despite that this + // reference is invalid. + // This HTTPRouteFilter will be marked as invalid in its status + // when translating to IR because the referenced configmap can't be + // found. + if err != nil { + r.log.Error(err, + "failed to process DirectResponse ValueRef for HTTPRouteFilter", + "filter", filter, "ValueRef", filter.Spec.DirectResponse.Body.ValueRef.Name) + } + + resourceMap.allAssociatedNamespaces.Insert(filter.Namespace) + if !resourceMap.allAssociatedConfigMaps.Has(utils.NamespacedName(configMap).String()) { + resourceMap.allAssociatedConfigMaps.Insert(utils.NamespacedName(configMap).String()) + resourceTree.ConfigMaps = append(resourceTree.ConfigMaps, configMap) + r.log.Info("processing ConfigMap", "namespace", filter.Namespace, "name", string(filter.Spec.DirectResponse.Body.ValueRef.Name)) + } + } +} diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index 2ad12069f98..ab3c098961e 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -47,6 +47,7 @@ const ( secretEnvoyExtensionPolicyIndex = "secretEnvoyExtensionPolicyIndex" httpRouteFilterHTTPRouteIndex = "httpRouteFilterHTTPRouteIndex" configMapBtpIndex = "configMapBtpIndex" + configMapHTTPRouteFilterIndex = "configMapHTTPRouteFilterIndex" ) func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error { @@ -672,6 +673,35 @@ func configMapBtpIndexFunc(rawObj client.Object) []string { return configMapReferences } +// addRouteFilterIndexers adds indexing on HTTPRouteFilter, for ConfigMap objects that are +// referenced in HTTPRouteFilter objects. This helps in querying for HTTPRouteFilters that are +// affected by a particular ConfigMap CRUD. +func addRouteFilterIndexers(ctx context.Context, mgr manager.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(ctx, &egv1a1.HTTPRouteFilter{}, + configMapHTTPRouteFilterIndex, configMapRouteFilterIndexFunc); err != nil { + return err + } + return nil +} + +func configMapRouteFilterIndexFunc(rawObj client.Object) []string { + filter := rawObj.(*egv1a1.HTTPRouteFilter) + var configMapReferences []string + if filter.Spec.DirectResponse != nil && + filter.Spec.DirectResponse.Body != nil && + filter.Spec.DirectResponse.Body.ValueRef != nil { + if string(filter.Spec.DirectResponse.Body.ValueRef.Kind) == resource.KindConfigMap { + configMapReferences = append(configMapReferences, + types.NamespacedName{ + Namespace: filter.Namespace, + Name: string(filter.Spec.DirectResponse.Body.ValueRef.Name), + }.String(), + ) + } + } + return configMapReferences +} + // addBtlsIndexers adds indexing on BackendTLSPolicy, for ConfigMap objects that are // referenced in BackendTLSPolicy objects. This helps in querying for BackendTLSPolicies that are // affected by a particular ConfigMap CRUD. diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go index bfdecbd6e24..ae4f63ef3e9 100644 --- a/internal/provider/kubernetes/predicates.go +++ b/internal/provider/kubernetes/predicates.go @@ -632,6 +632,18 @@ func (r *gatewayAPIReconciler) validateConfigMapForReconcile(obj client.Object) return true } + routeFilterList := &egv1a1.HTTPRouteFilterList{} + if err := r.client.List(context.Background(), routeFilterList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(configMapHTTPRouteFilterIndex, utils.NamespacedName(configMap).String()), + }); err != nil { + r.log.Error(err, "unable to find associated HTTPRouteFilter") + return false + } + + if len(routeFilterList.Items) > 0 { + return true + } + return false } diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go index 74bc1312e58..dcc01631f3b 100644 --- a/internal/provider/kubernetes/routes.go +++ b/internal/provider/kubernetes/routes.go @@ -243,9 +243,11 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam if err != nil { return err } + for i := range httpFilters { filter := httpFilters[i] resourceMap.httpRouteFilters[utils.GetNamespacedNameWithGroupKind(&filter)] = &filter + r.processRouteFilterConfigMapRef(ctx, &filter, resourceMap, resourceTree) } extensionRefFilters, err := r.getExtensionRefFilters(ctx) diff --git a/internal/xds/translator/custom_response.go b/internal/xds/translator/custom_response.go index 1d1bf3a5d2c..e5d48d21bfd 100644 --- a/internal/xds/translator/custom_response.go +++ b/internal/xds/translator/custom_response.go @@ -375,12 +375,13 @@ func (c *customResponse) buildStatusCodeCELMatcher(codeRange ir.StatusCodeRange) } func (c *customResponse) buildAction(r ir.ResponseOverrideRule) (*matcherv3.Matcher_OnMatch_Action, error) { - response := &policyv3.LocalResponsePolicy{ - Body: &corev3.DataSource{ + response := &policyv3.LocalResponsePolicy{} + if r.Response.Body != nil && *r.Response.Body != "" { + response.Body = &corev3.DataSource{ Specifier: &corev3.DataSource_InlineString{ - InlineString: r.Response.Body, + InlineString: *r.Response.Body, }, - }, + } } if r.Response.ContentType != nil && *r.Response.ContentType != "" { diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go index e1d790268dc..3750194fb16 100644 --- a/internal/xds/translator/route.go +++ b/internal/xds/translator/route.go @@ -440,8 +440,20 @@ func buildXdsURLRewriteAction(destName string, urlRewrite *ir.URLRewrite, pathMa return routeAction } -func buildXdsDirectResponseAction(res *ir.DirectResponse) *routev3.DirectResponseAction { - routeAction := &routev3.DirectResponseAction{Status: res.StatusCode} +func buildXdsDirectResponseAction(res *ir.CustomResponse) *routev3.DirectResponseAction { + routeAction := &routev3.DirectResponseAction{} + if res.StatusCode != nil { + routeAction.Status = *res.StatusCode + } + + if res.Body != nil && *res.Body != "" { + routeAction.Body = &corev3.DataSource{ + Specifier: &corev3.DataSource_InlineString{ + InlineString: *res.Body, + }, + } + } + return routeAction } diff --git a/internal/xds/translator/testdata/in/xds-ir/accesslog-cel.yaml b/internal/xds/translator/testdata/in/xds-ir/accesslog-cel.yaml index e9cff901d3d..405c2372d91 100644 --- a/internal/xds/translator/testdata/in/xds-ir/accesslog-cel.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/accesslog-cel.yaml @@ -51,6 +51,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/accesslog-endpoint-stats.yaml b/internal/xds/translator/testdata/in/xds-ir/accesslog-endpoint-stats.yaml index b5244667de9..2355c6504cf 100644 --- a/internal/xds/translator/testdata/in/xds-ir/accesslog-endpoint-stats.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/accesslog-endpoint-stats.yaml @@ -47,6 +47,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/accesslog-formatters.yaml b/internal/xds/translator/testdata/in/xds-ir/accesslog-formatters.yaml index 7c1024879d8..e4e088d349b 100644 --- a/internal/xds/translator/testdata/in/xds-ir/accesslog-formatters.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/accesslog-formatters.yaml @@ -55,6 +55,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/accesslog-multi-cel.yaml b/internal/xds/translator/testdata/in/xds-ir/accesslog-multi-cel.yaml index fab193fe564..95b4971cd39 100644 --- a/internal/xds/translator/testdata/in/xds-ir/accesslog-multi-cel.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/accesslog-multi-cel.yaml @@ -55,6 +55,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/accesslog-types.yaml b/internal/xds/translator/testdata/in/xds-ir/accesslog-types.yaml index d2458abfce9..b42a839f018 100644 --- a/internal/xds/translator/testdata/in/xds-ir/accesslog-types.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/accesslog-types.yaml @@ -179,6 +179,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/accesslog-without-format.yaml b/internal/xds/translator/testdata/in/xds-ir/accesslog-without-format.yaml index 40aef558e3e..90e9f0e0c9b 100644 --- a/internal/xds/translator/testdata/in/xds-ir/accesslog-without-format.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/accesslog-without-format.yaml @@ -59,6 +59,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/accesslog.yaml b/internal/xds/translator/testdata/in/xds-ir/accesslog.yaml index 26f0f5663f8..5169bae040e 100644 --- a/internal/xds/translator/testdata/in/xds-ir/accesslog.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/accesslog.yaml @@ -68,6 +68,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/tracing-datadog.yaml b/internal/xds/translator/testdata/in/xds-ir/tracing-datadog.yaml index 1cc60f85e0e..1ed5b3aef3c 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tracing-datadog.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tracing-datadog.yaml @@ -44,6 +44,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/tracing-endpoint-stats.yaml b/internal/xds/translator/testdata/in/xds-ir/tracing-endpoint-stats.yaml index b5ee8b57dd9..1d8c4b7a338 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tracing-endpoint-stats.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tracing-endpoint-stats.yaml @@ -49,6 +49,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/tracing-zipkin.yaml b/internal/xds/translator/testdata/in/xds-ir/tracing-zipkin.yaml index 9b4e57fd74f..dded17dd193 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tracing-zipkin.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tracing-zipkin.yaml @@ -50,6 +50,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/in/xds-ir/tracing.yaml b/internal/xds/translator/testdata/in/xds-ir/tracing.yaml index 2bd8aff1b7d..b5cccf6dbab 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tracing.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tracing.yaml @@ -68,6 +68,3 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 - directResponse: - body: "Unknown custom filter type: UnsupportedType" - statusCode: 500 diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-cel.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-cel.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/accesslog-cel.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-cel.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-endpoint-stats.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-endpoint-stats.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/accesslog-endpoint-stats.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-endpoint-stats.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-multi-cel.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-multi-cel.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/accesslog-multi-cel.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-multi-cel.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-types.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-types.routes.yaml index ff2210f8d50..084eabe4105 100644 --- a/internal/xds/translator/testdata/out/xds-ir/accesslog-types.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-types.routes.yaml @@ -13,8 +13,10 @@ sectionName: http name: envoy-gateway/gateway-1/http/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-without-format.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-without-format.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/accesslog-without-format.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-without-format.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/accesslog.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/accesslog.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-direct-response.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-direct-response.routes.yaml index b214e8b05a3..d4a7fa5ae20 100644 --- a/internal/xds/translator/testdata/out/xds-ir/http-route-direct-response.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-direct-response.routes.yaml @@ -6,6 +6,8 @@ name: first-listener/* routes: - directResponse: + body: + inlineString: 'Unknown custom filter type: UnsupportedType' status: 500 match: prefix: / diff --git a/internal/xds/translator/testdata/out/xds-ir/tracing-datadog.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tracing-datadog.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tracing-datadog.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tracing-datadog.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/tracing-endpoint-stats.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tracing-endpoint-stats.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tracing-endpoint-stats.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tracing-endpoint-stats.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/tracing-zipkin.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tracing-zipkin.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tracing-zipkin.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tracing-zipkin.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/tracing.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tracing.routes.yaml index b214e8b05a3..ea343799ac1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tracing.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tracing.routes.yaml @@ -5,8 +5,10 @@ - '*' name: first-listener/* routes: - - directResponse: - status: 500 - match: + - match: prefix: / name: direct-route + route: + cluster: direct-route-dest + upgradeConfigs: + - upgradeType: websocket