Caddy: v2: TLS client authentication

Created on 18 Jul 2019  Â·  12Comments  Â·  Source: caddyserver/caddy

Implement TLS client auth into Caddy 2.

Caddy 2 provides advanced config facilities to enable highly-specific TLS client auth controls. We just need to take advantage of it.

  • [x] Whitelist CA certs
  • [x] Whitelist each leaf cert
  • [ ] 'ask' endpoint (Update: might not be needed; see discussion)
deferred

Most helpful comment

Thanks @francislavoie!

Yeah, you can use placeholders to add it to the request if you need. That part is a bit WIP because we have an issue where the cert is not URL encoded so it breaks when being put in a header, but that should be fixed soon. See #3767, and also #3757 is somewhat related here.

Those links were very useful. I did not realize you could read client cert fields from header_up - Since I only need the serial, subject & issuer caddy already supports what I need to do :+1: - and after some tinkering with h2c (it's a grpc service) I got it working now.

This actually is a lot better than my initial thought of having a ask_url since I now only have to do one request per incoming connection. Can anybody else think of a valid use-case of a ask_url?

All 12 comments

Hi,

What is the status of this?

I think this should goes into the http/servers/tls_connection_policies.
The name of the field can be something link client_CAs.
It would take the form of DER encoded certificate authorities themselves encoded into base64 to be compatible with JSON.

http/servers/tls_connection_policies:

{
    "match": {},
    "alpn": [],
    "cipher_suites": [],
    "certificate_selection": {},
    "client_CAs": [
        "MIIDSzCCAjOgAwI......E/lYx0qGtr0xHQ=="
    ]
}

If this value is provided the verification is required as it is with Caddy 1.

Encoding certificate into this form is trivial:

openssl x509 -inform PEM -outform DER -in ca.crt | base64 -w 0

If this is looking OK for you I can start working on it.
I can't try version 2 for the moment because I need TLS client authentication.

@alexandrestein I'd be happy to have your help!

I think this should goes into the http/servers/tls_connection_policies.

Yes, you're on the right track! That is where client authentication config belongs.

The name of the field can be something link client_CAs.

Let's call it client_authentication. Which CAs to allow or disallow is only one particular aspect of client auth.

Here's the structure I have in mind:

"client_authentication": {
    "trusted_ca_certs": [ ... ],
    "trusted_leaf_certs": [ ... ],
    "ask_url": "..."
}

So basically, instead of straightaway assuming a list of CA certificates, we make an object that allows more than one validation option.

An initial PR wouldn't have to implement all this right away, but I want to make sure it's possible later. Still, I won't get around to client auth for some time yet (I'm working on the proxy currently), so contributions would be welcomed!

What do you think about this? Let's discuss it and get the best solution.

@alexandrestein Ah, while I was writing my response (which I started this morning, then got sidetracked with stuff), you have opened a PR. Thanks! I'll review it, but I still think we need to adjust the structure like this.

Cool, the only thing left to do after #2731 is the ask_url.

If we want the "asking" to be done solely via TCP/TLS, and not even use HTTPS, maybe we just need an ask_address or ask_endpoint instead of a full URL.

Will revisit this later if there's demand for it.

@mholt Is there a way to use this from a Caddyfile, similar to v1's clients subdirective for tls? If not, are there any plans to add this back in the future? Thanks!

Probably around ~v2.1.

Cool, I'll use the JSON for now in that case — thanks for such a quick response 😇

Cool, the only thing left to do after #2731 is the ask_url. [...]
Will revisit this later if there's demand for it.

Just commenting here to indicate I have a use-case for this: I have several services that require a client certificate to be accessed.
They check the serial of the cert and do some internal database lookups to determine if it is valid. Only if valid I would forward the request to the actual service. For this an ask_url would be very useful. This would allow me to put those services behind caddy (with its great certificate management) and not expose them directly like I have to do right now.

@mholt: Maybe you could sketch out the ideas you had for ask_url a bit more. I'm interested in creating a PR for this.

(Note: I might be slow on responding in the next few days...)

EDIT: Also one question, could the client certificate also be forwarded to the actual endpoint? Since the ask_url and the actual endpoints are stateless I would need to somehow tell the actual endpoint what cert we're referring to.

@sahib so basically the config would probably go in the ClientAuthentication struct which is part of the TLS connection policies: https://github.com/caddyserver/caddy/blob/97caf368eea8d2c33a7786fbe3471b83b5b294dc/modules/caddytls/connpolicy.go#L281

The additional logic to ask the backend would probably happen in verifyPeerCertificate which is a callback that the TLS internals will calls when verification is happening.

I'm not sure exactly how the request should look. The ask endpoint for on-demand certificates, that we already have implemented, makes GET requests with a query param being the domain name to ask about. But here, a cert is the input, which is a lot bigger, so I'm not sure a query param is ideal. There's a few different ways it could be done, e.g. POST with form data, or with JSON, or just a raw cert plaintext. We could have multiple modes for it, but we should definitely figure out which one is the best default (and probably not overengineer it).

Also one question, could the client certificate also be forwarded to the actual endpoint?

Yeah, you can use placeholders to add it to the request if you need. That part is a bit WIP because we have an issue where the cert is not URL encoded so it breaks when being put in a header, but that should be fixed soon. See https://github.com/caddyserver/caddy/issues/3767, and also #3757 is somewhat related here.

Thanks @francislavoie!

Yeah, you can use placeholders to add it to the request if you need. That part is a bit WIP because we have an issue where the cert is not URL encoded so it breaks when being put in a header, but that should be fixed soon. See #3767, and also #3757 is somewhat related here.

Those links were very useful. I did not realize you could read client cert fields from header_up - Since I only need the serial, subject & issuer caddy already supports what I need to do :+1: - and after some tinkering with h2c (it's a grpc service) I got it working now.

This actually is a lot better than my initial thought of having a ask_url since I now only have to do one request per incoming connection. Can anybody else think of a valid use-case of a ask_url?

Can anybody else think of a valid use-case of a ask_url?

If there is not any further use case, I would definitely prefer we don't implement it. :) Unless, of course, a legitimate use case does arise. Thanks for the discussion!!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

muhammadmuzzammil1998 picture muhammadmuzzammil1998  Â·  3Comments

dafanasiev picture dafanasiev  Â·  3Comments

wayneashleyberry picture wayneashleyberry  Â·  3Comments

crvv picture crvv  Â·  3Comments

mikolysz picture mikolysz  Â·  3Comments