linkerd2: identity: Proxy fails to validate certificates with name constraints

Linkerd cannot be deployed when its CA certificate(s) use name constraints. Proxies fail to validate issued certificates.

Repro/Investigation

  1. Generate a self-signed certificate with name constraints limiting issued certificates to foo.bar:

bar.tpl:

{
    "subject": {{ toJson .Subject }},
    "issuer": {{ toJson .Subject }},
    "keyUsage": ["certSign"],
    "basicConstraints": {
        "isCA": true,
        "maxPathLen": 1
    },
    "nameConstraints": {
        "critical": true,
        "permittedDNSDomains": ["foo.bar"]
    }
}
:; step certificate create foo.bar cert.pem key.pem --template=bar.tpl --not-after=24h --no-password --insecure
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 223894858428966857916253664437917250458 (0xa87094cb1d1a378f60aa362777a4f39a)
    Signature Algorithm: ECDSA-SHA256
        Issuer: CN=foo.bar
        Validity
            Not Before: Aug 30 20:07:42 2022 UTC
            Not After : Aug 31 20:07:42 2022 UTC
        Subject: CN=foo.bar
        Subject Public Key Info:
            Public Key Algorithm: ECDSA
                Public-Key: (256 bit)
                X:
                    10:f9:57:fa:30:30:93:4d:9b:e1:64:20:54:a1:1a:
                    20:76:28:5e:31:c4:6f:19:f6:62:9d:fe:9b:68:e8:
                    14:ee
                Y:
                    58:aa:27:73:08:24:78:a9:57:f1:35:fe:53:d9:2d:
                    83:01:d4:fd:c3:b6:44:26:16:06:a0:fb:6f:d8:4b:
                    6b:c8
                Curve: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:1
            X509v3 Subject Key Identifier:
                A3:E8:20:63:F8:F4:35:D0:0D:47:79:B7:AD:F3:CD:3B:EF:2C:74:AE
            X509v3 Name Constraints: critical
                Permitted:
                  DNS: foo.bar
    Signature Algorithm: ECDSA-SHA256
         30:45:02:20:60:b2:5c:96:44:30:0c:5b:fb:63:98:90:7d:60:
         7e:75:ce:88:81:70:8a:01:97:e8:f5:e4:29:30:bf:87:04:ad:
         02:21:00:ed:fa:f6:71:15:c0:26:99:ec:c7:9a:a3:4d:e6:5f:
         e0:e4:d0:fb:9e:3a:3e:30:26:0f:90:25:ba:0a:17:6f:52
  1. Install Linkerd using this certificate:
:; linkerd install --crds |k apply -f -
:; linkerd install \
    --set identityTrustDomain="foo.bar" \
    --set-file identity.issuer.tls.crtPEM=cert.pem \
    --set-file identity.issuer.tls.keyPEM=key.pem \
    --set-file identityTrustAnchorsPEM=cert.pem \
    |k apply -f -
  1. The control plane does not start.

The identity controller becomes ‘running’ and it appears to issue a certificate to its local proxy:

:; k logs -n linkerd linkerd-identity-5c9d8bbb4c-n8n8n
Defaulted container "identity" out of: identity, linkerd-proxy, linkerd-init (init)
time="2022-08-30T20:13:40Z" level=info msg="running version stable-2.12.0"
time="2022-08-30T20:13:40Z" level=info msg="starting admin server on :9990"
time="2022-08-30T20:13:40Z" level=info msg="starting gRPC server on :8080"
time="2022-08-30T20:13:46Z" level=info msg="issued certificate for linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bar until 2022-08-31 20:07:42 +0000 UTC: 4a28173a08c2e9629eeb982e8b2699b6c6007eef8b2377b31eeb2a6149bb371f"

But the proxy fails to validate the certificate:

:; k logs -n linkerd linkerd-identity-5c9d8bbb4c-n8n8n -c linkerd-proxy
...
[     0.011665s] ERROR ThreadId(02) identity: linkerd_proxy_identity_client::certify: Failed to obtain identity error=invalid peer certificate contents: invalid peer certificate: UnknownIssuer

The rest of the control plane fails to start because the identity controller never becomes ready.


This process works when name constraints are omitted from the certificate:

