From 71d8c8d47894932273f5db28103d3c0e12e7430b Mon Sep 17 00:00:00 2001 From: igoramf Date: Thu, 18 Jun 2026 16:43:13 -0300 Subject: [PATCH 1/2] fix(decoredirect): preserve path on apex redirect using \$request_uri MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The permanent-redirect annotation was set to the bare target URL (e.g. https://www.client.com), causing nginx to redirect all requests to the root — discarding the path. Append nginx's \$request_uri variable so the full path and query string are preserved. Co-Authored-By: Claude Sonnet 4.6 --- internal/controller/decoredirect_controller.go | 2 +- internal/controller/decoredirect_controller_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/decoredirect_controller.go b/internal/controller/decoredirect_controller.go index 16a4482..bf30df6 100644 --- a/internal/controller/decoredirect_controller.go +++ b/internal/controller/decoredirect_controller.go @@ -140,7 +140,7 @@ func (r *DecoRedirectReconciler) reconcileIngress(ctx context.Context, rd *decos // nginx returns the configured redirect code (default 307) via the permanent-redirect annotation before reaching any backend. _, err := controllerutil.CreateOrUpdate(ctx, r.Client, ingress, func() error { ingress.Annotations = map[string]string{ - "nginx.ingress.kubernetes.io/permanent-redirect": rd.Spec.To, + "nginx.ingress.kubernetes.io/permanent-redirect": rd.Spec.To + "$request_uri", "nginx.ingress.kubernetes.io/permanent-redirect-code": strconv.Itoa(code), } ingress.Spec = networkingv1.IngressSpec{ diff --git a/internal/controller/decoredirect_controller_test.go b/internal/controller/decoredirect_controller_test.go index a3846db..64069c1 100644 --- a/internal/controller/decoredirect_controller_test.go +++ b/internal/controller/decoredirect_controller_test.go @@ -80,7 +80,7 @@ var _ = Describe("DecoRedirect Controller", func() { Name: "redirect-client-com", Namespace: rdNS, }, ing)).To(Succeed()) Expect(*ing.Spec.IngressClassName).To(Equal("nginx")) - Expect(ing.Annotations["nginx.ingress.kubernetes.io/permanent-redirect"]).To(Equal(toDomain)) + Expect(ing.Annotations["nginx.ingress.kubernetes.io/permanent-redirect"]).To(Equal(toDomain + "$request_uri")) Expect(ing.Spec.TLS[0].Hosts).To(ContainElement(fromDomain)) Expect(ing.Spec.TLS[0].SecretName).To(Equal("tls-client-com")) Expect(ing.Spec.Rules[0].Host).To(Equal(fromDomain)) From 998467acc44c386cfb2d82c6bb4e12e3f5dcb069 Mon Sep 17 00:00:00 2001 From: igoramf Date: Thu, 18 Jun 2026 17:07:07 -0300 Subject: [PATCH 2/2] fix(decoredirect): use server-snippet to preserve path on apex redirect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit permanent-redirect annotation does not support nginx variables — the admission webhook rejects values containing $. Switch to server-snippet with $request_uri so the full path and query string are preserved. Requires allow-snippet-annotations: true and annotations-risk-level: Critical in the nginx ingress controller config (infra_applications PRs #156, #157). Co-Authored-By: Claude Sonnet 4.6 --- internal/controller/decoredirect_controller.go | 8 ++++---- internal/controller/decoredirect_controller_test.go | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/controller/decoredirect_controller.go b/internal/controller/decoredirect_controller.go index bf30df6..718dce3 100644 --- a/internal/controller/decoredirect_controller.go +++ b/internal/controller/decoredirect_controller.go @@ -6,7 +6,6 @@ import ( "fmt" "net" "net/http" - "strconv" "strings" "time" @@ -137,11 +136,12 @@ func (r *DecoRedirectReconciler) reconcileIngress(ctx context.Context, rd *decos code = *rd.Spec.RedirectCode } - // nginx returns the configured redirect code (default 307) via the permanent-redirect annotation before reaching any backend. + // server-snippet injects a raw nginx return directive that preserves the full request path and + // query string via $request_uri. The permanent-redirect annotation cannot be used here because + // the nginx admission webhook rejects values containing nginx variables (e.g. $request_uri). _, err := controllerutil.CreateOrUpdate(ctx, r.Client, ingress, func() error { ingress.Annotations = map[string]string{ - "nginx.ingress.kubernetes.io/permanent-redirect": rd.Spec.To + "$request_uri", - "nginx.ingress.kubernetes.io/permanent-redirect-code": strconv.Itoa(code), + "nginx.ingress.kubernetes.io/server-snippet": fmt.Sprintf("return %d %s$request_uri;", code, rd.Spec.To), } ingress.Spec = networkingv1.IngressSpec{ IngressClassName: &r.IngressClass, diff --git a/internal/controller/decoredirect_controller_test.go b/internal/controller/decoredirect_controller_test.go index 64069c1..d66b0e1 100644 --- a/internal/controller/decoredirect_controller_test.go +++ b/internal/controller/decoredirect_controller_test.go @@ -71,7 +71,7 @@ var _ = Describe("DecoRedirect Controller", func() { Expect(cert.Spec.SecretName).To(Equal("tls-client-com")) }) - It("should create an Ingress with permanent-redirect annotation", func() { + It("should create an Ingress with server-snippet redirect preserving path", func() { _, err := newReconciler().Reconcile(ctx, reconcile.Request{NamespacedName: nn}) Expect(err).NotTo(HaveOccurred()) @@ -80,7 +80,7 @@ var _ = Describe("DecoRedirect Controller", func() { Name: "redirect-client-com", Namespace: rdNS, }, ing)).To(Succeed()) Expect(*ing.Spec.IngressClassName).To(Equal("nginx")) - Expect(ing.Annotations["nginx.ingress.kubernetes.io/permanent-redirect"]).To(Equal(toDomain + "$request_uri")) + Expect(ing.Annotations["nginx.ingress.kubernetes.io/server-snippet"]).To(Equal("return 307 " + toDomain + "$request_uri;")) Expect(ing.Spec.TLS[0].Hosts).To(ContainElement(fromDomain)) Expect(ing.Spec.TLS[0].SecretName).To(Equal("tls-client-com")) Expect(ing.Spec.Rules[0].Host).To(Equal(fromDomain)) @@ -149,7 +149,7 @@ var _ = Describe("DecoRedirect Controller", func() { Expect(count).To(Equal(1)) }) - It("should set permanent-redirect-code to 307 by default", func() { + It("should use return 307 in server-snippet by default", func() { _, err := newReconciler().Reconcile(ctx, reconcile.Request{NamespacedName: nn}) Expect(err).NotTo(HaveOccurred()) @@ -157,10 +157,10 @@ var _ = Describe("DecoRedirect Controller", func() { Expect(k8sClient.Get(ctx, types.NamespacedName{ Name: "redirect-client-com", Namespace: rdNS, }, ing)).To(Succeed()) - Expect(ing.Annotations["nginx.ingress.kubernetes.io/permanent-redirect-code"]).To(Equal("307")) + Expect(ing.Annotations["nginx.ingress.kubernetes.io/server-snippet"]).To(ContainSubstring("return 307 ")) }) - It("should use redirectCode 301 in the Ingress annotation when specified", func() { + It("should use return 301 in server-snippet when redirectCode is 301", func() { code := 301 rd301 := &decositesv1alpha1.DecoRedirect{ ObjectMeta: metav1.ObjectMeta{Name: "test-redirect-301", Namespace: rdNS}, @@ -181,7 +181,7 @@ var _ = Describe("DecoRedirect Controller", func() { Expect(k8sClient.Get(ctx, types.NamespacedName{ Name: "redirect-redirect301-com", Namespace: rdNS, }, ing)).To(Succeed()) - Expect(ing.Annotations["nginx.ingress.kubernetes.io/permanent-redirect-code"]).To(Equal("301")) + Expect(ing.Annotations["nginx.ingress.kubernetes.io/server-snippet"]).To(ContainSubstring("return 301 ")) }) })