Skip to content

Commit deb35fe

Browse files
igoramfclaude
andauthored
fix(decoredirect): use configuration-snippet to preserve ACME challenge (#24)
server-snippet runs at the nginx server block level (HTTP + HTTPS) before location matching, intercepting Let's Encrypt HTTP-01 ACME challenge requests and breaking certificate issuance for new domains. configuration-snippet is scoped to the location / block — the cert-manager ACME solver creates a more specific location (/.well-known/acme-challenge/TOKEN) that takes priority, so challenges are served correctly while path is still preserved via $request_uri on all other requests. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9ab8a5e commit deb35fe

2 files changed

Lines changed: 12 additions & 10 deletions

File tree

internal/controller/decoredirect_controller.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,14 @@ func (r *DecoRedirectReconciler) reconcileIngress(ctx context.Context, rd *decos
136136
code = *rd.Spec.RedirectCode
137137
}
138138

139-
// server-snippet injects a raw nginx return directive that preserves the full request path and
140-
// query string via $request_uri. The permanent-redirect annotation cannot be used here because
141-
// the nginx admission webhook rejects values containing nginx variables (e.g. $request_uri).
139+
// configuration-snippet injects the return directive into the location / block so the path and
140+
// query string are preserved via $request_uri. server-snippet cannot be used because it runs at
141+
// the server block level (HTTP + HTTPS) before location matching, which intercepts Let's Encrypt
142+
// HTTP-01 ACME challenge requests (/.well-known/acme-challenge/) and breaks cert issuance.
143+
// With configuration-snippet the cert-manager ACME solver location (more specific path) wins.
142144
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, ingress, func() error {
143145
ingress.Annotations = map[string]string{
144-
"nginx.ingress.kubernetes.io/server-snippet": fmt.Sprintf("return %d %s$request_uri;", code, rd.Spec.To),
146+
"nginx.ingress.kubernetes.io/configuration-snippet": fmt.Sprintf("return %d %s$request_uri;", code, rd.Spec.To),
145147
}
146148
ingress.Spec = networkingv1.IngressSpec{
147149
IngressClassName: &r.IngressClass,

internal/controller/decoredirect_controller_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ var _ = Describe("DecoRedirect Controller", func() {
7171
Expect(cert.Spec.SecretName).To(Equal("tls-client-com"))
7272
})
7373

74-
It("should create an Ingress with server-snippet redirect preserving path", func() {
74+
It("should create an Ingress with configuration-snippet redirect preserving path", func() {
7575
_, err := newReconciler().Reconcile(ctx, reconcile.Request{NamespacedName: nn})
7676
Expect(err).NotTo(HaveOccurred())
7777

@@ -80,7 +80,7 @@ var _ = Describe("DecoRedirect Controller", func() {
8080
Name: "redirect-client-com", Namespace: rdNS,
8181
}, ing)).To(Succeed())
8282
Expect(*ing.Spec.IngressClassName).To(Equal("nginx"))
83-
Expect(ing.Annotations["nginx.ingress.kubernetes.io/server-snippet"]).To(Equal("return 307 " + toDomain + "$request_uri;"))
83+
Expect(ing.Annotations["nginx.ingress.kubernetes.io/configuration-snippet"]).To(Equal("return 307 " + toDomain + "$request_uri;"))
8484
Expect(ing.Spec.TLS[0].Hosts).To(ContainElement(fromDomain))
8585
Expect(ing.Spec.TLS[0].SecretName).To(Equal("tls-client-com"))
8686
Expect(ing.Spec.Rules[0].Host).To(Equal(fromDomain))
@@ -149,18 +149,18 @@ var _ = Describe("DecoRedirect Controller", func() {
149149
Expect(count).To(Equal(1))
150150
})
151151

152-
It("should use return 307 in server-snippet by default", func() {
152+
It("should use return 307 in configuration-snippet by default", func() {
153153
_, err := newReconciler().Reconcile(ctx, reconcile.Request{NamespacedName: nn})
154154
Expect(err).NotTo(HaveOccurred())
155155

156156
ing := &networkingv1.Ingress{}
157157
Expect(k8sClient.Get(ctx, types.NamespacedName{
158158
Name: "redirect-client-com", Namespace: rdNS,
159159
}, ing)).To(Succeed())
160-
Expect(ing.Annotations["nginx.ingress.kubernetes.io/server-snippet"]).To(ContainSubstring("return 307 "))
160+
Expect(ing.Annotations["nginx.ingress.kubernetes.io/configuration-snippet"]).To(ContainSubstring("return 307 "))
161161
})
162162

163-
It("should use return 301 in server-snippet when redirectCode is 301", func() {
163+
It("should use return 301 in configuration-snippet when redirectCode is 301", func() {
164164
code := 301
165165
rd301 := &decositesv1alpha1.DecoRedirect{
166166
ObjectMeta: metav1.ObjectMeta{Name: "test-redirect-301", Namespace: rdNS},
@@ -181,7 +181,7 @@ var _ = Describe("DecoRedirect Controller", func() {
181181
Expect(k8sClient.Get(ctx, types.NamespacedName{
182182
Name: "redirect-redirect301-com", Namespace: rdNS,
183183
}, ing)).To(Succeed())
184-
Expect(ing.Annotations["nginx.ingress.kubernetes.io/server-snippet"]).To(ContainSubstring("return 301 "))
184+
Expect(ing.Annotations["nginx.ingress.kubernetes.io/configuration-snippet"]).To(ContainSubstring("return 301 "))
185185
})
186186
})
187187

0 commit comments

Comments
 (0)