Add support for app-root nginx annotation

This commit is contained in:
LBF38
2026-01-26 17:44:04 +01:00
committed by GitHub
parent 27912e3849
commit a9c5a3828b
6 changed files with 167 additions and 6 deletions
@@ -316,6 +316,7 @@ The following annotations are organized by category for easier navigation.
| 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-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. |
@@ -356,7 +357,6 @@ The following annotations are organized by category for easier navigation.
| Annotation | 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-ioaffinity-canary-behavior" href="#opt-nginx-ingress-kubernetes-ioaffinity-canary-behavior" title="#opt-nginx-ingress-kubernetes-ioaffinity-canary-behavior">`nginx.ingress.kubernetes.io/affinity-canary-behavior`</a> | |
| <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> | |
| <a id="opt-nginx-ingress-kubernetes-ioauth-tls-secret" href="#opt-nginx-ingress-kubernetes-ioauth-tls-secret" title="#opt-nginx-ingress-kubernetes-ioauth-tls-secret">`nginx.ingress.kubernetes.io/auth-tls-secret`</a> | |
@@ -25,6 +25,7 @@ type ingressConfig struct {
UseRegex *bool `annotation:"nginx.ingress.kubernetes.io/use-regex"`
RewriteTarget *string `annotation:"nginx.ingress.kubernetes.io/rewrite-target"`
AppRoot *string `annotation:"nginx.ingress.kubernetes.io/app-root"`
PermanentRedirect *string `annotation:"nginx.ingress.kubernetes.io/permanent-redirect"`
PermanentRedirectCode *int `annotation:"nginx.ingress.kubernetes.io/permanent-redirect-code"`
@@ -0,0 +1,22 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-app-root
namespace: default
annotations:
nginx.ingress.kubernetes.io/app-root: foo
spec:
ingressClassName: nginx
rules:
- host: app-root.localhost
http:
paths:
- path: /bar
pathType: Prefix
backend:
service:
name: whoami
port:
number: 80
@@ -0,0 +1,22 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-app-root
namespace: default
annotations:
nginx.ingress.kubernetes.io/app-root: /foo
spec:
ingressClassName: nginx
rules:
- host: app-root.localhost
http:
paths:
- path: /bar
pathType: Prefix
backend:
service:
name: whoami
port:
number: 80
@@ -793,6 +793,12 @@ func (p *Provider) loadCertificates(ctx context.Context, ingress *netv1.Ingress,
}
func (p *Provider) applyMiddlewares(namespace, routerKey, rulePath string, ingressConfig ingressConfig, hasTLS bool, rt *dynamic.Router, conf *dynamic.Configuration) error {
applyAppRootConfiguration(routerKey, ingressConfig, rt, conf)
// Apply SSL redirect is mandatory to be applied after all other middlewares.
// TODO: check how to remove this, and create the HTTP router elsewhere.
p.applySSLRedirectConfiguration(routerKey, ingressConfig, hasTLS, rt, conf)
if err := p.applyBasicAuthConfiguration(namespace, routerKey, ingressConfig, rt, conf); err != nil {
return fmt.Errorf("applying basic auth configuration: %w", err)
}
@@ -807,10 +813,6 @@ func (p *Provider) applyMiddlewares(namespace, routerKey, rulePath string, ingre
applyRewriteTargetConfiguration(rulePath, routerKey, ingressConfig, rt, conf)
// Apply SSL redirect is mandatory to be applied after all other middlewares.
// TODO: check how to remove this, and create the HTTP router elsewhere.
p.applySSLRedirectConfiguration(routerKey, ingressConfig, hasTLS, rt, conf)
applyRedirect(routerKey, ingressConfig, rt, conf)
applyUpstreamVhost(routerKey, ingressConfig, rt, conf)
@@ -910,6 +912,22 @@ func applyRewriteTargetConfiguration(rulePath, routerName string, ingressConfig
rt.Middlewares = append(rt.Middlewares, rewriteTargetMiddlewareName)
}
func applyAppRootConfiguration(routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) {
if ingressConfig.AppRoot == nil || !strings.HasPrefix(*ingressConfig.AppRoot, "/") {
return
}
appRootMiddlewareName := routerName + "-app-root"
conf.HTTP.Middlewares[appRootMiddlewareName] = &dynamic.Middleware{
RedirectRegex: &dynamic.RedirectRegex{
Regex: `^(https?://[^/]+)/$`,
Replacement: "$1" + *ingressConfig.AppRoot,
},
}
rt.Middlewares = append(rt.Middlewares, appRootMiddlewareName)
}
func (p *Provider) applyBasicAuthConfiguration(namespace, routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) error {
if ingressConfig.AuthType == nil {
return nil
@@ -1125,7 +1143,7 @@ func (p *Provider) applySSLRedirectConfiguration(routerName string, ingressConfi
ForcePermanentRedirect: true,
},
}
rt.Middlewares = append([]string{redirectMiddlewareName}, rt.Middlewares...)
rt.Middlewares = append(rt.Middlewares, redirectMiddlewareName)
}
// An Ingress that is not forcing sslRedirect and has no TLS configuration does not redirect,
@@ -809,6 +809,104 @@ func TestLoadIngresses(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "App Root",
paths: []string{
"services.yml",
"ingressclasses.yml",
"ingresses/18-ingress-with-app-root.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-app-root-rule-0-path-0": {
Rule: "Host(`app-root.localhost`) && (Path(`/bar`) || PathPrefix(`/bar/`))",
RuleSyntax: "default",
Service: "default-ingress-with-app-root-whoami-80",
Middlewares: []string{"default-ingress-with-app-root-rule-0-path-0-app-root"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-ingress-with-app-root-rule-0-path-0-app-root": {
RedirectRegex: &dynamic.RedirectRegex{
Regex: `^(https?://[^/]+)/$`,
Replacement: "$1/foo",
},
},
},
Services: map[string]*dynamic.Service{
"default-ingress-with-app-root-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),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: dynamic.DefaultFlushInterval,
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "App Root - no prefix slash",
paths: []string{
"services.yml",
"ingressclasses.yml",
"ingresses/18-ingress-with-app-root-wrong.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-app-root-rule-0-path-0": {
Rule: "Host(`app-root.localhost`) && (Path(`/bar`) || PathPrefix(`/bar/`))",
RuleSyntax: "default",
Service: "default-ingress-with-app-root-whoami-80",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-ingress-with-app-root-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),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: dynamic.DefaultFlushInterval,
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Default Backend",
defaultBackendServiceName: "whoami",