ingress-nginx: Permanent Redirect with Cert-Manager conflicts with Well-Known ACME validator

NGINX Ingress controller version:

NGINX Ingress controller
  Release:       v0.41.2
  Build:         d8a93551e6e5798fc4af3eb910cef62ecddc8938
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.19.4

Kubernetes version (use kubectl version):

Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:28:09Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18+", GitVersion:"v1.18.12-gke.1205", GitCommit:"160736941f00e54acb1c0a4647166b6f6eb211d9", GitTreeState:"clean", BuildDate:"2021-01-05T23:44:13Z", GoVersion:"go1.13.15b4", Compiler:"gc", Platform:"linux/amd64"}

Environment:

  • Cloud provider or hardware configuration: GKE with private nodes
  • OS (e.g. from /etc/os-release): ID=COS, 13310.1041.24
  • Kernel (e.g. uname -a): Linux gke-foo-prod-foo-prod-n-bbdae2b2-4bfo 5.4.49+ #1 SMP Sun Oct 18 19:43:35 PDT 2020 x86_64 Intel(R) Xeon(R) CPU @ 2.20GHz GenuineIntel GNU/Linux

What happened:

We’re deploying an Ingress with annotation to permantently redirect to a new domain, instead of using the actual backend webserver for this.

Additionally, ssl certs are managed using cert-manager.

Unfortunately, ingress-nginx does create the well-known ingress, but includes the 301 redirect from the annotation nevertheless. As such, proxy_pass to the ACME solver pod is essentially ignored.

As the 301 redirect occurs, Let’s Encrypt is unable to validate the certificate request.

What you expected to happen:

While the annotations should be used on the locations, applying a redirect on the well-known URL location isn’t what Let’s Encrypt expects.

Instead, requests to the well-known ACME Challenges should pass to the ACME solver pod.

How to reproduce it:

Minimum ingress YAML:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    acme.cert-manager.io/http01-edit-in-place: "true"
    cert-manager.io/cluster-issuer: letsencrypt-prod
    cert-manager.io/issue-temporary-certificate: "true"
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/permanent-redirect: https://example.ch$uri$is_args$args
  labels:
    app: live-ch-ch
  name: shopware-live-ch-ch-redirects
  namespace: shopware-platform-385-live-ch
spec:
  rules:
  - host: www.example.ch
    http:
      paths:
      - backend:
          serviceName: shopware-live-ch-ch
          servicePort: 80
        path: /
        pathType: ImplementationSpecific
  - host: shop.example.ch
    http:
      paths:
      - backend:
          serviceName: cm-acme-http-solver-pgkqx
          servicePort: 8089
        path: /.well-known/acme-challenge/xxx
        pathType: ImplementationSpecific
      - backend:
          serviceName: cm-acme-http-solver-pgkqx
          servicePort: 8089
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - www.example.ch
    - shop.example.ch
    secretName: live-ch-ch-redirect-tls

This generates the following ingress nginx.conf (excerpt):

        ## start server shop.example.ch
        server {
                server_name shop.example.ch ;

                listen 80  ;
                listen 443  ssl http2 ;

                set $proxy_upstream_name "-";

                ssl_certificate_by_lua_block {
                        certificate.call()
                }

                location /.well-known/acme-challenge/xxx/ {

                        set $namespace      "shopware-platform-385-live-ch";
                        set $ingress_name   "shopware-live-ch-ch-redirects";
                        set $service_name   "";
                        set $service_port   "";
                        set $location_path  "/.well-known/acme-challenge/xxx/";
(...)
                        # In case of errors try the next upstream server before returning an error
                        proxy_next_upstream                     error timeout;
                        proxy_next_upstream_timeout             0;
                        proxy_next_upstream_tries               3;

                        return 301 https://example.ch$uri$is_args$args;

                        proxy_pass http://upstream_balancer;

                        proxy_redirect                          off;

                }

CURLing the endpoint, results in a redirect instead of serving the ACME validation response.

curl -i -k 'https://shop.example.ch/.well-known/acme-challenge/xxx'
HTTP/2 301
date: Mon, 08 Feb 2021 12:18:47 GMT
content-type: text/html
content-length: 162
location: https://example.ch/.well-known/acme-challenge/xxx
strict-transport-security: max-age=15724800; includeSubDomains

<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>

After removing the redirect-annotation, cert-manager and subsequently Let’s Encrypt were able to validate the request without issues. Afterwards, adding the redirect back did also work as expected.

I traced this back to nginx.tmpl#L1329.

Most likely, this could be solved by ensuring that a redirect is not added if the location starts with “.well-known/acme-challenge”. I could draft a PR, but my go-template-skills are to be improved.

Additionally (not related to this…), a lot of configuration could be removed if a redirect is present: there’s no need for proxy_pass directives or similar.

Anything else we need to know:

Switching to DNS-solver instead of HTTP would solve the underlying validation issue. Due to corporate constraints, we’re unable to use DNS to validate the ACME requests.

/kind bug

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 3
  • Comments: 15 (4 by maintainers)

Most upvoted comments

For me what helped was the removal of: acme.cert-manager.io/http01-edit-in-place: "true"

With above well-known path is created on a separate Ingress resource and it does not inherit nginx.ingress.kubernetes.io/permanent-redirect:

Config that works:

  • ingress:
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: kk-com
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    cert-manager.io/issue-temporary-certificate: "true"
    nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
    nginx.ingress.kubernetes.io/permanent-redirect: https://www.kk.gt
spec:
  tls:
    - hosts:
        - kk.com
        - www.kk.com
      secretName: certs-tls-com
  rules:
    - host: www.kk.com
      http:
        paths:
          - backend:
              serviceName: woo-web
              servicePort: 80
            path: /
  • issuer:
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: cert-manager
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: info@kk.gt
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
      - http01:
          ingress:
            class: nginx

@dbacinski wow thank you!!! it’s helped me

For me what helped was the removal of: acme.cert-manager.io/http01-edit-in-place: "true"

Ok, I will close it then. Feel free to open an new issue if the problem surfaces again

/close