Skip to content

Commit

Permalink
feat: direct response (envoyproxy#4508)
Browse files Browse the repository at this point in the history
* feat: direct response

Relates to envoyproxy#2714

Signed-off-by: Arko Dasgupta <[email protected]>

* provider logic

Signed-off-by: Arko Dasgupta <[email protected]>

* default status code is 200

Signed-off-by: Arko Dasgupta <[email protected]>

---------

Signed-off-by: Arko Dasgupta <[email protected]>
  • Loading branch information
arkodg authored and rudrakhp committed Oct 24, 2024
1 parent 4f0266c commit 8323ebe
Show file tree
Hide file tree
Showing 46 changed files with 340 additions and 187 deletions.
16 changes: 14 additions & 2 deletions api/v1alpha1/clienttrafficpolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,26 @@ type ClientIPDetectionSettings struct {
}

// XForwardedForSettings provides configuration for using X-Forwarded-For headers for determining the client IP address.
// Refer to https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for
// for more details.
// +kubebuilder:validation:XValidation:rule="(has(self.numTrustedHops) && !has(self.trustedCIDRs)) || (!has(self.numTrustedHops) && has(self.trustedCIDRs))", message="either numTrustedHops or trustedCIDRs must be set"
type XForwardedForSettings struct {
// NumTrustedHops controls the number of additional ingress proxy hops from the right side of XFF HTTP
// headers to trust when determining the origin client's IP address.
// Refer to https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for
// for more details.
// Only one of NumTrustedHops and TrustedCIDRs must be set.
//
// +optional
NumTrustedHops *uint32 `json:"numTrustedHops,omitempty"`

// TrustedCIDRs is a list of trusted CIDRs to trust when
// evaluating the remote IP address to determine the original client's IP address.
// Only one of NumTrustedHops and TrustedCIDRs must be set.
//
// +optional
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:ItemsFormat=cidr
// +notImplementedHide
TrustedCIDRs []string `json:"trustedCIDRs,omitempty"`
}

// CustomHeaderExtensionSettings provides configuration for determining the client IP address for a request based on
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/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 @@ -85,11 +85,23 @@ spec:
description: |-
NumTrustedHops controls the number of additional ingress proxy hops from the right side of XFF HTTP
headers to trust when determining the origin client's IP address.
Refer to https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for
for more details.
Only one of NumTrustedHops and TrustedCIDRs must be set.
format: int32
type: integer
trustedCIDRs:
description: |-
TrustedCIDRs is a list of trusted CIDRs to trust when
evaluating the remote IP address to determine the original client's IP address.
Only one of NumTrustedHops and TrustedCIDRs must be set.
items:
type: string
minItems: 1
type: array
type: object
x-kubernetes-validations:
- message: either numTrustedHops or trustedCIDRs must be set
rule: (has(self.numTrustedHops) && !has(self.trustedCIDRs))
|| (!has(self.numTrustedHops) && has(self.trustedCIDRs))
type: object
x-kubernetes-validations:
- message: customHeader cannot be used in conjunction with xForwardedFor
Expand Down
87 changes: 46 additions & 41 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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))
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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{
Expand All @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions internal/gatewayapi/envoyextensionpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
53 changes: 40 additions & 13 deletions internal/gatewayapi/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -846,15 +848,13 @@ 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{
Path: &ir.ExtendedHTTPPathModifier{
RegexMatchReplace: rmr,
},
}
return
}
}
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)),
}
}

Expand All @@ -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)),
}
}

Expand All @@ -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)),
}
}
Loading

0 comments on commit 8323ebe

Please sign in to comment.