caddy icon indicating copy to clipboard operation
caddy copied to clipboard

Feature: optional verification of DNSNames in client cert

Open LukeHandle opened this issue 4 years ago • 12 comments

I was looking at client_auth to protect an endpoint for server-server communication. It's a bit of a faff though to do all the private CA /signing bits - could we instead leverage LE certs (or any valid cert) for client auth? eg. If we know we control a specific domain, we could then allow client auth for certs valid on that (wildcard) domain

As a thought on how this would work, having a list of trusted cert names which Caddy verifies client certs against. This would need wildcard support (eg. *.int.example.com). It would need to at least check the SAN / DNSNames, though possibly CN as well for legacy certs (generic client certs might only have CN?).

I think this could be added here: https://github.com/caddyserver/caddy/blob/792fca40f18b7c528b00a7dea508bdfd0821dd8c/modules/caddytls/connpolicy.go#L399-L427

Interested on thoughts / "am I being silly" / opinions :D

LukeHandle avatar Mar 26 '21 13:03 LukeHandle

What is the "server-server communication" exactly? Just reverse proxying?

mholt avatar Apr 08 '21 20:04 mholt

Hi Matt 👋, In this particular scenario, I was wanting to protect an HTTPS endpoint for shipping logs to.

All the nodes will run Caddy (running an app, proxy Prometheus node_exporter metrics, other bits etc.) and could then piggyback on those existing certs to then ship log data to a Caddy instance in-front of Loki via promtail (which supports TSL client auth).

eg.

Promtail grabs existing LE cert -> pushes to Caddy client_auth configured instance -> Proxies to Loki

The issue is controlling the ingress into the Loki instance. Alternatives are either locking down entry to specific IP sources, basic auth (would likely just share the same creds to all the servers) or using another layer (eg. wireguard) to protect the traffic.

LukeHandle avatar Apr 09 '21 15:04 LukeHandle

Hmm, well:

could we instead leverage LE certs (or any valid cert) for client auth? eg. If we know we control a specific domain, we could then allow client auth for certs valid on that (wildcard) domain

That's not really how TLS client auth works. You can't just trust the domain that's in the cert, you have to trust the authority that issues it, because any authority can issue a cert for your trusted domains. If you trust the authority, configure its root for client auth.

Closing, since this is already implemented; but feel free to continue discussion if needed.

mholt avatar Apr 09 '21 16:04 mholt

Hi @mholt, I appreciate your time. I think I badly explained myself by being too broad with how I would implement this, vs. how I would use it.

Story:

How can I use TLS client auth without having to manage my own CA ? I want the benefits of client cert protection, but not the burden of running my own CA. I already trust public CAs (eg. Let's Encrypt) to correctly issue certificates for domains they verified control of.

For my specific use case, I would set the trusted CA to Let's Encrypt/ZeroSSL, then at verifyPeerCertificate, we check the client cert issuer, and then ALSO check the presented SANs matches a defined pattern (eg. *.example.com).

Crucially - we are not just trusting any cert from a CA. We are setting a wildcard SAN domain list to trust as well. "The client cert must be from LE for the domain server1.example.com".

I used to use internal/private CAs for internal apps as acquiring certs was a pain ($$). From a security perspective, it's probably easier for many small/medium orgs to trust a professional CA (and bonus for being free) - especially considering your browser already trusts it - vs. running your own in the office. They'll have proper process from managing the private CA keys vs. your office server...

===

While this might not be how other people have traditionally used TLS client auth, I don't think there is a technical limitation here. The fact the certs are free makes this more appealing than maybe it used to be. The feature itself wouldn't need to rely on LE. For a private CA, it could just be an extra layer of security of limiting access to specific client certs.

A parallel would be the VERIFY_IDENTITY in MySQL - https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode You aren't just checking the CA / cert chain is valid, but also the presented hostname/SAN matches something.

===

edit: To be clear as well, I also respect this might be too niche and not worth the time etc. 😄

LukeHandle avatar Apr 13 '21 23:04 LukeHandle

Hmm. I'll give it some thought. Further discussion welcomed in the meantime.

mholt avatar Apr 19 '21 21:04 mholt

Roughly thinking about this, the "easy part" is the SAN check on the incoming client side. Config would be something like tls -> client_auth -> trusted_names which is a list accepting wildcards (similar to the existing "one label" wildcard host rules). Then add that logic in verifyPeerCertificate.

edit: In fact, trusted_name (no plural) wouldn't be a list, it would just accept being defined multiple times, similar to the other trusted_* options. eg. trusted_name *.int.example.com, trusted_name *.int.example.co.uk

When trusted_names is set, it is then part of the verification - no changes would be needed to mode selection.

The complication I see would be selecting the CA/leaf in a user friendly way. The primary use of trusted_names I would anticipate would be with LE or ZeroSSL issued certs. For default caddy configs, the issuer isn't hardset and will fallback, thus you would want the leaf's for both of the issuers.

Which, isn't difficult to specify multiple trusted_leaf_cert, though is an extra gotcha/trap to fall into which could be solved internally (though more code complexity).

===

I've also just checked the reverse_proxy/transport client_auth docs and it appears it already supports this idea?

tls_client_auth enables TLS client authentication one of two ways: (1) by specifying a domain name for which Caddy should obtain a certificate and keep it renewed

The implication here is that Caddy already supports using LE/ZeroSSL to acquire a client cert to connect to external, so this feature would tie in well.

LukeHandle avatar Apr 21 '21 15:04 LukeHandle

Will revisit this sometime after the 2.4 release.

mholt avatar May 02 '21 18:05 mholt

Our use case is:

  • we have a root ca that we trust, and every moving part in the system is getting certificates from that root ca.
  • we use caddy to do proxying and would like to connect to the api port with tls client certificate

Problem:

  • We can only specify which CA to trust, which means that any server connecting to the API is allowed.

Solution: https://github.com/influxdata/telegraf/blob/2d458ac7de87b6beaca08268b2457642af532585/plugins/common/tls/config.go#L194 and along with that a field in client_certificate that says dns_names = ["san1"]

isodude avatar Feb 15 '22 09:02 isodude