diff --git a/VERSION b/VERSION index 503f4b1293..d1f79a9413 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.2.7 +v1.2.8 diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go index 1e57786d0a..9ef305acb0 100644 --- a/api/v1alpha1/shared_types.go +++ b/api/v1alpha1/shared_types.go @@ -22,7 +22,7 @@ const ( // DefaultDeploymentMemoryResourceRequests for deployment memory resource DefaultDeploymentMemoryResourceRequests = "512Mi" // DefaultEnvoyProxyImage is the default image used by envoyproxy - DefaultEnvoyProxyImage = "docker.io/envoyproxy/envoy:distroless-v1.32.3" + DefaultEnvoyProxyImage = "docker.io/envoyproxy/envoy:distroless-v1.32.4" // DefaultShutdownManagerCPUResourceRequests for shutdown manager cpu resource DefaultShutdownManagerCPUResourceRequests = "10m" // DefaultShutdownManagerMemoryResourceRequests for shutdown manager memory resource @@ -30,7 +30,7 @@ const ( // DefaultShutdownManagerImage is the default image used for the shutdown manager. DefaultShutdownManagerImage = "docker.io/envoyproxy/gateway-dev:latest" // DefaultRateLimitImage is the default image used by ratelimit. - DefaultRateLimitImage = "docker.io/envoyproxy/ratelimit:ae4cee11" + DefaultRateLimitImage = "docker.io/envoyproxy/ratelimit:0141a24f" // HTTPProtocol is the common-used http protocol. HTTPProtocol = "http" // GRPCProtocol is the common-used grpc protocol. diff --git a/charts/gateway-helm/README.md b/charts/gateway-helm/README.md index b49a6ba7e9..11ed2a80a7 100644 --- a/charts/gateway-helm/README.md +++ b/charts/gateway-helm/README.md @@ -102,7 +102,7 @@ To uninstall the chart: | global.images.envoyGateway.image | string | `nil` | | | global.images.envoyGateway.pullPolicy | string | `nil` | | | global.images.envoyGateway.pullSecrets | list | `[]` | | -| global.images.ratelimit.image | string | `"docker.io/envoyproxy/ratelimit:ae4cee11"` | | +| global.images.ratelimit.image | string | `"docker.io/envoyproxy/ratelimit:0141a24f"` | | | global.images.ratelimit.pullPolicy | string | `"IfNotPresent"` | | | global.images.ratelimit.pullSecrets | list | `[]` | | | kubernetesClusterDomain | string | `"cluster.local"` | | diff --git a/charts/gateway-helm/values.tmpl.yaml b/charts/gateway-helm/values.tmpl.yaml index a9d3c88df5..896c711e59 100644 --- a/charts/gateway-helm/values.tmpl.yaml +++ b/charts/gateway-helm/values.tmpl.yaml @@ -12,7 +12,7 @@ global: pullSecrets: [] ratelimit: # This is the full image name including the hub, repo, and tag. - image: "docker.io/envoyproxy/ratelimit:ae4cee11" + image: "docker.io/envoyproxy/ratelimit:0141a24f" # Specify image pull policy if default behavior isn't desired. # Default behavior: latest images will be Always else IfNotPresent. pullPolicy: IfNotPresent diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod index 2ae1badd04..b0a14dfcb9 100644 --- a/examples/extension-server/go.mod +++ b/examples/extension-server/go.mod @@ -16,7 +16,7 @@ require ( require ( cel.dev/expr v0.16.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum index 960a9ee8a0..0895c80387 100644 --- a/examples/extension-server/go.sum +++ b/examples/extension-server/go.sum @@ -2,8 +2,8 @@ cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y= cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/go.mod b/go.mod index 103a5e7ff3..5914bb1efb 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,10 @@ replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 require ( fortio.org/fortio v1.67.1 fortio.org/log v1.17.1 - github.com/Masterminds/semver/v3 v3.3.0 - github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 + github.com/Masterminds/semver/v3 v3.3.1 + github.com/avast/retry-go v3.0.0+incompatible + github.com/cenkalti/backoff/v4 v4.3.0 + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/docker/cli v27.3.1+incompatible github.com/dominikbraun/graph v0.23.0 @@ -223,7 +225,6 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.3 // indirect diff --git a/go.sum b/go.sum index 12cc6d6855..fce2b5e016 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= @@ -97,6 +97,8 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -123,8 +125,8 @@ github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= diff --git a/internal/cmd/egctl/testdata/translate/in/backend-endpoint.yaml b/internal/cmd/egctl/testdata/translate/in/backend-endpoint.yaml index d2aa0f78f0..f845c0941b 100644 --- a/internal/cmd/egctl/testdata/translate/in/backend-endpoint.yaml +++ b/internal/cmd/egctl/testdata/translate/in/backend-endpoint.yaml @@ -44,3 +44,34 @@ spec: - ip: address: 0.0.0.0 port: 3000 +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: example-com-tls-policy +spec: + targetRefs: + - group: 'gateway.envoyproxy.io' + kind: Backend + name: backend + validation: + wellKnownCACertificates: "System" + hostname: www.example.com +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyExtensionPolicy +metadata: + name: ext-proc-example +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + extProc: + - backendRefs: + - name: grpc-ext-proc + port: 9002 + processingMode: + request: {} + response: + body: Streamed diff --git a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml index d3f3ed2c77..4ced5040f4 100644 --- a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml @@ -1,3 +1,28 @@ +backendTLSPolicies: +- kind: BackendTLSPolicy + metadata: + creationTimestamp: null + name: example-com-tls-policy + namespace: envoy-gateway-system + spec: + targetRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend + validation: + hostname: www.example.com + wellKnownCACertificates: System + status: + ancestors: + - ancestorRef: + name: eg + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller backends: - kind: Backend metadata: @@ -16,6 +41,41 @@ backends: reason: Accepted status: "True" type: Accepted +envoyExtensionPolicies: +- kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: ext-proc-example + namespace: envoy-gateway-system + spec: + extProc: + - backendRefs: + - name: grpc-ext-proc + port: 9002 + processingMode: + request: {} + response: + body: Streamed + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: eg + namespace: envoy-gateway-system + conditions: + - lastTransitionTime: null + message: |- + Wasm: wasm cache is not initialized + ExtProc: service envoy-gateway-system/grpc-ext-proc not found. + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller gatewayClass: kind: GatewayClass metadata: diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index 68bf84b33c..f8a8c4cd9f 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -188,15 +188,9 @@ func (t *Translator) processURLRewriteFilter( if filterContext.URLRewrite != nil { if hasMultipleCoreRewrites(rewrite, filterContext.URLRewrite) || hasConflictingCoreAndExtensionRewrites(rewrite, filterContext.URLRewrite) { - 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", - ) + updateRouteStatusForFilter( + filterContext, + "Cannot configure multiple urlRewrite filters for a single HTTPRouteRule") return } } @@ -209,15 +203,7 @@ func (t *Translator) processURLRewriteFilter( if rewrite.Hostname != nil { if err := t.validateHostname(string(*rewrite.Hostname)); err != nil { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - err.Error(), - ) + updateRouteStatusForFilter(filterContext, err.Error()) return } redirectHost := string(*rewrite.Hostname) @@ -230,29 +216,15 @@ func (t *Translator) processURLRewriteFilter( switch rewrite.Path.Type { case gwapiv1.FullPathHTTPPathModifier: if rewrite.Path.ReplacePrefixMatch != nil { - errMsg := "ReplacePrefixMatch cannot be set when rewrite path type is \"ReplaceFullPath\"" - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + "ReplacePrefixMatch cannot be set when rewrite path type is \"ReplaceFullPath\"") return } if rewrite.Path.ReplaceFullPath == nil { - errMsg := "ReplaceFullPath must be set when rewrite path type is \"ReplaceFullPath\"" - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + "ReplaceFullPath must be set when rewrite path type is \"ReplaceFullPath\"") return } if rewrite.Path.ReplaceFullPath != nil { @@ -264,29 +236,15 @@ func (t *Translator) processURLRewriteFilter( } case gwapiv1.PrefixMatchHTTPPathModifier: if rewrite.Path.ReplaceFullPath != nil { - errMsg := "ReplaceFullPath cannot be set when rewrite path type is \"ReplacePrefixMatch\"" - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + "ReplaceFullPath cannot be set when rewrite path type is \"ReplacePrefixMatch\"") return } if rewrite.Path.ReplacePrefixMatch == nil { - errMsg := "ReplacePrefixMatch must be set when rewrite path type is \"ReplacePrefixMatch\"" - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + "ReplacePrefixMatch must be set when rewrite path type is \"ReplacePrefixMatch\"") return } if rewrite.Path.ReplacePrefixMatch != nil { @@ -297,16 +255,11 @@ func (t *Translator) processURLRewriteFilter( } } default: - errMsg := fmt.Sprintf("Rewrite path type: %s is invalid, only \"ReplaceFullPath\" and \"ReplacePrefixMatch\" are supported", rewrite.Path.Type) - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Rewrite path type: %s is invalid, only \"ReplaceFullPath\" and \"ReplacePrefixMatch\" are supported", + rewrite.Path.Type)) return } } @@ -320,15 +273,9 @@ func (t *Translator) processRedirectFilter( ) { // Can't have two redirects for the same route if filterContext.RedirectResponse != nil { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "Cannot configure multiple requestRedirect filters for a single HTTPRouteRule", - ) + updateRouteStatusForFilter( + filterContext, + "Cannot configure multiple requestRedirect filters for a single HTTPRouteRule") return } @@ -343,31 +290,16 @@ func (t *Translator) processRedirectFilter( if *redirect.Scheme == "http" || *redirect.Scheme == "https" { redir.Scheme = redirect.Scheme } else { - errMsg := fmt.Sprintf("Scheme: %s is unsupported, only 'https' and 'http' are supported", *redirect.Scheme) - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf("Scheme: %s is unsupported, only 'https' and 'http' are supported", *redirect.Scheme)) return } } if redirect.Hostname != nil { if err := t.validateHostname(string(*redirect.Hostname)); err != nil { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - err.Error(), - ) + updateRouteStatusForFilter(filterContext, err.Error()) } else { redirectHost := string(*redirect.Hostname) redir.Hostname = &redirectHost @@ -389,16 +321,11 @@ func (t *Translator) processRedirectFilter( } } default: - errMsg := fmt.Sprintf("Redirect path type: %s is invalid, only \"ReplaceFullPath\" and \"ReplacePrefixMatch\" are supported", redirect.Path.Type) - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Redirect path type: %s is invalid, only \"ReplaceFullPath\" and \"ReplacePrefixMatch\" are supported", + redirect.Path.Type)) return } } @@ -410,15 +337,7 @@ func (t *Translator) processRedirectFilter( redir.StatusCode = &redirectCode } else { errMsg := fmt.Sprintf("Status code %d is invalid, only 302 and 301 are supported", redirectCode) - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter(filterContext, errMsg) return } } @@ -449,28 +368,19 @@ func (t *Translator) processRequestHeaderModifierFilter( for _, addHeader := range headersToAdd { emptyFilterConfig = false if addHeader.Name == "" { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "RequestHeaderModifier Filter cannot add a header with an empty name", - ) + updateRouteStatusForFilter( + filterContext, + "RequestHeaderModifier Filter cannot add a header with an empty name") // try to process the rest of the headers and produce a valid config. continue } - // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names - if strings.Contains(string(addHeader.Name), "/") || strings.Contains(string(addHeader.Name), ":") { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - fmt.Sprintf("RequestHeaderModifier Filter cannot set headers with a '/' or ':' character in them. Header: %q", string(addHeader.Name)), + if !isModifiableHeader(string(addHeader.Name)) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. The RequestHeaderModifier filter cannot set the Host header or headers with a '/' "+ + "or ':' character in them. To modify the Host header use the URLRewrite or the HTTPRouteFilter filter.", + string(addHeader.Name)), ) continue } @@ -506,27 +416,19 @@ func (t *Translator) processRequestHeaderModifierFilter( for _, setHeader := range headersToSet { if setHeader.Name == "" { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "RequestHeaderModifier Filter cannot set a header with an empty name", - ) + updateRouteStatusForFilter( + filterContext, + "RequestHeaderModifier Filter cannot set a header with an empty name") continue } - // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names - if strings.Contains(string(setHeader.Name), "/") || strings.Contains(string(setHeader.Name), ":") { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - fmt.Sprintf("RequestHeaderModifier Filter cannot set headers with a '/' or ':' character in them. Header: '%s'", string(setHeader.Name)), + + if !isModifiableHeader(string(setHeader.Name)) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. The RequestHeaderModifier filter cannot set the Host header or headers with a '/' "+ + "or ':' character in them. To modify the Host header use the URLRewrite or the HTTPRouteFilter filter.", + string(setHeader.Name)), ) continue } @@ -562,14 +464,19 @@ func (t *Translator) processRequestHeaderModifierFilter( } for _, removedHeader := range headersToRemove { if removedHeader == "" { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "RequestHeaderModifier Filter cannot remove a header with an empty name", + updateRouteStatusForFilter( + filterContext, + "RequestHeaderModifier Filter cannot remove a header with an empty name") + continue + } + + if !isModifiableHeader(removedHeader) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. The RequestHeaderModifier filter cannot remove the Host header or headers with a '/' "+ + "or ':' character in them.", + removedHeader), ) continue } @@ -591,18 +498,30 @@ func (t *Translator) processRequestHeaderModifierFilter( // Update the status if the filter failed to configure any valid headers to add/remove if len(filterContext.AddRequestHeaders) == 0 && len(filterContext.RemoveRequestHeaders) == 0 && !emptyFilterConfig { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "RequestHeaderModifier Filter did not provide valid configuration to add/set/remove any headers", - ) + updateRouteStatusForFilter( + filterContext, + "RequestHeaderModifier Filter did not provide valid configuration to add/set/remove any headers") } } +func updateRouteStatusForFilter(filterContext *HTTPFiltersContext, message string) { + routeStatus := GetRouteStatus(filterContext.Route) + status.SetRouteStatusCondition(routeStatus, + filterContext.ParentRef.routeParentStatusIdx, + filterContext.Route.GetGeneration(), + gwapiv1.RouteConditionAccepted, + metav1.ConditionFalse, + gwapiv1.RouteReasonUnsupportedValue, + message, + ) +} + +func isModifiableHeader(headerName string) bool { + // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names + // And Envoy does not allow modification the pseudo headers and the host header + return !strings.Contains(headerName, "/") && !strings.Contains(headerName, ":") && !strings.EqualFold(headerName, "host") +} + func (t *Translator) processResponseHeaderModifierFilter( headerModifier *gwapiv1.HTTPHeaderFilter, filterContext *HTTPFiltersContext, @@ -621,29 +540,19 @@ func (t *Translator) processResponseHeaderModifierFilter( for _, addHeader := range headersToAdd { emptyFilterConfig = false if addHeader.Name == "" { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "ResponseHeaderModifier Filter cannot add a header with an empty name", - ) + updateRouteStatusForFilter( + filterContext, + "ResponseHeaderModifier Filter cannot add a header with an empty name") // try to process the rest of the headers and produce a valid config. continue } - // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names - if strings.Contains(string(addHeader.Name), "/") || strings.Contains(string(addHeader.Name), ":") { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - fmt.Sprintf("ResponseHeaderModifier Filter cannot set headers with a '/' or ':' character in them. Header: %q", string(addHeader.Name)), - ) + if !isModifiableHeader(string(addHeader.Name)) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. The ResponseHeaderModifier filter cannot set the Host header or headers with a '/' "+ + "or ':' character in them.", + string(addHeader.Name))) continue } // Check if the header is a duplicate @@ -678,28 +587,19 @@ func (t *Translator) processResponseHeaderModifierFilter( for _, setHeader := range headersToSet { if setHeader.Name == "" { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "ResponseHeaderModifier Filter cannot set a header with an empty name", - ) + updateRouteStatusForFilter( + filterContext, + "ResponseHeaderModifier Filter cannot set a header with an empty name") continue } - // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names - if strings.Contains(string(setHeader.Name), "/") || strings.Contains(string(setHeader.Name), ":") { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - fmt.Sprintf("ResponseHeaderModifier Filter cannot set headers with a '/' or ':' character in them. Header: '%s'", string(setHeader.Name)), - ) + + if !isModifiableHeader(string(setHeader.Name)) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. The ResponseHeaderModifier filter cannot set the Host header or headers with a '/' "+ + "or ':' character in them.", + string(setHeader.Name))) continue } @@ -734,15 +634,18 @@ func (t *Translator) processResponseHeaderModifierFilter( } for _, removedHeader := range headersToRemove { if removedHeader == "" { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "ResponseHeaderModifier Filter cannot remove a header with an empty name", - ) + updateRouteStatusForFilter( + filterContext, + "ResponseHeaderModifier Filter cannot remove a header with an empty name") + continue + } + if !isModifiableHeader(removedHeader) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. The ResponseHeaderModifier filter cannot remove the Host header or headers with a '/' "+ + "or ':' character in them.", + removedHeader)) continue } @@ -764,15 +667,9 @@ func (t *Translator) processResponseHeaderModifierFilter( // Update the status if the filter failed to configure any valid headers to add/remove if len(filterContext.AddResponseHeaders) == 0 && len(filterContext.RemoveResponseHeaders) == 0 && !emptyFilterConfig { - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - "ResponseHeaderModifier Filter did not provide valid configuration to add/set/remove any headers", - ) + updateRouteStatusForFilter( + filterContext, + "ResponseHeaderModifier Filter did not provide valid configuration to add/set/remove any headers") } } @@ -794,15 +691,9 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec if filterContext.URLRewrite != nil { if hasMultipleExtensionRewrites(hrf.Spec.URLRewrite, filterContext.URLRewrite) || hasConflictingExtensionAndCoreRewrites(hrf.Spec.URLRewrite, filterContext.URLRewrite) { - 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", - ) + updateRouteStatusForFilter( + filterContext, + "Cannot configure multiple urlRewrite filters for a single HTTPRouteRule") return } } @@ -811,30 +702,16 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec if hrf.Spec.URLRewrite.Path.Type == egv1a1.RegexHTTPPathModifier { if hrf.Spec.URLRewrite.Path.ReplaceRegexMatch == nil || hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern == "" { - errMsg := "ReplaceRegexMatch Pattern 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, - ) + updateRouteStatusForFilter( + filterContext, + "ReplaceRegexMatch Pattern must be set when rewrite path type is \"ReplaceRegexMatch\"") 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, - ) + updateRouteStatusForFilter( + filterContext, + "ReplaceRegexMatch must be a valid RE2 regular expression") return } @@ -863,16 +740,9 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec var hm *ir.HTTPHostModifier if hrf.Spec.URLRewrite.Hostname.Type == egv1a1.HeaderHTTPHostnameModifier { if hrf.Spec.URLRewrite.Hostname.Header == nil { - errMsg := "Header must be set when rewrite path type is \"Header\"" - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + "Header must be set when rewrite path type is \"Header\"") return } hm = &ir.HTTPHostModifier{ @@ -1012,14 +882,7 @@ func (t *Translator) processUnresolvedHTTPFilter(errMsg string, filterContext *H gwapiv1.RouteReasonBackendNotFound, errMsg, ) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter(filterContext, errMsg) filterContext.DirectResponse = &ir.CustomResponse{ StatusCode: ptr.To(uint32(500)), } @@ -1027,31 +890,16 @@ func (t *Translator) processUnresolvedHTTPFilter(errMsg string, filterContext *H func (t *Translator) processUnsupportedHTTPFilter(filterType string, filterContext *HTTPFiltersContext) { errMsg := fmt.Sprintf("Unsupported filter type: %s", filterType) - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter(filterContext, errMsg) filterContext.DirectResponse = &ir.CustomResponse{ StatusCode: ptr.To(uint32(500)), } } func (t *Translator) processInvalidHTTPFilter(filterType string, filterContext *HTTPFiltersContext, err error) { - errMsg := fmt.Sprintf("Invalid filter %s: %v", filterType, err) - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf("Invalid filter %s: %v", filterType, err)) filterContext.DirectResponse = &ir.CustomResponse{ StatusCode: ptr.To(uint32(500)), } diff --git a/internal/gatewayapi/resource/load.go b/internal/gatewayapi/resource/load.go index 78f84d16d8..755c17edba 100644 --- a/internal/gatewayapi/resource/load.go +++ b/internal/gatewayapi/resource/load.go @@ -20,6 +20,7 @@ import ( utilyaml "k8s.io/apimachinery/pkg/util/yaml" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapiv1a3 "sigs.k8s.io/gateway-api/apis/v1alpha3" "sigs.k8s.io/yaml" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -44,10 +45,7 @@ func LoadResourcesFromYAMLBytes(yamlBytes []byte, addMissingResources bool) (*Re // loadKubernetesYAMLToResources converts a Kubernetes YAML string into GatewayAPI Resources. // TODO: add support for kind: -// - EnvoyExtensionPolicy (gateway.envoyproxy.io/v1alpha1) -// - HTTPRouteFilter (gateway.envoyproxy.io/v1alpha1) // - BackendLPPolicy (gateway.networking.k8s.io/v1alpha2) -// - BackendTLSPolicy (gateway.networking.k8s.io/v1alpha3) // - ReferenceGrant (gateway.networking.k8s.io/v1alpha2) // - TLSRoute (gateway.networking.k8s.io/v1alpha2) func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Resources, error) { @@ -337,6 +335,32 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res Data: typedData.(map[string]string), } resources.ConfigMaps = append(resources.ConfigMaps, configMap) + case KindBackendTLSPolicy: + typedSpec := spec.Interface() + backendTLSPolicy := &gwapiv1a3.BackendTLSPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: KindBackendTLSPolicy, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: typedSpec.(gwapiv1a3.BackendTLSPolicySpec), + } + resources.BackendTLSPolicies = append(resources.BackendTLSPolicies, backendTLSPolicy) + case KindEnvoyExtensionPolicy: + typedSpec := spec.Interface() + envoyExtensionPolicy := &egv1a1.EnvoyExtensionPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: KindEnvoyExtensionPolicy, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: typedSpec.(egv1a1.EnvoyExtensionPolicySpec), + } + resources.EnvoyExtensionPolicies = append(resources.EnvoyExtensionPolicies, envoyExtensionPolicy) } return nil diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-headers.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-headers.in.yaml index aaf5bad87f..869f9122c2 100644 --- a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-headers.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-headers.in.yaml @@ -30,7 +30,7 @@ httpRoutes: rules: - matches: - path: - value: "/" + value: "/foo" backendRefs: - name: service-1 port: 8080 @@ -42,7 +42,57 @@ httpRoutes: value: "some-value" - name: "good-header" value: "some-value" - add: - - name: "example/2" +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/bar" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + remove: + - "example/2" + set: + - name: "good-header" + value: "some-value" +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-3 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/baz" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + set: + - name: "host" + value: "example.com" + - name: "good-header" value: "some-value" - diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-headers.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-headers.out.yaml index 339e6a1f41..b1428fee39 100644 --- a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-headers.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-headers.out.yaml @@ -17,7 +17,7 @@ gateways: protocol: HTTP status: listeners: - - attachedRoutes: 1 + - attachedRoutes: 3 conditions: - lastTransitionTime: null message: Sending translated listener configuration to the data plane @@ -60,9 +60,6 @@ httpRoutes: port: 8080 filters: - requestHeaderModifier: - add: - - name: example/2 - value: some-value set: - name: example:1 value: some-value @@ -71,13 +68,109 @@ httpRoutes: type: RequestHeaderModifier matches: - path: - value: / + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example:1". The RequestHeaderModifier filter cannot set + the Host header or headers with a ''/'' or '':'' character in them. To modify + the Host header use the URLRewrite or the HTTPRouteFilter filter.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - requestHeaderModifier: + remove: + - example/2 + set: + - name: good-header + value: some-value + type: RequestHeaderModifier + matches: + - path: + value: /bar + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example/2". The RequestHeaderModifier filter cannot remove + the Host header or headers with a ''/'' or '':'' character in them.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-3 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - requestHeaderModifier: + set: + - name: host + value: example.com + - name: good-header + value: some-value + type: RequestHeaderModifier + matches: + - path: + value: /baz status: parents: - conditions: - lastTransitionTime: null - message: 'RequestHeaderModifier Filter cannot set headers with a ''/'' or - '':'' character in them. Header: ''example:1''' + message: 'Header: "host". The RequestHeaderModifier filter cannot set the + Host header or headers with a ''/'' or '':'' character in them. To modify + the Host header use the URLRewrite or the HTTPRouteFilter filter.' reason: UnsupportedValue status: "False" type: Accepted diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-headers.in.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-headers.in.yaml index 6e1e57425b..95da714409 100644 --- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-headers.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-headers.in.yaml @@ -30,7 +30,7 @@ httpRoutes: rules: - matches: - path: - value: "/" + value: "/foo" backendRefs: - name: service-1 port: 8080 @@ -42,7 +42,57 @@ httpRoutes: value: "some-value" - name: "good-header" value: "some-value" - add: - - name: "example/2" +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/bar" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + remove: + - "example/2" + set: + - name: "good-header" + value: "some-value" +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-3 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/baz" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: "host" + value: "example.com" + - name: "good-header" value: "some-value" - diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-headers.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-headers.out.yaml index 077e6e866e..3fee996d45 100644 --- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-headers.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-headers.out.yaml @@ -17,7 +17,7 @@ gateways: protocol: HTTP status: listeners: - - attachedRoutes: 1 + - attachedRoutes: 3 conditions: - lastTransitionTime: null message: Sending translated listener configuration to the data plane @@ -60,9 +60,6 @@ httpRoutes: port: 8080 filters: - responseHeaderModifier: - add: - - name: example/2 - value: some-value set: - name: example:1 value: some-value @@ -71,13 +68,107 @@ httpRoutes: type: ResponseHeaderModifier matches: - path: - value: / + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example:1". The ResponseHeaderModifier filter cannot set + the Host header or headers with a ''/'' or '':'' character in them.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - responseHeaderModifier: + remove: + - example/2 + set: + - name: good-header + value: some-value + type: ResponseHeaderModifier + matches: + - path: + value: /bar + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example/2". The ResponseHeaderModifier filter cannot remove + the Host header or headers with a ''/'' or '':'' character in them.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-3 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - responseHeaderModifier: + set: + - name: host + value: example.com + - name: good-header + value: some-value + type: ResponseHeaderModifier + matches: + - path: + value: /baz status: parents: - conditions: - lastTransitionTime: null - message: 'ResponseHeaderModifier Filter cannot set headers with a ''/'' or - '':'' character in them. Header: ''example:1''' + message: 'Header: "host". The ResponseHeaderModifier filter cannot set the + Host header or headers with a ''/'' or '':'' character in them.' reason: UnsupportedValue status: "False" type: Accepted diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml index 33f61c29ac..8d7a0f29fe 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml @@ -56,7 +56,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml index aa3d4d2dea..dca73a69bf 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml @@ -227,7 +227,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml index 27f1cfc340..efe53616ad 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml @@ -198,7 +198,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml index b9dfe81c4d..582c9b1003 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml @@ -236,7 +236,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml index 31c087d6b8..c197762b9a 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml @@ -227,7 +227,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml index 7cd8c3672e..06027c7d68 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml @@ -227,7 +227,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml index 64fdb9cd76..a2140535cb 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml @@ -232,7 +232,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml index aa832e1938..f89a85ecb6 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml @@ -56,7 +56,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml index 0e7438971b..3a8a914dd7 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml @@ -229,7 +229,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml index 59a396661c..a4d5e375d1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml @@ -227,7 +227,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml index c6c2e95bc2..9e1eea6084 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml @@ -227,7 +227,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml index f571a656e2..e9b362a24a 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml @@ -227,7 +227,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml index 6669ca1c2a..981c708ae1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml @@ -227,7 +227,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml index 8a48e16335..02ebacb535 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml @@ -59,7 +59,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml index 9080ce0b8b..4b53d5ad4e 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml @@ -60,7 +60,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml index 5344242530..2cb26dd6d8 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml @@ -231,7 +231,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml index 41ec060e1c..13b681fe9d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml @@ -202,7 +202,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml index 172348b987..1b5babaab6 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml @@ -232,7 +232,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml index a819b7f591..b6acb282d2 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml @@ -232,7 +232,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml index 9016b8e261..f6dea0d39f 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml @@ -240,7 +240,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml index 5a2e6b3e2b..55b2f4ead3 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml @@ -231,7 +231,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml index 659dcbb60f..8f8ebcd8ae 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml @@ -231,7 +231,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml index d54be4c52e..ba8a9747a7 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml @@ -236,7 +236,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml index 946f572e97..441533d25d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml @@ -60,7 +60,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml index 44d76b56d6..73c365cff6 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml @@ -231,7 +231,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml index c48ce3a768..2fd6becd69 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml @@ -233,7 +233,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml index db91ba2dcc..ec8e1ead89 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml @@ -231,7 +231,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml index 493dcf36d1..29f90e0b5e 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml @@ -231,7 +231,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml index 2e590d6058..7829bba900 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml @@ -231,7 +231,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml index c2bb6a1bb6..d11da975d6 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml @@ -231,7 +231,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.32.3 + image: docker.io/envoyproxy/envoy:distroless-v1.32.4 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/default.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/default.yaml index 84af681d9e..6af6849e3b 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/default.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/default.yaml @@ -86,7 +86,7 @@ spec: value: :19001 - name: PROMETHEUS_MAPPER_YAML value: /etc/statsd-exporter/conf.yaml - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/disable-prometheus.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/disable-prometheus.yaml index de4c83da5f..017b8154eb 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/disable-prometheus.yaml @@ -76,7 +76,7 @@ spec: value: tcp - name: REDIS_URL value: redis.redis.svc:6379 - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/enable-tracing-custom.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/enable-tracing-custom.yaml index 6971dc186e..b901fc37f1 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/enable-tracing-custom.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/enable-tracing-custom.yaml @@ -101,7 +101,7 @@ spec: value: "0.6" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://trace-collector.envoy-gateway-system.svc.cluster.local:4317 - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/enable-tracing.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/enable-tracing.yaml index b78ec434d6..386b108a32 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/enable-tracing.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/enable-tracing.yaml @@ -101,7 +101,7 @@ spec: value: "1.0" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://trace-collector.envoy-gateway-system.svc.cluster.local:4318 - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/merge-annotations.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/merge-annotations.yaml index 945e60e676..97ceb278ff 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/merge-annotations.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/merge-annotations.yaml @@ -88,7 +88,7 @@ spec: value: :19001 - name: PROMETHEUS_MAPPER_YAML value: /etc/statsd-exporter/conf.yaml - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/merge-labels.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/merge-labels.yaml index 9cd149a109..69a0153b6c 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/merge-labels.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/merge-labels.yaml @@ -88,7 +88,7 @@ spec: value: :19001 - name: PROMETHEUS_MAPPER_YAML value: /etc/statsd-exporter/conf.yaml - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/patch-deployment.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/patch-deployment.yaml index 21b6e9fbfd..c93d833bd9 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/patch-deployment.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/patch-deployment.yaml @@ -86,7 +86,7 @@ spec: value: :19001 - name: PROMETHEUS_MAPPER_YAML value: /etc/statsd-exporter/conf.yaml - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/with-node-selector.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/with-node-selector.yaml index 4700c613c3..8d979a8632 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/with-node-selector.yaml @@ -86,7 +86,7 @@ spec: value: :19001 - name: PROMETHEUS_MAPPER_YAML value: /etc/statsd-exporter/conf.yaml - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/with-topology-spread-constraints.yaml index c784904d25..530bedf028 100644 --- a/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/ratelimit/testdata/deployments/with-topology-spread-constraints.yaml @@ -86,7 +86,7 @@ spec: value: :19001 - name: PROMETHEUS_MAPPER_YAML value: /etc/statsd-exporter/conf.yaml - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f imagePullPolicy: IfNotPresent name: envoy-ratelimit ports: diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 4259823c01..611fe1245c 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -215,8 +215,7 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques string(gwapiv1.GatewayClassReasonInvalidParameters), msg) r.resources.GatewayClassStatuses.Store(utils.NamespacedName(gc), &gc.Status) - r.log.Error(err, "failed to process parametersRef for gatewayclass", "name", managedGC.Name) - return reconcile.Result{}, err + return reconcile.Result{}, nil } } diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index 7626ea32d5..4a8d44395b 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -120,6 +120,22 @@ func backendHTTPRouteIndexFunc(rawObj client.Object) []string { ) } } + + // Check for a RequestMirror filter to also include the backendRef from that filter + for _, filter := range rule.Filters { + if filter.Type != gwapiv1.HTTPRouteFilterRequestMirror { + continue + } + + mirrorBackendRef := filter.RequestMirror.BackendRef + + backendRefs = append(backendRefs, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(mirrorBackendRef.Namespace, httproute.Namespace), + Name: string(mirrorBackendRef.Name), + }.String(), + ) + } } return backendRefs } diff --git a/internal/provider/kubernetes/predicates_test.go b/internal/provider/kubernetes/predicates_test.go index 8ff155f46f..073de5a582 100644 --- a/internal/provider/kubernetes/predicates_test.go +++ b/internal/provider/kubernetes/predicates_test.go @@ -418,6 +418,52 @@ func TestValidateEndpointSliceForReconcile(t *testing.T) { endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "service"), expect: true, }, + { + name: "mirrored backend route exists", + configs: []client.Object{ + test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil), + sampleGateway, + &gwapiv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "httproute-test", + }, + Spec: gwapiv1.HTTPRouteSpec{ + CommonRouteSpec: gwapiv1.CommonRouteSpec{ + ParentRefs: []gwapiv1.ParentReference{ + {Name: gwapiv1.ObjectName("scheduled-status-test")}, + }, + }, + Rules: []gwapiv1.HTTPRouteRule{ + { + BackendRefs: []gwapiv1.HTTPBackendRef{ + { + BackendRef: gwapiv1.BackendRef{ + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: gwapiv1.ObjectName("service"), + Port: ptr.To(gwapiv1.PortNumber(80)), + }, + }, + }, + }, + Filters: []gwapiv1.HTTPRouteFilter{ + { + Type: gwapiv1.HTTPRouteFilterRequestMirror, + RequestMirror: &gwapiv1.HTTPRequestMirrorFilter{ + BackendRef: gwapiv1.BackendObjectReference{ + Name: "mirror-service", + Port: ptr.To(gwapiv1.PortNumber(80)), + }, + }, + }, + }, + }, + }, + }, + }, + }, + endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "mirror-service"), + expect: true, + }, } // Create the reconciler. diff --git a/internal/wasm/cache.go b/internal/wasm/cache.go index 785d8d3701..7a045038f0 100644 --- a/internal/wasm/cache.go +++ b/internal/wasm/cache.go @@ -67,11 +67,16 @@ type localFileCache struct { // option sets for configuring the cache. CacheOptions + // permissionCheckCache is the cache for permission check for private OCI images. + // The permission check is run periodically by a background goroutine and the result is cached. + permissionCheckCache *permissionCache + // logger logger logging.Logger } func (c *localFileCache) Start(ctx context.Context) { + c.permissionCheckCache.Start(ctx) go c.purge(ctx) } @@ -131,9 +136,12 @@ type cacheEntry struct { func newLocalFileCache(options CacheOptions, logger logging.Logger) *localFileCache { options = options.sanitize() cache := &localFileCache{ - httpFetcher: NewHTTPFetcher(options.HTTPRequestTimeout, options.HTTPRequestMaxRetries, logger), - modules: make(map[moduleKey]*cacheEntry), - checksums: make(map[string]*checksumEntry), + httpFetcher: NewHTTPFetcher(options.HTTPRequestTimeout, options.HTTPRequestMaxRetries, logger), + modules: make(map[moduleKey]*cacheEntry), + checksums: make(map[string]*checksumEntry), + permissionCheckCache: newPermissionCache( + permissionCacheOptions{}, + logger), CacheOptions: options, logger: logger, } @@ -220,7 +228,7 @@ func (c *localFileCache) getOrFetch(key cacheKey, opts GetOptions) (*cacheEntry, if ce != nil { // We still need to check if the pull secret is correct if it is a private OCI image. if u.Scheme == "oci" && ce.isPrivate { - if err = c.checkPermission(ctx, u, insecure, opts); err != nil { + if _, err := c.permissionCheckCache.IsAllowed(ctx, u, opts.PullSecret, insecure); err != nil { return nil, err } } @@ -250,7 +258,24 @@ func (c *localFileCache) getOrFetch(key cacheKey, opts GetOptions) (*cacheEntry, if len(opts.PullSecret) > 0 { isPrivate = true } - if imageBinaryFetcher, dChecksum, err = c.prepareFetch(ctx, u, insecure, opts); err != nil { + + imageBinaryFetcher, dChecksum, err = c.prepareFetch(ctx, u, insecure, opts.PullSecret) + + if isPrivate { + e := &permissionCacheEntry{ + image: u, + fetcherOption: &ImageFetcherOption{ + Insecure: insecure, + PullSecret: opts.PullSecret, + }, + lastCheck: time.Now(), + lastAccess: time.Now(), + checkError: err, + } + c.permissionCheckCache.Put(e) + } + + if err != nil { wasmRemoteFetchTotal.WithFailure(reasonManifestError).Increment() return nil, fmt.Errorf("could not fetch Wasm OCI image: %w", err) } @@ -287,24 +312,16 @@ func (c *localFileCache) getOrFetch(key cacheKey, opts GetOptions) (*cacheEntry, return c.addEntry(key, b, isPrivate) } -func (c *localFileCache) checkPermission(ctx context.Context, u *url.URL, insecure bool, opts GetOptions) error { - // Try to get the image metadata to check if the pull secret is correct. - if _, _, err := c.prepareFetch(ctx, u, insecure, opts); err != nil { - return fmt.Errorf("failed to login to private registry: %w", err) - } - return nil -} - // prepareFetch won't fetch the binary, but it will prepare the binaryFetcher and actualDigest. func (c *localFileCache) prepareFetch( - ctx context.Context, url *url.URL, insecure bool, opts GetOptions) ( + ctx context.Context, url *url.URL, insecure bool, pullSecret []byte) ( binaryFetcher func() ([]byte, error), actualDigest string, err error, ) { imgFetcherOps := ImageFetcherOption{ Insecure: insecure, } - if len(opts.PullSecret) > 0 { - imgFetcherOps.PullSecret = opts.PullSecret + if len(pullSecret) > 0 { + imgFetcherOps.PullSecret = pullSecret } fetcher := NewImageFetcher(ctx, imgFetcherOps, c.logger) if binaryFetcher, actualDigest, err = fetcher.PrepareFetch(url.Host + url.Path); err != nil { diff --git a/internal/wasm/premissioncache.go b/internal/wasm/premissioncache.go new file mode 100644 index 0000000000..e680574ad9 --- /dev/null +++ b/internal/wasm/premissioncache.go @@ -0,0 +1,266 @@ +// 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. + +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package wasm + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "net/http" + "net/url" + "sync" + "time" + + "github.com/avast/retry-go" + "github.com/google/go-containerregistry/pkg/v1/remote/transport" + + "github.com/envoyproxy/gateway/internal/logging" +) + +type permissionCacheOptions struct { + // checkInterval is the interval to recheck the permission for the cached permission entries. + checkInterval time.Duration + + // permissionExpiry is the expiry time for permission cache entry. + // The permission cache entry will be updated by rechecking the OCI image permission against the pull secret. + permissionExpiry time.Duration + + // cacheExpiry is the expiry time for the permission cache. + // The permission cache will be removed if it is not accessed for the specified expiry time. + // This is used to purge the cache. + cacheExpiry time.Duration +} + +// sanitize validates and sets the default values for the permission cache options. +func (o *permissionCacheOptions) sanitize() { + if o.checkInterval == 0 { + o.checkInterval = 5 * time.Minute + } + if o.permissionExpiry == 0 { + o.permissionExpiry = 1 * time.Hour + } + if o.cacheExpiry == 0 { + o.cacheExpiry = 24 * time.Hour + } +} + +// permissionCache is a cache for permission check for private OCI images. +// After a new permission is put into the cache, it will be checked periodically by a background goroutine. +// It is used to avoid blocking the translator due to the permission check. +type permissionCache struct { + sync.Mutex + permissionCacheOptions + + cache map[string]*permissionCacheEntry + logger logging.Logger +} + +// permissionCacheEntry is an entry in the permission cache. +type permissionCacheEntry struct { + // The oci image URL. + image *url.URL + // fetcherOption contains the pull secret for the image. + fetcherOption *ImageFetcherOption + // The last time the pull secret is checked against the image. + lastCheck time.Time + // The error returned by the OCI registry when checking the permission. + // If error is not nil, the permission is not allowed. + // If it's a permission error, it's represented by a transport.Error with 401 or 403 HTTP status code. + // But it's not necessarily a permission error, it could be other errors like network error, non-exist image, etc. + // In this case, the permission is also not allowed. + checkError error + // The last time the cache entry is accessed. + lastAccess time.Time +} + +func (e *permissionCacheEntry) key() string { + return permissionCacheKey(e.image, e.fetcherOption.PullSecret) +} + +// isPermissionExpired returns true if the permission check is older +// than the specified expiry duration. If this is true, the entry +// should be rechecked. +func (e *permissionCacheEntry) isPermissionExpired(expiry time.Duration) bool { + return time.Now().After(e.lastCheck.Add(expiry)) +} + +// isCacheExpired returns true if the cache entry has not been accessed +// for the specified expiry duration. If this is true, the entry +// should be removed. +func (e *permissionCacheEntry) isCacheExpired(expiry time.Duration) bool { + return time.Now().After(e.lastAccess.Add(expiry)) +} + +// permissionCacheKey generates a key for a permission cache entry. +// The key is the hex encoded of concatenation of the image URL and the pull secret. +func permissionCacheKey(image *url.URL, pullSecret []byte) string { + b := make([]byte, len(image.String())+len(pullSecret)) + copy(b, image.String()) + copy(b[len(image.String()):], pullSecret) + return hex.EncodeToString(b) +} + +// newPermissionCache creates a new permission cache with a given TTL. +func newPermissionCache(options permissionCacheOptions, logger logging.Logger) *permissionCache { + options.sanitize() + return &permissionCache{ + cache: make(map[string]*permissionCacheEntry), + permissionCacheOptions: options, + logger: logger, + } +} + +// checkAndUpdatePermission checks the permission of the image against the pull secret and updates the cache entry. +func (p *permissionCache) checkAndUpdatePermission(ctx context.Context, e *permissionCacheEntry) error { + fetcher := NewImageFetcher(ctx, *e.fetcherOption, p.logger) + _, _, err := fetcher.PrepareFetch(e.image.Host + e.image.Path) + e.checkError = err + e.lastCheck = time.Now() + return err +} + +// start starts a background goroutine to periodically check the permission for the cached permission entries. +func (p *permissionCache) Start(ctx context.Context) { + go func() { + ticker := time.NewTicker(p.checkInterval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + func() { + p.Lock() + defer p.Unlock() + for _, e := range p.cache { + if e.isCacheExpired(p.cacheExpiry) { + p.logger.Info("removing permission cache entry", "image", e.image.String()) + delete(p.cache, e.key()) + continue + } + if e.isPermissionExpired(p.permissionExpiry) { + const retryAttempts = 3 + const retryDelay = 1 * time.Second + p.logger.Info("rechecking permission for image", "image", e.image.String()) + err := retry.Do( + func() error { + err := p.checkAndUpdatePermission(ctx, e) + if err != nil && isRetriableError(err) { + p.logger.Error( + err, + "failed to check permission for image, will retry again", + "image", + e.image.String()) + return err + } + return nil + }, + retry.Attempts(retryAttempts), + retry.DelayType(retry.BackOffDelay), + retry.Delay(retryDelay), + retry.Context(ctx), + ) + if err != nil { + p.logger.Error( + err, + fmt.Sprintf("failed to recheck permission for image after %d attempts", retryAttempts), + "image", + e.image.String()) + } + } + } + }() + case <-ctx.Done(): + return + } + } + }() +} + +// isRetriableError checks if the error is retriable. +// If the error is a permission error, it's not retriable. For example, 401 and 403 HTTP status code. +func isRetriableError(err error) bool { + var terr *transport.Error + if errors.As(err, &terr) { + if terr.StatusCode == http.StatusUnauthorized || terr.StatusCode == http.StatusForbidden { + return false + } + } + return true +} + +// put puts a new permission cache entry into the cache. +func (p *permissionCache) Put(e *permissionCacheEntry) { + p.Lock() + defer p.Unlock() + e.lastAccess = time.Now() + e.lastCheck = time.Now() + p.cache[e.key()] = e +} + +// IsAllowed checks if the given image is allowed to be accessed with the provided pull secret. +// If the permission is not found in the cache, this method will block until the permission is checked and cached. +// This blocking won't be too long as it's only for the first time permission check and won't retry. Subsequent +// permission checks will be done in a background goroutine by the permission cache. +// +// If any error occurs, the permission is considered not allowed. +// The error can be a permission error or other errors like network error, non-exist image, etc. +func (p *permissionCache) IsAllowed(ctx context.Context, image *url.URL, pullSecret []byte, insecure bool) (bool, error) { + p.Lock() + defer p.Unlock() + key := permissionCacheKey(image, pullSecret) + if e, ok := p.cache[key]; ok { + e.lastAccess = time.Now() + return e.checkError == nil, e.checkError + } + + e := &permissionCacheEntry{ + image: image, + fetcherOption: &ImageFetcherOption{ + Insecure: insecure, + PullSecret: pullSecret, + }, + } + // Do not retry if the permission check fails because we don't want to block the translator for too long. + // The permission check will be retried in the background goroutine by the permission cache. + if err := p.checkAndUpdatePermission(ctx, e); err != nil { + p.logger.Error(err, "failed to check permission for image", "image", e.image.String()) + } + e.lastAccess = time.Now() + p.cache[key] = e + return e.checkError == nil, e.checkError +} + +// getForTest is a test helper to get a permission cache entry from the cache. +func (p *permissionCache) getForTest(key string) (permissionCacheEntry, bool) { + p.Lock() + defer p.Unlock() + entry, ok := p.cache[key] + if !ok { + return permissionCacheEntry{}, false + } + return *entry, true +} + +// deleteForTest is a test helper to delete a permission cache entry from the cache. +func (p *permissionCache) deleteForTest(key string) { + p.Lock() + defer p.Unlock() + delete(p.cache, key) +} diff --git a/internal/wasm/premissioncache_test.go b/internal/wasm/premissioncache_test.go new file mode 100644 index 0000000000..2fdb95ff18 --- /dev/null +++ b/internal/wasm/premissioncache_test.go @@ -0,0 +1,282 @@ +// 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. + +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package wasm + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "sync" + "testing" + "time" + + "github.com/google/go-containerregistry/pkg/registry" + "github.com/stretchr/testify/require" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/logging" +) + +func TestPermissionCache(t *testing.T) { + lock := sync.Mutex{} + // Flag to control whether the permission check should fail. + failPermissionCheck := false + + // Set up a fake registry for OCI images. + reg := registry.New() + tos := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + lock.Lock() + defer lock.Unlock() + if failPermissionCheck { + http.Error(w, "permission denied", http.StatusUnauthorized) + return + } + reg.ServeHTTP(w, r) + })) + defer tos.Close() + ou, err := url.Parse(tos.URL) + if err != nil { + t.Fatal(err) + } + _, _ = setupOCIRegistry(t, ou.Host) + ociURLWithTag := fmt.Sprintf("oci://%s/test/valid/docker:v0.1.0", ou.Host) + ociURLWithLatestTag := fmt.Sprintf("oci://%s/test/valid/docker:latest", ou.Host) + image, _ := url.Parse(ociURLWithTag) + latestImage, _ := url.Parse(ociURLWithLatestTag) + secret := []byte("") + + t.Run("Cached permission should be updated", func(t *testing.T) { + lock.Lock() + failPermissionCheck = false + lock.Unlock() + + ctx := context.Background() + defer ctx.Done() + cache, entry := setupTestPermissionCache( + permissionCacheOptions{ + checkInterval: 10 * time.Nanosecond, + permissionExpiry: 10 * time.Nanosecond, + }, + image, + latestImage, + secret) + cache.Start(ctx) + + lastAccessTime := entry.lastAccess + lastCheckTime := entry.lastCheck + + time.Sleep(1 * time.Millisecond) + allowed, err := cache.IsAllowed(context.Background(), image, secret, true) + require.True( + t, + allowed, + "permission should be rechecked and allowed after permission expired") + require.NoError( + t, + err, + "permission should be rechecked and allowed after permission expired") + + entry, ok := cache.getForTest(entry.key()) + require.True(t, ok, "cache entry should exist") + require.True(t, entry.lastAccess.After(lastAccessTime), "last access time should be updated") + require.True(t, entry.lastCheck.After(lastCheckTime), "last check time should be updated") + }) + + t.Run("Cached permission failed after recheck", func(t *testing.T) { + lock.Lock() + failPermissionCheck = true + lock.Unlock() + + ctx := context.Background() + defer ctx.Done() + cache, entry := setupTestPermissionCache( + permissionCacheOptions{ + checkInterval: 10 * time.Nanosecond, + permissionExpiry: 10 * time.Nanosecond, + }, + image, + latestImage, + secret) + cache.Start(ctx) + + lastAccessTime := entry.lastAccess + lastCheckTime := entry.lastCheck + + time.Sleep(1 * time.Millisecond) + allowed, err := cache.IsAllowed(context.Background(), image, secret, true) + require.False(t, isRetriableError(err), "permission check error should not be retriable") + require.False( + t, + allowed, + "permission should be rechecked and denied after permission expired and secret is invalid") + require.Error( + t, + err, + "permission should be rechecked and denied after permission expired and secret is invalid") + + entry, ok := cache.getForTest(entry.key()) + require.True(t, ok, "cache entry should exist") + require.True(t, entry.lastAccess.After(lastAccessTime), "last access time should be updated") + require.True(t, entry.lastCheck.After(lastCheckTime), "last check time should be updated") + }) + + t.Run("Cached permission should be removed after expiry", func(t *testing.T) { + lock.Lock() + failPermissionCheck = false + lock.Unlock() + + ctx := context.Background() + defer ctx.Done() + cache, entry := setupTestPermissionCache( + permissionCacheOptions{ + checkInterval: 10 * time.Nanosecond, + cacheExpiry: 10 * time.Nanosecond, + }, + image, + latestImage, + secret) + cache.Start(ctx) + + lastAccessTime := entry.lastAccess + lastCheckTime := entry.lastCheck + + time.Sleep(1 * time.Millisecond) + key := entry.key() + entry, ok := cache.getForTest(key) + require.False(t, ok, "cache entry should be removed after expiry") + allowed, err := cache.IsAllowed(context.Background(), image, secret, true) + require.True(t, + allowed, + "permission should be rechecked and allowed after cache removed") + require.NoError(t, + err, + "permission should be rechecked and allowed after cache removed") + entry, ok = cache.getForTest(key) + require.True(t, ok, "expired entry should be added after recheck") + require.True(t, entry.lastAccess.After(lastAccessTime), "last access time should be updated") + require.True(t, entry.lastCheck.After(lastCheckTime), "last check time should be updated") + }) + + t.Run("Non-exist permission should be checked and cached after first access for allowed permission", func(t *testing.T) { + lock.Lock() + failPermissionCheck = false + lock.Unlock() + + ctx := context.Background() + defer ctx.Done() + cache, entry := setupTestPermissionCache( + permissionCacheOptions{ + checkInterval: 10 * time.Nanosecond, + }, + image, + latestImage, + secret) + key := entry.key() + // remove the cache entry + cache.deleteForTest(key) + cache.Start(ctx) + + _, ok := cache.getForTest(key) + require.False(t, ok, "cache entry should not exist before access") + + now := time.Now() + allowed, err := cache.IsAllowed(context.Background(), image, secret, true) + require.True(t, + allowed, + "non-exist permission should be checked and allowed at first access") + require.NoError(t, + err, + "non-exist permission should be checked and allowed at first access") + + entry, ok = cache.getForTest(key) + require.True(t, ok, "non-exist permission should be added to the cache after first access ") + require.True(t, entry.lastAccess.After(now), "last access time should be updated after first access") + require.True(t, entry.lastCheck.After(now), "last check time should be updated after first access") + }) + + t.Run("Non-exist permission should be checked and cached after first access for denied permission", func(t *testing.T) { + lock.Lock() + failPermissionCheck = true + lock.Unlock() + + ctx := context.Background() + defer ctx.Done() + cache, entry := setupTestPermissionCache( + permissionCacheOptions{ + checkInterval: 10 * time.Nanosecond, + }, + image, + latestImage, + secret) + key := entry.key() + // remove the cache entry + cache.deleteForTest(key) + cache.Start(ctx) + + _, ok := cache.getForTest(key) + require.False(t, ok, "cache entry should not exist before access") + + now := time.Now() + allowed, err := cache.IsAllowed(context.Background(), image, secret, true) + require.False(t, + allowed, + "non-exist permission should be checked and denied at first access if secret is invalid") + require.Error(t, + err, + "non-exist permission should be checked and denied at first access if secret is invalid") + + entry, ok = cache.getForTest(key) + require.True(t, ok, "non-exist permission should be added to the cache after first access ") + require.True(t, entry.lastAccess.After(now), "last access time should be updated after first access") + require.True(t, entry.lastCheck.After(now), "last check time should be updated after first access") + }) +} + +// setupTestPermissionCache sets up a permission cache for testing. +func setupTestPermissionCache(options permissionCacheOptions, image, latestImage *url.URL, secret []byte) (*permissionCache, permissionCacheEntry) { + // Setup the permission cache. + cache := newPermissionCache( + options, + logging.DefaultLogger(egv1a1.LogLevelInfo)) + + entry := &permissionCacheEntry{ + image: image, + fetcherOption: &ImageFetcherOption{ + PullSecret: secret, + Insecure: true, + }, + lastCheck: time.Now(), + } + cache.Put(entry) + + // Add one more entry for the latest image to test the cache can handle multiple entries correctly. + cache.Put(&permissionCacheEntry{ + image: latestImage, + fetcherOption: &ImageFetcherOption{ + PullSecret: secret, + Insecure: true, + }, + lastCheck: time.Now(), + }) + + return cache, *entry +} diff --git a/internal/xds/translator/accesslog.go b/internal/xds/translator/accesslog.go index 991c8e342c..304f22da49 100644 --- a/internal/xds/translator/accesslog.go +++ b/internal/xds/translator/accesslog.go @@ -336,10 +336,10 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ ResourceAttributes: convertToKeyValueList(otel.Resources, false), } - if otel.Text == nil { - return nil, errors.New("otel.Text is nil") + var format string + if otel.Text != nil && *otel.Text != "" { + format = *otel.Text } - format := *otel.Text if format != "" { al.Body = &otlpcommonv1.AnyValue{ @@ -349,9 +349,17 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ } } - al.Attributes = convertToKeyValueList(otel.Attributes, true) + var attrs map[string]string + if len(otel.Attributes) != 0 { + attrs = otel.Attributes + } else if len(otel.Attributes) == 0 && format == "" { + // if there are no attributes and text format is unset, use the default EnvoyJSONLogFields + attrs = EnvoyJSONLogFields + } + + al.Attributes = convertToKeyValueList(attrs, true) + formatters := accessLogOpenTelemetryFormatters(format, attrs) - formatters := accessLogOpenTelemetryFormatters(format, otel.Attributes) if len(formatters) != 0 { al.Formatters = formatters } diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 5f6cb7da74..3271a4f907 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -2,7 +2,6 @@ date: Pending # Changes that are expected to cause an incompatibility with previous versions, such as deletions or modifications to existing APIs. breaking changes: | - Add a breaking change here # Updates addressing vulnerabilities, security flaws, or compliance requirements. security updates: | @@ -14,11 +13,9 @@ bug fixes: | # Enhancements that improve performance. performance improvements: | - Add a performance improvement here # Deprecated features or APIs. deprecations: | - Add a deprecation here # Other notable changes not covered by the above sections. Other changes: | diff --git a/release-notes/v1.2.8.yaml b/release-notes/v1.2.8.yaml new file mode 100644 index 0000000000..e5a070f20e --- /dev/null +++ b/release-notes/v1.2.8.yaml @@ -0,0 +1,21 @@ +date: March 25, 2025 + +security updates: | + Fixed vulnerability CVE-2025-30157, where local replies were incorrectly sent to the ext_proc server. + Included ratelimit security fixes related to the golang net/http package. + +bug fixes: | + Added support for BackendTLSPolicy and EnvoyExtensionPolicy parsing in Standalone mode. + Fixed endpoint updates when mirrored backend Pod IPs change. + Fix not logging an error and returning it in the K8s Reconcile method when a GatewayClass is not accepted. + Fixed validation of host header in RequestHeaderModifier filter. + Fixed an OpenTelemetry access log sink failure caused by an 'otel.Text is nil' error. +# Enhancements that improve performance. +performance improvements: | + Added a cache for the Wasm OCI image permission checks and check the pullSecrets against the OCI image registry in + a background goroutine. + +# Other notable changes not covered by the above sections. +Other changes: | + Bumped envoy to v1.32.4. + Bumped ratelimit to 0141a24f. diff --git a/site/content/en/latest/install/gateway-helm-api.md b/site/content/en/latest/install/gateway-helm-api.md index 1035fdea50..b32bcda77d 100644 --- a/site/content/en/latest/install/gateway-helm-api.md +++ b/site/content/en/latest/install/gateway-helm-api.md @@ -66,7 +66,7 @@ The Helm chart for Envoy Gateway | global.images.envoyGateway.image | string | `nil` | | | global.images.envoyGateway.pullPolicy | string | `nil` | | | global.images.envoyGateway.pullSecrets | list | `[]` | | -| global.images.ratelimit.image | string | `"docker.io/envoyproxy/ratelimit:ae4cee11"` | | +| global.images.ratelimit.image | string | `"docker.io/envoyproxy/ratelimit:0141a24f"` | | | global.images.ratelimit.pullPolicy | string | `"IfNotPresent"` | | | global.images.ratelimit.pullSecrets | list | `[]` | | | kubernetesClusterDomain | string | `"cluster.local"` | | diff --git a/site/content/en/news/releases/notes/v1.2.8.md b/site/content/en/news/releases/notes/v1.2.8.md new file mode 100644 index 0000000000..9301613d42 --- /dev/null +++ b/site/content/en/news/releases/notes/v1.2.8.md @@ -0,0 +1,26 @@ +--- +title: "v1.2.8" +publishdate: 2025-03-25 +--- + +Date: March 25, 2025 + +## Security updates +- Fixed vulnerability CVE-2025-30157, where local replies were incorrectly sent to the ext_proc server. +- Included ratelimit security fixes related to the golang net/http package. + +## Bug fixes +- Added support for BackendTLSPolicy and EnvoyExtensionPolicy parsing in Standalone mode. +- Fixed endpoint updates when mirrored backend Pod IPs change. +- Fix not logging an error and returning it in the K8s Reconcile method when a GatewayClass is not accepted. +- Fixed validation of host header in RequestHeaderModifier filter. +- Fixed an OpenTelemetry access log sink failure caused by an 'otel.Text is nil' error. + +## Performance improvements +- Added a cache for the Wasm OCI image permission checks and check the pullSecrets against the OCI image registry in +- a background goroutine. + +## Other changes +- Bumped envoy to v1.32.4. +- Bumped ratelimit to 0141a24f. + diff --git a/site/content/zh/latest/install/gateway-helm-api.md b/site/content/zh/latest/install/gateway-helm-api.md index 1035fdea50..b32bcda77d 100644 --- a/site/content/zh/latest/install/gateway-helm-api.md +++ b/site/content/zh/latest/install/gateway-helm-api.md @@ -66,7 +66,7 @@ The Helm chart for Envoy Gateway | global.images.envoyGateway.image | string | `nil` | | | global.images.envoyGateway.pullPolicy | string | `nil` | | | global.images.envoyGateway.pullSecrets | list | `[]` | | -| global.images.ratelimit.image | string | `"docker.io/envoyproxy/ratelimit:ae4cee11"` | | +| global.images.ratelimit.image | string | `"docker.io/envoyproxy/ratelimit:0141a24f"` | | | global.images.ratelimit.pullPolicy | string | `"IfNotPresent"` | | | global.images.ratelimit.pullSecrets | list | `[]` | | | kubernetesClusterDomain | string | `"cluster.local"` | | diff --git a/test/e2e/testdata/accesslog-otel-default.yaml b/test/e2e/testdata/accesslog-otel-default.yaml new file mode 100644 index 0000000000..764e8e02e5 --- /dev/null +++ b/test/e2e/testdata/accesslog-otel-default.yaml @@ -0,0 +1,61 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: accesslog-gtw + namespace: gateway-conformance-infra +spec: + gatewayClassName: {GATEWAY_CLASS_NAME} + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same + infrastructure: + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: accesslog-otel +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: accesslog-otel + namespace: gateway-conformance-infra +spec: + ipFamily: IPv4 + telemetry: + accessLog: + settings: + - matches: + - "'x-envoy-logged' in request.headers" + sinks: + - type: OpenTelemetry + openTelemetry: + backendRefs: + - name: otel-collector + namespace: monitoring + port: 4317 + resources: + k8s.cluster.name: "envoy-gateway" + shutdown: + drainTimeout: 5s + minDrainDuration: 1s +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: accesslog-otel + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: accesslog-gtw + rules: + - matches: + - path: + type: PathPrefix + value: /otel + backendRefs: + - name: infra-backend-v2 + port: 8080 diff --git a/test/e2e/testdata/accesslog-otel-json.yaml b/test/e2e/testdata/accesslog-otel-json.yaml new file mode 100644 index 0000000000..219315112c --- /dev/null +++ b/test/e2e/testdata/accesslog-otel-json.yaml @@ -0,0 +1,79 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: accesslog-gtw + namespace: gateway-conformance-infra +spec: + gatewayClassName: {GATEWAY_CLASS_NAME} + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same + infrastructure: + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: accesslog-otel +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: accesslog-otel + namespace: gateway-conformance-infra +spec: + ipFamily: IPv4 + telemetry: + accessLog: + settings: + - format: + type: JSON + json: + time: "%START_TIME%" + method: "%REQ(:METHOD)%" + metadata: "%METADATA(ROUTE:envoy-gateway:resources)%" + x-envoy-original-path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" + protocol: "%PROTOCOL%" + responseCode: "%RESPONSE_CODE%" + responseFlags: "%RESPONSE_FLAGS%" + bytesReceived: "%BYTES_RECEIVED%" + bytesSent: "%BYTES_SENT%" + duration: "%DURATION%" + x-forwarded-for: "%REQ(X-FORWARDED-FOR)%" + user-agent: "%REQ(USER-AGENT)%" + x-request-id: "%REQ(X-REQUEST-ID)%" + authority: "%REQ(:AUTHORITY)%" + upstreamHost: "%UPSTREAM_HOST%" + matches: + - "'x-envoy-logged' in request.headers" + sinks: + - type: OpenTelemetry + openTelemetry: + backendRefs: + - name: otel-collector + namespace: monitoring + port: 4317 + resources: + k8s.cluster.name: "envoy-gateway" + shutdown: + drainTimeout: 5s + minDrainDuration: 1s +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: accesslog-otel + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: accesslog-gtw + rules: + - matches: + - path: + type: PathPrefix + value: /otel + backendRefs: + - name: infra-backend-v2 + port: 8080 diff --git a/test/e2e/tests/accesslog.go b/test/e2e/tests/accesslog.go index 4edc12f7c5..a9a7ca26a3 100644 --- a/test/e2e/tests/accesslog.go +++ b/test/e2e/tests/accesslog.go @@ -21,7 +21,7 @@ import ( ) func init() { - ConformanceTests = append(ConformanceTests, FileAccessLogTest, OpenTelemetryTest, ALSTest) + ConformanceTests = append(ConformanceTests, FileAccessLogTest, OpenTelemetryTestText, OpenTelemetryTestJSON, ALSTest, OpenTelemetryTestJSONAsDefault) } var FileAccessLogTest = suite.ConformanceTest{ @@ -116,9 +116,9 @@ var FileAccessLogTest = suite.ConformanceTest{ }, } -var OpenTelemetryTest = suite.ConformanceTest{ - ShortName: "OpenTelemetryAccessLog", - Description: "Make sure OpenTelemetry access log is working", +var OpenTelemetryTestText = suite.ConformanceTest{ + ShortName: "OpenTelemetryTextAccessLog", + Description: "Make sure OpenTelemetry text access log is working", Manifests: []string{"testdata/accesslog-otel.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { labels := map[string]string{ @@ -174,6 +174,112 @@ var OpenTelemetryTest = suite.ConformanceTest{ }, } +var OpenTelemetryTestJSONAsDefault = suite.ConformanceTest{ + ShortName: "OpenTelemetryAccessLogJSONAsDefault", + Description: "Make sure OpenTelemetry JSON access log is working as default when no format or type is specified", + Manifests: []string{"testdata/accesslog-otel-default.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + labels := map[string]string{ + "k8s_namespace_name": "envoy-gateway-system", + "exporter": "OTLP", + } + + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "accesslog-otel", Namespace: ns} + gwNN := types.NamespacedName{Name: "accesslog-gtw", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + t.Run("Positive", func(t *testing.T) { + expectedResponse := httputils.ExpectedResponse{ + Request: httputils.Request{ + Path: "/otel", + Headers: map[string]string{ + "x-envoy-logged": "1", + }, + }, + Response: httputils.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + // make sure listener is ready + httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + + runLogTest(t, suite, gwAddr, expectedResponse, labels, "", 1) + }) + + t.Run("Negative", func(t *testing.T) { + expectedResponse := httputils.ExpectedResponse{ + Request: httputils.Request{ + Path: "/otel", + // envoy will not log this request without the header x-envoy-logged + }, + Response: httputils.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + // make sure listener is ready + httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + + runLogTest(t, suite, gwAddr, expectedResponse, labels, "", 0) + }) + }, +} + +var OpenTelemetryTestJSON = suite.ConformanceTest{ + ShortName: "OpenTelemetryAccessLogJSON", + Description: "Make sure OpenTelemetry JSON access log is working with custom JSON attributes", + Manifests: []string{"testdata/accesslog-otel-json.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + labels := map[string]string{ + "k8s_namespace_name": "envoy-gateway-system", + "exporter": "OTLP", + } + + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "accesslog-otel", Namespace: ns} + gwNN := types.NamespacedName{Name: "accesslog-gtw", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + t.Run("Positive", func(t *testing.T) { + expectedResponse := httputils.ExpectedResponse{ + Request: httputils.Request{ + Path: "/otel", + Headers: map[string]string{ + "x-envoy-logged": "1", + }, + }, + Response: httputils.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + // make sure listener is ready + httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + + runLogTest(t, suite, gwAddr, expectedResponse, labels, "", 1) + }) + + t.Run("Negative", func(t *testing.T) { + expectedResponse := httputils.ExpectedResponse{ + Request: httputils.Request{ + Path: "/otel", + // envoy will not log this request without the header x-envoy-logged + }, + Response: httputils.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + // make sure listener is ready + httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + + runLogTest(t, suite, gwAddr, expectedResponse, labels, "", 0) + }) + }, +} + var ALSTest = suite.ConformanceTest{ ShortName: "ALS", Description: "Make sure ALS access log is working", diff --git a/test/e2e/tests/wasm_oci.go b/test/e2e/tests/wasm_oci.go index 514ab93735..f9f74cec21 100644 --- a/test/e2e/tests/wasm_oci.go +++ b/test/e2e/tests/wasm_oci.go @@ -197,7 +197,7 @@ var OCIWasmTest = suite.ConformanceTest{ t, suite.Client, types.NamespacedName{Name: testEEP, Namespace: testNS}, suite.ControllerName, - ancestorRef, "failed to login to private registry") + ancestorRef, "UNAUTHORIZED: authentication required") }) // Verify that the wasm module can't be loaded if the password is incorrect @@ -232,7 +232,7 @@ var OCIWasmTest = suite.ConformanceTest{ t, suite.Client, types.NamespacedName{Name: testEEP, Namespace: testNS}, suite.ControllerName, - ancestorRef, "failed to login to private registry") + ancestorRef, "UNAUTHORIZED: authentication required") }) }, } diff --git a/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml b/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml index e4541b8cd2..4bba4e1f50 100644 --- a/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml +++ b/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml @@ -37,7 +37,7 @@ data: kubernetes: rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f patch: type: StrategicMerge value: diff --git a/test/helm/gateway-helm/control-plane-with-pdb.out.yaml b/test/helm/gateway-helm/control-plane-with-pdb.out.yaml index 210af77b53..243833482c 100644 --- a/test/helm/gateway-helm/control-plane-with-pdb.out.yaml +++ b/test/helm/gateway-helm/control-plane-with-pdb.out.yaml @@ -52,7 +52,7 @@ data: kubernetes: rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f patch: type: StrategicMerge value: diff --git a/test/helm/gateway-helm/default-config.out.yaml b/test/helm/gateway-helm/default-config.out.yaml index ebd8d9db5c..dffdc90ac3 100644 --- a/test/helm/gateway-helm/default-config.out.yaml +++ b/test/helm/gateway-helm/default-config.out.yaml @@ -37,7 +37,7 @@ data: kubernetes: rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f patch: type: StrategicMerge value: diff --git a/test/helm/gateway-helm/deployment-custom-topology.out.yaml b/test/helm/gateway-helm/deployment-custom-topology.out.yaml index c78eb3d246..7d580d91fc 100644 --- a/test/helm/gateway-helm/deployment-custom-topology.out.yaml +++ b/test/helm/gateway-helm/deployment-custom-topology.out.yaml @@ -37,7 +37,7 @@ data: kubernetes: rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f patch: type: StrategicMerge value: diff --git a/test/helm/gateway-helm/deployment-images-config.out.yaml b/test/helm/gateway-helm/deployment-images-config.out.yaml index f09e89d191..c7d4d18e2c 100644 --- a/test/helm/gateway-helm/deployment-images-config.out.yaml +++ b/test/helm/gateway-helm/deployment-images-config.out.yaml @@ -37,7 +37,7 @@ data: kubernetes: rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f patch: type: StrategicMerge value: diff --git a/test/helm/gateway-helm/deployment-priorityclass.out.yaml b/test/helm/gateway-helm/deployment-priorityclass.out.yaml index 9063afac9d..7d3ed99c6b 100644 --- a/test/helm/gateway-helm/deployment-priorityclass.out.yaml +++ b/test/helm/gateway-helm/deployment-priorityclass.out.yaml @@ -37,7 +37,7 @@ data: kubernetes: rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f patch: type: StrategicMerge value: diff --git a/test/helm/gateway-helm/deployment-securitycontext.out.yaml b/test/helm/gateway-helm/deployment-securitycontext.out.yaml index cf23490af9..f0a5dff525 100644 --- a/test/helm/gateway-helm/deployment-securitycontext.out.yaml +++ b/test/helm/gateway-helm/deployment-securitycontext.out.yaml @@ -37,7 +37,7 @@ data: kubernetes: rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f patch: type: StrategicMerge value: diff --git a/test/helm/gateway-helm/service-annotations.out.yaml b/test/helm/gateway-helm/service-annotations.out.yaml index 83ea20ab6a..e15f842700 100644 --- a/test/helm/gateway-helm/service-annotations.out.yaml +++ b/test/helm/gateway-helm/service-annotations.out.yaml @@ -37,7 +37,7 @@ data: kubernetes: rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:ae4cee11 + image: docker.io/envoyproxy/ratelimit:0141a24f patch: type: StrategicMerge value: