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

Multiple TLS Client Certs: Only the First Certificate is Being Accepted by Nginx #4234

Closed
johannes-gehrs opened this issue Jun 27, 2019 · 23 comments · Fixed by #4556
Closed

Comments

@johannes-gehrs
Copy link

johannes-gehrs commented Jun 27, 2019

NGINX Ingress controller version:
0.24.1 but we were also able to replicate the problem in 0.23.0.

Kubernetes version (use kubectl version):

Environment:

  • Cloud provider or hardware configuration: AWS
  • OS (e.g. from /etc/os-release): Debian Stretch
  • Kernel (e.g. uname -a): Linux ip-172-20-81-79 4.9.0-9-amd64 Basic structure  #1 SMP Debian 4.9.168-1+deb9u2 (2019-05-13) x86_64 Linux
  • Install tools: Managed by KOPS
  • Others:

What happened:

When providing multiple client certificates to nginx in one file, i.e. using the secret auth-tls-secret it did not accept clients authenticating based on the second TLS certificate. It did accept clients authenticating based on the first certificate.

When it does not accept the certificate it generates a 400 bad request "The SSL certificate error".

When trying to replicate the issue using a pure nginx config (i.e. not using the ingress) we were not able to replicate the problem.

The generated nginx configuration looks correct to us, i.e. the certificates get put into the nginx config file and this looks fine to us.

What you expected to happen:

According to the nginx docs you can put multiple client certs in the file referenced in ssl_client_certificate and nginx should accept any one of those. This is also the behavior we saw when trying to isolate the behavior in a pure Docker/Nginx setup, but in ingress nginx it does not work for unknown reasons.

How to reproduce it (as minimally and precisely as possible):

Use the TLS client auth feature with more than one certificate.

Anything else we need to know:

Motivation for use of this feature is having zero-downtime certificate exchange, blue-green-style.

Let us know if we can provide additional info to ease replication of this issue.

@aledbf
Copy link
Member

aledbf commented Jul 11, 2019

When providing multiple client certificates to nginx in one file

How are you doing this exactly?

@aledbf
Copy link
Member

aledbf commented Jul 11, 2019

According to the nginx docs you can put multiple client certs in the file referenced in ssl_client_certificate and nginx should accept any one of those.

Yes but we parse the cert in go from the secret in order to generate the one nginx will use. Not sure this is supported

@slideroi
Copy link

slideroi commented Sep 5, 2019

@johannes-gehrs did you find a way around this problem?

I'm having the same problem in NGINX Ingress 0.25.0 with kubernetes version 1.15.2.

@aledbf is this not supported when using NGINX as the ingress controller in kubernetes? If not, do you know if there are plans to support it?

Thanks for any info!

How are you doing this exactly?

in case it's still not clear how to dupe this:

Steps to dupe:

  • create two PEM encoded CA certs, CA A and CA B, with one or more client certs generated with each of the CA certs
  • each will look like:
-----BEGIN CERTIFICATE-----
<cert body>
-----END CERTIFICATE-----
  • create a combined file that contains both CA certs in it, one after the other
    • file should look something like:
-----BEGIN CERTIFICATE-----
<cert A body>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<cert B body>
-----END CERTIFICATE-----
  • insert this combined cert in the secret referenced by ssl_client_certificate
  • install client certs for both CA A and CA B
  • hit your ingress controller with a browser and pick a client cert from CA A
    • authentication will occur successfully
  • hit your ingress controller with a browser and pick a client cert from CA B
  • NGINX will return the 400 bad request "The SSL certificate error" error
    • now edit your combined file that contains both certs and reorder them so that B is first and A is second and insert that in the secret referenced by ssl_client_certificate
    • it should look something like:
-----BEGIN CERTIFICATE-----
<cert B body>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<cert A body>
-----END CERTIFICATE-----
  • repeat your test, now B will validate successfully and A will now result in the 400 error

@slideroi
Copy link

slideroi commented Sep 6, 2019

It sure looks like this functionality should be supported in nginx-ingress. Even on the kubernetes/ingress-nginx readme documentation it says that it's supported and even has instructions on how to do it (from https://github.com/kubernetes/ingress-nginx/tree/master/docs/examples/auth/client-certs):

You can have as many certificates as you want. If they're in the binary DER format, you can convert them as the following:
openssl x509 -in certificate.der -inform der -out certificate.crt -outform pem
Then, you can concatenate them all in only one file, named 'ca.crt' as the following:
cat certificate1.crt certificate2.crt certificate3.crt >> ca.crt

So it seems like it should be supported, I just must be doing something wrong.

@johannes-gehrs
Copy link
Author

It is still unresolved for us. Using a pure nginx setup from the official Docker containers with a basic config we were unable to replicate the issue.

If I were to investigate further I would try to compare the configs and then enable options from the generated ingress config one by one. My suspicion is that one of the options used interferes with nginx's ability to accept multiple certs.

I don't think that it is necessarily your fault @slideroi.

@slideroi
Copy link

slideroi commented Sep 9, 2019

Thanks for the reply!

@hobti01
Copy link
Contributor

hobti01 commented Sep 12, 2019

Only the first CA is used, the "rest" of the PEM block is ignored with _

pemCABlock, _ := pem.Decode(ca)

Decode function expects repeated calls to get the "next" PEM block.

Unfortunately a show stopper for us. It's really too bad the documentation is contrary to this.

@aledbf
Copy link
Member

aledbf commented Sep 12, 2019

Please use the image quay.io/kubernetes-ingress-controller/nginx-ingress-controller:dev to test if this issue is fixed.

@hobti01
Copy link
Contributor

hobti01 commented Sep 13, 2019

Thank you for looking at this. Attempting to replace the existing image provides:

-------------------------------------------------------------------------------
NGINX Ingress controller
  Release:       dev
  Build:         git-74724bffa
  Repository:    https://github.com/aledbf/ingress-nginx
  nginx version: openresty/1.15.8.2

-------------------------------------------------------------------------------

I0913 00:27:33.481044       8 flags.go:194] Watching for Ingress class: v2
W0913 00:27:33.481090       8 flags.go:197] Only Ingresses with class "v2" will be processed by this Ingress controller
W0913 00:27:33.481286       8 flags.go:223] SSL certificate chain completion is disabled (--enable-ssl-chain-completion=false)
W0913 00:27:33.481341       8 client_config.go:541] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
I0913 00:27:33.481503       8 main.go:181] Creating API client for https://172.20.0.1:443
I0913 00:27:33.491873       8 main.go:225] Running in Kubernetes cluster version v1.13+ (v1.13.10-eks-5ac0f1) - git (clean) commit 5ac0f1d9ab2c254ea2b0ce3534fd72932094c6e1 - platform linux/amd64
...
E0913 00:27:36.220446       8 runtime.go:73] Observed a panic: "invalid memory address or nil pointer dereference" (runtime error: invalid memory address or nil pointer dereference)
goroutine 152 [running]:
k8s.io/apimachinery/pkg/util/runtime.logPanic(0x14d3aa0, 0x2393300)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/runtime/runtime.go:69 +0x7b
k8s.io/apimachinery/pkg/util/runtime.HandleCrash(0x0, 0x0, 0x0)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/runtime/runtime.go:51 +0x82
panic(0x14d3aa0, 0x2393300)
        /home/aledbf/.gimme/versions/go1.13.linux.amd64/src/runtime/panic.go:679 +0x1b2
k8s.io/ingress-nginx/internal/ingress/controller/store.(*k8sStore).getPemCertificate(0xc000126400, 0xc000775fe0, 0x1b, 0x0, 0xc0007e5358, 0x4df131)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/backend_ssl.go:178 +0x66b
k8s.io/ingress-nginx/internal/ingress/controller/store.(*k8sStore).syncSecret(0xc000126400, 0xc000775fe0, 0x1b)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/backend_ssl.go:45 +0x121
k8s.io/ingress-nginx/internal/ingress/controller/store.(*k8sStore).GetAuthCertificate(0xc000126400, 0xc000775fe0, 0x1b, 0x7, 0xc000775fe8, 0x13)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/store.go:810 +0x210
k8s.io/ingress-nginx/internal/ingress/annotations/authtls.authTLS.Parse(0x19202a0, 0xc000126400, 0xc0002bd800, 0xc000251dd0, 0xc000126fa8, 0x239f6d0, 0xc000126f20)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/annotations/authtls/main.go:103 +0xf4
k8s.io/ingress-nginx/internal/ingress/annotations.Extractor.Extract(0xc000251dd0, 0xc0002bd800, 0x1)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/annotations/annotations.go:177 +0x146
k8s.io/ingress-nginx/internal/ingress/controller/store.(*k8sStore).syncIngress(0xc000126400, 0xc0002bd800)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/store.go:652 +0x337
k8s.io/ingress-nginx/internal/ingress/controller/store.New.func5(0x169b400, 0xc000a52870)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/store.go:356 +0x273
k8s.io/client-go/tools/cache.ResourceEventHandlerFuncs.OnAdd(...)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/tools/cache/controller.go:195
k8s.io/client-go/tools/cache.(*processorListener).run.func1.1(0x60, 0x164db20, 0x1)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/tools/cache/shared_informer.go:607 +0x218
k8s.io/apimachinery/pkg/util/wait.ExponentialBackoff(0x989680, 0x3ff0000000000000, 0x3fb999999999999a, 0x5, 0x0, 0xc0007e5dd8, 0x0, 0xc00040f5f8)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:284 +0x51
k8s.io/client-go/tools/cache.(*processorListener).run.func1()
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/tools/cache/shared_informer.go:601 +0x79
k8s.io/apimachinery/pkg/util/wait.JitterUntil.func1(0xc00040f740)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:152 +0x5e
k8s.io/apimachinery/pkg/util/wait.JitterUntil(0xc0007e5f40, 0xdf8475800, 0x0, 0x42e801, 0xc000c60e40)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:153 +0xf8
k8s.io/apimachinery/pkg/util/wait.Until(...)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:88
k8s.io/client-go/tools/cache.(*processorListener).run(0xc000253580)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/tools/cache/shared_informer.go:599 +0x9b
k8s.io/apimachinery/pkg/util/wait.(*Group).Start.func1(0xc0001512c0, 0xc000cdc6c0)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:71 +0x59
created by k8s.io/apimachinery/pkg/util/wait.(*Group).Start
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:69 +0x62
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1152feb]

goroutine 152 [running]:
k8s.io/apimachinery/pkg/util/runtime.HandleCrash(0x0, 0x0, 0x0)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/runtime/runtime.go:58 +0x105
panic(0x14d3aa0, 0x2393300)
        /home/aledbf/.gimme/versions/go1.13.linux.amd64/src/runtime/panic.go:679 +0x1b2
k8s.io/ingress-nginx/internal/ingress/controller/store.(*k8sStore).getPemCertificate(0xc000126400, 0xc000775fe0, 0x1b, 0x0, 0xc0007e5358, 0x4df131)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/backend_ssl.go:178 +0x66b
k8s.io/ingress-nginx/internal/ingress/controller/store.(*k8sStore).syncSecret(0xc000126400, 0xc000775fe0, 0x1b)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/backend_ssl.go:45 +0x121
k8s.io/ingress-nginx/internal/ingress/controller/store.(*k8sStore).GetAuthCertificate(0xc000126400, 0xc000775fe0, 0x1b, 0x7, 0xc000775fe8, 0x13)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/store.go:810 +0x210
k8s.io/ingress-nginx/internal/ingress/annotations/authtls.authTLS.Parse(0x19202a0, 0xc000126400, 0xc0002bd800, 0xc000251dd0, 0xc000126fa8, 0x239f6d0, 0xc000126f20)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/annotations/authtls/main.go:103 +0xf4
k8s.io/ingress-nginx/internal/ingress/annotations.Extractor.Extract(0xc000251dd0, 0xc0002bd800, 0x1)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/annotations/annotations.go:177 +0x146
k8s.io/ingress-nginx/internal/ingress/controller/store.(*k8sStore).syncIngress(0xc000126400, 0xc0002bd800)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/store.go:652 +0x337
k8s.io/ingress-nginx/internal/ingress/controller/store.New.func5(0x169b400, 0xc000a52870)
        /home/aledbf/go/src/k8s.io/ingress-nginx/internal/ingress/controller/store/store.go:356 +0x273
k8s.io/client-go/tools/cache.ResourceEventHandlerFuncs.OnAdd(...)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/tools/cache/controller.go:195
k8s.io/client-go/tools/cache.(*processorListener).run.func1.1(0x60, 0x164db20, 0x1)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/tools/cache/shared_informer.go:607 +0x218
k8s.io/apimachinery/pkg/util/wait.ExponentialBackoff(0x989680, 0x3ff0000000000000, 0x3fb999999999999a, 0x5, 0x0, 0xc0007e5dd8, 0x0, 0xc00040f5f8)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:284 +0x51
k8s.io/client-go/tools/cache.(*processorListener).run.func1()
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/tools/cache/shared_informer.go:601 +0x79
k8s.io/apimachinery/pkg/util/wait.JitterUntil.func1(0xc00040f740)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:152 +0x5e
k8s.io/apimachinery/pkg/util/wait.JitterUntil(0xc0007e5f40, 0xdf8475800, 0x0, 0x42e801, 0xc000c60e40)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:153 +0xf8
k8s.io/apimachinery/pkg/util/wait.Until(...)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:88
k8s.io/client-go/tools/cache.(*processorListener).run(0xc000253580)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/tools/cache/shared_informer.go:599 +0x9b
k8s.io/apimachinery/pkg/util/wait.(*Group).Start.func1(0xc0001512c0, 0xc000cdc6c0)
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:71 +0x59
created by k8s.io/apimachinery/pkg/util/wait.(*Group).Start
        /home/aledbf/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:69 +0x62

@aledbf
Copy link
Member

aledbf commented Sep 13, 2019

@hobti01 please use quay.io/kubernetes-ingress-controller/nginx-ingress-controller:dev-ca
(for some reason the stack error is not right)

@hobti01
Copy link
Contributor

hobti01 commented Sep 13, 2019

Thank you @aledbf for the updated versions but they do not solve the 400 error for us. We continue to receive 400 Bad Request The SSL certificate error openresty

2019/09/13 06:35:27 [info] 90#90: *6265 client SSL certificate verify error: (2:unable to get issuer certificate) while reading client request headers

While a read of the code looks like it would solve the multiple CA issue, that does not seem to be the first and current problem with our configuration.

@aledbf
Copy link
Member

aledbf commented Sep 13, 2019

@hobti01 let's keep the scope of this issue to the multiple certificate support, something #4556 fixes.
Please open a new issue with the new problem. Also, it would be great if you can provide the required steps to reproduce the issue (openssl commands, secrets, etc)

@johannes-gehrs
Copy link
Author

johannes-gehrs commented Sep 13, 2019

The explanation/fix seems weird to me because I am pretty sure that the additional certificate did show up in the generated nginx config. But I may misremember or just may have been mistaken back when we tried this. Will surely try out this fix. Thanks @aledbf.

@hobti01
Copy link
Contributor

hobti01 commented Sep 13, 2019

Thanks @aledbf for the fix. Maybe we will pursue our issue in the future but we observed the same issue with "plain" nginx 1.16, so we have not found any nginx based solution. For now we are reverting to a previous architecture that does not use nginx or have the functionality we want in order to pursue other topics.

@aledbf
Copy link
Member

aledbf commented Sep 13, 2019

Will surely try out this fix

@johannes-gehrs please use the test docker image I posted in my previous comment and let me know if the fix works.

@aledbf
Copy link
Member

aledbf commented Sep 13, 2019

Maybe we will pursue our issue in the future but we observed the same issue with "plain" nginx 1.16, so we have not found any nginx based solution.

@hobti01 again, if you can open a new issue with something I can use to reproduce the problem, I am more than happy to see what I can do. The hardest part of fixing this kind of issues is always related to the examples to reproduce it.

@slideroi
Copy link

@aledbf I tested this with quay.io/kubernetes-ingress-controller/nginx-ingress-controller:dev-ca and it is still not working. It behaves exactly as before. Were you able to dupe this before the fix? Is there anything I can do to help you with this? Are there any logs I could get more details from? Thanks!

@aledbf
Copy link
Member

aledbf commented Sep 17, 2019

Were you able to dupe this before the fix?

No. The fix just makes sure we pass all the certs contains in the ca field.

Is there anything I can do to help you with this?

Yes. Provide a full example that reproduces the issue.

@slideroi
Copy link

slideroi commented Sep 23, 2019

@aledbf thanks for all your help on this! I tried to reproduce the failure from scratch with all new certs and I haven't been able to. I can reproduce the problem at will with my original two certs, but with any new cert I generate it works just fine. And I can combine either of the original certs with any number of my newly generated certs in any order and it works just fine.

There's just something about those original two certs that don't like each other for some reason. I'm pretty sure I used the exact same cert generation settings for them, but I'm not sure how to check that for sure so I'm just going to call it good.

Thanks again!

Turns out those original two certs that were generated and don't like to live together are problematic because they happened to both be generate with the same subject.

@slideroi
Copy link

slideroi commented Sep 23, 2019

I'm going to include my steps to dupe (which will be steps to verify with the current fix) because I've already got them written up and maybe they'll be useful for someone who is initially trying to test multiple certs in nginx ingress or even just setting it up to start with. Also attaching a zip file with the certs I generated using the instructions below. MultiCertTestFiles.zip

1) Cert generation commands (you can skip these if you're just going to use the cert's I've attached)

  • Create two different CA key's:
    openssl genrsa -out rootCAOne.key 2048
    openssl genrsa -out rootCATwo.key 2048
  • Now create a self signed root (make sure multiple CA's have different -subj's):
    openssl req -x509 -new -nodes -key rootCAOne.key -sha256 -days 1024 -out rootCAOne.crt -subj "/C=US/ST=CA/O=OrgOne/CN=Test Root Cert One"
    openssl req -x509 -new -nodes -key rootCATwo.key -sha256 -days 1024 -out rootCATwo.crt -subj "/C=US/ST=CA/O=OrgTwo/CN=Test Root Cert Two"
  • Create user certificate keys:
    openssl genrsa -out userOne.key 2048
    openssl genrsa -out userTwo.key 2048
  • Create user's cert signing request (CSR):
    openssl req -new -sha256 -key userOne.key -subj "/C=US/ST=CA/O=OrgOne/CN=userone" -out userOne.csr
    openssl req -new -sha256 -key userTwo.key -subj "/C=US/ST=CA/O=OrgTwo/CN=usertwo" -out userTwo.csr
  • Create user certs:
    openssl x509 -req -in userOne.csr -CA rootCAOne.crt -CAkey rootCAOne.key -CAcreateserial -out userOne.crt -days 1024 -sha256
    openssl x509 -req -in userTwo.csr -CA rootCATwo.crt -CAkey rootCATwo.key -CAcreateserial -out userTwo.crt -days 1024 -sha256
  • Create PFX files with passwords "Password1":
    openssl pkcs12 -export -out userOne.pfx -inkey userOne.key -in userOne.crt -password pass:Password1 -caname "User One"
    openssl pkcs12 -export -out userTwo.pfx -inkey userTwo.key -in userTwo.crt -password pass:Password1 -caname "User Two"
  • Now create the combined CA files:
    cat rootCAOne.crt rootCATwo.crt >> bothRootCAsOneThenTwo.crt
    cat rootCATwo.crt rootCAOne.crt >> bothRootCAsTwoThenOne.crt

2) Install cert and configure PKI

  • In order for these to work you'll need to set up some environment variables with some of your ingress controller settings. These are the enviroment variables that need to be set up (or you can just hard code them in the kubectl commands below):
  • name of your ingress controller:
    INGRESS_CONTROLLER_NAME=<ingress controller name>
  • namespace of your ingress controller:
    INGRESS_CONTROLLER_NAMESPACE=<ingress controller namespace>
  • ip address/host name and port of the box that you've set this up one
    HOST_IP=<host name or IP of host>
    HOST_PORT=443
  • First try with rootCAOne.crt
    kubectl create secret generic ca-secret -n=$INGRESS_CONTROLLER_NAMESPACE --from-file=ca.crt=rootCAOne.crt --dry-run -o yaml | kubectl apply -f -
  • Enable PKI client authentication in nginx (you don't need these settings to be exactly what I have here, these are just what I'm currently playing with)
    kubectl annotate ingress $INGRESS_CONTROLLER_NAME -n $INGRESS_CONTROLLER_NAMESPACE -o yaml --overwrite nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream='true'
    kubectl annotate ingress $INGRESS_CONTROLLER_NAME -n $INGRESS_CONTROLLER_NAMESPACE -o yaml --overwrite nginx.ingress.kubernetes.io/auth-tls-verify-depth='10'
    kubectl annotate ingress $INGRESS_CONTROLLER_NAME -n $INGRESS_CONTROLLER_NAMESPACE -o yaml --overwrite nginx.ingress.kubernetes.io/auth-tls-secret=${INGRESS_CONTROLLER_NAMESPACE}/ca-secret
    kubectl annotate ingress $INGRESS_CONTROLLER_NAME -n $INGRESS_CONTROLLER_NAMESPACE -o yaml --overwrite nginx.ingress.kubernetes.io/auth-tls-verify-client='on'

3) Test cert One

  • You'll need the client certs on the box you're testing from and the commands below assume that you're in the same directory as the certs.
  • You can either use curl to test this or you can use a web browser. I've been testing with both. I'm going to include instructions primarily for curl, but the following is a quick overview of the basic steps for doing the same thing in chrome on windows::
  • Setting up client auth test with chrome on windows 10:
    ** Install both client authentication keys in to the windows cert store by double clicking on userOne.pfx and userTwo.pfx. In the Certificate Import Wizard just accept all the defaults by clicking next until you get to the password page. Enter the password, "Password1", then continue clicking next through the rest of the wizard until you click finish at the end. Then do the same for the other client cert.
    ** Make sure all of your chrome incognito windows are closed and then launch one. Browse to your website that is secured by client authl. It should pop up the certificate that is signed by the cert that you've currently installed. Select it and it should let you through.
  • Testing with curl:
  • Hit the site that has been secured by client authentication in your nginx ingress controller with curl using the userOne cert/key:
    curl --insecure --cert userOne.crt:Password1 --key userOne.key https://$HOST_IP:$HOST_PORT
  • This will return whatever the secured web page is
  • Now test it with the userTwo cert/key:
    curl --insecure --cert userTwo.crt:Password1 --key userTwo.key https://$HOST_IP:$HOST_PORT
  • This will correctly return a 400 SSL cert error page

4 )Test cert Two

  • Now let's test to make sure that the userTwo cert really is fine by changing the cert in the server (make sure you're in the directory with the rootCATwo.crt):
    kubectl create secret generic ca-secret -n=$INGRESS_CONTROLLER_NAMESPACE --from-file=ca.crt=rootCATwo.crt --dry-run -o yaml | kubectl apply -f -
  • The ingress sees the change but doesn't seem to put it into effect with just that change (we're looking for "Backend successfully reloaded." in the ingress logs), so run the following two commands to toggle client auth off then back on. That will force a reload which will then pick up the new cert.
    kubectl annotate ingress $INGRESS_CONTROLLER_NAME -n $INGRESS_CONTROLLER_NAMESPACE -o yaml --overwrite nginx.ingress.kubernetes.io/auth-tls-verify-client='off'
    kubectl annotate ingress $INGRESS_CONTROLLER_NAME -n $INGRESS_CONTROLLER_NAMESPACE -o yaml --overwrite nginx.ingress.kubernetes.io/auth-tls-verify-client='on'
  • Now let's test the two client certs again now that our ingress controller has the second cert installed:
    curl --insecure --cert userOne.crt:Password1 --key userOne.key https://$HOST_IP:$HOST_PORT
  • As expected, this now comes back with the 400 SSL cert error since our server now has the rootCATwo.crt that didn't sign our userOne.crt cert.
  • Now let's try the userTwo.crt cert.
    curl --insecure --cert userTwo.crt:Password1 --key userTwo.key https://$HOST_IP:$HOST_PORT
  • This cert now works and our protect web page comes back.

5 )Test both certs combined

  • Now let's swap to the combined cert with the One cert first, then disable and enable client auth to force a refresh
    kubectl create secret generic ca-secret -n=$INGRESS_CONTROLLER_NAMESPACE --from-file=ca.crt=bothRootCAsOneThenTwo.crt --dry-run -o yaml | kubectl apply -f -
    kubectl annotate ingress $INGRESS_CONTROLLER_NAME -n $INGRESS_CONTROLLER_NAMESPACE -o yaml --overwrite nginx.ingress.kubernetes.io/auth-tls-verify-client='off'
    kubectl annotate ingress $INGRESS_CONTROLLER_NAME -n $INGRESS_CONTROLLER_NAMESPACE -o yaml --overwrite nginx.ingress.kubernetes.io/auth-tls-verify-client='on'
  • Now we test hitting our server with both of our client certs. With both certs loaded we should get a valid response for both client keys.
    curl --insecure --cert userOne.crt:Password1 --key userOne.key https://$HOST_IP:$HOST_PORT
    curl --insecure --cert userTwo.crt:Password1 --key userTwo.key https://$HOST_IP:$HOST_PORT

@johannes-gehrs
Copy link
Author

Thank you very much @slideroi for pointing out the connection to the identical subject. This was the information we lacked for understanding under which conditions this occurs. We have cleanly replicated the issue here and we have filed an upstream nginx issue here.

@slideroi
Copy link

Very welcome, it's good to see everyone get to the bottom of the issues we were seeing.

@johannes-gehrs
Copy link
Author

johannes-gehrs commented Oct 13, 2019

FYI nginx have closed the issue saying that this is expected behavior.

You are using two self-signed certificates with identical subjects, and asks nginx to use both these certificates as trusted CA roots. This is not going to work, as certificate verification implies that appropriate certificate is looked up via it's issuer DN, and hence no two CAs can have identical subjects.
Instead, consider configuring appropriate root CA certificate in the ssl_client_certificate, and use client certificates signed by the root certificate in question. This is expected to work even if subject DNs are identical in the client certificates.

I guess it makes sense, so the only way forward would be to use different subjects / DNs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants