Merge 0bd35dc388
into de1a4c463c
This commit is contained in:
commit
276c08130e
13 changed files with 308 additions and 2 deletions
|
@ -108,9 +108,11 @@
|
||||||
| Redirect | temporal-redirect | Medium | location |
|
| Redirect | temporal-redirect | Medium | location |
|
||||||
| Redirect | temporal-redirect-code | Low | location |
|
| Redirect | temporal-redirect-code | Low | location |
|
||||||
| Rewrite | app-root | Medium | location |
|
| Rewrite | app-root | Medium | location |
|
||||||
|
| Rewrite | force-ssl-forbid-http | Medium | location |
|
||||||
| Rewrite | force-ssl-redirect | Medium | location |
|
| Rewrite | force-ssl-redirect | Medium | location |
|
||||||
| Rewrite | preserve-trailing-slash | Medium | location |
|
| Rewrite | preserve-trailing-slash | Medium | location |
|
||||||
| Rewrite | rewrite-target | Medium | ingress |
|
| Rewrite | rewrite-target | Medium | ingress |
|
||||||
|
| Rewrite | ssl-forbid-http | Low | location |
|
||||||
| Rewrite | ssl-redirect | Low | location |
|
| Rewrite | ssl-redirect | Low | location |
|
||||||
| Rewrite | use-regex | Low | location |
|
| Rewrite | use-regex | Low | location |
|
||||||
| SSLCipher | ssl-ciphers | Low | ingress |
|
| SSLCipher | ssl-ciphers | Low | ingress |
|
||||||
|
|
|
@ -59,6 +59,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|
||||||
|[nginx.ingress.kubernetes.io/cors-expose-headers](#enable-cors)|string|
|
|[nginx.ingress.kubernetes.io/cors-expose-headers](#enable-cors)|string|
|
||||||
|[nginx.ingress.kubernetes.io/cors-allow-credentials](#enable-cors)|"true" or "false"|
|
|[nginx.ingress.kubernetes.io/cors-allow-credentials](#enable-cors)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/cors-max-age](#enable-cors)|number|
|
|[nginx.ingress.kubernetes.io/cors-max-age](#enable-cors)|number|
|
||||||
|
|[nginx.ingress.kubernetes.io/force-ssl-forbid-http](#server-side-https-enforcement-through-forbidden-errors)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/force-ssl-redirect](#server-side-https-enforcement-through-redirect)|"true" or "false"|
|
|[nginx.ingress.kubernetes.io/force-ssl-redirect](#server-side-https-enforcement-through-redirect)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/from-to-www-redirect](#redirect-fromto-www)|"true" or "false"|
|
|[nginx.ingress.kubernetes.io/from-to-www-redirect](#redirect-fromto-www)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/http2-push-preload](#http2-push-preload)|"true" or "false"|
|
|[nginx.ingress.kubernetes.io/http2-push-preload](#http2-push-preload)|"true" or "false"|
|
||||||
|
@ -104,6 +105,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|
||||||
|[nginx.ingress.kubernetes.io/session-cookie-path](#cookie-affinity)|string|
|
|[nginx.ingress.kubernetes.io/session-cookie-path](#cookie-affinity)|string|
|
||||||
|[nginx.ingress.kubernetes.io/session-cookie-samesite](#cookie-affinity)|string|"None", "Lax" or "Strict"|
|
|[nginx.ingress.kubernetes.io/session-cookie-samesite](#cookie-affinity)|string|"None", "Lax" or "Strict"|
|
||||||
|[nginx.ingress.kubernetes.io/session-cookie-secure](#cookie-affinity)|string|
|
|[nginx.ingress.kubernetes.io/session-cookie-secure](#cookie-affinity)|string|
|
||||||
|
|[nginx.ingress.kubernetes.io/ssl-forbid-http](#server-side-https-enforcement-through-forbidden-errors)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/ssl-redirect](#server-side-https-enforcement-through-redirect)|"true" or "false"|
|
|[nginx.ingress.kubernetes.io/ssl-redirect](#server-side-https-enforcement-through-redirect)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/ssl-passthrough](#ssl-passthrough)|"true" or "false"|
|
|[nginx.ingress.kubernetes.io/ssl-passthrough](#ssl-passthrough)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/stream-snippet](#stream-snippet)|string|
|
|[nginx.ingress.kubernetes.io/stream-snippet](#stream-snippet)|string|
|
||||||
|
@ -627,6 +629,21 @@ This can be achieved by using the `nginx.ingress.kubernetes.io/force-ssl-redirec
|
||||||
|
|
||||||
To preserve the trailing slash in the URI with `ssl-redirect`, set `nginx.ingress.kubernetes.io/preserve-trailing-slash: "true"` annotation for that particular resource.
|
To preserve the trailing slash in the URI with `ssl-redirect`, set `nginx.ingress.kubernetes.io/preserve-trailing-slash: "true"` annotation for that particular resource.
|
||||||
|
|
||||||
|
### Server-side HTTPS enforcement through forbidden errors
|
||||||
|
|
||||||
|
In certain scenarios, you might prefer to return a 403 Forbidden error response instead of redirecting traffic to the HTTPS port.
|
||||||
|
This approach helps prevent misconfigured clients from inadvertently leaking sensitive data over unencrypted connections.
|
||||||
|
|
||||||
|
This can be enabled globally using `ssl-forbid-http: "true"` in the [ConfigMap][./configmap.md#ssl-forbid-http].
|
||||||
|
|
||||||
|
To configure this feature for specific Ingress resources, you can use the `nginx.ingress.kubernetes.io/ssl-forbid-http: "true"`
|
||||||
|
annotation in the particular resource.
|
||||||
|
|
||||||
|
When using SSL off-loading outside of the cluster (e.g. AWS ELB), it may be useful to enforce 403 Forbidden errors to HTTP requests
|
||||||
|
even when there is no TLS certificate available.
|
||||||
|
|
||||||
|
This can be achieved by using the `nginx.ingress.kubernetes.io/force-ssl-forbid-http: "true"` annotation in the particular resource.
|
||||||
|
|
||||||
### Redirect from/to www
|
### Redirect from/to www
|
||||||
|
|
||||||
In some scenarios, it is required to redirect from `www.domain.com` to `domain.com` or vice versa, which way the redirect is performed depends on the configured `host` value in the Ingress object.
|
In some scenarios, it is required to redirect from `www.domain.com` to `domain.com` or vice versa, which way the redirect is performed depends on the configured `host` value in the Ingress object.
|
||||||
|
|
|
@ -189,6 +189,8 @@ The following table shows a configuration option's name, type, and the default v
|
||||||
| [proxy-request-buffering](#proxy-request-buffering) | string | "on" | |
|
| [proxy-request-buffering](#proxy-request-buffering) | string | "on" | |
|
||||||
| [ssl-redirect](#ssl-redirect) | bool | "true" | |
|
| [ssl-redirect](#ssl-redirect) | bool | "true" | |
|
||||||
| [force-ssl-redirect](#force-ssl-redirect) | bool | "false" | |
|
| [force-ssl-redirect](#force-ssl-redirect) | bool | "false" | |
|
||||||
|
| [ssl-forbid-http](#ssl-forbid-http) | bool | "false" | |
|
||||||
|
| [force-ssl-forbid-http](#force-ssl-forbid-http) | bool | "false" | |
|
||||||
| [denylist-source-range](#denylist-source-range) | []string | []string{} | |
|
| [denylist-source-range](#denylist-source-range) | []string | []string{} | |
|
||||||
| [whitelist-source-range](#whitelist-source-range) | []string | []string{} | |
|
| [whitelist-source-range](#whitelist-source-range) | []string | []string{} | |
|
||||||
| [skip-access-log-urls](#skip-access-log-urls) | []string | []string{} | |
|
| [skip-access-log-urls](#skip-access-log-urls) | []string | []string{} | |
|
||||||
|
@ -1154,6 +1156,18 @@ _**default:**_ "true"
|
||||||
Sets the global value of redirects (308) to HTTPS if the server has a default TLS certificate (defined in extra-args).
|
Sets the global value of redirects (308) to HTTPS if the server has a default TLS certificate (defined in extra-args).
|
||||||
_**default:**_ "false"
|
_**default:**_ "false"
|
||||||
|
|
||||||
|
## ssl-forbid-http
|
||||||
|
|
||||||
|
Sets the global value of 403 Forbidden errors to HTTP if the server has a TLS certificate (defined in an Ingress rule).
|
||||||
|
|
||||||
|
_**default:**_ "false"
|
||||||
|
|
||||||
|
## force-ssl-forbid-http
|
||||||
|
|
||||||
|
Sets the global value of 403 Forbidden errors to HTTP if the server has a default TLS certificate (defined in extra-args).
|
||||||
|
|
||||||
|
_**default:**_ "false"
|
||||||
|
|
||||||
## denylist-source-range
|
## denylist-source-range
|
||||||
|
|
||||||
Sets the default denylisted IPs for each `server` block. This can be overwritten by an annotation on an Ingress rule.
|
Sets the default denylisted IPs for each `server` block. This can be overwritten by an annotation on an Ingress rule.
|
||||||
|
|
|
@ -78,21 +78,31 @@ HSTS is enabled by default.
|
||||||
|
|
||||||
To disable this behavior use `hsts: "false"` in the configuration [ConfigMap][ConfigMap].
|
To disable this behavior use `hsts: "false"` in the configuration [ConfigMap][ConfigMap].
|
||||||
|
|
||||||
## Server-side HTTPS enforcement through redirect
|
## Server-side HTTPS enforcement
|
||||||
|
|
||||||
By default the controller redirects HTTP clients to the HTTPS port
|
By default the controller redirects HTTP clients to the HTTPS port
|
||||||
443 using a 308 Permanent Redirect response if TLS is enabled for that Ingress.
|
443 using a 308 Permanent Redirect response if TLS is enabled for that Ingress.
|
||||||
|
|
||||||
This can be disabled globally using `ssl-redirect: "false"` in the NGINX [config map][ConfigMap],
|
This can be disabled globally using `ssl-redirect: "false"` in the [config map][ConfigMap],
|
||||||
or per-Ingress with the `nginx.ingress.kubernetes.io/ssl-redirect: "false"`
|
or per-Ingress with the `nginx.ingress.kubernetes.io/ssl-redirect: "false"`
|
||||||
annotation in the particular resource.
|
annotation in the particular resource.
|
||||||
|
|
||||||
|
In certain scenarios, you might prefer to return a 403 Forbidden error response instead of redirecting traffic to the HTTPS port.
|
||||||
|
This approach helps prevent misconfigured clients from inadvertently leaking sensitive data over unencrypted connections.
|
||||||
|
|
||||||
|
This can be enabled globally using `ssl-forbid-http: "true"` in the [config map][ConfigMap],
|
||||||
|
or per-Ingress with the `nginx.ingress.kubernetes.io/ssl-forbid-http: "true"` annotation in the particular resource.
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
When using SSL offloading outside of cluster (e.g. AWS ELB) it may be useful to enforce a
|
When using SSL offloading outside of cluster (e.g. AWS ELB) it may be useful to enforce a
|
||||||
redirect to HTTPS even when there is no TLS certificate available.
|
redirect to HTTPS even when there is no TLS certificate available.
|
||||||
This can be achieved by using the `nginx.ingress.kubernetes.io/force-ssl-redirect: "true"`
|
This can be achieved by using the `nginx.ingress.kubernetes.io/force-ssl-redirect: "true"`
|
||||||
annotation in the particular resource.
|
annotation in the particular resource.
|
||||||
|
|
||||||
|
Similarly, you can enforce 403 Forbidden errors to HTTP requests using the
|
||||||
|
`nginx.ingress.kubernetes.io/force-ssl-forbid-http: "true"` annotation in the particular
|
||||||
|
resource.
|
||||||
|
|
||||||
## Automated Certificate Management with cert-manager
|
## Automated Certificate Management with cert-manager
|
||||||
|
|
||||||
[cert-manager] automatically requests missing or expired certificates from a range of
|
[cert-manager] automatically requests missing or expired certificates from a range of
|
||||||
|
|
|
@ -32,6 +32,8 @@ const (
|
||||||
sslRedirectAnnotation = "ssl-redirect"
|
sslRedirectAnnotation = "ssl-redirect"
|
||||||
preserveTrailingSlashAnnotation = "preserve-trailing-slash"
|
preserveTrailingSlashAnnotation = "preserve-trailing-slash"
|
||||||
forceSSLRedirectAnnotation = "force-ssl-redirect"
|
forceSSLRedirectAnnotation = "force-ssl-redirect"
|
||||||
|
sslForbidHTTPAnnotation = "ssl-forbid-http"
|
||||||
|
forceSSLForbidHTTPAnnotation = "force-ssl-forbid-http"
|
||||||
useRegexAnnotation = "use-regex"
|
useRegexAnnotation = "use-regex"
|
||||||
appRootAnnotation = "app-root"
|
appRootAnnotation = "app-root"
|
||||||
)
|
)
|
||||||
|
@ -64,6 +66,18 @@ var rewriteAnnotations = parser.Annotation{
|
||||||
Risk: parser.AnnotationRiskMedium,
|
Risk: parser.AnnotationRiskMedium,
|
||||||
Documentation: `This annotation forces the redirection to HTTPS even if the Ingress is not TLS Enabled`,
|
Documentation: `This annotation forces the redirection to HTTPS even if the Ingress is not TLS Enabled`,
|
||||||
},
|
},
|
||||||
|
sslForbidHTTPAnnotation: {
|
||||||
|
Validator: parser.ValidateBool,
|
||||||
|
Scope: parser.AnnotationScopeLocation,
|
||||||
|
Risk: parser.AnnotationRiskLow,
|
||||||
|
Documentation: `This annotation defines if the location section should forbid HTTP requests`,
|
||||||
|
},
|
||||||
|
forceSSLForbidHTTPAnnotation: {
|
||||||
|
Validator: parser.ValidateBool,
|
||||||
|
Scope: parser.AnnotationScopeLocation,
|
||||||
|
Risk: parser.AnnotationRiskMedium,
|
||||||
|
Documentation: `This annotation forces the forbidden error to HTTP even if the Ingress is not TLS Enabled`,
|
||||||
|
},
|
||||||
useRegexAnnotation: {
|
useRegexAnnotation: {
|
||||||
Validator: parser.ValidateBool,
|
Validator: parser.ValidateBool,
|
||||||
Scope: parser.AnnotationScopeLocation,
|
Scope: parser.AnnotationScopeLocation,
|
||||||
|
@ -88,6 +102,10 @@ type Config struct {
|
||||||
SSLRedirect bool `json:"sslRedirect"`
|
SSLRedirect bool `json:"sslRedirect"`
|
||||||
// ForceSSLRedirect indicates if the location section is accessible SSL only
|
// ForceSSLRedirect indicates if the location section is accessible SSL only
|
||||||
ForceSSLRedirect bool `json:"forceSSLRedirect"`
|
ForceSSLRedirect bool `json:"forceSSLRedirect"`
|
||||||
|
// SSLForbidHTTP indicates if the location section is accessible SSL only
|
||||||
|
SSLForbidHTTP bool `json:"sslForbidHTTP"`
|
||||||
|
// ForceSSLForbidHTTP indicates if the location section is accessible SSL only
|
||||||
|
ForceSSLForbidHTTP bool `json:"forceSSLForbidHTTP"`
|
||||||
// PreserveTrailingSlash indicates if the trailing slash should be kept during a tls redirect
|
// PreserveTrailingSlash indicates if the trailing slash should be kept during a tls redirect
|
||||||
PreserveTrailingSlash bool `json:"preserveTrailingSlash"`
|
PreserveTrailingSlash bool `json:"preserveTrailingSlash"`
|
||||||
// AppRoot defines the Application Root that the Controller must redirect if it's in '/' context
|
// AppRoot defines the Application Root that the Controller must redirect if it's in '/' context
|
||||||
|
@ -113,6 +131,12 @@ func (r1 *Config) Equal(r2 *Config) bool {
|
||||||
if r1.ForceSSLRedirect != r2.ForceSSLRedirect {
|
if r1.ForceSSLRedirect != r2.ForceSSLRedirect {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if r1.SSLForbidHTTP != r2.SSLForbidHTTP {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.ForceSSLForbidHTTP != r2.ForceSSLForbidHTTP {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if r1.AppRoot != r2.AppRoot {
|
if r1.AppRoot != r2.AppRoot {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -172,6 +196,22 @@ func (a rewrite) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
config.ForceSSLRedirect = a.r.GetDefaultBackend().ForceSSLRedirect
|
config.ForceSSLRedirect = a.r.GetDefaultBackend().ForceSSLRedirect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.SSLForbidHTTP, err = parser.GetBoolAnnotation(sslForbidHTTPAnnotation, ing, a.annotationConfig.Annotations)
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsValidationError(err) {
|
||||||
|
klog.Warningf("%s is invalid, defaulting to '%t'", sslForbidHTTPAnnotation, a.r.GetDefaultBackend().SSLForbidHTTP)
|
||||||
|
}
|
||||||
|
config.SSLForbidHTTP = a.r.GetDefaultBackend().SSLForbidHTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
config.ForceSSLForbidHTTP, err = parser.GetBoolAnnotation(forceSSLForbidHTTPAnnotation, ing, a.annotationConfig.Annotations)
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsValidationError(err) {
|
||||||
|
klog.Warningf("%s is invalid, defaulting to '%t'", forceSSLForbidHTTPAnnotation, a.r.GetDefaultBackend().ForceSSLForbidHTTP)
|
||||||
|
}
|
||||||
|
config.ForceSSLForbidHTTP = a.r.GetDefaultBackend().ForceSSLForbidHTTP
|
||||||
|
}
|
||||||
|
|
||||||
config.UseRegex, err = parser.GetBoolAnnotation(useRegexAnnotation, ing, a.annotationConfig.Annotations)
|
config.UseRegex, err = parser.GetBoolAnnotation(useRegexAnnotation, ing, a.annotationConfig.Annotations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsValidationError(err) {
|
if errors.IsValidationError(err) {
|
||||||
|
|
|
@ -213,6 +213,70 @@ func TestForceSSLRedirect(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSSLForbidHTTP(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
forbid, ok := i.(*Config)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Forbid type")
|
||||||
|
}
|
||||||
|
if forbid.SSLForbidHTTP {
|
||||||
|
t.Errorf("Expected false but returned true")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[parser.GetAnnotationWithPrefix("ssl-forbid-http")] = "true"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, err = NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
forbid, ok = i.(*Config)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Forbid type")
|
||||||
|
}
|
||||||
|
if !forbid.SSLForbidHTTP {
|
||||||
|
t.Errorf("Expected true but returned false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForceSSLForbidHTTP(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
forbid, ok := i.(*Config)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Forbid type")
|
||||||
|
}
|
||||||
|
if forbid.ForceSSLForbidHTTP {
|
||||||
|
t.Errorf("Expected false but returned true")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[parser.GetAnnotationWithPrefix("force-ssl-forbid-http")] = "true"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, err = NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
forbid, ok = i.(*Config)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Forbid type")
|
||||||
|
}
|
||||||
|
if !forbid.ForceSSLForbidHTTP {
|
||||||
|
t.Errorf("Expected true but returned false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAppRoot(t *testing.T) {
|
func TestAppRoot(t *testing.T) {
|
||||||
ap := NewParser(mockBackend{redirect: true})
|
ap := NewParser(mockBackend{redirect: true})
|
||||||
|
|
||||||
|
|
|
@ -435,6 +435,8 @@ func locationConfigForLua(l, a interface{}) string {
|
||||||
force_ssl_redirect = string_to_bool(ngx.var.force_ssl_redirect),
|
force_ssl_redirect = string_to_bool(ngx.var.force_ssl_redirect),
|
||||||
ssl_redirect = string_to_bool(ngx.var.ssl_redirect),
|
ssl_redirect = string_to_bool(ngx.var.ssl_redirect),
|
||||||
force_no_ssl_redirect = string_to_bool(ngx.var.force_no_ssl_redirect),
|
force_no_ssl_redirect = string_to_bool(ngx.var.force_no_ssl_redirect),
|
||||||
|
force_ssl_forbid_http = string_to_bool(ngx.var.force_ssl_forbid_http),
|
||||||
|
ssl_forbid_http = string_to_bool(ngx.var.ssl_forbid_http),
|
||||||
preserve_trailing_slash = string_to_bool(ngx.var.preserve_trailing_slash),
|
preserve_trailing_slash = string_to_bool(ngx.var.preserve_trailing_slash),
|
||||||
use_port_in_redirects = string_to_bool(ngx.var.use_port_in_redirects),
|
use_port_in_redirects = string_to_bool(ngx.var.use_port_in_redirects),
|
||||||
*/
|
*/
|
||||||
|
@ -443,12 +445,16 @@ func locationConfigForLua(l, a interface{}) string {
|
||||||
set $force_ssl_redirect "%t";
|
set $force_ssl_redirect "%t";
|
||||||
set $ssl_redirect "%t";
|
set $ssl_redirect "%t";
|
||||||
set $force_no_ssl_redirect "%t";
|
set $force_no_ssl_redirect "%t";
|
||||||
|
set $force_ssl_forbid_http "%t";
|
||||||
|
set $ssl_forbid_http "%t";
|
||||||
set $preserve_trailing_slash "%t";
|
set $preserve_trailing_slash "%t";
|
||||||
set $use_port_in_redirects "%t";
|
set $use_port_in_redirects "%t";
|
||||||
`,
|
`,
|
||||||
location.Rewrite.ForceSSLRedirect,
|
location.Rewrite.ForceSSLRedirect,
|
||||||
location.Rewrite.SSLRedirect,
|
location.Rewrite.SSLRedirect,
|
||||||
isLocationInLocationList(l, all.Cfg.NoTLSRedirectLocations),
|
isLocationInLocationList(l, all.Cfg.NoTLSRedirectLocations),
|
||||||
|
location.Rewrite.ForceSSLForbidHTTP,
|
||||||
|
location.Rewrite.SSLForbidHTTP,
|
||||||
location.Rewrite.PreserveTrailingSlash,
|
location.Rewrite.PreserveTrailingSlash,
|
||||||
location.UsePortInRedirects,
|
location.UsePortInRedirects,
|
||||||
)
|
)
|
||||||
|
|
|
@ -126,6 +126,13 @@ type Backend struct {
|
||||||
// This is useful if doing SSL offloading outside of cluster eg AWS ELB
|
// This is useful if doing SSL offloading outside of cluster eg AWS ELB
|
||||||
ForceSSLRedirect bool `json:"force-ssl-redirect"`
|
ForceSSLRedirect bool `json:"force-ssl-redirect"`
|
||||||
|
|
||||||
|
// Enables or disables forbidden errors (403) to HTTP
|
||||||
|
SSLForbidHTTP bool `json:"ssl-forbid-http"`
|
||||||
|
|
||||||
|
// Enables or disables forbidden errors (403) to HTTP even without TLS cert
|
||||||
|
// This is useful if doing SSL offloading outside of cluster eg AWS ELB
|
||||||
|
ForceSSLForbidHTTP bool `json:"force-ssl-forbid-http"`
|
||||||
|
|
||||||
// Enables or disables the specification of port in redirects
|
// Enables or disables the specification of port in redirects
|
||||||
// Default: false
|
// Default: false
|
||||||
UsePortInRedirects bool `json:"use-port-in-redirects"`
|
UsePortInRedirects bool `json:"use-port-in-redirects"`
|
||||||
|
|
|
@ -62,6 +62,18 @@ local function randomseed()
|
||||||
math.randomseed(seed)
|
math.randomseed(seed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function forbid_http(location_config)
|
||||||
|
if location_config.force_ssl_forbid_http and ngx.var.pass_access_scheme == "http" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if ngx.var.pass_access_scheme ~= "http" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return location_config.ssl_forbid_http and certificate_configured_for_current_request()
|
||||||
|
end
|
||||||
|
|
||||||
local function redirect_to_https(location_config)
|
local function redirect_to_https(location_config)
|
||||||
if location_config.force_no_ssl_redirect then
|
if location_config.force_no_ssl_redirect then
|
||||||
return false
|
return false
|
||||||
|
@ -115,6 +127,8 @@ function _M.rewrite()
|
||||||
force_ssl_redirect = string_to_bool(ngx.var.force_ssl_redirect),
|
force_ssl_redirect = string_to_bool(ngx.var.force_ssl_redirect),
|
||||||
ssl_redirect = string_to_bool(ngx.var.ssl_redirect),
|
ssl_redirect = string_to_bool(ngx.var.ssl_redirect),
|
||||||
force_no_ssl_redirect = string_to_bool(ngx.var.force_no_ssl_redirect),
|
force_no_ssl_redirect = string_to_bool(ngx.var.force_no_ssl_redirect),
|
||||||
|
force_ssl_forbid_http = string_to_bool(ngx.var.force_ssl_forbid_http),
|
||||||
|
ssl_forbid_http = string_to_bool(ngx.var.ssl_forbid_http),
|
||||||
preserve_trailing_slash = string_to_bool(ngx.var.preserve_trailing_slash),
|
preserve_trailing_slash = string_to_bool(ngx.var.preserve_trailing_slash),
|
||||||
use_port_in_redirects = string_to_bool(ngx.var.use_port_in_redirects),
|
use_port_in_redirects = string_to_bool(ngx.var.use_port_in_redirects),
|
||||||
}
|
}
|
||||||
|
@ -154,6 +168,10 @@ function _M.rewrite()
|
||||||
ngx.var.pass_port = 443
|
ngx.var.pass_port = 443
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if forbid_http(location_config) then
|
||||||
|
ngx.exit(ngx.HTTP_FORBIDDEN)
|
||||||
|
end
|
||||||
|
|
||||||
if redirect_to_https(location_config) then
|
if redirect_to_https(location_config) then
|
||||||
local request_uri = ngx.var.request_uri
|
local request_uri = ngx.var.request_uri
|
||||||
-- do not append a trailing slash on redirects unless enabled by annotations
|
-- do not append a trailing slash on redirects unless enabled by annotations
|
||||||
|
|
|
@ -115,6 +115,8 @@ http {
|
||||||
force_ssl_redirect = false,
|
force_ssl_redirect = false,
|
||||||
ssl_redirect = false,
|
ssl_redirect = false,
|
||||||
force_no_ssl_redirect = false,
|
force_no_ssl_redirect = false,
|
||||||
|
force_ssl_forbid_http = false,
|
||||||
|
ssl_forbid_http = false,
|
||||||
use_port_in_redirects = false,
|
use_port_in_redirects = false,
|
||||||
})
|
})
|
||||||
balancer.rewrite()
|
balancer.rewrite()
|
||||||
|
|
|
@ -158,6 +158,8 @@ lua_shared_dict ocsp_response_cache 5M;
|
||||||
force_ssl_redirect = false,
|
force_ssl_redirect = false,
|
||||||
ssl_redirect = false,
|
ssl_redirect = false,
|
||||||
force_no_ssl_redirect = false,
|
force_no_ssl_redirect = false,
|
||||||
|
force_ssl_forbid_http = false,
|
||||||
|
ssl_forbid_http = false,
|
||||||
use_port_in_redirects = false,
|
use_port_in_redirects = false,
|
||||||
})
|
})
|
||||||
balancer.rewrite()
|
balancer.rewrite()
|
||||||
|
|
51
test/e2e/annotations/forcesslforbidhttp.go
Normal file
51
test/e2e/annotations/forcesslforbidhttp.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package annotations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = framework.DescribeAnnotation("force-ssl-forbid-http", func() {
|
||||||
|
f := framework.NewDefaultFramework("forcesslforbidhttp")
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
f.NewEchoDeployment()
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should send forbidden errors for http", func() {
|
||||||
|
host := "forcesslforbid.bar.com"
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/force-ssl-forbid-http": "true",
|
||||||
|
"nginx.ingress.kubernetes.io/force-ssl-redirect": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET("/").
|
||||||
|
WithHeader("Host", host).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusForbidden)
|
||||||
|
})
|
||||||
|
})
|
73
test/e2e/annotations/sslforbidhttp.go
Normal file
73
test/e2e/annotations/sslforbidhttp.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package annotations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = framework.DescribeAnnotation("ssl-forbid-http", func() {
|
||||||
|
f := framework.NewDefaultFramework("sslforbidhttp")
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
f.NewEchoDeployment()
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should send forbidden errors for http when tls is present", func() {
|
||||||
|
host := "sslforbid.bar.com"
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/ssl-forbid-http": "true",
|
||||||
|
"nginx.ingress.kubernetes.io/ssl-redirect": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, framework.EchoService, 80, annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET("/").
|
||||||
|
WithHeader("Host", host).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should pass through for http when tls is absent", func() {
|
||||||
|
host := "sslforbidnotls.bar.com"
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/ssl-forbid-http": "true",
|
||||||
|
"nginx.ingress.kubernetes.io/ssl-redirect": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
expectBodyRequestURI := fmt.Sprintf("request_uri=http://%v:80", host)
|
||||||
|
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET("/").
|
||||||
|
WithHeader("Host", host).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Body().Contains(expectBodyRequestURI)
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue