Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ingress-nginx intermittently serves the default certificate instead of a configured tls certificate for rules without a host #7153

Closed
kevgrig opened this issue May 22, 2021 · 38 comments · Fixed by #7239
Labels
triage/needs-information Indicates an issue needs more information in order to work on it.

Comments

@kevgrig
Copy link
Contributor

kevgrig commented May 22, 2021

NGINX Ingress controller version:

Installed with the Digital Ocean NGINX Ingress Controller 1-Click App. Looks to be NGINX 0.44.0 through the 3.23.0 Helm chart:

$ kubectl get deployment ingress-nginx-controller --namespace=ingress-nginx -o jsonpath='{.metadata.labels}' {"app.kubernetes.io/component":"controller","app.kubernetes.io/instance":"ingress-nginx","app.kubernetes.io/managed-by":"Helm","app.kubernetes.io/name":"ingress-nginx","app.kubernetes.io/version":"0.44.0","helm.sh/chart":"ingress-nginx-3.23.0"}

Kubernetes version:

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.5", GitCommit:"6b1d87acf3c8253c123756b9e61dac642678305f", GitTreeState:"archive", BuildDate:"2021-03-30T00:00:00Z", GoVersion:"go1.16", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:20:00Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}

Environment:

  • Cloud provider or hardware configuration: Digital Ocean
  • OS (e.g. from /etc/os-release): Debian GNU/Linux 10 (buster)
  • Kernel (e.g. uname -a): Linux pool-o1y0v82td-8wjdr 4.19.0-11-amd64 #1 SMP Debian 4.19.146-1 (2020-09-17) x86_64 GNU/Linux
  • Install tools: N/A
  • Others: N/A

What happened:

As detailed in cert-manager issue #4012, cert-manager is creating the certificate and ingress-nginx seems to be picking it up, but the default fake certificate is still being served:

$ curl -vk https://example.myplaceonline.com/ 2>&1 | grep -e subject: -e issuer: -e Hello
*  subject: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
*  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
<html><head><title>HTTP Hello World</title></head><body><h1>Hello from helloworldweb-849f6d4b9f-ppcr6</h1></body></html>

cert-manager created the certificate:

$ kubectl describe certificate ingress1-cert --namespace=testns1
[...]
Events:
  Type    Reason     Age    From          Message
  ----    ------     ----   ----          -------
  Normal  Issuing    2m11s  cert-manager  Issuing certificate as Secret does not exist
  Normal  Generated  2m10s  cert-manager  Stored new private key in temporary Secret resource "ingress1-cert-q9hgc"
  Normal  Requested  2m10s  cert-manager  Created new CertificateRequest resource "ingress1-cert-xmtml"
  Normal  Issuing    63s    cert-manager  The certificate has been successfully issued

Describing the ingress shows the TLS certificate:

$ kubectl describe ingress ingress1 --namespace=testns1
Name:             ingress1
Namespace:        testns1
Address:          143.198.245.101
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
  ingress1-cert terminates example.myplaceonline.com
Rules:
  Host        Path  Backends
  ----        ----  --------
  *           
              /(.*)   helloworldweb:80 (10.244.0.47:80)
Annotations:  cert-manager.io/issuer: letsencrypt-staging-issuer
              nginx.ingress.kubernetes.io/rewrite-target: /$1
Events:
  Type    Reason  Age                From                      Message
  ----    ------  ----               ----                      -------
  Normal  Sync    19m (x3 over 20m)  nginx-ingress-controller  Scheduled for sync

Running the ingress with --v=5 debug logging shows the following "ssl" related entries which suggests it found the certificate but might not be applying it?

