-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: crypto/tls: dynamically reload root certificate authorities #64796
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
Comments
cc @golang/security |
The discussion at #22836 mentions that it's possible to replicate the normal certificate verification behavior by using specifying Obviously a |
maybe to introduce a new callback in tls.Config that allows for dynamically retrieving the latest CertPool, similar to the existing callback functions. // GetRootCAs returns the set of root certificate authorities
// that clients use when verifying server certificates.
//
// If GetRootCAs is nil, then the RootCAs is used.
GetRootCAs func() (*x509.CertPool) The client handshake will work with the proposed changes as follows: func (c *Config) rootCAs() *x509.CertPool{
if c.GetRootCAs == nil {
return c.RootCAs
}
return c.GetRootCAs()
} func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
} else if !c.config.InsecureSkipVerify {
opts := x509.VerifyOptions{
Roots: c.config.rootCAs(),
CurrentTime: c.config.time(),
DNSName: c.config.ServerName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
chains, err := certs[0].Verify(opts)
if err != nil {
c.sendAlert(alertBadCertificate)
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
}
c.verifiedChains, err = fipsAllowedChains(chains)
if err != nil {
c.sendAlert(alertBadCertificate)
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
}
}
} |
A GetRootCAs function would greatly help in a fast-rotating custom PKI scenario. EDIT: reading the envoy proxy issue linked above, another (likely better) work around is this advancedtls package |
It sounds like the concrete proposal here is to make the following change to the tls.Config type:
This seems reasonable. |
@rolandshoemaker is there a need to write up a proposal doc for this? If not, I'd be happy to implement this. |
@rolandshoemaker I like this proposal for root CAs and even if this ticket does not explicitly mention about client CAs, I guess the same applies for the ClientCAs field so for consistency reason the corresponding GetClientCAs would be beneficial. |
Ah yes, I overlooked that. Updated my comment to reflect both sides. |
On the server side there is already GetConfigForClient, which allows rotating the ClientCAs field. I'm a bit uneasy about adding callbacks piecemeal for every single thing one might want to rotate in a client-side tls.Config. Is there a strong reason Certificates and RootCAs need callbacks, and not MinVersion and NextProtos and EncryptedClientHelloConfigList? At least the GetClientCertificate callback takes input from the handshake. I always found argument-less callbacks an indication of something that went wrong. |
Of course I also forgot about GetConfigForClient. I think for RootCAs though this still makes sense, we've heard from multiple people who need long running servers that have pools that change on a not particularly infrequent basis, and who explicitly don't want to have to restart the server in order to reload the pool. I think for a lot of other things (like MinVersion and NextProtos) which are unlikely to change particularly frequently, this wouldn't make sense. |
My employer has ways of updating trust stores on disk and we're hoping to be able to remove roots as quickly as possible without requiring everything to always restart. If we update the file, we can implement something to reload the bundle and prune the removed root. |
@FiloSottile ah that's right, I forgot about GetConfigForClient. I would find it very useful to have a callback for root CAs for this reason, right now I have to recreate the client's http.Transport whenever CA certificate change is detected, so that I can provide a new Transport with new tls.Config and updated root CAs. |
This proposal has been added to the active column of the proposals project |
Change https://go.dev/cl/670815 mentions this issue: |
@gakesson for |
Something to add here is that the GetConfigForClient mechanism can be problematic. For example, the gRPC library will clone and modify the provided tls.Config struct: https://github.com/grpc/grpc-go/blob/master/credentials/tls.go#L237 These changes won't be reflected in the client connection: as that config will be whatever is returned by GetConfigForClient. This new, proposed mechanism alleviates that issue (and is important for those of use who need to dynamically update client and server CA certificates). I just wanted to point out that GetConfigForClient is not a full solution in some cases. |
@Galabar001 since |
That's a solid idea. What I think we'll usually see with "GetConfigForClient" is a cached tls.Config protected by a mutex. There will be a background thread that routinely loads a new tls.Config and swaps out the original. GetConfigForClient simply locks the mutex and returns either the current tls.Config, or a copy of it (for safety). Instead, what could be cached would be the tls.Certificate and CertPool. The initial tls.Config that you create would be fully filled out. Then, you'd just call your "ClientHelloInfo.Config" function above, copy that config, replace the Certificates and RootCAs/ClientCAs values, and return that copy. Yeah, that would be much better than the current situation. However, if the RootCAs and ClientCAs could be updated dynamically, we could avoid GetConfigForClient entirely. At the moment, though, I think we are not in a great position -- we don't have a mechanism that works for 100% of the current (Google library) use cases without some amount of hacking (e.g. filling in the NextProtos for gRPC configs before calling the gRPC library). |
Proposal Details
The current state of the
tls.Config.RootCAs
presents a challenge when it comes to dynamically reloading the Root Certificate Authorities. The existing approaches involve either disabling automatic certificate and host verification or limiting functionality to non-proxied requests. This proposal seeks to address this limitation by introducing a mechanism for dynamic reloading of Root CAs.The current workaround for dynamically updating Root CAs involves using the
VerifyPeerCertificate
, but it requires disabling automatic certificate and host verification by setting theInsecureSkipVerify
field to true. This approach might introduce some security risks if the custom implementation is not correct.Alternatively, a custom TLS Dialer could be used. However, this approach falls short when dealing with proxied requests, limiting its applicability in scenarios where proxies are used.
To enhance the flexibility of TLS configurations, this issue proposes modifying the behaviour of
tls.Config.RootCAs
to support dynamic reloading by introducing a function similar toGetClientCertificate
.Note that in the past, a similar request was rejected. Since commenting on that issue is disabled, I decided to create a new issue to see if something has changed. Having such a mechanism in the standard library would help the Kubernetes community address kubernetes/kubernetes#119483.
Update:
It largely depends on how the
tls.Config.RootCAs
is used internally. Another solution would be to accept an interface instead of*x509.CertPool
and allow clients to inject a thread-safe implementation, enabling trust reloading.The text was updated successfully, but these errors were encountered: