mirror of
https://github.com/traefik/traefik.git
synced 2026-05-05 04:16:25 +02:00
Implement server-snippet and configuration-snippet annotations
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
6163601db0
commit
d680fef7f1
@ -393,6 +393,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
||||
| <a id="opt-providers-kubernetesingress-token" href="#opt-providers-kubernetesingress-token" title="#opt-providers-kubernetesingress-token">providers.kubernetesingress.token</a> | Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. | |
|
||||
| <a id="opt-providers-kubernetesingressnginx" href="#opt-providers-kubernetesingressnginx" title="#opt-providers-kubernetesingressnginx">providers.kubernetesingressnginx</a> | Enables Kubernetes Ingress NGINX provider. | false |
|
||||
| <a id="opt-providers-kubernetesingressnginx-allowcrossnamespaceresources" href="#opt-providers-kubernetesingressnginx-allowcrossnamespaceresources" title="#opt-providers-kubernetesingressnginx-allowcrossnamespaceresources">providers.kubernetesingressnginx.allowcrossnamespaceresources</a> | Allow Ingress to reference resources (e.g. ConfigMaps, Secrets) in different namespaces. | false |
|
||||
| <a id="opt-providers-kubernetesingressnginx-allowsnippetannotations" href="#opt-providers-kubernetesingressnginx-allowsnippetannotations" title="#opt-providers-kubernetesingressnginx-allowsnippetannotations">providers.kubernetesingressnginx.allowsnippetannotations</a> | Enables to parse and add -snippet annotations/directives. | false |
|
||||
| <a id="opt-providers-kubernetesingressnginx-certauthfilepath" href="#opt-providers-kubernetesingressnginx-certauthfilepath" title="#opt-providers-kubernetesingressnginx-certauthfilepath">providers.kubernetesingressnginx.certauthfilepath</a> | Kubernetes certificate authority file path (not needed for in-cluster client). | |
|
||||
| <a id="opt-providers-kubernetesingressnginx-clientbodybuffersize" href="#opt-providers-kubernetesingressnginx-clientbodybuffersize" title="#opt-providers-kubernetesingressnginx-clientbodybuffersize">providers.kubernetesingressnginx.clientbodybuffersize</a> | Default buffer size for reading client request body. | 16384 |
|
||||
| <a id="opt-providers-kubernetesingressnginx-controllerclass" href="#opt-providers-kubernetesingressnginx-controllerclass" title="#opt-providers-kubernetesingressnginx-controllerclass">providers.kubernetesingressnginx.controllerclass</a> | Ingress Class Controller value this controller satisfies. | k8s.io/ingress-nginx |
|
||||
|
||||
@ -278,16 +278,16 @@ The following annotations are organized by category for easier navigation.
|
||||
|
||||
### Authentication
|
||||
|
||||
| Annotation | Limitations / Notes |
|
||||
|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-type" href="#opt-nginx-ingress-kubernetes-ioauth-type" title="#opt-nginx-ingress-kubernetes-ioauth-type">`nginx.ingress.kubernetes.io/auth-type`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-secret" href="#opt-nginx-ingress-kubernetes-ioauth-secret" title="#opt-nginx-ingress-kubernetes-ioauth-secret">`nginx.ingress.kubernetes.io/auth-secret`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-secret-type" href="#opt-nginx-ingress-kubernetes-ioauth-secret-type" title="#opt-nginx-ingress-kubernetes-ioauth-secret-type">`nginx.ingress.kubernetes.io/auth-secret-type`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-realm" href="#opt-nginx-ingress-kubernetes-ioauth-realm" title="#opt-nginx-ingress-kubernetes-ioauth-realm">`nginx.ingress.kubernetes.io/auth-realm`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-url" href="#opt-nginx-ingress-kubernetes-ioauth-url" title="#opt-nginx-ingress-kubernetes-ioauth-url">`nginx.ingress.kubernetes.io/auth-url`</a> | Only URL and response headers copy supported. Forward auth behaves differently than NGINX. It supports minimal variable interpolation by using the following NGINX variables: `$scheme`, `$host`, `$http_*`, `$best_http_host`, `$hostname`, `$request_uri`, `$escaped_request_uri`, `$query_string`, `$args`, `$arg_*`, `$remote_addr`. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-signin" href="#opt-nginx-ingress-kubernetes-ioauth-signin" title="#opt-nginx-ingress-kubernetes-ioauth-signin">`nginx.ingress.kubernetes.io/auth-signin`</a> | Redirects to signin URL on 401 response. It supports minimal variable interpolation by using the following NGINX variables: `$scheme`, `$host`, `$http_*`, `$best_http_host`, `$hostname`, `$request_uri`, `$escaped_request_uri`, `$query_string`, `$args`, `$arg_*`, `$remote_addr`. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-method" href="#opt-nginx-ingress-kubernetes-ioauth-method" title="#opt-nginx-ingress-kubernetes-ioauth-method">`nginx.ingress.kubernetes.io/auth-method`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-response-headers" href="#opt-nginx-ingress-kubernetes-ioauth-response-headers" title="#opt-nginx-ingress-kubernetes-ioauth-response-headers">`nginx.ingress.kubernetes.io/auth-response-headers`</a> | |
|
||||
| Annotation | Limitations / Notes |
|
||||
|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-type" href="#opt-nginx-ingress-kubernetes-ioauth-type" title="#opt-nginx-ingress-kubernetes-ioauth-type">`nginx.ingress.kubernetes.io/auth-type`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-secret" href="#opt-nginx-ingress-kubernetes-ioauth-secret" title="#opt-nginx-ingress-kubernetes-ioauth-secret">`nginx.ingress.kubernetes.io/auth-secret`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-secret-type" href="#opt-nginx-ingress-kubernetes-ioauth-secret-type" title="#opt-nginx-ingress-kubernetes-ioauth-secret-type">`nginx.ingress.kubernetes.io/auth-secret-type`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-realm" href="#opt-nginx-ingress-kubernetes-ioauth-realm" title="#opt-nginx-ingress-kubernetes-ioauth-realm">`nginx.ingress.kubernetes.io/auth-realm`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-url" href="#opt-nginx-ingress-kubernetes-ioauth-url" title="#opt-nginx-ingress-kubernetes-ioauth-url">`nginx.ingress.kubernetes.io/auth-url`</a> | Only URL and response headers copy supported. Forward auth behaves differently than NGINX. It supports minimal variable interpolation by using the following NGINX variables: `$scheme`, `$host`, `$http_*`, `$hostname`, `$request_uri`, `$request_method`, `$query_string`, `$args`, `$arg_*`, `$remote_addr`, `$uri`, `$document_uri`, `$server_name`, `$server_port`, `$content_type`, `$content_length`, `$cookie_*`, `$is_args`, `$best_http_host`, `$escaped_request_uri`, `$proxy_add_x_forwarded_for`. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-signin" href="#opt-nginx-ingress-kubernetes-ioauth-signin" title="#opt-nginx-ingress-kubernetes-ioauth-signin">`nginx.ingress.kubernetes.io/auth-signin`</a> | Redirects to signin URL on 401 response. It supports minimal variable interpolation by using the following NGINX variables: `$scheme`, `$host`, `$http_*`, `$hostname`, `$request_uri`, `$request_method`, `$query_string`, `$args`, `$arg_*`, `$remote_addr`, `$uri`, `$document_uri`, `$server_name`, `$server_port`, `$content_type`, `$content_length`, `$cookie_*`, `$is_args`, `$best_http_host`, `$escaped_request_uri`, `$proxy_add_x_forwarded_for`. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-method" href="#opt-nginx-ingress-kubernetes-ioauth-method" title="#opt-nginx-ingress-kubernetes-ioauth-method">`nginx.ingress.kubernetes.io/auth-method`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-response-headers" href="#opt-nginx-ingress-kubernetes-ioauth-response-headers" title="#opt-nginx-ingress-kubernetes-ioauth-response-headers">`nginx.ingress.kubernetes.io/auth-response-headers`</a> | |
|
||||
|
||||
### SSL/TLS
|
||||
|
||||
@ -345,18 +345,20 @@ The following annotations are organized by category for easier navigation.
|
||||
|
||||
### Routing
|
||||
|
||||
| Annotation | Limitations / Notes |
|
||||
|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioapp-root" href="#opt-nginx-ingress-kubernetes-ioapp-root" title="#opt-nginx-ingress-kubernetes-ioapp-root">`nginx.ingress.kubernetes.io/app-root`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iofrom-to-www-redirect" href="#opt-nginx-ingress-kubernetes-iofrom-to-www-redirect" title="#opt-nginx-ingress-kubernetes-iofrom-to-www-redirect">`nginx.ingress.kubernetes.io/from-to-www-redirect`</a> | Doesn't support wildcard hosts. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iouse-regex" href="#opt-nginx-ingress-kubernetes-iouse-regex" title="#opt-nginx-ingress-kubernetes-iouse-regex">`nginx.ingress.kubernetes.io/use-regex`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iorewrite-target" href="#opt-nginx-ingress-kubernetes-iorewrite-target" title="#opt-nginx-ingress-kubernetes-iorewrite-target">`nginx.ingress.kubernetes.io/rewrite-target`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iopermanent-redirect" href="#opt-nginx-ingress-kubernetes-iopermanent-redirect" title="#opt-nginx-ingress-kubernetes-iopermanent-redirect">`nginx.ingress.kubernetes.io/permanent-redirect`</a> | Defaults to a 301 Moved Permanently status code. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iopermanent-redirect-code" href="#opt-nginx-ingress-kubernetes-iopermanent-redirect-code" title="#opt-nginx-ingress-kubernetes-iopermanent-redirect-code">`nginx.ingress.kubernetes.io/permanent-redirect-code`</a> | Only valid 3XX HTTP Status Codes are accepted. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iotemporal-redirect" href="#opt-nginx-ingress-kubernetes-iotemporal-redirect" title="#opt-nginx-ingress-kubernetes-iotemporal-redirect">`nginx.ingress.kubernetes.io/temporal-redirect`</a> | Takes precedence over the `permanent-redirect` annotation. Defaults to a 302 Found status code. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iotemporal-redirect-code" href="#opt-nginx-ingress-kubernetes-iotemporal-redirect-code" title="#opt-nginx-ingress-kubernetes-iotemporal-redirect-code">`nginx.ingress.kubernetes.io/temporal-redirect-code`</a> | Only valid 3XX HTTP Status Codes are accepted. |
|
||||
| Annotation | Limitations / Notes |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioapp-root" href="#opt-nginx-ingress-kubernetes-ioapp-root" title="#opt-nginx-ingress-kubernetes-ioapp-root">`nginx.ingress.kubernetes.io/app-root`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iofrom-to-www-redirect" href="#opt-nginx-ingress-kubernetes-iofrom-to-www-redirect" title="#opt-nginx-ingress-kubernetes-iofrom-to-www-redirect">`nginx.ingress.kubernetes.io/from-to-www-redirect`</a> | Doesn't support wildcard hosts. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iouse-regex" href="#opt-nginx-ingress-kubernetes-iouse-regex" title="#opt-nginx-ingress-kubernetes-iouse-regex">`nginx.ingress.kubernetes.io/use-regex`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iorewrite-target" href="#opt-nginx-ingress-kubernetes-iorewrite-target" title="#opt-nginx-ingress-kubernetes-iorewrite-target">`nginx.ingress.kubernetes.io/rewrite-target`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iopermanent-redirect" href="#opt-nginx-ingress-kubernetes-iopermanent-redirect" title="#opt-nginx-ingress-kubernetes-iopermanent-redirect">`nginx.ingress.kubernetes.io/permanent-redirect`</a> | Defaults to a 301 Moved Permanently status code. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iopermanent-redirect-code" href="#opt-nginx-ingress-kubernetes-iopermanent-redirect-code" title="#opt-nginx-ingress-kubernetes-iopermanent-redirect-code">`nginx.ingress.kubernetes.io/permanent-redirect-code`</a> | Only valid 3XX HTTP Status Codes are accepted. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iotemporal-redirect" href="#opt-nginx-ingress-kubernetes-iotemporal-redirect" title="#opt-nginx-ingress-kubernetes-iotemporal-redirect">`nginx.ingress.kubernetes.io/temporal-redirect`</a> | Takes precedence over the `permanent-redirect` annotation. Defaults to a 302 Found status code. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iotemporal-redirect-code" href="#opt-nginx-ingress-kubernetes-iotemporal-redirect-code" title="#opt-nginx-ingress-kubernetes-iotemporal-redirect-code">`nginx.ingress.kubernetes.io/temporal-redirect-code`</a> | Only valid 3XX HTTP Status Codes are accepted. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iocustom-http-errors" href="#opt-nginx-ingress-kubernetes-iocustom-http-errors" title="#opt-nginx-ingress-kubernetes-iocustom-http-errors">`nginx.ingress.kubernetes.io/custom-http-errors`</a> | Specifies a comma-separated list of HTTP status codes that should be intercepted and served by an error page backend. When any of these status codes occur, the request is forwarded to the global default backend, or to the backend defined by the [default-backend](#opt-nginx-ingress-kubernetes-iodefault-backend) annotation if specified. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioserver-alias" href="#opt-nginx-ingress-kubernetes-ioserver-alias" title="#opt-nginx-ingress-kubernetes-ioserver-alias">`nginx.ingress.kubernetes.io/server-alias`</a> | Ignored if the alias conflicts with an existing Ingress Host rule. Ingress Host rules always take precedence. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioserver-snippet" href="#opt-nginx-ingress-kubernetes-ioserver-snippet" title="#opt-nginx-ingress-kubernetes-ioserver-snippet">`nginx.ingress.kubernetes.io/server-snippet`</a> | Supported directives: `add_header`, `more_set_headers`, `proxy_set_header`, `more_set_input_headers`, `set`, `if`, `return code [text]`. It supports minimal variable interpolation by using the following NGINX variables: `$scheme`, `$host`, `$http_*`, `$hostname`, `$request_uri`, `$request_method`, `$query_string`, `$args`, `$arg_*`, `$remote_addr`, `$uri`, `$document_uri`, `$server_name`, `$server_port`, `$content_type`, `$content_length`, `$cookie_*`, `$is_args`, `$best_http_host`, `$escaped_request_uri`, `$proxy_add_x_forwarded_for`. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioconfiguration-snippet" href="#opt-nginx-ingress-kubernetes-ioconfiguration-snippet" title="#opt-nginx-ingress-kubernetes-ioconfiguration-snippet">`nginx.ingress.kubernetes.io/configuration-snippet`</a> | Supported directives: `add_header`, `more_set_headers`, `proxy_set_header`, `more_set_input_headers`, `set`, `if`, `return code [text]`. It supports minimal variable interpolation by using the following NGINX variables: `$scheme`, `$host`, `$http_*`, `$hostname`, `$request_uri`, `$request_method`, `$query_string`, `$args`, `$arg_*`, `$remote_addr`, `$uri`, `$document_uri`, `$server_name`, `$server_port`, `$content_type`, `$content_length`, `$cookie_*`, `$is_args`, `$best_http_host`, `$escaped_request_uri`, `$proxy_add_x_forwarded_for`. |
|
||||
|
||||
### IP Whitelist
|
||||
|
||||
@ -438,7 +440,6 @@ The following annotations are organized by category for easier navigation.
|
||||
| <a id="opt-nginx-ingress-kubernetes-iocanary-by-cookie" href="#opt-nginx-ingress-kubernetes-iocanary-by-cookie" title="#opt-nginx-ingress-kubernetes-iocanary-by-cookie">`nginx.ingress.kubernetes.io/canary-by-cookie`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iocanary-weight" href="#opt-nginx-ingress-kubernetes-iocanary-weight" title="#opt-nginx-ingress-kubernetes-iocanary-weight">`nginx.ingress.kubernetes.io/canary-weight`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iocanary-weight-total" href="#opt-nginx-ingress-kubernetes-iocanary-weight-total" title="#opt-nginx-ingress-kubernetes-iocanary-weight-total">`nginx.ingress.kubernetes.io/canary-weight-total`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioconfiguration-snippet" href="#opt-nginx-ingress-kubernetes-ioconfiguration-snippet" title="#opt-nginx-ingress-kubernetes-ioconfiguration-snippet">`nginx.ingress.kubernetes.io/configuration-snippet`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors" href="#opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors" title="#opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors">`nginx.ingress.kubernetes.io/disable-proxy-intercept-errors`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iolimit-rate-after" href="#opt-nginx-ingress-kubernetes-iolimit-rate-after" title="#opt-nginx-ingress-kubernetes-iolimit-rate-after">`nginx.ingress.kubernetes.io/limit-rate-after`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iolimit-rate" href="#opt-nginx-ingress-kubernetes-iolimit-rate" title="#opt-nginx-ingress-kubernetes-iolimit-rate">`nginx.ingress.kubernetes.io/limit-rate`</a> | |
|
||||
@ -461,7 +462,6 @@ The following annotations are organized by category for easier navigation.
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioproxy-ssl-protocols" href="#opt-nginx-ingress-kubernetes-ioproxy-ssl-protocols" title="#opt-nginx-ingress-kubernetes-ioproxy-ssl-protocols">`nginx.ingress.kubernetes.io/proxy-ssl-protocols`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioenable-rewrite-log" href="#opt-nginx-ingress-kubernetes-ioenable-rewrite-log" title="#opt-nginx-ingress-kubernetes-ioenable-rewrite-log">`nginx.ingress.kubernetes.io/enable-rewrite-log`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iosatisfy" href="#opt-nginx-ingress-kubernetes-iosatisfy" title="#opt-nginx-ingress-kubernetes-iosatisfy">`nginx.ingress.kubernetes.io/satisfy`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioserver-snippet" href="#opt-nginx-ingress-kubernetes-ioserver-snippet" title="#opt-nginx-ingress-kubernetes-ioserver-snippet">`nginx.ingress.kubernetes.io/server-snippet`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iosession-cookie-conditional-samesite-none" href="#opt-nginx-ingress-kubernetes-iosession-cookie-conditional-samesite-none" title="#opt-nginx-ingress-kubernetes-iosession-cookie-conditional-samesite-none">`nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iosession-cookie-change-on-failure" href="#opt-nginx-ingress-kubernetes-iosession-cookie-change-on-failure" title="#opt-nginx-ingress-kubernetes-iosession-cookie-change-on-failure">`nginx.ingress.kubernetes.io/session-cookie-change-on-failure`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iossl-ciphers" href="#opt-nginx-ingress-kubernetes-iossl-ciphers" title="#opt-nginx-ingress-kubernetes-iossl-ciphers">`nginx.ingress.kubernetes.io/ssl-ciphers`</a> | |
|
||||
|
||||
1
go.mod
1
go.mod
@ -74,6 +74,7 @@ require (
|
||||
github.com/traefik/paerser v0.2.2
|
||||
github.com/traefik/traefik/dynamic/ext v0.0.0-00010101000000-000000000000
|
||||
github.com/traefik/yaegi v0.16.1
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701 // latest tag is too old.
|
||||
github.com/unrolled/render v1.0.2
|
||||
github.com/unrolled/secure v1.0.9
|
||||
github.com/valyala/fasthttp v1.58.0
|
||||
|
||||
8
go.sum
8
go.sum
@ -742,6 +742,8 @@ github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhK
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/imega/luaformatter v0.0.0-20211025140405-86b0a68d6bef h1:RC993DdTIHNItsyLj79fgZNLzrf9tBN0GR6W5ZPms6s=
|
||||
github.com/imega/luaformatter v0.0.0-20211025140405-86b0a68d6bef/go.mod h1:i2XCfvmO94HrEOQWllihhtPrkvNfuB2R2p/o6+OVnRU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.7.0 h1:QgP5mlBE9sGnzplpnf96pr+p7uqlIlL4W2GAP3n+XZg=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.7.0/go.mod h1:Y/0W1+TZir7ypoQZYd2IrnVOKB3Tq6oegAQeSVN/+EU=
|
||||
@ -1274,6 +1276,10 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ=
|
||||
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
|
||||
github.com/timtadh/lexmachine v0.2.2 h1:g55RnjdYazm5wnKv59pwFcBJHOyvTPfDEoz21s4PHmY=
|
||||
github.com/timtadh/lexmachine v0.2.2/go.mod h1:GBJvD5OAfRn/gnp92zb9KTgHLB7akKyxmVivoYCcjQI=
|
||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
@ -1294,6 +1300,8 @@ github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
||||
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
||||
github.com/transip/gotransip/v6 v6.26.1 h1:MeqIjkTBBsZwWAK6giZyMkqLmKMclVHEuTNmoBdx4MA=
|
||||
github.com/transip/gotransip/v6 v6.26.1/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701 h1:JgeHIJzRSEdcuLXufZrni5+a4yDnBhQG+DdKhqCFhq0=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701/go.mod h1:ALbEe81QPWOZjDKCKNWodG2iqCMtregG8+ebQgjx2+4=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
|
||||
@ -58,6 +58,7 @@ type Middleware struct {
|
||||
|
||||
// ingress-nginx middlewares.
|
||||
AuthTLSPassCertificateToUpstream *AuthTLSPassCertificateToUpstream `json:"authTLSPassCertificateToUpstream,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
Snippet *Snippet `json:"snippet,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
@ -895,3 +896,11 @@ type URLRewrite struct {
|
||||
Path *string `json:"path,omitempty"`
|
||||
PathPrefix *string `json:"pathPrefix,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Snippet holds the NGINX snippet configuration.
|
||||
type Snippet struct {
|
||||
ServerSnippet string `json:"serverSnippet,omitempty"`
|
||||
ConfigurationSnippet string `json:"configurationSnippet,omitempty"`
|
||||
}
|
||||
|
||||
@ -1112,6 +1112,11 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
|
||||
*out = new(AuthTLSPassCertificateToUpstream)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Snippet != nil {
|
||||
in, out := &in.Snippet, &out.Snippet
|
||||
*out = new(Snippet)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -1810,6 +1815,22 @@ func (in *Service) DeepCopy() *Service {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Snippet) DeepCopyInto(out *Snippet) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Snippet.
|
||||
func (in *Snippet) DeepCopy() *Snippet {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Snippet)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SourceCriterion) DeepCopyInto(out *SourceCriterion) {
|
||||
*out = *in
|
||||
|
||||
@ -157,7 +157,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
address := fa.address
|
||||
if fa.interpolate {
|
||||
address = ingressnginx.ReplaceVariables(address, req)
|
||||
address = ingressnginx.ReplaceVariables(address, req, nil)
|
||||
}
|
||||
|
||||
forwardReqMethod := http.MethodGet
|
||||
@ -268,7 +268,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// If the signin URL doesn't contain "rd=" parameter,
|
||||
// add it with the original request URL to match the NGINX behavior.
|
||||
if !strings.Contains(signinURL, "rd=") {
|
||||
suffix := "rd=$scheme://$host$escaped_request_uri"
|
||||
suffix := "rd=$scheme://$best_http_host$escaped_request_uri"
|
||||
if !strings.Contains(signinURL, "?") {
|
||||
signinURL += "?" + suffix
|
||||
} else {
|
||||
@ -276,7 +276,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
signinURL = ingressnginx.ReplaceVariables(signinURL, req)
|
||||
signinURL = ingressnginx.ReplaceVariables(signinURL, req, nil)
|
||||
}
|
||||
|
||||
tracer.CaptureResponse(forwardSpan, forwardResponse.Header, http.StatusFound, trace.SpanKindClient)
|
||||
|
||||
@ -2,8 +2,10 @@ package ingressnginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@ -18,23 +20,39 @@ const (
|
||||
httpHeaders = "$http_"
|
||||
hostname = "$hostname"
|
||||
requestURI = "$request_uri"
|
||||
requestMethod = "$request_method"
|
||||
queryString = "$query_string"
|
||||
args = "$args"
|
||||
arg = "$arg_"
|
||||
remoteAddress = "$remote_addr"
|
||||
uri = "$uri"
|
||||
documentURI = "$document_uri"
|
||||
serverName = "$server_name"
|
||||
serverPort = "$server_port"
|
||||
contentType = "$content_type"
|
||||
contentLength = "$content_length"
|
||||
cookie = "$cookie_"
|
||||
isArgs = "$is_args"
|
||||
|
||||
// Variables set by ingress-nginx template.
|
||||
bestHTTPHost = "$best_http_host"
|
||||
escapedRequestURI = "$escaped_request_uri"
|
||||
bestHTTPHost = "$best_http_host"
|
||||
escapedRequestURI = "$escaped_request_uri"
|
||||
proxyAddXForwardedFor = "$proxy_add_x_forwarded_for"
|
||||
)
|
||||
|
||||
// varRegexp is a regular expression to match NGINX variables in the form of $variable or $variable_name.
|
||||
var varRegexp = regexp.MustCompile(`\$[a-zA-Z_][a-zA-Z0-9_]*`)
|
||||
// varRegexp is a regular expression to match NGINX variables in the form of $variable, $variable_name,
|
||||
// or capture group references $1-$9.
|
||||
var varRegexp = regexp.MustCompile(`\$[a-zA-Z_][a-zA-Z0-9_]*|\$[1-9]`)
|
||||
|
||||
// ReplaceVariables replaces NGINX variables in the given string with their corresponding values from the HTTP request.
|
||||
func ReplaceVariables(str string, req *http.Request) string {
|
||||
// Today this supports the `$scheme`, `$host`, `$http_*`, `$best_http_host`, `$hostname`, `$request_uri`,
|
||||
// `$escaped_request_uri`, `$query_string`, `$args`, `$arg_*`, `$remote_addr`, `$request_method`,
|
||||
// `$uri`, `$document_uri`, `$server_name`, `$server_port`, `$content_type`, `$content_length`,
|
||||
// `$cookie_*`, `$is_args`, and `$proxy_add_x_forwarded_for` variables.
|
||||
// Custom variables can be passed through the vars param.
|
||||
func ReplaceVariables(str string, req *http.Request, vars map[string]string) string {
|
||||
return varRegexp.ReplaceAllStringFunc(str, func(variable string) string {
|
||||
val, err := variableValue(variable, req)
|
||||
val, err := variableValue(variable, req, vars)
|
||||
if err != nil {
|
||||
log.Ctx(req.Context()).Debug().Err(err).Msgf("Error replacing variable: %s", variable)
|
||||
return variable
|
||||
@ -43,8 +61,8 @@ func ReplaceVariables(str string, req *http.Request) string {
|
||||
})
|
||||
}
|
||||
|
||||
// variableValue returns the value of the given NGINX variable based on the HTTP request.
|
||||
func variableValue(variable string, req *http.Request) (string, error) {
|
||||
// variableValue returns the value of the given NGINX variable based on the HTTP request and the custom vars map.
|
||||
func variableValue(variable string, req *http.Request, vars map[string]string) (string, error) {
|
||||
// $http_name variables are used to access HTTP headers in the request.
|
||||
if header, ok := strings.CutPrefix(variable, httpHeaders); ok {
|
||||
return strings.Join(req.Header.Values(strings.ReplaceAll(header, "_", "-")), ","), nil
|
||||
@ -55,10 +73,34 @@ func variableValue(variable string, req *http.Request) (string, error) {
|
||||
return req.URL.Query().Get(arg), nil
|
||||
}
|
||||
|
||||
// $cookie_name variables are used to access cookie values in the request.
|
||||
if name, ok := strings.CutPrefix(variable, cookie); ok {
|
||||
c, _ := req.Cookie(name)
|
||||
if c == nil {
|
||||
return "", nil
|
||||
}
|
||||
return c.Value, nil
|
||||
}
|
||||
|
||||
switch variable {
|
||||
case host, hostname, bestHTTPHost:
|
||||
case host:
|
||||
// NGINX's $host returns the hostname without port, lowercased.
|
||||
if hostOnly, _, err := net.SplitHostPort(req.Host); err == nil {
|
||||
return strings.ToLower(hostOnly), nil
|
||||
}
|
||||
return strings.ToLower(req.Host), nil
|
||||
|
||||
case bestHTTPHost:
|
||||
// ingress-nginx's $best_http_host preserves the port.
|
||||
return req.Host, nil
|
||||
|
||||
case hostname:
|
||||
h, err := os.Hostname()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting hostname: %w", err)
|
||||
}
|
||||
return h, nil
|
||||
|
||||
case requestURI:
|
||||
return req.URL.RequestURI(), nil
|
||||
|
||||
@ -72,12 +114,66 @@ func variableValue(variable string, req *http.Request) (string, error) {
|
||||
return "http", nil
|
||||
|
||||
case args, queryString:
|
||||
return req.URL.Query().Encode(), nil
|
||||
return req.URL.RawQuery, nil
|
||||
|
||||
case remoteAddress:
|
||||
return req.RemoteAddr, nil
|
||||
return stripPort(req.RemoteAddr), nil
|
||||
|
||||
case proxyAddXForwardedFor:
|
||||
clientIP := stripPort(req.RemoteAddr)
|
||||
if prior := req.Header.Get("X-Forwarded-For"); prior != "" {
|
||||
return prior + ", " + clientIP, nil
|
||||
}
|
||||
return clientIP, nil
|
||||
|
||||
case requestMethod:
|
||||
return req.Method, nil
|
||||
|
||||
case uri, documentURI:
|
||||
return req.URL.Path, nil
|
||||
|
||||
case serverName:
|
||||
if hostOnly, _, err := net.SplitHostPort(req.Host); err == nil {
|
||||
return hostOnly, nil
|
||||
}
|
||||
return req.Host, nil
|
||||
|
||||
case serverPort:
|
||||
if _, port, err := net.SplitHostPort(req.Host); err == nil {
|
||||
return port, nil
|
||||
}
|
||||
if req.TLS != nil {
|
||||
return "443", nil
|
||||
}
|
||||
return "80", nil
|
||||
|
||||
case contentType:
|
||||
return req.Header.Get("Content-Type"), nil
|
||||
|
||||
case contentLength:
|
||||
return req.Header.Get("Content-Length"), nil
|
||||
|
||||
case isArgs:
|
||||
if req.URL.RawQuery != "" {
|
||||
return "?", nil
|
||||
}
|
||||
return "", nil
|
||||
|
||||
default:
|
||||
if value, ok := vars[variable]; ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unsupported variable: %s", variable)
|
||||
}
|
||||
}
|
||||
|
||||
// stripPort removes the port from a host:port address.
|
||||
// It handles both IPv4 (192.168.1.1:8080) and IPv6 ([::1]:8080) formats.
|
||||
func stripPort(addr string) string {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return addr
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package ingressnginx
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -13,6 +15,7 @@ func Test_ReplaceVariables(t *testing.T) {
|
||||
desc string
|
||||
src string
|
||||
req *http.Request
|
||||
vars map[string]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
@ -28,10 +31,11 @@ func Test_ReplaceVariables(t *testing.T) {
|
||||
expected: `val=baz.com`,
|
||||
},
|
||||
{
|
||||
desc: "$hostname",
|
||||
src: "val=$hostname",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo/bar?key=value&other=test", http.NoBody),
|
||||
expected: `val=baz.com`,
|
||||
desc: "$hostname",
|
||||
src: "val=$hostname",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo/bar?key=value&other=test", http.NoBody),
|
||||
// $hostname returns the machine hostname (os.Hostname), not the HTTP Host header.
|
||||
expected: "val=" + mustHostname(t),
|
||||
},
|
||||
{
|
||||
desc: "$http_*",
|
||||
@ -59,13 +63,13 @@ func Test_ReplaceVariables(t *testing.T) {
|
||||
desc: "$args",
|
||||
src: "val=$args",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com?q=test&test=1&test=2&token=token_1234&val=foo,bar,baz", http.NoBody),
|
||||
expected: `val=q=test&test=1&test=2&token=token_1234&val=foo%2Cbar%2Cbaz`,
|
||||
expected: `val=q=test&test=1&test=2&token=token_1234&val=foo,bar,baz`,
|
||||
},
|
||||
{
|
||||
desc: "$query_string",
|
||||
src: "val=$query_string",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com?q=test&test=1&test=2&token=token_1234&val=foo,bar,baz", http.NoBody),
|
||||
expected: `val=q=test&test=1&test=2&token=token_1234&val=foo%2Cbar%2Cbaz`,
|
||||
expected: `val=q=test&test=1&test=2&token=token_1234&val=foo,bar,baz`,
|
||||
},
|
||||
{
|
||||
desc: "$host && $escaped_request_uri",
|
||||
@ -85,13 +89,178 @@ func Test_ReplaceVariables(t *testing.T) {
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo/bar?key=value&other=test", http.NoBody),
|
||||
expected: "val=${invalid}",
|
||||
},
|
||||
{
|
||||
desc: "$scheme http",
|
||||
src: "val=$scheme",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo/bar", http.NoBody),
|
||||
expected: "val=http",
|
||||
},
|
||||
{
|
||||
desc: "$scheme https",
|
||||
src: "val=$scheme",
|
||||
req: mustNewRequestWithTLS(t, http.MethodGet, "https://baz.com/foo/bar"),
|
||||
expected: "val=https",
|
||||
},
|
||||
{
|
||||
desc: "$request_uri",
|
||||
src: "val=$request_uri",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo/bar?key=value&other=test", http.NoBody),
|
||||
expected: "val=/foo/bar?key=value&other=test",
|
||||
},
|
||||
{
|
||||
desc: "$remote_addr",
|
||||
src: "val=$remote_addr",
|
||||
req: mustNewRequestWithRemoteAddr(t, http.MethodGet, "http://baz.com/foo/bar", "192.168.1.1:12345"),
|
||||
expected: "val=192.168.1.1",
|
||||
},
|
||||
{
|
||||
desc: "$remote_addr without port",
|
||||
src: "val=$remote_addr",
|
||||
req: mustNewRequestWithRemoteAddr(t, http.MethodGet, "http://baz.com/foo/bar", "192.168.1.1"),
|
||||
expected: "val=192.168.1.1",
|
||||
},
|
||||
{
|
||||
desc: "custom vars",
|
||||
src: "val=$custom_var",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo/bar", http.NoBody),
|
||||
vars: map[string]string{"$custom_var": "custom_value"},
|
||||
expected: "val=custom_value",
|
||||
},
|
||||
{
|
||||
desc: "$uri",
|
||||
src: "val=$uri",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo/bar?key=value", http.NoBody),
|
||||
expected: "val=/foo/bar",
|
||||
},
|
||||
{
|
||||
desc: "$document_uri",
|
||||
src: "val=$document_uri",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo/bar?key=value", http.NoBody),
|
||||
expected: "val=/foo/bar",
|
||||
},
|
||||
{
|
||||
desc: "$server_name with port",
|
||||
src: "val=$server_name",
|
||||
req: mustNewRequestWithHost(t, http.MethodGet, "http://baz.com:8080/foo", "baz.com:8080"),
|
||||
expected: "val=baz.com",
|
||||
},
|
||||
{
|
||||
desc: "$server_name without port",
|
||||
src: "val=$server_name",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo", http.NoBody),
|
||||
expected: "val=baz.com",
|
||||
},
|
||||
{
|
||||
desc: "$server_port with explicit port",
|
||||
src: "val=$server_port",
|
||||
req: mustNewRequestWithHost(t, http.MethodGet, "http://baz.com:8080/foo", "baz.com:8080"),
|
||||
expected: "val=8080",
|
||||
},
|
||||
{
|
||||
desc: "$server_port without port http",
|
||||
src: "val=$server_port",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo", http.NoBody),
|
||||
expected: "val=80",
|
||||
},
|
||||
{
|
||||
desc: "$server_port without port https",
|
||||
src: "val=$server_port",
|
||||
req: mustNewRequestWithTLS(t, http.MethodGet, "https://baz.com/foo"),
|
||||
expected: "val=443",
|
||||
},
|
||||
{
|
||||
desc: "$content_type",
|
||||
src: "val=$content_type",
|
||||
req: mustNewRequestWithHeaders(t, http.MethodGet, "http://baz.com/foo", map[string][]string{
|
||||
"Content-Type": {"application/json"},
|
||||
}),
|
||||
expected: "val=application/json",
|
||||
},
|
||||
{
|
||||
desc: "$content_length",
|
||||
src: "val=$content_length",
|
||||
req: mustNewRequestWithHeaders(t, http.MethodGet, "http://baz.com/foo", map[string][]string{
|
||||
"Content-Length": {"42"},
|
||||
}),
|
||||
expected: "val=42",
|
||||
},
|
||||
{
|
||||
desc: "$cookie_session",
|
||||
src: "val=$cookie_session",
|
||||
req: mustNewRequestWithCookie(t, http.MethodGet, "http://baz.com/foo", "session", "abc123"),
|
||||
expected: "val=abc123",
|
||||
},
|
||||
{
|
||||
desc: "$cookie_* not found",
|
||||
src: "val=$cookie_missing",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo", http.NoBody),
|
||||
expected: "val=",
|
||||
},
|
||||
{
|
||||
desc: "$is_args with query string",
|
||||
src: "val=$is_args",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo?key=value", http.NoBody),
|
||||
expected: "val=?",
|
||||
},
|
||||
{
|
||||
desc: "$is_args without query string",
|
||||
src: "val=$is_args",
|
||||
req: httptest.NewRequest(http.MethodGet, "http://baz.com/foo", http.NoBody),
|
||||
expected: "val=",
|
||||
},
|
||||
{
|
||||
desc: "$host strips port",
|
||||
src: "val=$host",
|
||||
req: mustNewRequestWithHost(t, http.MethodGet, "http://baz.com:8080/foo", "baz.com:8080"),
|
||||
expected: "val=baz.com",
|
||||
},
|
||||
{
|
||||
desc: "$host lowercased",
|
||||
src: "val=$host",
|
||||
req: mustNewRequestWithHost(t, http.MethodGet, "http://BAZ.COM/foo", "BAZ.COM"),
|
||||
expected: "val=baz.com",
|
||||
},
|
||||
{
|
||||
desc: "$best_http_host preserves port",
|
||||
src: "val=$best_http_host",
|
||||
req: mustNewRequestWithHost(t, http.MethodGet, "http://baz.com:8080/foo", "baz.com:8080"),
|
||||
expected: "val=baz.com:8080",
|
||||
},
|
||||
{
|
||||
desc: "$server_name with IPv6 and port",
|
||||
src: "val=$server_name",
|
||||
req: mustNewRequestWithHost(t, http.MethodGet, "http://[::1]:8080/foo", "[::1]:8080"),
|
||||
expected: "val=::1",
|
||||
},
|
||||
{
|
||||
desc: "$server_port with IPv6 and port",
|
||||
src: "val=$server_port",
|
||||
req: mustNewRequestWithHost(t, http.MethodGet, "http://[::1]:8080/foo", "[::1]:8080"),
|
||||
expected: "val=8080",
|
||||
},
|
||||
{
|
||||
desc: "$proxy_add_x_forwarded_for without existing header",
|
||||
src: "val=$proxy_add_x_forwarded_for",
|
||||
req: mustNewRequestWithRemoteAddr(t, http.MethodGet, "http://baz.com/foo", "192.168.1.1:12345"),
|
||||
expected: "val=192.168.1.1",
|
||||
},
|
||||
{
|
||||
desc: "$proxy_add_x_forwarded_for with existing header",
|
||||
src: "val=$proxy_add_x_forwarded_for",
|
||||
req: func() *http.Request {
|
||||
r := mustNewRequestWithRemoteAddr(t, http.MethodGet, "http://baz.com/foo", "10.0.0.1:9999")
|
||||
r.Header.Set("X-Forwarded-For", "203.0.113.50, 70.41.3.18")
|
||||
return r
|
||||
}(),
|
||||
expected: "val=203.0.113.50, 70.41.3.18, 10.0.0.1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := ReplaceVariables(testCase.src, testCase.req)
|
||||
got := ReplaceVariables(testCase.src, testCase.req, testCase.vars)
|
||||
require.Equal(t, testCase.expected, got)
|
||||
})
|
||||
}
|
||||
@ -105,3 +274,48 @@ func mustNewRequestWithHeaders(t *testing.T, method, target string, headers map[
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func mustNewRequestWithTLS(t *testing.T, method, target string) *http.Request {
|
||||
t.Helper()
|
||||
|
||||
req := httptest.NewRequest(method, target, http.NoBody)
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func mustNewRequestWithRemoteAddr(t *testing.T, method, target, remoteAddr string) *http.Request {
|
||||
t.Helper()
|
||||
|
||||
req := httptest.NewRequest(method, target, http.NoBody)
|
||||
req.RemoteAddr = remoteAddr
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func mustNewRequestWithHost(t *testing.T, method, target, host string) *http.Request {
|
||||
t.Helper()
|
||||
|
||||
req := httptest.NewRequest(method, target, http.NoBody)
|
||||
req.Host = host
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func mustNewRequestWithCookie(t *testing.T, method, target, cookieName, cookieValue string) *http.Request {
|
||||
t.Helper()
|
||||
|
||||
req := httptest.NewRequest(method, target, http.NoBody)
|
||||
req.AddCookie(&http.Cookie{Name: cookieName, Value: cookieValue})
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func mustHostname(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
h, err := os.Hostname()
|
||||
require.NoError(t, err)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
1234
pkg/middlewares/ingressnginx/snippet/action.go
Normal file
1234
pkg/middlewares/ingressnginx/snippet/action.go
Normal file
File diff suppressed because it is too large
Load Diff
53
pkg/middlewares/ingressnginx/snippet/directive_context.go
Normal file
53
pkg/middlewares/ingressnginx/snippet/directive_context.go
Normal file
@ -0,0 +1,53 @@
|
||||
package snippet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/tufanbarisyildirim/gonginx/config"
|
||||
)
|
||||
|
||||
const (
|
||||
contextServer = "server"
|
||||
contextLocation = "location"
|
||||
contextIf = "if"
|
||||
contextIfInLocation = "if_in_location"
|
||||
)
|
||||
|
||||
var directiveContexts = map[string][]string{
|
||||
"add_header": {contextServer, contextLocation, contextIfInLocation},
|
||||
"more_set_headers": {contextServer, contextLocation, contextIf},
|
||||
"more_clear_headers": {contextServer, contextLocation, contextIf},
|
||||
"proxy_set_header": {contextServer, contextLocation},
|
||||
"more_set_input_headers": {contextServer, contextLocation, contextIf},
|
||||
"more_clear_input_headers": {contextServer, contextLocation, contextIf},
|
||||
"if": {contextServer, contextLocation},
|
||||
"set": {contextServer, contextLocation, contextIf},
|
||||
"return": {contextServer, contextLocation, contextIf},
|
||||
"rewrite": {contextServer, contextLocation, contextIf},
|
||||
"location": {contextServer},
|
||||
"allow": {contextServer, contextLocation, contextIf},
|
||||
"deny": {contextServer, contextLocation, contextIf},
|
||||
"proxy_hide_header": {contextServer, contextLocation},
|
||||
"expires": {contextServer, contextLocation, contextIfInLocation},
|
||||
}
|
||||
|
||||
// isAllowedInContext checks if the directive is allowed in the context of its parent directive.
|
||||
func isAllowedInContext(directive config.IDirective) error {
|
||||
ctx := directive.GetParent().GetName()
|
||||
allowedCtxs := directiveContexts[directive.GetName()]
|
||||
|
||||
if slices.Contains(allowedCtxs, ctx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if slices.Contains(allowedCtxs, contextIfInLocation) && ctx == contextIf {
|
||||
// Here we are checking if the parent of the "if" directive is a "location" directive,
|
||||
// which means that the "if" directive is inside a "location" block.
|
||||
if directive.GetParent().GetParent().GetName() == contextLocation {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("context %s is not valid for this directive %s: %+v ", ctx, directive.GetName(), allowedCtxs)
|
||||
}
|
||||
232
pkg/middlewares/ingressnginx/snippet/snippet.go
Normal file
232
pkg/middlewares/ingressnginx/snippet/snippet.go
Normal file
@ -0,0 +1,232 @@
|
||||
package snippet
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/tufanbarisyildirim/gonginx/parser"
|
||||
)
|
||||
|
||||
const typeName = "Snippet"
|
||||
|
||||
// Snippet is a middleware allowing to parse and interpret NGINX snippets containing directives.
|
||||
type Snippet struct {
|
||||
next http.Handler
|
||||
name string
|
||||
serverActions *actions
|
||||
configurationActions *actions
|
||||
}
|
||||
|
||||
// New creates a new Snippet middleware instance.
|
||||
// It parses the provided snippets and builds the corresponding actions.
|
||||
func New(ctx context.Context, next http.Handler, config *dynamic.Snippet, name string) (h http.Handler, err error) {
|
||||
// Here we are adding a recover block as the snippet parsing can panic.
|
||||
defer func() {
|
||||
if recErr := recover(); recErr != nil {
|
||||
err = fmt.Errorf("snippet parsing recover: %v", recErr)
|
||||
}
|
||||
}()
|
||||
|
||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||
logger.Debug().Msg("Creating middleware")
|
||||
|
||||
if config.ServerSnippet == "" && config.ConfigurationSnippet == "" {
|
||||
return nil, errors.New("at least one of serverSnippet or configurationSnippet option must be provided")
|
||||
}
|
||||
|
||||
parserOptions := []parser.Option{
|
||||
parser.WithSkipComments(),
|
||||
parser.WithCustomDirectives("more_set_headers", "more_set_input_headers", "more_clear_headers", "more_clear_input_headers", "proxy_hide_header"),
|
||||
}
|
||||
|
||||
var serverActions *actions
|
||||
if config.ServerSnippet != "" {
|
||||
// Parse the snippet, note that we are wrapping the server snippet in a server block to ensure that it is parsed in the correct context.
|
||||
p := parser.NewStringParser(fmt.Sprintf("server{%s}", config.ServerSnippet), parserOptions...)
|
||||
|
||||
conf, parseErr := p.Parse()
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("parsing server-snippet: %w", parseErr)
|
||||
}
|
||||
|
||||
serverActions, err = buildActions(conf.GetDirectives()[0].GetBlock())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building actions from server-snippet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
buildErr error
|
||||
configurationActions *actions
|
||||
)
|
||||
if config.ConfigurationSnippet != "" {
|
||||
// Parse the snippet, note that we are wrapping the configuration snippet in a location block to ensure that it is parsed in the correct context.
|
||||
p := parser.NewStringParser(fmt.Sprintf("location / {%s}", config.ConfigurationSnippet), parserOptions...)
|
||||
|
||||
conf, parseErr := p.Parse()
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("parsing configuration-snippet: %w", parseErr)
|
||||
}
|
||||
|
||||
configurationActions, buildErr = buildActions(conf.GetDirectives()[0].GetBlock())
|
||||
if buildErr != nil {
|
||||
return nil, fmt.Errorf("building actions from configuration-snippet: %w", buildErr)
|
||||
}
|
||||
}
|
||||
|
||||
return &Snippet{
|
||||
next: next,
|
||||
name: name,
|
||||
serverActions: serverActions,
|
||||
configurationActions: configurationActions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Snippet) GetTracingInformation() (string, string) {
|
||||
return s.name, typeName
|
||||
}
|
||||
|
||||
func (s *Snippet) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
wrappedRW := &snippetResponseWriter{ResponseWriter: rw}
|
||||
|
||||
ctx := &actionContext{
|
||||
vars: make(map[string]string),
|
||||
nonMergeablePostActions: make(map[string][]action),
|
||||
}
|
||||
|
||||
stop, err := s.serverActions.Execute(wrappedRW, req, ctx)
|
||||
if err != nil {
|
||||
http.Error(wrappedRW, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if stop {
|
||||
if err = executePostActions(wrappedRW, req, ctx); err != nil {
|
||||
http.Error(wrappedRW, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeResponse(wrappedRW, req, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// In NGINX, proxy_set_header directives in the server snippet are ignored
|
||||
// because the generated location block always contains proxy_set_header directives that override them.
|
||||
ctx.nonMergeablePostActions["proxy_set_header"] = nil
|
||||
|
||||
// rewrite...break in the server snippet stops all directive processing,
|
||||
// but post-actions (headers, etc.) and upstream forwarding still proceed.
|
||||
if !ctx.stopAllDirectives {
|
||||
ctx.stopCurrentBlock = false
|
||||
|
||||
stop, err = s.configurationActions.Execute(wrappedRW, req, ctx)
|
||||
if err != nil {
|
||||
http.Error(wrappedRW, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = executePostActions(wrappedRW, req, ctx); err != nil {
|
||||
http.Error(wrappedRW, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if stop {
|
||||
writeResponse(wrappedRW, req, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
s.next.ServeHTTP(wrappedRW, req)
|
||||
}
|
||||
|
||||
// snippetResponseWriter wraps http.ResponseWriter to intercept WriteHeader calls.
|
||||
// This allows deferred response header operations (e.g., proxy_hide_header, conditional
|
||||
// header setting with -s/-t flags) to be applied based on the actual response status
|
||||
// code and content type set by the upstream.
|
||||
type snippetResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
headerWritten bool
|
||||
onWriteHeader []func(code int, h http.Header)
|
||||
}
|
||||
|
||||
func (w *snippetResponseWriter) WriteHeader(code int) {
|
||||
if w.headerWritten {
|
||||
return
|
||||
}
|
||||
w.headerWritten = true
|
||||
for _, fn := range w.onWriteHeader {
|
||||
fn(code, w.Header())
|
||||
}
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *snippetResponseWriter) Write(b []byte) (int, error) {
|
||||
if !w.headerWritten {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying ResponseWriter, enabling http.ResponseController
|
||||
// to discover the underlying writer's capabilities (Flusher, Hijacker, etc.).
|
||||
func (w *snippetResponseWriter) Unwrap() http.ResponseWriter {
|
||||
return w.ResponseWriter
|
||||
}
|
||||
|
||||
// Flush implements http.Flusher.
|
||||
func (w *snippetResponseWriter) Flush() {
|
||||
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Hijack implements http.Hijacker.
|
||||
func (w *snippetResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if h, ok := w.ResponseWriter.(http.Hijacker); ok {
|
||||
return h.Hijack()
|
||||
}
|
||||
return nil, nil, fmt.Errorf("not a hijacker: %T", w.ResponseWriter)
|
||||
}
|
||||
|
||||
// writeResponse writes the final response based on the action context.
|
||||
// For redirect status codes (301, 302, 303, 307, 308) with a URL, it performs an HTTP redirect.
|
||||
// For other status codes, it writes the status code and optional body text.
|
||||
func writeResponse(rw http.ResponseWriter, req *http.Request, ctx *actionContext) {
|
||||
if ctx.statusCode == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.redirectURL != "" {
|
||||
http.Redirect(rw, req, ctx.redirectURL, ctx.statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(ctx.statusCode)
|
||||
if ctx.body != "" {
|
||||
_, _ = rw.Write([]byte(ctx.body))
|
||||
}
|
||||
}
|
||||
|
||||
func executePostActions(rw http.ResponseWriter, req *http.Request, ctx *actionContext) error {
|
||||
for _, postActions := range ctx.nonMergeablePostActions {
|
||||
for _, postAction := range postActions {
|
||||
if _, err := postAction(rw, req, ctx); err != nil {
|
||||
return fmt.Errorf("executing non-mergeable configuration action: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, postActions := range ctx.mergeablePostActions {
|
||||
if _, err := postActions(rw, req, ctx); err != nil {
|
||||
return fmt.Errorf("executing mergeable configuration action: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
1499
pkg/middlewares/ingressnginx/snippet/snippet_test.go
Normal file
1499
pkg/middlewares/ingressnginx/snippet/snippet_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -97,6 +97,9 @@ type ingressConfig struct {
|
||||
ProxyBuffersNumber *int `annotation:"nginx.ingress.kubernetes.io/proxy-buffers-number"`
|
||||
// ProxyMaxTempFileSize sets the maximum size of a temporary file used to buffer responses.
|
||||
ProxyMaxTempFileSize *string `annotation:"nginx.ingress.kubernetes.io/proxy-max-temp-file-size"`
|
||||
|
||||
ConfigurationSnippet *string `annotation:"nginx.ingress.kubernetes.io/configuration-snippet"`
|
||||
ServerSnippet *string `annotation:"nginx.ingress.kubernetes.io/server-snippet"`
|
||||
}
|
||||
|
||||
// parseIngressConfig parses the annotations from an Ingress object into an ingressConfig struct.
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
---
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ingress-with-both-snippets
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/server-snippet: |
|
||||
add_header X-Server-Snippet "server-value";
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
add_header X-Configuration-Snippet "configuration-value";
|
||||
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: snippet.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
@ -0,0 +1,23 @@
|
||||
---
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ingress-with-configuration-snippet
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
add_header X-Configuration-Snippet "configuration-value";
|
||||
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: snippet.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
@ -0,0 +1,23 @@
|
||||
---
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ingress-with-server-snippet
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/server-snippet: |
|
||||
add_header X-Server-Snippet "server-value";
|
||||
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: snippet.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
@ -88,6 +88,12 @@ type certBlocks struct {
|
||||
Certificate *tls.Certificate
|
||||
}
|
||||
|
||||
type ingress struct {
|
||||
*netv1.Ingress
|
||||
|
||||
IngressConfig ingressConfig
|
||||
}
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
@ -129,6 +135,8 @@ type Provider struct {
|
||||
AllowCrossNamespaceResources bool `description:"Allow Ingress to reference resources (e.g. ConfigMaps, Secrets) in different namespaces." json:"allowCrossNamespaceResources,omitempty" toml:"allowCrossNamespaceResources,omitempty" yaml:"allowCrossNamespaceResources,omitempty" export:"true"`
|
||||
GlobalAllowedResponseHeaders []string `description:"List of allowed response headers inside the custom headers annotations." json:"globalAllowedResponseHeaders,omitempty" toml:"globalAllowedResponseHeaders,omitempty" yaml:"globalAllowedResponseHeaders,omitempty" export:"true"`
|
||||
|
||||
AllowSnippetAnnotations bool `description:"Enables to parse and add -snippet annotations/directives." json:"allowSnippetAnnotations,omitempty" toml:"allowSnippetAnnotations,omitempty" yaml:"allowSnippetAnnotations,omitempty" export:"true"`
|
||||
|
||||
// NonTLSEntryPoints contains the names of entrypoints that are configured without TLS.
|
||||
NonTLSEntryPoints []string `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
|
||||
@ -338,17 +346,31 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
}
|
||||
ingressClasses = filterIngressClass(ics, p.IngressClassByName, p.IngressClass, p.ControllerClass)
|
||||
|
||||
ingresses := p.k8sClient.ListIngresses()
|
||||
var ingresses []ingress
|
||||
|
||||
hosts := make(map[string]bool)
|
||||
for _, ing := range ingresses {
|
||||
serverSnippets := make(map[string]string)
|
||||
for _, ing := range p.k8sClient.ListIngresses() {
|
||||
if !p.shouldProcessIngress(ing, ingressClasses) {
|
||||
continue
|
||||
}
|
||||
|
||||
logger := log.Ctx(ctx).With().Str("ingress", ing.Name).Str("namespace", ing.Namespace).Logger()
|
||||
ingressConfig := parseIngressConfig(ing)
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
hosts[strings.ToLower(rule.Host)] = true
|
||||
hosts[rule.Host] = true
|
||||
|
||||
if srvSnippet := ptr.Deref(ingressConfig.ServerSnippet, ""); srvSnippet != "" {
|
||||
if serverSnippets[rule.Host] != "" {
|
||||
logger.Debug().Msgf("Ignoring Server snippet because it is already defined for Host: %s", rule.Host)
|
||||
} else {
|
||||
serverSnippets[rule.Host] = srvSnippet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ingresses = append(ingresses, ingress{Ingress: ing, IngressConfig: ingressConfig})
|
||||
}
|
||||
|
||||
uniqCerts := make(map[string]*tls.CertAndStores)
|
||||
@ -357,31 +379,25 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
logger := log.Ctx(ctx).With().Str("ingress", ingress.Name).Str("namespace", ingress.Namespace).Logger()
|
||||
ctxIngress := logger.WithContext(ctx)
|
||||
|
||||
if !p.shouldProcessIngress(ingress, ingressClasses) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := p.updateIngressStatus(ingress); err != nil {
|
||||
if err := p.updateIngressStatus(ingress.Ingress); err != nil {
|
||||
logger.Error().Err(err).Msg("Error while updating ingress status")
|
||||
}
|
||||
|
||||
var hasTLS bool
|
||||
if len(ingress.Spec.TLS) > 0 {
|
||||
hasTLS = true
|
||||
if err := p.loadCertificates(ctxIngress, ingress, uniqCerts); err != nil {
|
||||
if err := p.loadCertificates(ctxIngress, ingress.Ingress, uniqCerts); err != nil {
|
||||
logger.Error().Err(err).Msg("Error configuring TLS")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ingressConfig := parseIngressConfig(ingress)
|
||||
|
||||
var clientAuthTLSOptionName string
|
||||
if ingressConfig.AuthTLSSecret != nil {
|
||||
tlsOptName := provider.Normalize(ingress.Namespace + "-" + ingress.Name + "-" + *ingressConfig.AuthTLSSecret)
|
||||
if ingress.IngressConfig.AuthTLSSecret != nil {
|
||||
tlsOptName := provider.Normalize(ingress.Namespace + "-" + ingress.Name + "-" + *ingress.IngressConfig.AuthTLSSecret)
|
||||
|
||||
if _, exists := tlsOptions[tlsOptName]; !exists {
|
||||
tlsOpt, err := p.buildClientAuthTLSOption(ingress.Namespace, ingressConfig)
|
||||
tlsOpt, err := p.buildClientAuthTLSOption(ingress.Namespace, ingress.IngressConfig)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Error configuring client auth TLS")
|
||||
continue
|
||||
@ -393,7 +409,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
clientAuthTLSOptionName = tlsOptName
|
||||
}
|
||||
|
||||
namedServersTransport, err := p.buildServersTransport(ctxIngress, ingress.Namespace, ingress.Name, ingressConfig)
|
||||
namedServersTransport, err := p.buildServersTransport(ctxIngress, ingress.Namespace, ingress.Name, ingress.IngressConfig)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Ignoring Ingress cannot create proxy SSL configuration")
|
||||
continue
|
||||
@ -402,7 +418,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
var defaultBackendService *dynamic.Service
|
||||
if ingress.Spec.DefaultBackend != nil && ingress.Spec.DefaultBackend.Service != nil {
|
||||
var err error
|
||||
defaultBackendService, err = p.buildService(ingress.Namespace, *ingress.Spec.DefaultBackend, ingressConfig)
|
||||
defaultBackendService, err = p.buildService(ingress.Namespace, *ingress.Spec.DefaultBackend, ingress.IngressConfig)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Str("serviceName", ingress.Spec.DefaultBackend.Service.Name).
|
||||
@ -421,7 +437,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
Service: defaultBackendName,
|
||||
}
|
||||
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, defaultBackendName, "", "", ingress.Spec.DefaultBackend, hosts, ingressConfig, hasTLS, rt, conf); err != nil {
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, defaultBackendName, "", "", ingress.Spec.DefaultBackend, hosts, ingress.IngressConfig, hasTLS, rt, conf, ""); err != nil {
|
||||
logger.Error().Err(err).Msg("Error applying middlewares")
|
||||
}
|
||||
|
||||
@ -439,7 +455,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
rtTLS.TLS.Options = clientAuthTLSOptionName
|
||||
}
|
||||
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, defaultBackendTLSName, "", "", ingress.Spec.DefaultBackend, hosts, ingressConfig, false, rtTLS, conf); err != nil {
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, defaultBackendTLSName, "", "", ingress.Spec.DefaultBackend, hosts, ingress.IngressConfig, false, rtTLS, conf, ""); err != nil {
|
||||
logger.Error().Err(err).Msg("Error applying middlewares")
|
||||
}
|
||||
|
||||
@ -453,7 +469,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
}
|
||||
|
||||
for ri, rule := range ingress.Spec.Rules {
|
||||
if ptr.Deref(ingressConfig.SSLPassthrough, false) {
|
||||
if ptr.Deref(ingress.IngressConfig.SSLPassthrough, false) {
|
||||
if rule.Host == "" {
|
||||
logger.Error().Err(err).Msg("Cannot process ssl-passthrough for rule without host")
|
||||
continue
|
||||
@ -477,7 +493,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
continue
|
||||
}
|
||||
|
||||
service, err := p.buildPassthroughService(ingress.Namespace, *backend, ingressConfig)
|
||||
service, err := p.buildPassthroughService(ingress.Namespace, *backend, ingress.IngressConfig)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("Cannot create passthrough service for %s", backend.Service.Name)
|
||||
continue
|
||||
@ -516,7 +532,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
Service: key,
|
||||
}
|
||||
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, key, "", "", ingress.Spec.DefaultBackend, hosts, ingressConfig, hasTLS, rt, conf); err != nil {
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, key, "", "", ingress.Spec.DefaultBackend, hosts, ingress.IngressConfig, hasTLS, rt, conf, serverSnippets[rule.Host]); err != nil {
|
||||
logger.Error().Err(err).Msg("Error applying middlewares")
|
||||
}
|
||||
|
||||
@ -533,7 +549,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
rtTLS.TLS.Options = clientAuthTLSOptionName
|
||||
}
|
||||
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, key+"-tls", "", "", ingress.Spec.DefaultBackend, hosts, ingressConfig, false, rtTLS, conf); err != nil {
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, key+"-tls", "", "", ingress.Spec.DefaultBackend, hosts, ingress.IngressConfig, false, rtTLS, conf, serverSnippets[rule.Host]); err != nil {
|
||||
logger.Error().Err(err).Msg("Error applying middlewares")
|
||||
}
|
||||
|
||||
@ -568,7 +584,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
// TODO: if no service, do not add middlewares and 503.
|
||||
serviceName := provider.Normalize(ingress.Namespace + "-" + ingress.Name + "-" + pa.Backend.Service.Name + "-" + portString)
|
||||
|
||||
service, err := p.buildService(ingress.Namespace, pa.Backend, ingressConfig)
|
||||
service, err := p.buildService(ingress.Namespace, pa.Backend, ingress.IngressConfig)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Str("serviceName", pa.Backend.Service.Name).
|
||||
@ -579,7 +595,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
}
|
||||
|
||||
rt := &dynamic.Router{
|
||||
Rule: buildRule(ctxIngress, rule.Host, pa, ingressConfig, hosts),
|
||||
Rule: buildRule(ctxIngress, rule.Host, pa, ingress.IngressConfig, hosts),
|
||||
// "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax.
|
||||
RuleSyntax: "default",
|
||||
Service: serviceName,
|
||||
@ -602,7 +618,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
conf.HTTP.ServersTransports[namedServersTransport.Name] = namedServersTransport.ServersTransport
|
||||
}
|
||||
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, routerKey, pa.Path, rule.Host, &pa.Backend, hosts, ingressConfig, hasTLS, rt, conf); err != nil {
|
||||
if err := p.applyMiddlewares(ingress.Namespace, ingress.Name, routerKey, pa.Path, rule.Host, &pa.Backend, hosts, ingress.IngressConfig, hasTLS, rt, conf, serverSnippets[rule.Host]); err != nil {
|
||||
logger.Error().Err(err).Msg("Error applying middlewares")
|
||||
}
|
||||
}
|
||||
@ -968,12 +984,11 @@ func (p *Provider) loadCertificates(ctx context.Context, ingress *netv1.Ingress,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) applyMiddlewares(namespace, ingressName, routerKey, rulePath, ruleHost string, backend *netv1.IngressBackend, hosts map[string]bool, ingressConfig ingressConfig, hasTLS bool, rt *dynamic.Router, conf *dynamic.Configuration) error {
|
||||
func (p *Provider) applyMiddlewares(namespace, ingressName, routerKey, rulePath, ruleHost string, backend *netv1.IngressBackend, hosts map[string]bool, ingressConfig ingressConfig, hasTLS bool, rt *dynamic.Router, conf *dynamic.Configuration, serverSnippet string) error {
|
||||
err := p.applyCustomHTTPErrors(namespace, ingressName, routerKey, backend, ingressConfig, rt, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
applyAppRootConfiguration(routerKey, ingressConfig, rt, conf)
|
||||
applyFromToWwwRedirect(hosts, ruleHost, routerKey, ingressConfig, rt, conf)
|
||||
applyRedirect(routerKey, ingressConfig, rt, conf)
|
||||
@ -1010,11 +1025,38 @@ func (p *Provider) applyMiddlewares(namespace, ingressName, routerKey, rulePath,
|
||||
return fmt.Errorf("applying custom headers: %w", err)
|
||||
}
|
||||
|
||||
if err := p.applySnippets(routerKey, serverSnippet, ingressConfig, rt, conf); err != nil {
|
||||
return fmt.Errorf("applying snippets: %w", err)
|
||||
}
|
||||
|
||||
p.applyRetry(routerKey, ingressConfig, rt, conf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) applySnippets(routerName, serverSnippet string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) error {
|
||||
configurationSnippet := ptr.Deref(ingressConfig.ConfigurationSnippet, "")
|
||||
if serverSnippet == "" && configurationSnippet == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !p.AllowSnippetAnnotations {
|
||||
return errors.New("snippet annotations are not allowed")
|
||||
}
|
||||
|
||||
snippetMiddlewareName := routerName + "-snippet"
|
||||
conf.HTTP.Middlewares[snippetMiddlewareName] = &dynamic.Middleware{
|
||||
Snippet: &dynamic.Snippet{
|
||||
ServerSnippet: serverSnippet,
|
||||
ConfigurationSnippet: configurationSnippet,
|
||||
},
|
||||
}
|
||||
|
||||
rt.Middlewares = append(rt.Middlewares, snippetMiddlewareName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) applyCustomHTTPErrors(namespace, ingressName, routerName string, targetedService *netv1.IngressBackend, config ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) error {
|
||||
customHTTPErrors := ptr.Deref(config.CustomHTTPErrors, p.CustomHTTPErrors)
|
||||
if len(customHTTPErrors) == 0 {
|
||||
@ -1210,7 +1252,6 @@ func applyFromToWwwRedirect(hosts map[string]bool, ruleHost, routerName string,
|
||||
return
|
||||
}
|
||||
|
||||
ruleHost = strings.ToLower(ruleHost)
|
||||
wwwType := strings.HasPrefix(ruleHost, "www.")
|
||||
wildcardType := strings.HasPrefix(ruleHost, "*.")
|
||||
bypass := wwwType && hosts[strings.TrimPrefix(ruleHost, "www.")] || !wwwType && hosts["www."+ruleHost] || wildcardType
|
||||
|
||||
@ -30,6 +30,7 @@ func TestLoadIngresses(t *testing.T) {
|
||||
defaultBackendServiceNamespace string
|
||||
allowCrossNamespaceResources bool
|
||||
globalAllowedResponseHeaders []string
|
||||
allowSnippetAnnotations bool
|
||||
paths []string
|
||||
expected *dynamic.Configuration
|
||||
}{
|
||||
@ -1301,10 +1302,13 @@ func TestLoadIngresses(t *testing.T) {
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-rewrite-target-no-regex-rule-0-path-0": {
|
||||
Rule: "Host(`rewrite-target-no-regex.localhost`) && Path(`/original`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-rewrite-target-no-regex-whoami-80",
|
||||
Middlewares: []string{"default-ingress-with-rewrite-target-no-regex-rule-0-path-0-rewrite-target", "default-ingress-with-rewrite-target-no-regex-rule-0-path-0-retry"},
|
||||
Rule: "Host(`rewrite-target-no-regex.localhost`) && Path(`/original`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-rewrite-target-no-regex-whoami-80",
|
||||
Middlewares: []string{
|
||||
"default-ingress-with-rewrite-target-no-regex-rule-0-path-0-rewrite-target",
|
||||
"default-ingress-with-rewrite-target-no-regex-rule-0-path-0-retry",
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
@ -1314,7 +1318,9 @@ func TestLoadIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"default-ingress-with-rewrite-target-no-regex-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{Attempts: 3},
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
@ -3205,10 +3211,13 @@ func TestLoadIngresses(t *testing.T) {
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-custom-http-errors-and-default-backend-rule-0-path-0": {
|
||||
Rule: "Host(`whoami.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-custom-http-errors-and-default-backend-whoami-80",
|
||||
Middlewares: []string{"default-ingress-with-custom-http-errors-and-default-backend-rule-0-path-0-custom-http-errors", "default-ingress-with-custom-http-errors-and-default-backend-rule-0-path-0-retry"},
|
||||
Rule: "Host(`whoami.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-custom-http-errors-and-default-backend-whoami-80",
|
||||
Middlewares: []string{
|
||||
"default-ingress-with-custom-http-errors-and-default-backend-rule-0-path-0-custom-http-errors",
|
||||
"default-ingress-with-custom-http-errors-and-default-backend-rule-0-path-0-retry",
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
@ -3225,7 +3234,9 @@ func TestLoadIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"default-ingress-with-custom-http-errors-and-default-backend-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{Attempts: 3},
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
@ -3296,10 +3307,13 @@ func TestLoadIngresses(t *testing.T) {
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-custom-http-errors-rule-0-path-0": {
|
||||
Rule: "Host(`whoami.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-custom-http-errors-whoami-80",
|
||||
Middlewares: []string{"default-ingress-with-custom-http-errors-rule-0-path-0-custom-http-errors", "default-ingress-with-custom-http-errors-rule-0-path-0-retry"},
|
||||
Rule: "Host(`whoami.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-custom-http-errors-whoami-80",
|
||||
Middlewares: []string{
|
||||
"default-ingress-with-custom-http-errors-rule-0-path-0-custom-http-errors",
|
||||
"default-ingress-with-custom-http-errors-rule-0-path-0-retry",
|
||||
},
|
||||
},
|
||||
"default-backend": {
|
||||
Rule: "PathPrefix(`/`)",
|
||||
@ -3329,7 +3343,9 @@ func TestLoadIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"default-ingress-with-custom-http-errors-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{Attempts: 3},
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
@ -3466,7 +3482,9 @@ func TestLoadIngresses(t *testing.T) {
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-default-backend-annotation-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{Attempts: 3},
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
@ -3997,6 +4015,713 @@ func TestLoadIngresses(t *testing.T) {
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Server snippet with allowSnippetAnnotations enabled",
|
||||
allowSnippetAnnotations: true,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-server-snippet.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-server-snippet-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-server-snippet-whoami-80",
|
||||
Middlewares: []string{
|
||||
"default-ingress-with-server-snippet-rule-0-path-0-snippet",
|
||||
"default-ingress-with-server-snippet-rule-0-path-0-retry",
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-server-snippet-rule-0-path-0-snippet": {
|
||||
Snippet: &dynamic.Snippet{
|
||||
ServerSnippet: "add_header X-Server-Snippet \"server-value\";\n",
|
||||
},
|
||||
},
|
||||
"default-ingress-with-server-snippet-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-server-snippet-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-server-snippet",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-server-snippet": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Configuration snippet with allowSnippetAnnotations enabled",
|
||||
allowSnippetAnnotations: true,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-configuration-snippet.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-configuration-snippet-whoami-80",
|
||||
Middlewares: []string{
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0-snippet",
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0-retry",
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0-snippet": {
|
||||
Snippet: &dynamic.Snippet{
|
||||
ConfigurationSnippet: "add_header X-Configuration-Snippet \"configuration-value\";\n",
|
||||
},
|
||||
},
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-configuration-snippet-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-configuration-snippet",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-configuration-snippet": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Both snippets with allowSnippetAnnotations enabled",
|
||||
allowSnippetAnnotations: true,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-both-snippets.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-both-snippets-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-both-snippets-whoami-80",
|
||||
Middlewares: []string{
|
||||
"default-ingress-with-both-snippets-rule-0-path-0-snippet",
|
||||
"default-ingress-with-both-snippets-rule-0-path-0-retry",
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-both-snippets-rule-0-path-0-snippet": {
|
||||
Snippet: &dynamic.Snippet{
|
||||
ServerSnippet: "add_header X-Server-Snippet \"server-value\";\n",
|
||||
ConfigurationSnippet: "add_header X-Configuration-Snippet \"configuration-value\";\n",
|
||||
},
|
||||
},
|
||||
"default-ingress-with-both-snippets-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-both-snippets-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-both-snippets",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-both-snippets": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Server snippet with allowSnippetAnnotations disabled",
|
||||
allowSnippetAnnotations: false,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-server-snippet.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-server-snippet-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-server-snippet-whoami-80",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-server-snippet-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-server-snippet",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-server-snippet": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Configuration snippet with allowSnippetAnnotations disabled",
|
||||
allowSnippetAnnotations: false,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-configuration-snippet.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-configuration-snippet-whoami-80",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-configuration-snippet-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-configuration-snippet",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-configuration-snippet": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Both snippets with allowSnippetAnnotations disabled",
|
||||
allowSnippetAnnotations: false,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-both-snippets.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-both-snippets-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-both-snippets-whoami-80",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-both-snippets-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-both-snippets",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-both-snippets": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Server snippet with allowSnippetAnnotations enabled",
|
||||
allowSnippetAnnotations: true,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-server-snippet.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-server-snippet-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-server-snippet-whoami-80",
|
||||
Middlewares: []string{"default-ingress-with-server-snippet-rule-0-path-0-snippet", "default-ingress-with-server-snippet-rule-0-path-0-retry"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-server-snippet-rule-0-path-0-snippet": {
|
||||
Snippet: &dynamic.Snippet{
|
||||
ServerSnippet: "add_header X-Server-Snippet \"server-value\";\n",
|
||||
},
|
||||
},
|
||||
"default-ingress-with-server-snippet-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-server-snippet-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-server-snippet",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-server-snippet": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Configuration snippet with allowSnippetAnnotations enabled",
|
||||
allowSnippetAnnotations: true,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-configuration-snippet.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-configuration-snippet-whoami-80",
|
||||
Middlewares: []string{"default-ingress-with-configuration-snippet-rule-0-path-0-snippet", "default-ingress-with-configuration-snippet-rule-0-path-0-retry"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0-snippet": {
|
||||
Snippet: &dynamic.Snippet{
|
||||
ConfigurationSnippet: "add_header X-Configuration-Snippet \"configuration-value\";\n",
|
||||
},
|
||||
},
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-configuration-snippet-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-configuration-snippet",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-configuration-snippet": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Both snippets with allowSnippetAnnotations enabled",
|
||||
allowSnippetAnnotations: true,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-both-snippets.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-both-snippets-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-both-snippets-whoami-80",
|
||||
Middlewares: []string{"default-ingress-with-both-snippets-rule-0-path-0-snippet", "default-ingress-with-both-snippets-rule-0-path-0-retry"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-both-snippets-rule-0-path-0-snippet": {
|
||||
Snippet: &dynamic.Snippet{
|
||||
ServerSnippet: "add_header X-Server-Snippet \"server-value\";\n",
|
||||
ConfigurationSnippet: "add_header X-Configuration-Snippet \"configuration-value\";\n",
|
||||
},
|
||||
},
|
||||
"default-ingress-with-both-snippets-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-both-snippets-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-both-snippets",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-both-snippets": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Server snippet with allowSnippetAnnotations disabled",
|
||||
allowSnippetAnnotations: false,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-server-snippet.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-server-snippet-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-server-snippet-whoami-80",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-server-snippet-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-server-snippet",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-server-snippet": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Configuration snippet with allowSnippetAnnotations disabled",
|
||||
allowSnippetAnnotations: false,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-configuration-snippet.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-configuration-snippet-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-configuration-snippet-whoami-80",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-configuration-snippet-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-configuration-snippet",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-configuration-snippet": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Both snippets with allowSnippetAnnotations disabled",
|
||||
allowSnippetAnnotations: false,
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-both-snippets.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-both-snippets-rule-0-path-0": {
|
||||
Rule: "Host(`snippet.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-both-snippets-whoami-80",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-both-snippets-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{URL: "http://10.10.0.1:80"},
|
||||
{URL: "http://10.10.0.2:80"},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ServersTransport: "default-ingress-with-both-snippets",
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-both-snippets": {
|
||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||
DialTimeout: ptypes.Duration(60 * time.Second),
|
||||
ReadTimeout: ptypes.Duration(60 * time.Second),
|
||||
WriteTimeout: ptypes.Duration(60 * time.Second),
|
||||
IdleConnTimeout: ptypes.Duration(60 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Auth TLS pass certificate to upstream",
|
||||
paths: []string{
|
||||
@ -4645,6 +5370,7 @@ func TestLoadIngresses(t *testing.T) {
|
||||
k8sClient: client,
|
||||
defaultBackendServiceName: test.defaultBackendServiceName,
|
||||
defaultBackendServiceNamespace: test.defaultBackendServiceNamespace,
|
||||
AllowSnippetAnnotations: test.allowSnippetAnnotations,
|
||||
NonTLSEntryPoints: []string{"web"},
|
||||
allowedHeaders: test.globalAllowedResponseHeaders,
|
||||
AllowCrossNamespaceResources: test.allowCrossNamespaceResources,
|
||||
|
||||
@ -26,6 +26,7 @@ import (
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/headers"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/inflightreq"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/ingressnginx/authtlspasscertificatetoupstream"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/ingressnginx/snippet"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/ipallowlist"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/ipwhitelist"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
@ -428,6 +429,16 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
||||
}
|
||||
}
|
||||
|
||||
// ingress-nginx middlewares.
|
||||
if config.Snippet != nil {
|
||||
if middleware != nil {
|
||||
return nil, badConf
|
||||
}
|
||||
middleware = func(next http.Handler) (http.Handler, error) {
|
||||
return snippet.New(ctx, next, config.Snippet, middlewareName)
|
||||
}
|
||||
}
|
||||
|
||||
if middleware == nil {
|
||||
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user