I0522 17:21:30.359718       6 backend_ssl.go:41] "Syncing Secret" name="testns1/ingress1-cert"
I0522 17:21:30.360392       6 ssl.go:112] "parsing ssl certificate extensions"
I0522 17:21:30.360571       6 backend_ssl.go:145] "Configuring Secret \"testns1/ingress1-cert\" for TLS encryption (CN: [example.myplaceonline.com])"
I0522 17:21:30.360884       6 nginx.go:342] "Event received" type=UPDATE object="&Ingress{ObjectMeta:{ingress1  testns1  6f15804a-3817-4470-aa5e-f55f39d92688 159480 2 2021-05-22 02:00:46 +0000 UTC <nil> <nil> map[] map[cert-manager.io/issuer:letsencrypt-staging-issuer nginx.ingress.kubernetes.io/rewrite-target:/$1] [] []  [{kubectl-create Update networking.k8s.io/v1 2021-05-22 02:00:46 +0000 UTC FieldsV1 {\"f:metadata\":{\"f:annotations\":{\".\":{},\"f:nginx.ingress.kubernetes.io/rewrite-target\":{}}},\"f:spec\":{\"f:rules\":{}}}} {kubectl-edit Update networking.k8s.io/v1 2021-05-22 02:07:01 +0000 UTC FieldsV1 {\"f:metadata\":{\"f:annotations\":{\"f:cert-manager.io/issuer\":{}}},\"f:spec\":{\"f:tls\":{}}}} {nginx-ingress-controller Update networking.k8s.io/v1beta1 2021-05-22 17:21:30 +0000 UTC FieldsV1 {\"f:status\":{\"f:loadBalancer\":{\"f:ingress\":{}}}}}]},Spec:IngressSpec{Backend:nil,TLS:[]IngressTLS{IngressTLS{Hosts:[example.myplaceonline.com],SecretName:ingress1-cert,},},Rules:[]IngressRule{IngressRule{Host:,IngressRuleValue:IngressRuleValue{HTTP:&HTTPIngressRuleValue{Paths:[]HTTPIngressPath{HTTPIngressPath{Path:/(.*),Backend:IngressBackend{ServiceName:helloworldweb,ServicePort:{0 80 },Resource:nil,},PathType:*Prefix,},},},},},},IngressClassName:nil,},Status:IngressStatus{LoadBalancer:{[{143.198.245.101  []}]},},}"
I0522 17:21:30.361196       6 queue.go:87] "queuing" item="&Ingress{ObjectMeta:{ingress1  testns1  6f15804a-3817-4470-aa5e-f55f39d92688 159480 2 2021-05-22 02:00:46 +0000 UTC <nil> <nil> map[] map[cert-manager.io/issuer:letsencrypt-staging-issuer nginx.ingress.kubernetes.io/rewrite-target:/$1] [] []  [{kubectl-create Update networking.k8s.io/v1 2021-05-22 02:00:46 +0000 UTC FieldsV1 {\"f:metadata\":{\"f:annotations\":{\".\":{},\"f:nginx.ingress.kubernetes.io/rewrite-target\":{}}},\"f:spec\":{\"f:rules\":{}}}} {kubectl-edit Update networking.k8s.io/v1 2021-05-22 02:07:01 +0000 UTC FieldsV1 {\"f:metadata\":{\"f:annotations\":{\"f:cert-manager.io/issuer\":{}}},\"f:spec\":{\"f:tls\":{}}}} {nginx-ingress-controller Update networking.k8s.io/v1beta1 2021-05-22 17:21:30 +0000 UTC FieldsV1 {\"f:status\":{\"f:loadBalancer\":{\"f:ingress\":{}}}}}]},Spec:IngressSpec{Backend:nil,TLS:[]IngressTLS{IngressTLS{Hosts:[example.myplaceonline.com],SecretName:ingress1-cert,},},Rules:[]IngressRule{IngressRule{Host:,IngressRuleValue:IngressRuleValue{HTTP:&HTTPIngressRuleValue{Paths:[]HTTPIngressPath{HTTPIngressPath{Path:/(.*),Backend:IngressBackend{ServiceName:helloworldweb,ServicePort:{0 80 },Resource:nil,},PathType:*Prefix,},},},},},},IngressClassName:nil,},Status:IngressStatus{LoadBalancer:{[{143.198.245.101  []}]},},}"
I0522 17:21:30.361945       6 queue.go:128] "syncing" key="testns1/ingress1"

I reviewed /etc/nginx/nginx.conf but it seems the certificates are handled by a Lua module and I'm not sure how to dive into that.

How to reproduce it:

Reproduction steps detailed in cert-manager issue #4012.

Anything else we need to know: N/A

/kind bug

@kevgrig kevgrig added the kind/bug Categorizes issue or PR as related to a bug. label May 22, 2021
@kevgrig
Copy link
Contributor Author

kevgrig commented May 23, 2021

Tried a new cluster with a static manifest for 0.46.0 instead of the 1-Click App and it's the same problem:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.46.0/deploy/static/provider/do/deploy.yaml

@kevgrig
Copy link
Contributor Author

kevgrig commented May 27, 2021

Started to explore the nginx Lua code that serves the cert and it looks like it'll be non-trivial to understand and add debug to, so for now I just used a workaround of specifying a default certificate which works:

  1. Edit the ingress-nginx deployment:
    kubectl edit deployment ingress-nginx-controller --namespace=ingress-nginx
    
  2. Under spec/template/spec/containers/args, add:
    - --default-ssl-certificate=testns1/ingress1-cert
    
  3. Save and quit
  4. Wait for the new deployment to start:
    kubectl get pods --namespace=ingress-nginx --watch
    
  5. Re-test and it works:
    $ curl -vk https://example.myplaceonline.com/ 2>&1 | grep -e issuer:
    *  issuer: C=US; O=(STAGING) Let's Encrypt; CN=(STAGING) Artificial Apricot R3
    

@irbekrm
Copy link

irbekrm commented May 27, 2021

I guess you could also experiment with creating a cert-manager Certificate for the ingress explicitly instead of using the ingress-shim annotation.

(I imagine that if the problem is that the config does not get re-loaded, perhaps having the Secret already pre-existing when the Ingress comes up could solve this?)

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 5, 2021

@irbekrm If I understand correctly, you mean creating the certificate resource manually and then pointing to that in the ingress with spec.tls.hosts and spec.tls.secretName without metadata.annotations.cert-manager.io/issuer, right?

If so, I tried that and it didn't work:

  1. Create certificate manually:
    printf '{"apiVersion":"cert-manager.io/v1","kind":"Certificate","metadata":{"name":"%s","namespace":"%s"},"spec":{"secretName":"%s","duration":"2160h","renewBefore":"360h","subject":{"organizations":["%s"]},"isCA":false,"privateKey":{"algorithm":"RSA","encoding":"PKCS1","size":4096},"usages":["server auth","client auth"],"dnsNames":["%s"],"issuerRef":{"name":"%s"}}}' "my-certificate" "testns1" "my-certificate-key" "MyOrganization" "example.myplaceonline.com" "letsencrypt-production-issuer" | kubectl create -f -
    
  2. Certificate issued:
    [...]
      Normal  Issuing    7s    cert-manager  The certificate has been successfully issued
    
  3. Certificate looks good:
    $ kubectl get secret my-certificate-key --namespace=testns1 -o "jsonpath={.data['tls\.crt']}" | base64 -d | openssl x509 -in - -text | grep Issuer:
         Issuer: C = US, O = Let's Encrypt, CN = R3
    
  4. Edit the ingress:
    $ EDITOR=vi kubectl edit ingress ingress1 --namespace=testns1
    
    To add:
    spec:
      tls:
      - hosts:
        - example.myplaceonline.com
        secretName: my-certificate-key
    
  5. Ingress looks good:
    $ kubectl describe ingress ingress1 --namespace=testns1
    Name:             ingress1
    Namespace:        testns1
    Address:          64.225.89.179
    Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
    TLS:
      my-certificate-key terminates example.myplaceonline.com
    [...]
    
  6. TLS request still shows the default certificate:
    $ curl -vk https://example.myplaceonline.com/ 2>&1 | grep -e issuer:
    *  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
    

FYI, every time I do a test, I destroy the cluster, load balancer, and DNS A record, and start fresh, though I'm skipping all the other initial steps enumerated in the original report; this also means the IP and other things may change after I post this comment as I do further tests.

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 5, 2021 via email

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 5, 2021

@longwuyuan Hi Long, it shows a connection reset from the controller pod. The IP address is correct. What might this mean? Is this a potential networking issue inside Digital Ocean?

$ kubectl exec ingress-nginx-controller-57cb5bf694-vrpnm --namespace=ingress-nginx -- curl -kv https://example.myplaceonline.com/test/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 64.225.89.58:443...
* Connected to example.myplaceonline.com (64.225.89.58) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* OpenSSL SSL_connect: Connection reset by peer in connection to example.myplaceonline.com:443 
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
* Closing connection 0
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to example.myplaceonline.com:443 
command terminated with exit code 35

From outside the cluster, it works but shows the wrong cert:

$ curl -kv https://example.myplaceonline.com/test/
*   Trying 64.225.89.58:443...
* Connected to example.myplaceonline.com (64.225.89.58) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
*  start date: Jun  5 06:19:37 2021 GMT
*  expire date: Jun  5 06:19:37 2022 GMT
*  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x557e12677d80)
> GET /test/ HTTP/2
> Host: example.myplaceonline.com
> user-agent: curl/7.76.1
> accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200 
< date: Sat, 05 Jun 2021 06:31:41 GMT
< content-type: text/html
< content-length: 120
< last-modified: Sat, 05 Jun 2021 06:18:23 GMT
< strict-transport-security: max-age=15724800; includeSubDomains
< 
<html><head><title>HTTP Hello World</title></head><body><h1>Hello from helloworldweb-849f6d4b9f-t2j89</h1></body></html
* Connection #0 to host example.myplaceonline.com left intact

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 5, 2021 via email

@longwuyuan
Copy link
Contributor

By any chance, does this relate to the problem you are facing https://kubernetes.github.io/ingress-nginx/user-guide/tls/#default-ssl-certificate

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 5, 2021

@longwuyuan Hi Long,

By any chance, does this relate to the problem you are facing https://kubernetes.github.io/ingress-nginx/user-guide/tls/#default-ssl-certificate

Yes, if I use - --default-ssl-certificate=testns1/ingress1-cert, then it works fine; I noted this workaround earlier in the issue (although it doesn't seem like a good solution): #7153 (comment)

I'm a bit busy today but should be able to reproduce curl with controller logs later today or tomorrow.

Thanks for your help.

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 5, 2021

ok.
The last octet of the ipaddress in the kubectl describe ingress and the curl verbose log (line 1 .. trying) are different. Just want to confirm if that is expected.
Can you also please show kubectl get ing -A -o wide .

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 5, 2021

another log message needed is the below sequence of steps and the related logs ;

  • start a terminal to follow the ingress controller logs kubectl -n <ingresscontrollernamespace> logs -f <ingresscontrollerpodname>
  • edit the ingress in question and remove the tls section
  • check if the config was reloaded in the controllerpod logs
  • edit the ingress and add the tls section again
  • check if the ingresscontrollerpod showed reload of config
  • Copy paste the log messages (related to this test) in this issue

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 5, 2021

The last octet of the ipaddress in the kubectl describe ingress and the curl verbose log (line 1 .. trying) are different. Just want to confirm if that is expected.

I recreated the cluster and DNS A record between those tests so those were two different clusters. Now that I have active help with you, I will stick to a single cluster where we can diagnose the issue, thanks! I'll gather everything from scratch.

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 6, 2021

@longwuyuan Hi Long,

start a terminal to follow the ingress controller logs kubectl -n logs -f

$ kubectl logs ingress-nginx-controller-57cb5bf694-vrpnm --namespace=ingress-nginx -f

edit the ingress in question and remove the tls section
check if the config was reloaded in the controllerpod logs

I0606 17:44:31.474265       7 main.go:112] "successfully validated configuration, accepting" ingress="ingress1/test"
I0606 17:44:31.481021       7 event.go:282] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"test", Name:"ingress1", UID:"43a81b5b-dc12-4585-8707-c08045b44fa5", APIVersion:"networking.k8s.io/v1beta1", ResourceVersion:"273136", FieldPath:""}): type: 'Normal' reason: 'Sync' Scheduled for sync

edit the ingress and add the tls section again
check if the ingresscontrollerpod showed reload of config
Copy paste the log messages (related to this test) in this issue

I0606 17:45:28.391751       7 main.go:112] "successfully validated configuration, accepting" ingress="ingress1/test"
I0606 17:45:28.399717       7 event.go:282] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"test", Name:"ingress1", UID:"43a81b5b-dc12-4585-8707-c08045b44fa5", APIVersion:"networking.k8s.io/v1beta1", ResourceVersion:"273256", FieldPath:""}): type: 'Normal' reason: 'Sync' Scheduled for sync

Current ingress:

$ kubectl describe ingress ingress1 --namespace=test
Name:             ingress1
Namespace:        test
Address:          64.225.89.58
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
  test-api-certificate-key terminates example.myplaceonline.com
Rules:
  Host        Path  Backends
  ----        ----  --------
  *           
              /test/(.*)   helloworldweb:80 (10.244.0.254:80)
Annotations:  nginx.ingress.kubernetes.io/rewrite-target: /$1
Events:       <none>

Can you also please show kubectl get ing -A -o wide

$ kubectl get ing -A -o wide
NAMESPACE   NAME       CLASS    HOSTS   ADDRESS        PORTS     AGE
test        ingress1   <none>   *       64.225.89.58   80, 443   35h

Requested from external:

$ curl -kv https://example.myplaceonline.com/test/ 2>&1 | grep -e issuer: -e '<html>' -e Connected -e curl:
* Connected to example.myplaceonline.com (64.225.89.58) port 443 (#0)
*  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
<html><head><title>HTTP Hello World</title></head><body><h1>Hello from helloworldweb-849f6d4b9f-t2j89</h1></body></html>

This resulted in the following in the log tail:

69.110.60.183 - - [06/Jun/2021:17:46:09 +0000] "GET /test/ HTTP/2.0" 200 120 "-" "curl/7.76.1" 43 0.002 [test-helloworldweb-80] [] 10.244.0.254:80 120 0.004 200 d250cf7f46517d2bc746c11311db9e9f

Requested from controller:

$ kubectl exec ingress-nginx-controller-57cb5bf694-vrpnm --namespace=ingress-nginx -- curl -kv https://example.myplaceonline.com/test/ 2>&1 | grep -e issuer: -e '<html>' -e Connected -e curl:
* Connected to example.myplaceonline.com (64.225.89.58) port 443 (#0)
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to example.myplaceonline.com:443

This resulted in the following in the log tail:

2021/06/06 17:46:54 [error] 37#37: *578644 broken header: "�7|Y?���k�z�eK���74,�c͵���uK�{ /��Ʊ��pu@Ϋ������h����0�6�>�,�0�̨̩�+�/��$�(k" while reading PROXY protocol, client: 10.244.0.168, server: 0.0.0.0:443

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 6, 2021

Also just to confirm that the referenced certificate is good:

$ kubectl get secret test-api-certificate-key --namespace=test -o "jsonpath={.data['tls\.crt']}" | base64 -d | openssl x509 -in - -text | grep Issuer:
        Issuer: C = US, O = Let's Encrypt, CN = R3

@longwuyuan
Copy link
Contributor

Can you show this in zoom sharescreen ?

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 9, 2021

@longwuyuan Yes, sure, thanks! What days and times are good for you? I'm in Pacific Time (PT)

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 9, 2021 via email

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 9, 2021

@longwuyuan I'm not on Slack but I will join. I have a customer call in 40 minutes for work tonight and going to bed, but I'll join the Slack tomorrow...

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 9, 2021 via email

@longwuyuan
Copy link
Contributor

Please close the issue since resolved on zoom

@longwuyuan
Copy link
Contributor

/remove-kind bug

@k8s-ci-robot k8s-ci-robot removed the kind/bug Categorizes issue or PR as related to a bug. label Jun 9, 2021
@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 11, 2021

@longwuyuan Thank you for your time for the investigation. I started from scratch and found that the difference was that in the example we used, it had an explicit host:

spec:
  rules:
  - host: "test0.myplaceonline.com"
    http:
      [...]

Once I added an explicit host, then everything worked (including the cert-manager annotation shim). This was odd because lacking a host should apply to all hosts:

An optional host. In this example, no host is specified, so the rule applies to all inbound HTTP traffic through the IP address specified.

And previously describing the ingress showed a wildcard host:

Rules:
  Host        Path  Backends
  ----        ----  --------
  *           
              /(.*)   helloworldweb:80 (10.244.0.47:80)

So then I removed the host again, but it still worked! From previous testing, I knew that restarting the deployment didn't help which suggests it's not some initial process state issue.

So there still seems to be some strange initial host mapping issue. If anyone wants to debug this, I think I have a reproducible test case. Nevertheless, for now, everything works for me (after using the above workaround) and I'm happy.

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 11, 2021 via email

@kevgrig kevgrig changed the title Nginx ingress certificate not updated from original self-signed certificate ingress-nginx intermittently serves the default certificate instead of a configured tls certificate for rules without a host Jun 11, 2021
@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 11, 2021

@longwuyuan Updated title. Reproduction steps are the same as originally reported in the bug description. I've yet to try them outside of Digital Ocean. Note also that using helm did not help nor did manually fixing 0.46.0 to 0.47.0 that we observed (as detailed in #7229).

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 11, 2021 via email

@longwuyuan
Copy link
Contributor

/triage needs-information

@k8s-ci-robot k8s-ci-robot added the triage/needs-information Indicates an issue needs more information in order to work on it. label Jun 11, 2021
@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 11, 2021

Reproduction steps:

  1. Create Digital Ocean Kubernetes cluster (a 1 node cluster works)
  2. Click Download Config File and save to ~/.kube/config
  3. Install ingress-nginx either with Helm, the YAML file (with fix for Digital Ocean YAML has the wrong image version #7229), or the NGINX Ingress Controller 1-Click App. All methods reproduce the problem.
  4. Wait until the Digital Ocean Load Balancer is created and copy the IP address.
  5. Create DNS A record or entry in /etc/hosts/ for the load balancer IP and test host (in the following example, example.myplaceonline.com).
  6. Create testns1 namespace:
    printf '{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"testns1"}}' | kubectl create -f -
    
  7. Create Hello World website deployment:
    kubectl create deployment helloworldweb --image=strm/helloworld-http --namespace=testns1
    
  8. Expose deployment as a service:
    kubectl expose deployment helloworldweb --port=80 --target-port=80 --namespace=testns1
    
  9. Create ingress pointing to the helloworldweb service:
    printf '{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"name":"%s","namespace":"%s","annotations":{"nginx.ingress.kubernetes.io/rewrite-target":"/$1"}},"spec":{"rules":[{"http":{"paths":[{"path":"%s","pathType":"Prefix","backend":{"service":{"name":"%s","port":{"number":80}}}}]}}]}}' "ingress1" "testns1" "/(.*)" "helloworldweb" | kubectl create -f -
    
  10. Test that external port 443 shows the default self-signed certificate with CN=Kubernetes Ingress Controller Fake Certificate:
    $ curl -vk https://example.myplaceonline.com/ 2>&1 | grep issuer:
    *  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
    
  11. Install cert-manager:
    kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.3.1/cert-manager.yaml
    
  12. Wait until cert-manager is ready:
    $ kubectl get pods --namespace cert-manager
    NAME                                       READY   STATUS    RESTARTS   AGE
    cert-manager-7dd5854bb4-rztj8              1/1     Running   0          18s
    cert-manager-cainjector-64c949654c-9np86   1/1     Running   0          18s
    cert-manager-webhook-6bdffc7c9d-lp924      1/1     Running   0          18s
    
  13. Create Digital Ocean Personal Access Token for Digital Ocean DNS01 solver and convert to Base64 (replace TOKEN):
    echo -n 'TOKEN' | base64 -w 0
    
  14. Create DNS01 secret (replace BASE64TOKEN):
    printf '{"apiVersion":"v1","kind":"Secret","metadata":{"name":"digitalocean-dns","namespace":"testns1"},"data":{"access-token":"%s"}}' "BASE64TOKEN" | kubectl create -f -
    
  15. Create Issuer (replace email address):
    printf '{"apiVersion":"cert-manager.io/v1","kind":"Issuer","metadata":{"name":"letsencrypt-production-issuer","namespace":"testns1"},"spec":{"acme":{"email":"%s","server":"https://acme-v02.api.letsencrypt.org/directory","privateKeySecretRef":{"name":"letsencrypt-production-issuer-private-key"},"solvers":[{"dns01":{"digitalocean":{"tokenSecretRef":{"name":"digitalocean-dns","key":"access-token"}}}}]}}}' "contact@myplaceonline.com" | kubectl create -f -
    
  16. Edit the ingress:
    $ EDITOR=vi kubectl edit ingress ingress1 --namespace=testns1
    
    Add cert-manager.io/issuer: letsencrypt-production-issuer and tls:
    metadata:
      annotations:
        cert-manager.io/issuer: letsencrypt-production-issuer
    [...]
    spec:
      tls:
      - hosts:
          - example.myplaceonline.com
        secretName: example-tls
    
  17. Wait a few minutes until the certificate is ready:
    $ kubectl get certificates --namespace=testns1
    NAME            READY   SECRET          AGE
    example-tls     True    example-tls     77s
    
  18. New request to port 443 still shows the old certificate:
    $ curl -vk https://example.myplaceonline.com/ 2>&1 | grep issuer:
    *  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
    
  19. Edit the ingress and add a host to the rules and the curl works:
      rules:
      - host: example.myplaceonline.com
        http:
          [...]
    

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 11, 2021 via email

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 11, 2021

@longwuyuan I see no evidence that this is related to DO. I opened a forum post with DO and received no response. I can open a support ticket. What specifically should I ask? All evidence points to the issue in ingress-nginx: adding the host in the ingress fixes the issue; subsequently, after removing the host from the ingress, the issue remains fixed. It appears to be related to ingress-nginx initial handling of a wildcard host.

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 12, 2021

So this combo ;
(1) default-ssl-certificate = Not configured in ingress-controller
(2) ingress.spec.rules.host: *
(3) ingress.spec.tls.hosts: example.myplaceonline.com
(4) ingress.spec.tls.secretName: <cert with only above fqdn in subect>

I think is explained in the ingress spec ;
image

Can you check !

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 12, 2021

@longwuyuan That's very interesting. I found this in the Ingress documentation:

Keep in mind that TLS will not work on the default rule because the certificates would have to be issued for all the possible sub-domains. Therefore, hosts in the tls section need to explicitly match the host in the rules section.

It seems like it would be useful to print a warning about this somewhere? I doubt I will be the first person to hit this issue.

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 12, 2021

What I think confused me the most is that describing the ingress shows the following which suggests it's configured correctly. Maybe the describe command could be the place for a warning?

TLS:
my-certificate-key terminates example.myplaceonline.com

@longwuyuan
Copy link
Contributor

longwuyuan commented Jun 12, 2021 via email

@kevgrig
Copy link
Contributor Author

kevgrig commented Jun 12, 2021

Created PR #7239

@worldspawn
Copy link

worldspawn commented Oct 28, 2022

I've just had this issue with 1.1.3 (which i assume has #7239 in it). Seems to match it exactly, I had tls configured with a default backend and the ingress refused to serve the certificate. After changing from a default backend to a specific host it worked.

I was then able to change to back to a default backend and it continued to work. Just as describe here.

We use default backend because rewrite decodes the uri which broke the particular service i'm hosting.

@GDnsk
Copy link

GDnsk commented Nov 16, 2023

This issue happened to me as well, for some reason Ingress Controller was using default certificate(fake one). Had to:

1 - Setup rule host as a wrong one like:

...
spec:
  ingressClassName: nginx-custom-class
  rules:
    - host: potato.com
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
...

2 - Setup rule host with the correct one like:

...
spec:
  ingressClassName: nginx-custom-class
  rules:
    - host: www.myservice.com
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
...

If i don't setup host with a invalid one before it just doesn't work, ingress controller keeps sending the wrong certificate

@gianarb
Copy link

gianarb commented Nov 8, 2024

The same happened to me! I spent a day trying to figure out before I actually landed here. Just want to thank @kevgrig for this gem here!

I fixed such issue by setting a host in my http rule. Without a host I was getting the default incorrect certificate

@martinmalek
Copy link

martinmalek commented Dec 5, 2024

I had a similar experience. In my case the secret for the ingress was accidentally in default namespace. After changing to the same namespace as the ingress the problem seems to have gone away.

I want to remember that secrets are only available to pods within the same namespace. Does that go for ingress as well? If not why did it work intermittently?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage/needs-information Indicates an issue needs more information in order to work on it.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants