Add override for proxy_intercept_errors when using Custom HTTP Errors (#9497)
* added proxy-intercept-errors config option * fixed error when comparing locations * fixed missing location config from annotation added e2e test * reversed logic for proxy-intercept-errors to disable-proxy-intercept-errors * reversed logic to disable-proxy-intercept-errors * reversed logic * default has to be false * put comment in same line as return * run gofmt * fixing wrong Boilerplate header * updated code to new IngressAnnotation interface * fixes to satisfy PR comments * synced with upstream; fixed typo * gofumpt disableproxyintercepterrors.go * gofumpt
This commit is contained in:
parent
e0446d7554
commit
ad406b64d8
12 changed files with 395 additions and 108 deletions
|
@ -50,6 +50,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|
||||||
|[nginx.ingress.kubernetes.io/client-body-buffer-size](#client-body-buffer-size)|string|
|
|[nginx.ingress.kubernetes.io/client-body-buffer-size](#client-body-buffer-size)|string|
|
||||||
|[nginx.ingress.kubernetes.io/configuration-snippet](#configuration-snippet)|string|
|
|[nginx.ingress.kubernetes.io/configuration-snippet](#configuration-snippet)|string|
|
||||||
|[nginx.ingress.kubernetes.io/custom-http-errors](#custom-http-errors)|[]int|
|
|[nginx.ingress.kubernetes.io/custom-http-errors](#custom-http-errors)|[]int|
|
||||||
|
|[nginx.ingress.kubernetes.io/disable-proxy-intercept-errors](#disable-proxy-intercept-errors)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/default-backend](#default-backend)|string|
|
|[nginx.ingress.kubernetes.io/default-backend](#default-backend)|string|
|
||||||
|[nginx.ingress.kubernetes.io/enable-cors](#enable-cors)|"true" or "false"|
|
|[nginx.ingress.kubernetes.io/enable-cors](#enable-cors)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/cors-allow-origin](#enable-cors)|string|
|
|[nginx.ingress.kubernetes.io/cors-allow-origin](#enable-cors)|string|
|
||||||
|
@ -330,6 +331,17 @@ Example usage:
|
||||||
nginx.ingress.kubernetes.io/custom-http-errors: "404,415"
|
nginx.ingress.kubernetes.io/custom-http-errors: "404,415"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Disable Proxy intercept Errors
|
||||||
|
|
||||||
|
Like the [`disable-proxy-intercept-errors`](./configmap.md#disable-proxy-intercept-errors) value in the ConfigMap, this annotation allows to disable NGINX `proxy-intercept-errors` when `custom-http-errors` are set, but only for the NGINX location associated with this ingress. If a [default backend annotation](#default-backend) is specified on the ingress, the errors will be routed to that annotation's default backend service (instead of the global default backend).
|
||||||
|
Different ingresses can specify different sets of errors codes and there are UseCases where NGINX shall not intercept all errors returned from upstream.
|
||||||
|
If `disable-proxy-intercept-errors` is also specified globally, the annotation will override the global value for the given ingress' hostname and path.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
```
|
||||||
|
nginx.ingress.kubernetes.io/disable-proxy-intercept-errors: "false"
|
||||||
|
```
|
||||||
|
|
||||||
### Default Backend
|
### Default Backend
|
||||||
|
|
||||||
This annotation is of the form `nginx.ingress.kubernetes.io/default-backend: <svc name>` to specify a custom default backend. This `<svc name>` is a reference to a service inside of the same namespace in which you are applying this annotation. This annotation overrides the global default backend. In case the service has [multiple ports](https://kubernetes.io/docs/concepts/services-networking/service/#multi-port-services), the first one is the one which will receive the backend traffic.
|
This annotation is of the form `nginx.ingress.kubernetes.io/default-backend: <svc name>` to specify a custom default backend. This `<svc name>` is a reference to a service inside of the same namespace in which you are applying this annotation. This annotation overrides the global default backend. In case the service has [multiple ports](https://kubernetes.io/docs/concepts/services-networking/service/#multi-port-services), the first one is the one which will receive the backend traffic.
|
||||||
|
|
|
@ -180,6 +180,7 @@ The following table shows a configuration option's name, type, and the default v
|
||||||
|[stream-snippet](#stream-snippet)|string|""||
|
|[stream-snippet](#stream-snippet)|string|""||
|
||||||
|[location-snippet](#location-snippet)|string|""||
|
|[location-snippet](#location-snippet)|string|""||
|
||||||
|[custom-http-errors](#custom-http-errors)|[]int|[]int{}||
|
|[custom-http-errors](#custom-http-errors)|[]int|[]int{}||
|
||||||
|
|[disable-proxy-intercept-errors](#disable-proxy-intercept-errors)|bool|"false"|
|
||||||
|[proxy-body-size](#proxy-body-size)|string|"1m"||
|
|[proxy-body-size](#proxy-body-size)|string|"1m"||
|
||||||
|[proxy-connect-timeout](#proxy-connect-timeout)|int|5||
|
|[proxy-connect-timeout](#proxy-connect-timeout)|int|5||
|
||||||
|[proxy-read-timeout](#proxy-read-timeout)|int|60||
|
|[proxy-read-timeout](#proxy-read-timeout)|int|60||
|
||||||
|
@ -1128,10 +1129,18 @@ You can not use this to add new locations that proxy to the Kubernetes pods, as
|
||||||
|
|
||||||
Enables which HTTP codes should be passed for processing with the [error_page directive](https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page)
|
Enables which HTTP codes should be passed for processing with the [error_page directive](https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page)
|
||||||
|
|
||||||
Setting at least one code also enables [proxy_intercept_errors](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors) which are required to process error_page.
|
Setting at least one code also enables [proxy_intercept_errors](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors) if not disabled with [disable-proxy-intercept-errors](#disable-proxy-intercept-errors).
|
||||||
|
|
||||||
Example usage: `custom-http-errors: 404,415`
|
Example usage: `custom-http-errors: 404,415`
|
||||||
|
|
||||||
|
## disable-proxy-intercept-errors
|
||||||
|
|
||||||
|
Allows to disable [proxy-intercept-errors](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors).
|
||||||
|
|
||||||
|
Disabling [proxy_intercept_errors](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors) allows to pass upstream errors to client even if [custom-http-errors](#custom-http-errors) are set.
|
||||||
|
|
||||||
|
Example usage: `disable-proxy-intercept-errors: "true"`
|
||||||
|
|
||||||
## proxy-body-size
|
## proxy-body-size
|
||||||
|
|
||||||
Sets the maximum allowed size of the client request body.
|
Sets the maximum allowed size of the client request body.
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"dario.cat/mergo"
|
"dario.cat/mergo"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/annotations/disableproxyintercepterrors"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/opentelemetry"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/opentelemetry"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
|
||||||
|
@ -75,46 +76,47 @@ const DeniedKeyName = "Denied"
|
||||||
// Ingress defines the valid annotations present in one NGINX Ingress rule
|
// Ingress defines the valid annotations present in one NGINX Ingress rule
|
||||||
type Ingress struct {
|
type Ingress struct {
|
||||||
metav1.ObjectMeta
|
metav1.ObjectMeta
|
||||||
BackendProtocol string
|
BackendProtocol string
|
||||||
Aliases []string
|
Aliases []string
|
||||||
BasicDigestAuth auth.Config
|
BasicDigestAuth auth.Config
|
||||||
Canary canary.Config
|
Canary canary.Config
|
||||||
CertificateAuth authtls.Config
|
CertificateAuth authtls.Config
|
||||||
ClientBodyBufferSize string
|
ClientBodyBufferSize string
|
||||||
ConfigurationSnippet string
|
ConfigurationSnippet string
|
||||||
Connection connection.Config
|
Connection connection.Config
|
||||||
CorsConfig cors.Config
|
CorsConfig cors.Config
|
||||||
CustomHTTPErrors []int
|
CustomHTTPErrors []int
|
||||||
DefaultBackend *apiv1.Service
|
DisableProxyInterceptErrors bool
|
||||||
FastCGI fastcgi.Config
|
DefaultBackend *apiv1.Service
|
||||||
Denied *string
|
FastCGI fastcgi.Config
|
||||||
ExternalAuth authreq.Config
|
Denied *string
|
||||||
EnableGlobalAuth bool
|
ExternalAuth authreq.Config
|
||||||
HTTP2PushPreload bool
|
EnableGlobalAuth bool
|
||||||
Opentelemetry opentelemetry.Config
|
HTTP2PushPreload bool
|
||||||
Proxy proxy.Config
|
Opentelemetry opentelemetry.Config
|
||||||
ProxySSL proxyssl.Config
|
Proxy proxy.Config
|
||||||
RateLimit ratelimit.Config
|
ProxySSL proxyssl.Config
|
||||||
GlobalRateLimit globalratelimit.Config
|
RateLimit ratelimit.Config
|
||||||
Redirect redirect.Config
|
GlobalRateLimit globalratelimit.Config
|
||||||
Rewrite rewrite.Config
|
Redirect redirect.Config
|
||||||
Satisfy string
|
Rewrite rewrite.Config
|
||||||
ServerSnippet string
|
Satisfy string
|
||||||
ServiceUpstream bool
|
ServerSnippet string
|
||||||
SessionAffinity sessionaffinity.Config
|
ServiceUpstream bool
|
||||||
SSLPassthrough bool
|
SessionAffinity sessionaffinity.Config
|
||||||
UsePortInRedirects bool
|
SSLPassthrough bool
|
||||||
UpstreamHashBy upstreamhashby.Config
|
UsePortInRedirects bool
|
||||||
LoadBalancing string
|
UpstreamHashBy upstreamhashby.Config
|
||||||
UpstreamVhost string
|
LoadBalancing string
|
||||||
Denylist ipdenylist.SourceRange
|
UpstreamVhost string
|
||||||
XForwardedPrefix string
|
Denylist ipdenylist.SourceRange
|
||||||
SSLCipher sslcipher.Config
|
XForwardedPrefix string
|
||||||
Logs log.Config
|
SSLCipher sslcipher.Config
|
||||||
ModSecurity modsecurity.Config
|
Logs log.Config
|
||||||
Mirror mirror.Config
|
ModSecurity modsecurity.Config
|
||||||
StreamSnippet string
|
Mirror mirror.Config
|
||||||
Allowlist ipallowlist.SourceRange
|
StreamSnippet string
|
||||||
|
Allowlist ipallowlist.SourceRange
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extractor defines the annotation parsers to be used in the extraction of annotations
|
// Extractor defines the annotation parsers to be used in the extraction of annotations
|
||||||
|
@ -126,45 +128,46 @@ type Extractor struct {
|
||||||
func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
||||||
return Extractor{
|
return Extractor{
|
||||||
map[string]parser.IngressAnnotation{
|
map[string]parser.IngressAnnotation{
|
||||||
"Aliases": alias.NewParser(cfg),
|
"Aliases": alias.NewParser(cfg),
|
||||||
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
||||||
"Canary": canary.NewParser(cfg),
|
"Canary": canary.NewParser(cfg),
|
||||||
"CertificateAuth": authtls.NewParser(cfg),
|
"CertificateAuth": authtls.NewParser(cfg),
|
||||||
"ClientBodyBufferSize": clientbodybuffersize.NewParser(cfg),
|
"ClientBodyBufferSize": clientbodybuffersize.NewParser(cfg),
|
||||||
"ConfigurationSnippet": snippet.NewParser(cfg),
|
"ConfigurationSnippet": snippet.NewParser(cfg),
|
||||||
"Connection": connection.NewParser(cfg),
|
"Connection": connection.NewParser(cfg),
|
||||||
"CorsConfig": cors.NewParser(cfg),
|
"CorsConfig": cors.NewParser(cfg),
|
||||||
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
|
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
|
||||||
"DefaultBackend": defaultbackend.NewParser(cfg),
|
"DisableProxyInterceptErrors": disableproxyintercepterrors.NewParser(cfg),
|
||||||
"FastCGI": fastcgi.NewParser(cfg),
|
"DefaultBackend": defaultbackend.NewParser(cfg),
|
||||||
"ExternalAuth": authreq.NewParser(cfg),
|
"FastCGI": fastcgi.NewParser(cfg),
|
||||||
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
|
"ExternalAuth": authreq.NewParser(cfg),
|
||||||
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
|
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
|
||||||
"Opentelemetry": opentelemetry.NewParser(cfg),
|
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
|
||||||
"Proxy": proxy.NewParser(cfg),
|
"Opentelemetry": opentelemetry.NewParser(cfg),
|
||||||
"ProxySSL": proxyssl.NewParser(cfg),
|
"Proxy": proxy.NewParser(cfg),
|
||||||
"RateLimit": ratelimit.NewParser(cfg),
|
"ProxySSL": proxyssl.NewParser(cfg),
|
||||||
"GlobalRateLimit": globalratelimit.NewParser(cfg),
|
"RateLimit": ratelimit.NewParser(cfg),
|
||||||
"Redirect": redirect.NewParser(cfg),
|
"GlobalRateLimit": globalratelimit.NewParser(cfg),
|
||||||
"Rewrite": rewrite.NewParser(cfg),
|
"Redirect": redirect.NewParser(cfg),
|
||||||
"Satisfy": satisfy.NewParser(cfg),
|
"Rewrite": rewrite.NewParser(cfg),
|
||||||
"ServerSnippet": serversnippet.NewParser(cfg),
|
"Satisfy": satisfy.NewParser(cfg),
|
||||||
"ServiceUpstream": serviceupstream.NewParser(cfg),
|
"ServerSnippet": serversnippet.NewParser(cfg),
|
||||||
"SessionAffinity": sessionaffinity.NewParser(cfg),
|
"ServiceUpstream": serviceupstream.NewParser(cfg),
|
||||||
"SSLPassthrough": sslpassthrough.NewParser(cfg),
|
"SessionAffinity": sessionaffinity.NewParser(cfg),
|
||||||
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
"SSLPassthrough": sslpassthrough.NewParser(cfg),
|
||||||
"UpstreamHashBy": upstreamhashby.NewParser(cfg),
|
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
||||||
"LoadBalancing": loadbalancing.NewParser(cfg),
|
"UpstreamHashBy": upstreamhashby.NewParser(cfg),
|
||||||
"UpstreamVhost": upstreamvhost.NewParser(cfg),
|
"LoadBalancing": loadbalancing.NewParser(cfg),
|
||||||
"Allowlist": ipallowlist.NewParser(cfg),
|
"UpstreamVhost": upstreamvhost.NewParser(cfg),
|
||||||
"Denylist": ipdenylist.NewParser(cfg),
|
"Allowlist": ipallowlist.NewParser(cfg),
|
||||||
"XForwardedPrefix": xforwardedprefix.NewParser(cfg),
|
"Denylist": ipdenylist.NewParser(cfg),
|
||||||
"SSLCipher": sslcipher.NewParser(cfg),
|
"XForwardedPrefix": xforwardedprefix.NewParser(cfg),
|
||||||
"Logs": log.NewParser(cfg),
|
"SSLCipher": sslcipher.NewParser(cfg),
|
||||||
"BackendProtocol": backendprotocol.NewParser(cfg),
|
"Logs": log.NewParser(cfg),
|
||||||
"ModSecurity": modsecurity.NewParser(cfg),
|
"BackendProtocol": backendprotocol.NewParser(cfg),
|
||||||
"Mirror": mirror.NewParser(cfg),
|
"ModSecurity": modsecurity.NewParser(cfg),
|
||||||
"StreamSnippet": streamsnippet.NewParser(cfg),
|
"Mirror": mirror.NewParser(cfg),
|
||||||
|
"StreamSnippet": streamsnippet.NewParser(cfg),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes 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 disableproxyintercepterrors
|
||||||
|
|
||||||
|
import (
|
||||||
|
networking "k8s.io/api/networking/v1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
disableProxyInterceptErrorsAnnotation = "disable-proxy-intercept-errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var disableProxyInterceptErrorsAnnotations = parser.Annotation{
|
||||||
|
Group: "backend",
|
||||||
|
Annotations: parser.AnnotationFields{
|
||||||
|
disableProxyInterceptErrorsAnnotation: {
|
||||||
|
Validator: parser.ValidateBool,
|
||||||
|
Scope: parser.AnnotationScopeLocation,
|
||||||
|
Risk: parser.AnnotationRiskLow,
|
||||||
|
Documentation: `This annotation allows to disable NGINX proxy-intercept-errors when custom-http-errors are set.
|
||||||
|
If a default backend annotation is specified on the ingress, the errors will be routed to that annotation's default backend service (instead of the global default backend).
|
||||||
|
Different ingresses can specify different sets of errors codes and there are UseCases where NGINX shall not intercept all errors returned from upstream.`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type disableProxyInterceptErrors struct {
|
||||||
|
r resolver.Resolver
|
||||||
|
annotationConfig parser.Annotation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pie disableProxyInterceptErrors) GetDocumentation() parser.AnnotationFields {
|
||||||
|
return pie.annotationConfig.Annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pie disableProxyInterceptErrors) Validate(anns map[string]string) error {
|
||||||
|
maxrisk := parser.StringRiskToRisk(pie.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||||
|
return parser.CheckAnnotationRisk(anns, maxrisk, disableProxyInterceptErrorsAnnotations.Annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new disableProxyInterceptErrors annotation parser
|
||||||
|
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||||
|
return disableProxyInterceptErrors{
|
||||||
|
r: r,
|
||||||
|
annotationConfig: disableProxyInterceptErrorsAnnotations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pie disableProxyInterceptErrors) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
|
val, err := parser.GetBoolAnnotation(disableProxyInterceptErrorsAnnotation, ing, pie.annotationConfig.Annotations)
|
||||||
|
|
||||||
|
// A missing annotation is not a problem, just use the default
|
||||||
|
if err == errors.ErrMissingAnnotations {
|
||||||
|
return false, nil // default is false
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Kubernetes 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 disableproxyintercepterrors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
api "k8s.io/api/core/v1"
|
||||||
|
networking "k8s.io/api/networking/v1"
|
||||||
|
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIngress() *networking.Ingress {
|
||||||
|
return &networking.Ingress{
|
||||||
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: networking.IngressSpec{
|
||||||
|
DefaultBackend: &networking.IngressBackend{
|
||||||
|
Service: &networking.IngressServiceBackend{
|
||||||
|
Name: "default-backend",
|
||||||
|
Port: networking.ServiceBackendPort{
|
||||||
|
Number: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAnnotations(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
_, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[parser.GetAnnotationWithPrefix("disable-proxy-intercept-errors")] = "true"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
// test ingress using the annotation without a TLS section
|
||||||
|
_, err = NewParser(&resolver.Mock{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error parsing ingress with disable-proxy-intercept-errors")
|
||||||
|
}
|
||||||
|
}
|
|
@ -540,6 +540,10 @@ type Configuration struct {
|
||||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
|
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
|
||||||
ProxyHTTPVersion string `json:"proxy-http-version"`
|
ProxyHTTPVersion string `json:"proxy-http-version"`
|
||||||
|
|
||||||
|
// Disables NGINX proxy-intercept-errors when error_page/custom-http-errors are set
|
||||||
|
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors
|
||||||
|
DisableProxyInterceptErrors bool `json:"disable-proxy-intercept-errors,omitempty"`
|
||||||
|
|
||||||
// Sets the ipv4 addresses on which the server will accept requests.
|
// Sets the ipv4 addresses on which the server will accept requests.
|
||||||
BindAddressIpv4 []string `json:"bind-address-ipv4,omitempty"`
|
BindAddressIpv4 []string `json:"bind-address-ipv4,omitempty"`
|
||||||
|
|
||||||
|
@ -842,37 +846,39 @@ func NewDefault() Configuration {
|
||||||
VariablesHashBucketSize: 256,
|
VariablesHashBucketSize: 256,
|
||||||
VariablesHashMaxSize: 2048,
|
VariablesHashMaxSize: 2048,
|
||||||
UseHTTP2: true,
|
UseHTTP2: true,
|
||||||
|
DisableProxyInterceptErrors: false,
|
||||||
ProxyStreamTimeout: "600s",
|
ProxyStreamTimeout: "600s",
|
||||||
ProxyStreamNextUpstream: true,
|
ProxyStreamNextUpstream: true,
|
||||||
ProxyStreamNextUpstreamTimeout: "600s",
|
ProxyStreamNextUpstreamTimeout: "600s",
|
||||||
ProxyStreamNextUpstreamTries: 3,
|
ProxyStreamNextUpstreamTries: 3,
|
||||||
Backend: defaults.Backend{
|
Backend: defaults.Backend{
|
||||||
ProxyBodySize: bodySize,
|
ProxyBodySize: bodySize,
|
||||||
ProxyConnectTimeout: 5,
|
ProxyConnectTimeout: 5,
|
||||||
ProxyReadTimeout: 60,
|
ProxyReadTimeout: 60,
|
||||||
ProxySendTimeout: 60,
|
ProxySendTimeout: 60,
|
||||||
ProxyBuffersNumber: 4,
|
ProxyBuffersNumber: 4,
|
||||||
ProxyBufferSize: "4k",
|
ProxyBufferSize: "4k",
|
||||||
ProxyCookieDomain: "off",
|
ProxyCookieDomain: "off",
|
||||||
ProxyCookiePath: "off",
|
ProxyCookiePath: "off",
|
||||||
ProxyNextUpstream: "error timeout",
|
ProxyNextUpstream: "error timeout",
|
||||||
ProxyNextUpstreamTimeout: 0,
|
ProxyNextUpstreamTimeout: 0,
|
||||||
ProxyNextUpstreamTries: 3,
|
ProxyNextUpstreamTries: 3,
|
||||||
ProxyRequestBuffering: "on",
|
ProxyRequestBuffering: "on",
|
||||||
ProxyRedirectFrom: "off",
|
ProxyRedirectFrom: "off",
|
||||||
ProxyRedirectTo: "off",
|
ProxyRedirectTo: "off",
|
||||||
PreserveTrailingSlash: false,
|
PreserveTrailingSlash: false,
|
||||||
SSLRedirect: true,
|
SSLRedirect: true,
|
||||||
CustomHTTPErrors: []int{},
|
CustomHTTPErrors: []int{},
|
||||||
DenylistSourceRange: []string{},
|
DisableProxyInterceptErrors: false,
|
||||||
WhitelistSourceRange: []string{},
|
DenylistSourceRange: []string{},
|
||||||
SkipAccessLogURLs: []string{},
|
WhitelistSourceRange: []string{},
|
||||||
LimitRate: 0,
|
SkipAccessLogURLs: []string{},
|
||||||
LimitRateAfter: 0,
|
LimitRate: 0,
|
||||||
ProxyBuffering: "off",
|
LimitRateAfter: 0,
|
||||||
ProxyHTTPVersion: "1.1",
|
ProxyBuffering: "off",
|
||||||
ProxyMaxTempFileSize: "1024m",
|
ProxyHTTPVersion: "1.1",
|
||||||
ServiceUpstream: false,
|
ProxyMaxTempFileSize: "1024m",
|
||||||
|
ServiceUpstream: false,
|
||||||
},
|
},
|
||||||
UpstreamKeepaliveConnections: 320,
|
UpstreamKeepaliveConnections: 320,
|
||||||
UpstreamKeepaliveTime: "1h",
|
UpstreamKeepaliveTime: "1h",
|
||||||
|
|
|
@ -1526,6 +1526,7 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
|
||||||
loc.BackendProtocol = anns.BackendProtocol
|
loc.BackendProtocol = anns.BackendProtocol
|
||||||
loc.FastCGI = anns.FastCGI
|
loc.FastCGI = anns.FastCGI
|
||||||
loc.CustomHTTPErrors = anns.CustomHTTPErrors
|
loc.CustomHTTPErrors = anns.CustomHTTPErrors
|
||||||
|
loc.DisableProxyInterceptErrors = anns.DisableProxyInterceptErrors
|
||||||
loc.ModSecurity = anns.ModSecurity
|
loc.ModSecurity = anns.ModSecurity
|
||||||
loc.Satisfy = anns.Satisfy
|
loc.Satisfy = anns.Satisfy
|
||||||
loc.Mirror = anns.Mirror
|
loc.Mirror = anns.Mirror
|
||||||
|
|
|
@ -34,6 +34,13 @@ type Backend struct {
|
||||||
// toggles whether or not to remove trailing slashes during TLS redirects
|
// toggles whether or not to remove trailing slashes during TLS redirects
|
||||||
PreserveTrailingSlash bool `json:"preserve-trailing-slash"`
|
PreserveTrailingSlash bool `json:"preserve-trailing-slash"`
|
||||||
|
|
||||||
|
// allows usage of CustomHTTPErrors without intercepting service errors
|
||||||
|
// e.g. custom 404 and 503 when service-a does not exist or is not available
|
||||||
|
// but service-a can return 404 and 503 error codes without intercept
|
||||||
|
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors
|
||||||
|
// By default this is false
|
||||||
|
DisableProxyInterceptErrors bool `json:"disable-proxy-intercept-errors"`
|
||||||
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
||||||
// Sets the maximum allowed size of the client request body
|
// Sets the maximum allowed size of the client request body
|
||||||
ProxyBodySize string `json:"proxy-body-size"`
|
ProxyBodySize string `json:"proxy-body-size"`
|
||||||
|
|
|
@ -345,6 +345,11 @@ type Location struct {
|
||||||
// CustomHTTPErrors specifies the error codes that should be intercepted.
|
// CustomHTTPErrors specifies the error codes that should be intercepted.
|
||||||
// +optional
|
// +optional
|
||||||
CustomHTTPErrors []int `json:"custom-http-errors"`
|
CustomHTTPErrors []int `json:"custom-http-errors"`
|
||||||
|
// ProxyInterceptErrors disables error intecepting when using CustomHTTPErrors
|
||||||
|
// e.g. custom 404 and 503 when service-a does not exist or is not available
|
||||||
|
// but service-a can return 404 and 503 error codes without intercept
|
||||||
|
// +optional
|
||||||
|
DisableProxyInterceptErrors bool `json:"disable-proxy-intercept-errors"`
|
||||||
// ModSecurity allows to enable and configure modsecurity
|
// ModSecurity allows to enable and configure modsecurity
|
||||||
// +optional
|
// +optional
|
||||||
ModSecurity modsecurity.Config `json:"modsecurity"`
|
ModSecurity modsecurity.Config `json:"modsecurity"`
|
||||||
|
|
|
@ -466,6 +466,10 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if l1.DisableProxyInterceptErrors != l2.DisableProxyInterceptErrors {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -487,7 +487,7 @@ http {
|
||||||
ssl_certificate {{ $cfg.DefaultSSLCertificate.PemFileName }};
|
ssl_certificate {{ $cfg.DefaultSSLCertificate.PemFileName }};
|
||||||
ssl_certificate_key {{ $cfg.DefaultSSLCertificate.PemFileName }};
|
ssl_certificate_key {{ $cfg.DefaultSSLCertificate.PemFileName }};
|
||||||
|
|
||||||
{{ if gt (len $cfg.CustomHTTPErrors) 0 }}
|
{{ if and $cfg.CustomHTTPErrors (not $cfg.DisableProxyInterceptErrors) }}
|
||||||
proxy_intercept_errors on;
|
proxy_intercept_errors on;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
@ -1473,7 +1473,7 @@ stream {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{/* if a location-specific error override is set, add the proxy_intercept here */}}
|
{{/* if a location-specific error override is set, add the proxy_intercept here */}}
|
||||||
{{ if $location.CustomHTTPErrors }}
|
{{ if and $location.CustomHTTPErrors (not $location.DisableProxyInterceptErrors) }}
|
||||||
# Custom error pages per ingress
|
# Custom error pages per ingress
|
||||||
proxy_intercept_errors on;
|
proxy_intercept_errors on;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
100
test/e2e/annotations/disableproxyintercepterrors.go
Normal file
100
test/e2e/annotations/disableproxyintercepterrors.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes 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 annotations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
networking "k8s.io/api/networking/v1"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = framework.DescribeAnnotation("disable-proxy-intercept-errors", func() {
|
||||||
|
f := framework.NewDefaultFramework("disable-proxy-intercept-errors")
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
f.NewHttpbunDeployment()
|
||||||
|
f.NewEchoDeployment()
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("configures Nginx correctly", func() {
|
||||||
|
host := "pie.foo.com"
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/custom-http-errors": "404",
|
||||||
|
"nginx.ingress.kubernetes.io/disable-proxy-intercept-errors": "true",
|
||||||
|
"nginx.ingress.kubernetes.io/default-backend": framework.EchoService,
|
||||||
|
}
|
||||||
|
|
||||||
|
ingHTTPBunService := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBunService, 80, annotations)
|
||||||
|
f.EnsureIngress(ingHTTPBunService)
|
||||||
|
|
||||||
|
var serverConfig string
|
||||||
|
f.WaitForNginxServer(host, func(sc string) bool {
|
||||||
|
serverConfig = sc
|
||||||
|
return strings.Contains(serverConfig, fmt.Sprintf("server_name %s", host))
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.By("turning off proxy_intercept_errors directive")
|
||||||
|
assert.NotContains(ginkgo.GinkgoT(), serverConfig, "proxy_intercept_errors on;")
|
||||||
|
|
||||||
|
// the plan for client side testing
|
||||||
|
// create ingress where we disable intercept for code 404 - that error should get to the client
|
||||||
|
// the same ingress should intercept any other error (>300 and not 404) where we will get intercepted error
|
||||||
|
ginkgo.By("client test to check response - with intercept disabled")
|
||||||
|
requestID := "proxy_intercept_errors"
|
||||||
|
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET("/status/404").
|
||||||
|
WithHeader("Host", host).
|
||||||
|
WithHeader("x-request-id", requestID).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusNotFound).
|
||||||
|
Body().Empty()
|
||||||
|
|
||||||
|
ginkgo.By("client test to check response - with intercept enabled")
|
||||||
|
err := framework.UpdateIngress(f.KubeClientSet, f.Namespace, host, func(ingress *networking.Ingress) error {
|
||||||
|
ingress.ObjectMeta.Annotations["nginx.ingress.kubernetes.io/disable-proxy-intercept-errors"] = "false"
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host, func(sc string) bool {
|
||||||
|
if serverConfig != sc {
|
||||||
|
serverConfig = sc
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET("/status/404").
|
||||||
|
WithHeader("Host", host).
|
||||||
|
WithHeader("x-request-id", requestID).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Body().Contains("x-code=404").
|
||||||
|
Contains(fmt.Sprintf("x-ingress-name=%s", host)).
|
||||||
|
Contains(fmt.Sprintf("x-service-name=%s", framework.HTTPBunService)).
|
||||||
|
Contains(fmt.Sprintf("x-request-id=%s", requestID))
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue