Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions apis/projectcontour/v1alpha1/contourconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,32 @@ type EnvoyListenerConfig struct {
// +optional
DisableMergeSlashes *bool `json:"disableMergeSlashes,omitempty"`

// DisableNormalizePath disables Envoy's normalize_path option, which normalizes
// request URL paths according to RFC 3986 before HTTP filters run and before
// route matching. Disable this only if your backends treat request paths as
// opaque identifiers, for example object storage gateways, artifact registries,
// or APIs whose request signatures cover the raw path.
//
// Warning: when path normalization is disabled, route match conditions and
// external authorization policies are evaluated against the un-normalized path,
// so requests containing "." or ".." segments may bypass prefix based matching.
//
// Contour's default is false.
// +optional
DisableNormalizePath *bool `json:"disableNormalizePath,omitempty"`

// PathWithEscapedSlashesAction determines how Envoy handles request paths that
// contain escaped slash sequences (%2F, %2f, %5C and %5c). This action is applied
// before path normalization and merge slashes.
//
// Values: `keep_unchanged` (default), `reject_request`, `unescape_and_redirect`, `unescape_and_forward`
//
// Other values will produce an error.
// Contour's default is keep_unchanged.
// +kubebuilder:validation:Enum="keep_unchanged";"reject_request";"unescape_and_redirect";"unescape_and_forward"
// +optional
PathWithEscapedSlashesAction PathWithEscapedSlashesActionType `json:"pathWithEscapedSlashesAction,omitempty"`

// Defines the action to be applied to the Server header on the response path.
// When configured as overwrite, overwrites any Server header with "envoy".
// When configured as append_if_absent, if a Server header is present, pass it through, otherwise set it to "envoy".
Expand Down
12 changes: 9 additions & 3 deletions apis/projectcontour/v1alpha1/contourconfig_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,15 @@ func (e *EnvoyConfig) Validate() error {
}
}

// Envoy TLS configuration
if e.Listener != nil && e.Listener.TLS != nil {
return e.Listener.TLS.Validate()
if e.Listener != nil {
if err := e.Listener.PathWithEscapedSlashesAction.Validate(); err != nil {
return err
}

// Envoy TLS configuration
if e.Listener.TLS != nil {
return e.Listener.TLS.Validate()
}
}

return nil
Expand Down
29 changes: 29 additions & 0 deletions apis/projectcontour/v1alpha1/contourconfig_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,35 @@ func TestContourConfigurationSpecValidate(t *testing.T) {
c.Tracing.CustomTags = customTags
require.Error(t, c.Validate())
})

t.Run("path with escaped slashes action validation", func(t *testing.T) {
configWithAction := func(action contour_v1alpha1.PathWithEscapedSlashesActionType) contour_v1alpha1.ContourConfigurationSpec {
return contour_v1alpha1.ContourConfigurationSpec{
Envoy: &contour_v1alpha1.EnvoyConfig{
Listener: &contour_v1alpha1.EnvoyListenerConfig{
PathWithEscapedSlashesAction: action,
},
},
}
}

// An unset value is valid; contourconfig.Defaults() supplies keep_unchanged.
c := configWithAction("")
require.NoError(t, c.Validate())

for _, action := range []contour_v1alpha1.PathWithEscapedSlashesActionType{
contour_v1alpha1.KeepUnchangedPathWithEscapedSlashes,
contour_v1alpha1.RejectRequestPathWithEscapedSlashes,
contour_v1alpha1.UnescapeAndRedirectPathWithEscapedSlashes,
contour_v1alpha1.UnescapeAndForwardPathWithEscapedSlashes,
} {
c = configWithAction(action)
require.NoError(t, c.Validate(), "action %q should be valid", action)
}

c = configWithAction("reject")
require.ErrorContains(t, c.Validate(), `invalid path with escaped slashes action "reject"`)
})
}

func TestSanitizeCipherSuites(t *testing.T) {
Expand Down
52 changes: 52 additions & 0 deletions apis/projectcontour/v1alpha1/pathtransformation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright Project Contour 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 v1alpha1

import "fmt"

// PathWithEscapedSlashesActionType defines the action to take when a request path
// contains escaped slash sequences (%2F, %2f, %5C and %5c). The action is applied
// before path normalization and merge slashes.
type PathWithEscapedSlashesActionType string

const (
// KeepUnchangedPathWithEscapedSlashes keeps escaped slash sequences unchanged.
// This is the default value.
KeepUnchangedPathWithEscapedSlashes PathWithEscapedSlashesActionType = "keep_unchanged"

// RejectRequestPathWithEscapedSlashes rejects the request with a 400 response.
RejectRequestPathWithEscapedSlashes PathWithEscapedSlashesActionType = "reject_request"

// UnescapeAndRedirectPathWithEscapedSlashes unescapes the sequences and responds
// with a redirect to the normalized path. gRPC requests are rejected instead.
UnescapeAndRedirectPathWithEscapedSlashes PathWithEscapedSlashesActionType = "unescape_and_redirect"

// UnescapeAndForwardPathWithEscapedSlashes unescapes the sequences and forwards
// the request. This should not be used if intermediaries perform path based
// access control.
UnescapeAndForwardPathWithEscapedSlashes PathWithEscapedSlashesActionType = "unescape_and_forward"
)

func (a PathWithEscapedSlashesActionType) Validate() error {
switch a {
case KeepUnchangedPathWithEscapedSlashes,
RejectRequestPathWithEscapedSlashes,
UnescapeAndRedirectPathWithEscapedSlashes,
UnescapeAndForwardPathWithEscapedSlashes,
"":
return nil
default:
return fmt.Errorf("invalid path with escaped slashes action %q", a)
}
}
5 changes: 5 additions & 0 deletions apis/projectcontour/v1alpha1/zz_generated.deepcopy.go

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

17 changes: 17 additions & 0 deletions changelogs/unreleased/7703-agentdanabol-small.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Two new listener settings expose Envoy path transformations that Contour previously hardcoded:

- `disableNormalizePath` turns off RFC 3986 path normalization. Use this only if your backends treat request paths as opaque identifiers, such as object storage gateways, artifact registries, or APIs whose request signatures cover the raw path.
- `pathWithEscapedSlashesAction` controls how requests containing escaped slash sequences (`%2F`, `%2f`, `%5C`, `%5c`) are handled. Valid values are `keep_unchanged` (default), `reject_request`, `unescape_and_redirect` and `unescape_and_forward`. Setting this to `reject_request` or `unescape_and_redirect` closes an authorization bypass vector against prefix-matching routes and external authorization policies.

Both default to Contour's existing behavior, so upgrades are a no-op.

```yaml
envoy:
listener:
disableNormalizePath: false
pathWithEscapedSlashesAction: keep_unchanged
```

These settings are also available as `disableNormalizePath` and `pathWithEscapedSlashesAction` in the deprecated ConfigMap configuration.

Because these transformations run before route matching, route match conditions and external authorization policies always see the transformed path. See the [path transformation documentation](https://projectcontour.io/docs/main/configuration/#path-transformation) for how the settings interact and for the security implications of loosening them.
2 changes: 2 additions & 0 deletions cmd/contour/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,8 @@ func (s *Server) doServe() error {
DefaultHTTPVersions: parseDefaultHTTPVersions(contourConfiguration.Envoy.DefaultHTTPVersions),
AllowChunkedLength: !*contourConfiguration.Envoy.Listener.DisableAllowChunkedLength,
MergeSlashes: !*contourConfiguration.Envoy.Listener.DisableMergeSlashes,
DisableNormalizePath: *contourConfiguration.Envoy.Listener.DisableNormalizePath,
PathWithEscapedSlashesAction: contourConfiguration.Envoy.Listener.PathWithEscapedSlashesAction,
ServerHeaderTransformation: contourConfiguration.Envoy.Listener.ServerHeaderTransformation,
XffNumTrustedHops: *contourConfiguration.Envoy.Network.XffNumTrustedHops,
StripTrailingHostDot: *contourConfiguration.Envoy.Network.EnvoyStripTrailingHostDot,
Expand Down
14 changes: 14 additions & 0 deletions cmd/contour/servecontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,18 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co
serverHeaderTransformation = contour_v1alpha1.PassThroughServerHeader
}

var pathWithEscapedSlashesAction contour_v1alpha1.PathWithEscapedSlashesActionType
switch ctx.Config.PathWithEscapedSlashesAction {
case config.KeepUnchangedPathWithEscapedSlashes:
pathWithEscapedSlashesAction = contour_v1alpha1.KeepUnchangedPathWithEscapedSlashes
case config.RejectRequestPathWithEscapedSlashes:
pathWithEscapedSlashesAction = contour_v1alpha1.RejectRequestPathWithEscapedSlashes
case config.UnescapeAndRedirectPathWithEscapedSlashes:
pathWithEscapedSlashesAction = contour_v1alpha1.UnescapeAndRedirectPathWithEscapedSlashes
case config.UnescapeAndForwardPathWithEscapedSlashes:
pathWithEscapedSlashesAction = contour_v1alpha1.UnescapeAndForwardPathWithEscapedSlashes
}

var globalExtAuth *contour_v1.AuthorizationServer
if ctx.Config.GlobalExternalAuthorization.ExtensionService != "" {
nsedName := k8s.NamespacedNameFrom(ctx.Config.GlobalExternalAuthorization.ExtensionService)
Expand Down Expand Up @@ -570,6 +582,8 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co
Compression: compression,
DisableAllowChunkedLength: &ctx.Config.DisableAllowChunkedLength,
DisableMergeSlashes: &ctx.Config.DisableMergeSlashes,
DisableNormalizePath: &ctx.Config.DisableNormalizePath,
PathWithEscapedSlashesAction: pathWithEscapedSlashesAction,
ServerHeaderTransformation: serverHeaderTransformation,
ConnectionBalancer: ctx.Config.Listener.ConnectionBalancer,
PerConnectionBufferLimitBytes: ctx.Config.Listener.PerConnectionBufferLimitBytes,
Expand Down
30 changes: 26 additions & 4 deletions cmd/contour/servecontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,12 @@ func defaultContourConfiguration() contour_v1alpha1.ContourConfigurationSpec {
Namespace: "projectcontour",
},
Listener: &contour_v1alpha1.EnvoyListenerConfig{
UseProxyProto: ptr.To(false),
DisableAllowChunkedLength: ptr.To(false),
DisableMergeSlashes: ptr.To(false),
ServerHeaderTransformation: contour_v1alpha1.OverwriteServerHeader,
UseProxyProto: ptr.To(false),
DisableAllowChunkedLength: ptr.To(false),
DisableMergeSlashes: ptr.To(false),
DisableNormalizePath: ptr.To(false),
PathWithEscapedSlashesAction: contour_v1alpha1.KeepUnchangedPathWithEscapedSlashes,
ServerHeaderTransformation: contour_v1alpha1.OverwriteServerHeader,
TLS: &contour_v1alpha1.EnvoyListenerTLS{
EnvoyTLS: contour_v1alpha1.EnvoyTLS{
MinimumProtocolVersion: "",
Expand Down Expand Up @@ -756,6 +758,26 @@ func TestConvertServeContext(t *testing.T) {
return cfg
},
},
"disable normalize path": {
getServeContext: func(ctx *serveContext) *serveContext {
ctx.Config.DisableNormalizePath = true
return ctx
},
getContourConfiguration: func(cfg contour_v1alpha1.ContourConfigurationSpec) contour_v1alpha1.ContourConfigurationSpec {
cfg.Envoy.Listener.DisableNormalizePath = ptr.To(true)
return cfg
},
},
"path with escaped slashes action": {
getServeContext: func(ctx *serveContext) *serveContext {
ctx.Config.PathWithEscapedSlashesAction = config.UnescapeAndRedirectPathWithEscapedSlashes
return ctx
},
getContourConfiguration: func(cfg contour_v1alpha1.ContourConfigurationSpec) contour_v1alpha1.ContourConfigurationSpec {
cfg.Envoy.Listener.PathWithEscapedSlashesAction = contour_v1alpha1.UnescapeAndRedirectPathWithEscapedSlashes
return cfg
},
},
"server header transformation": {
getServeContext: func(ctx *serveContext) *serveContext {
ctx.Config.ServerHeaderTransformation = config.AppendIfAbsentServerHeader
Expand Down
11 changes: 11 additions & 0 deletions examples/contour/01-contour-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ data:
# that strips duplicate slashes from request URLs.
# disableMergeSlashes: false
#
# Disable Envoy's normalize_path option that normalizes request URL paths
# according to RFC 3986. Only disable this if your backends treat request
# paths as opaque identifiers. Note that route match conditions and external
# authorization policies then see the un-normalized path.
# disableNormalizePath: false
#
# How Envoy handles request paths containing escaped slash sequences
# (%2F, %2f, %5C and %5c). One of keep_unchanged (default), reject_request,
# unescape_and_redirect or unescape_and_forward.
# pathWithEscapedSlashesAction: keep_unchanged
#
# Disable HTTPProxy permitInsecure field
disablePermitInsecure: false
tls:
Expand Down
52 changes: 52 additions & 0 deletions examples/contour/01-crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,18 @@ spec:
which strips duplicate slashes from request URL paths.
Contour's default is false.
type: boolean
disableNormalizePath:
description: |-
DisableNormalizePath disables Envoy's normalize_path option, which normalizes
request URL paths according to RFC 3986 before HTTP filters run and before
route matching. Disable this only if your backends treat request paths as
opaque identifiers, for example object storage gateways, artifact registries,
or APIs whose request signatures cover the raw path.
Warning: when path normalization is disabled, route match conditions and
external authorization policies are evaluated against the un-normalized path,
so requests containing "." or ".." segments may bypass prefix based matching.
Contour's default is false.
type: boolean
httpMaxConcurrentStreams:
description: |-
Defines the value for SETTINGS_MAX_CONCURRENT_STREAMS Envoy will advertise in the
Expand Down Expand Up @@ -385,6 +397,20 @@ spec:
format: int32
minimum: 1
type: integer
pathWithEscapedSlashesAction:
description: |-
PathWithEscapedSlashesAction determines how Envoy handles request paths that
contain escaped slash sequences (%2F, %2f, %5C and %5c). This action is applied
before path normalization and merge slashes.
Values: `keep_unchanged` (default), `reject_request`, `unescape_and_redirect`, `unescape_and_forward`
Other values will produce an error.
Contour's default is keep_unchanged.
enum:
- keep_unchanged
- reject_request
- unescape_and_redirect
- unescape_and_forward
type: string
per-connection-buffer-limit-bytes:
description: |-
Defines the soft limit on size of the listener’s new connection read and write buffers in bytes.
Expand Down Expand Up @@ -4604,6 +4630,18 @@ spec:
which strips duplicate slashes from request URL paths.
Contour's default is false.
type: boolean
disableNormalizePath:
description: |-
DisableNormalizePath disables Envoy's normalize_path option, which normalizes
request URL paths according to RFC 3986 before HTTP filters run and before
route matching. Disable this only if your backends treat request paths as
opaque identifiers, for example object storage gateways, artifact registries,
or APIs whose request signatures cover the raw path.
Warning: when path normalization is disabled, route match conditions and
external authorization policies are evaluated against the un-normalized path,
so requests containing "." or ".." segments may bypass prefix based matching.
Contour's default is false.
type: boolean
httpMaxConcurrentStreams:
description: |-
Defines the value for SETTINGS_MAX_CONCURRENT_STREAMS Envoy will advertise in the
Expand Down Expand Up @@ -4649,6 +4687,20 @@ spec:
format: int32
minimum: 1
type: integer
pathWithEscapedSlashesAction:
description: |-
PathWithEscapedSlashesAction determines how Envoy handles request paths that
contain escaped slash sequences (%2F, %2f, %5C and %5c). This action is applied
before path normalization and merge slashes.
Values: `keep_unchanged` (default), `reject_request`, `unescape_and_redirect`, `unescape_and_forward`
Other values will produce an error.
Contour's default is keep_unchanged.
enum:
- keep_unchanged
- reject_request
- unescape_and_redirect
- unescape_and_forward
type: string
per-connection-buffer-limit-bytes:
description: |-
Defines the soft limit on size of the listener’s new connection read and write buffers in bytes.
Expand Down
Loading