Why is this so hard?
To send gRPC messages in Rust, one uses the tonic library. If you’re sending to an https endpoint, you’ll need to provide a ClientTlsConfig. By default, you’ll get a very distrustful one.
Here’s the error message:
the grpc server returns error (The service is currently unavailable): , detailed error message: error trying to connect: invalid peer certificate contents: invalid peer certificate: UnknownIssuer
Here’s the mitigation:
As far as I can tell, the ClientTlsConfig distrusts every certificate. It doesn’t seem to load the native certificates from the environment, like a civilized https client.
To make it trust something, give it a root certificate:
use tonic::transport::ClientTlsConfig; use tonic::transport::Certificate; let pem = tokio::fs::read("/etc/ssl/cert.pem").await.expect("oh no, the cert file wasn't loaded"); let cert = Certificate::from_pem(pem); ClientTlsConfig::new().ca_certificate(cert)
I couldn’t find a better way to get a certificate than from the filesystem. I’d like to use the rustls-native-certs library for that, but no, tonic won’t let me use those certificates, and it won’t let me use a ClientConfig from rustls (except in a very old version).
Where can I find the certificate to load?
If you’re lucky, you can find a nice big root certificate installed in your OS. I found mine in
/etc/ssl/cert.pem (on a Mac).
If you’re less lucky, you find a whole lot of small certificates. In my Docker container,
/etc/ssl/certs/ contains 137 of them. The ClientTlsConfig only lets me load one! Seriously, tonic??
Since I only need to connect to one endpoint, I only need one root certificate, as long as it is the root certificate used by the SSL certificate at my endpoint. So, which one is that? Investigate:
openssl s_client -connect api.honeycomb.io:443 -servername localhost
Here, please replace
api.honeycomb.io with the endpoint you’re going to send gRPC to.
CONNECTED(00000006) for a long time, and eventually prints out a lot of info about the certificate. Look for the Certificate chain section:
--- Certificate chain 0 s:/CN=*.honeycomb.io i:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon 1 s:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon i:/C=US/O=Amazon/CN=Amazon Root CA 1 2 s:/C=US/O=Amazon/CN=Amazon Root CA 1 i:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 3 s:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 i:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority ---
We want to load one of the certificates in this chain. Probably the last one. I looked in my filesystem:
$ ls /etc/ssl/certs/*Starfield*.pem /etc/ssl/certs/Starfield_Class_2_CA.pem /etc/ssl/certs/Starfield_Root_Certificate_Authority_-_G2.pem /etc/ssl/certs/Starfield_Services_Root_Certificate_Authority_-_G2.pem
The last one looked a lot like the
/CN=Starfield Services Root Certificate Authority - G2 in my certificate chain, so I used that one. It worked.
Good luck with this. I hope it gets easier in the future to use the native certs, like other languages and frameworks typically do.