{
    "subject": {{ toJson .Subject }},
    "issuer": {{ toJson .Subject }},
    "keyUsage": ["certSign"],
    "basicConstraints": {
        "isCA": true,
        "maxPathLen": 1
    }
}
:; step certificate create foo.bar cert.pem key.pem --template=bar-unconstrained.tpl --not-after=24h --no-password --insecure
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 22516031807147259098312418109158923000 (0x10f06e13bbe38643d7ce95ec4cf91af8)
    Signature Algorithm: ECDSA-SHA256
        Issuer: CN=foo.bar
        Validity
            Not Before: Aug 30 20:33:12 2022 UTC
            Not After : Aug 31 20:33:12 2022 UTC
        Subject: CN=foo.bar
        Subject Public Key Info:
            Public Key Algorithm: ECDSA
                Public-Key: (256 bit)
                X:
                    74:e5:86:db:e6:68:1d:a5:c7:1d:fd:21:29:2d:69:
                    de:f1:0a:57:7c:30:35:f1:9d:c4:c2:d3:61:cf:42:
                    09:19
                Y:
                    ab:00:91:94:2c:df:e5:7f:4b:13:8e:40:e5:04:07:
                    e9:0e:db:c9:03:83:a2:fb:42:80:37:cb:35:df:f4:
                    17:7d
                Curve: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:1
            X509v3 Subject Key Identifier:
                60:42:74:A2:23:17:6A:BD:94:1E:0B:87:4C:A8:70:7B:B8:BD:AD:55
    Signature Algorithm: ECDSA-SHA256
         30:45:02:20:63:24:33:92:8a:71:65:d3:9f:fd:da:44:ab:40:
         1e:74:6e:0c:58:f5:8d:93:98:31:76:be:e4:96:7a:97:a5:e2:
         02:21:00:a1:a0:93:4d:0e:bc:96:0c:af:99:4a:34:0a:40:5f:
         b6:d1:72:d5:ed:16:85:8a:3a:c4:e1:5e:90:43:80:e0:15
:; linkerd identity -n linkerd linkerd-identity-75f7d4df68-wd8km

POD linkerd-identity-75f7d4df68-wd8km (1 of 1)

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1 (0x1)
    Signature Algorithm: ECDSA-SHA256
        Issuer: CN=foo.bar
        Validity
            Not Before: Aug 30 20:38:17 2022 UTC
            Not After : Aug 31 20:38:27 2022 UTC
        Subject: CN=linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bar
        Subject Public Key Info:
            Public Key Algorithm: ECDSA
                Public-Key: (256 bit)
                X:
                    bb:43:be:97:a7:39:83:6c:e5:71:e1:17:71:c2:ef:
                    fc:64:fd:1e:cb:6f:5c:25:fe:8b:ce:1f:91:93:24:
                    40:bf
                Y:
                    07:89:d1:56:5d:8e:b7:b7:3c:9a:8a:a6:12:e3:19:
                    30:63:63:25:49:4b:8a:61:be:1f:5b:ed:f2:88:6c:
                    b8:79
                Curve: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Authority Key Identifier:
                keyid:5E:A4:79:4B:A0:EA:D3:8A:07:9F:14:8F:2A:77:E8:0E:0B:1A:4B:D7
            X509v3 Subject Alternative Name:
                DNS:linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bar

    Signature Algorithm: ECDSA-SHA256
         30:45:02:20:4d:85:c6:af:4b:04:aa:eb:89:8f:c8:64:f3:49:
         42:60:b0:66:6d:1a:22:5d:e9:41:3c:40:9d:54:8f:e0:d8:1c:
         02:21:00:d5:ee:41:06:a1:ca:fb:1c:40:91:e4:35:75:68:ae:
         23:9e:f3:7b:24:b2:4d:14:a6:dd:66:0d:43:7c:5f:79:04

Note that we can manually create an end-entity certificate (as used by a proxy) with:

:; step certificate create linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bar \
    ee-cert.pem ee-key.pem \
    --san=linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bar \
    --ca-key=key.pem --ca=cert.pem \
    --not-after=24h \
    --no-password --insecure
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 55110646295972328840139681094716422534 (0x2975eca5308fa2fde788e681d188f986)
    Signature Algorithm: ECDSA-SHA256
        Issuer: CN=foo.bar
        Validity
            Not Before: Aug 30 20:53:31 2022 UTC
            Not After : Aug 31 20:53:31 2022 UTC
        Subject: CN=linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bar
        Subject Public Key Info:
            Public Key Algorithm: ECDSA
                Public-Key: (256 bit)
                X:
                    52:b2:ad:f7:19:87:ef:f0:0c:e3:f4:4a:5c:3f:a1:
                    67:7b:b1:75:31:32:14:ba:42:56:f7:4a:58:b7:af:
                    fe:8c
                Y:
                    ed:fa:80:45:fb:30:51:8a:3e:86:bd:dd:0f:b8:75:
                    01:47:50:d7:c6:0a:24:ca:42:65:02:26:0a:36:db:
                    3d:4f
                Curve: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                Server Authentication, Client Authentication
            X509v3 Subject Key Identifier:
                0B:1B:29:6D:CF:76:77:A7:3E:40:7F:10:E6:0A:BD:69:69:BE:70:7B
            X509v3 Authority Key Identifier:
                keyid:5E:A4:79:4B:A0:EA:D3:8A:07:9F:14:8F:2A:77:E8:0E:0B:1A:4B:D7
            X509v3 Subject Alternative Name:
                DNS:linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bar
    Signature Algorithm: ECDSA-SHA256
         30:44:02:20:3d:0a:2b:dd:49:21:a9:95:3c:49:48:17:54:d0:
         99:c9:f7:c7:7b:73:da:39:86:1b:27:8a:e6:9b:5c:c8:68:eb:
         02:20:3e:16:16:bd:ff:82:99:16:c8:f6:60:bb:e5:55:5f:f0:
         de:6e:02:73:37:eb:d8:3a:b4:37:96:31:4e:a8:ce:29

step happily verifies that the end-entity certificate is signed by the CA, even when name constraints are present:

:; step certificate verify ee-cert.pem \
    --roots=cert.pem \
    --host=linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bar

If we generate an end entity certificate for an alternate suffix (idk.lol), step properly fails to validate the certificate.

:; step certificate create linkerd-identity.linkerd.serviceaccount.identity.linkerd.idk.lol ee-cert.pem ee-key.pem --san=linkerd-identity.linkerd.serviceaccount.identity.linkerd.idk.lol --ca-key=key.pem --ca=cert.pem --not-after=24h --no-password --insecure
...
:; step certificate verify ee-cert.pem \
    --roots=cert.pem \
    --host=linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bah
failed to verify certificate: x509: a root or intermediate certificate is not authorized to sign for this name: DNS name "linkerd-identity.linkerd.serviceaccount.identity.linkerd.foo.bah" is not permitted by any constraint

Suggested next steps

Why doesn’t the proxy validate its end entity certificates when name constraints are used?

To figure this out, I’d probably try to create a test (or repo) that attempts to use webpki to validate these certificates. If this works in a standalone repo, then we can try to figure out how Linkerd’s usage differs from a simpler example setup.

Note that it may be required to alter the format of the private key with, e.g.:

:; openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in key.pem -out pkcs8.key  

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 21 (11 by maintainers)

Commits related to this issue

Most upvoted comments

@MrFreezeex

Hi @hawkw, If I understand correctly this should be fixed with the latest edge release right?

Since we’ve switched to a version of webpki that supports name constraints, it ought to work — we’d love to hear back if you end up trying it.

Do note, though, that the cert-manager support for name constraints still doesn’t exist (see https://github.com/cert-manager/cert-manager/issues/3655). So, name constraints will probably work on the Linkerd side, but if you’re using cert-manager, I don’t believe you’ll have a way to specify them. This isn’t an issue if you’re using CAs other than cert-manager.

Now that we support this important capability, we will be adding integration tests to ensure the correct behavior is captured in all future releases.

I’m not sure where I went wrong last time, but I just tried - with a fresh set of CAs - to give you a full set of certificates. But now it works as expected.

Oh. Well, nevermind, then! Glad to hear everything works as expected!

I’m going to go ahead and close this issue, but please let us know if you have any other issues. If anything’s not working, we can re-open this, or open more specific issues to track anything that comes up. Thanks for all your help testing this stuff out!

I did some more tests and it works as advertised now. The fact that name constraints from root CAs are ignored isn’t that big of a thing, to be honest. Given that the root CA(s) should live outside of the cluster anyway, it’s all good.

Makes me very happy to have this capability available to me now, because it makes zero-trust multicluster setups a lot more secure now. Thanks for making this happen!

I just tested it with the latest edge release and with a NON-matching name constraint (e.g. .foo.local) on the issuer certificate I now correctly see the linkerd control plane not even starting:

ERROR ThreadId(02) identity: linkerd_proxy_identity_client::certify: Failed to obtain identity error=invalid peer certificate: Other(NameConstraintViolation)

With a matching name constraint (.cluster.local) everything works.

What doesn’t work though is having a name constraint on the root CA but not on the issuer CA. It does work with openssl, so I’d expect that to be the correct behavior then.

Wow, that was fast. By the time I read your comment the PR was already merged 🤯

I’ll give it another try once the next edge release is available.

Welp, I think I’ve figured out what’s going on here. The peer certificate verification is being performed through rustls, rather than calling into rustls-webpki directly. The proxy currently depends on rustls v0.20.8, which…still depends on webpki, rather than using rustls-webpki.

So, we will need to update the proxy’s rustls dependency to a newer version that actually uses the rustls-webpki fork.

I tested this locally with kind and the latest edge version, but couldn’t get it to work.

I added a name constraint to the root CA (trust root):

k get configmaps -n linkerd linkerd-identity-trust-roots -o json | jq -r '.data["ca-bundle.crt"]' | openssl x509 -noout -text -in /dev/stdin
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            c1:6e:2d:8c:6d:da:c7:f9:a2:0c:5c:ae:d7:96:46:db
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN = Root CA
        Validity
            Not Before: Sep 21 07:06:04 2023 GMT
            Not After : Sep 22 07:06:04 2023 GMT
        Subject: CN = Root CA
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:dd:46:b5:55:1c:7f:f4:a8:82:c2:ac:f6:72:93:
                    37:3b:7b:31:ee:cb:0a:fc:78:49:99:3a:1a:40:a3:
                    e6:05:36:05:fd:bd:04:c7:39:b5:89:88:39:96:f1:
                    94:cc:11:cf:6d:77:ca:d7:6e:8c:e5:4f:2d:bc:e4:
                    32:f5:7e:f5:29
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:10
            X509v3 Subject Key Identifier:
                12:08:30:2A:46:51:EB:22:91:47:10:45:93:6C:57:22:D5:71:E5:26
            X509v3 Name Constraints: critical
                Permitted:
                  DNS:.bravo.alpha.local
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:44:02:20:7c:57:c3:5a:cf:fc:ec:c1:dc:7d:6c:f6:f7:9b:
        ef:d3:19:e1:bb:5b:da:4e:d0:4c:73:e8:10:36:8f:aa:fb:0f:
        02:20:6a:a0:e9:13:ad:96:b2:05:d8:47:00:be:80:07:14:43:
        34:1a:73:22:d3:3c:bc:41:99:38:44:15:c3:c0:d9:f7

The issuer CA (used by linkerd-identity) doesn’t have a name constraint. (I tested this construct against openssl verify where it works correctly, hence I’m using that as my baseline.)

k get secret -n linkerd linkerd-identity-issuer -o json | jq -r '.data["crt.pem"]' | base64 -d | openssl x509 -noout -text -in /dev/stdin
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            40:b9:ce:b6:47:30:91:97:67:18:0b:66:6e:6e:ed:f4
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN = Root CA
        Validity
            Not Before: Sep 21 07:06:04 2023 GMT
            Not After : Aug  9 15:06:04 2024 GMT
        Subject: CN = Issuing CA
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:a5:5f:cb:38:c8:13:16:a5:8f:5a:06:7c:71:d5:
                    b8:4f:39:9c:16:1b:85:da:db:77:a1:79:fd:2b:14:
                    57:23:29:c0:80:2a:d4:77:e7:c8:ea:fa:33:7e:29:
                    21:58:f6:ab:02:ad:48:7f:c1:0a:b3:26:dc:77:02:
                    a3:10:38:1d:fa
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:9
            X509v3 Subject Key Identifier:
                B5:05:E0:3A:06:27:60:CC:89:D1:B9:53:53:B6:B7:FC:BC:2F:19:58
            X509v3 Authority Key Identifier:
                12:08:30:2A:46:51:EB:22:91:47:10:45:93:6C:57:22:D5:71:E5:26
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:45:02:21:00:86:d9:22:d7:7d:ae:74:a1:b7:9e:cc:7b:47:
        01:8a:b2:d1:59:f4:af:15:7d:24:ca:f3:b6:65:9e:d9:09:ef:
        95:02:20:4f:dd:e5:5e:77:49:25:35:fd:7c:97:14:1a:9c:50:
        76:82:a1:cb:11:f8:86:6c:12:0b:66:eb:7b:c3:88:14:89

As you can see the name constraint uses something other than the default cluster.local domain. But the cluster that I configured was using the standard, i.e. both the cluster domain and the identity trust domain were both left to the default of cluster.local.

The control plane successfully started and none of the linkerd-proxys complained about anything. I also tried emojivoto and that also just works.