Sunday, March 14, 2021

gRPC SSL certs on Windows with WSL2

The past few days has been one hell of a nightmare. I have been creating gRPC gateway services and we wanted to implement server side HTTP2 push. Turns out HTTP2 & gRPC requires you to implement SSL to take advantage of HTTP2 push in the browser. Implementing SSL is a pain in the a$$. You need to make sure everything is running with the correct hostnames and almost all of the documentation out there is for folks who have said the heck with it and opted for self signed certificates. I couldn't find any good tutorials for setting up a valid SSL cert. 

I ran into too many issues. The first one that took many hours to identify, if you are doing your development on Windows 10 + WSL2, it's not straight forward how best to update /etc/hosts. Recall that for HTTPS to work, you need the hostname to match which means you need DNS to resolve. Here's how to fix this. Open up a terminal on your linux (I use Ubuntu) and run ifconfig. Take the result of that and update your /etc/hosts file (this requires root/sudo.) Then open up notepad as an Administrator and update your c:\Windows\System32\drives\etc\hosts and save that. 

If you do this, you can successfully open up your web browser and use  your custom domain name AND use curl to test your custom domain name. 

I lost countless hours on this one part - trying like hell to figure out why my custom domain wasn't resolving to 127.0.0.1. I think Windows/WSL2 have something about 127.0.0.1 and the secret was getting the IP for the WSL2 Linux distro.

Once you've got your hostnames configured in /etc/hosts, you can move on to securing your golang services. One thing you may need to do is to concatenate your SSL certificate with the providers intermediary certificate. This isn't terribly difficult. Simple create a new text file, have your valid cert in PEM format then a new line and the intermediary. If done correctly, it will look like this: (these are not real - this is just an example)

-----BEGIN CERTIFICATE-----
t1befnVnPZlIlyEMDw/WPyR4Bfi1cemqjaKSxzR+lEtCTQC7xKM8678RMHBtZ7/v
NsfRUitk6otcQnBX/sErZmFxqdPyl7aOpifkj+pQV0mfn3bGTPhSPfc6BM84lZo8
W2HRMbWsw5Z5ZIwEfMEbaCtbtw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
-----END CERTIFICATE-----


You can start your gRPC service over TLS like this:

serverOptions := []grpc.ServerOption{
    grpc.UnaryInterceptor(interceptor.Unary()),
    grpc.StreamInterceptor(interceptor.Stream()),
}
if enableTLS {
    tlsCredentials, err := credentials.NewServerTLSFromFile(serverCertFile, serverKeyFile)
    if err != nil {
        return fmt.Errorf("cannot load TLS credentials: %w", err)
    }
    serverOptions = append(serverOptions, grpc.Creds(tlsCredentials))
}
s := grpc.NewServer(serverOptions...)

That will load the gRPC service over TLS in manner that any client can validate the SSL certificate. It's important to note that you may not need to add the intermediary certificate. Try starting your gRPC service and then connecting to it. I found this handy curl command for testing gRPC HTTP2 POSTs to get Streaming responses.

curl -i --http2 -H "Content-Type: application/grpc" -H "TE: trailers" -X POST https://youhost.com/grpc.Service/Message

Feel free to add -vvv to that if you're having issues. Now that you have your golang gRPC service running under TLS, you just need to adjust your gateway client to handle HTTPS connections. Here's a snippet:

config := &tls.Config{
    InsecureSkipVerify: false,
}
return grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(credentials.NewTLS(config)))


With that, your gateway can connect to the gRPC service using TLS. If you've got Nginx in front of everything, you may find this blog helpful. I found that it was 90% correct. They share some configuration that mostly works, but my version of Nginx didn't like putting the locations outside of the server entry.