When using gRPC over plain TCP the client establishes a channel with the server like this (in ruby):
stub = Helloworld::Greeter::Stub.new(service_url, :this_channel_is_insecure)
but then, when I implement TLS on the server and put in my LetsEncrypt certificate on the server, the client must establish a secure connection like this (in ruby):
creds = GRPC::Core::Credentials.new(load_certs) # load_certs typically loads a CA roots file
stub = Helloworld::Greeter::Stub.new(service_url, creds)
That code with the comment is taken from the official gRPC docs.
My question is, why does the client need a certificate here? I thought that it was the server that needed a certificate, and the client makes sure it's valid. When my browser connects to secure web sites, does it bring it's own certificate to the table? If not, why does the gRPC client need one?
From my reading the client knows about some trusted authorities, to one of which the server's certificate chain links. And the comment in the code above refers to a "CA roots file" Which might be the certificate of the authority at the top of the chain. So maybe the gRPC client compares the certificate at the root of the server's chain to it's own - but if that's the case, what happens if it gets the wrong CA?
All of the docs and posts explaining how to establish a secure connection from a gRPC client say to read the certificate from a local file, but none of them say what that certificate is, or where it comes from.
What am I missing?
EDIT:
I discovered on my computer a directory with a bunch of what appears to be CA root certificates. /etc/ssl/certs/
One of them appear to be the authority that verifies LetsEncrypt, which I use on the server, so I tried reading that certificate in the client like this:
GRPC::Core::ChannelCredentials.new(File.read('/etc/ssl/certs/ISRG_Root_X1.pem'))
but it only resulted in this error
Handshake failed with fatal error SSL_ERROR_SSL: error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED.