gRPC SSL Configuration

By default gRPC projects uses ASP.NET Core's trusted Development certificate (typically created on install), or can be configured with:

dotnet dev-certs https --trust

Many other languages requires this development certificate in order to establish a secure SSL connection, which can be exported with:

Exporting your Development Certificate

Export your localhost self-signed .NET Core development certificate with:

dotnet dev-certs https --export-path .

If that fails see if you can diagnose and resolve the issue from the verbose output:

dotnet dev-certs https --export-path . --verbose

If you can't keep copy of the certificates thumbprint, then export it via Windows Certificate Manager:

certmgr

  1. Go to Personal > Certificates
  2. Select certificate Issued To localhost
    • If you have multiple certificates it's likely the one that expires the latest (double-check with thumbprint above to make sure)
  3. Click on Action > All Tasks > Export
  4. In the Export Wizard select No, do not export the private key
  5. Select 2nd option Base-64 encoded X.509 (.CER)
  6. Save to localhost.cer

Generating a new Development Certificate

Should you prefer, you can create and use your own self-signed certificate using this OpenSSL script.

A quick way to download them is using the mix tool:

x mix -name ProjectName gen.https.sh

Otherwise you can create local text files and manually copy them with the contents below:

gen-dev.https.sh

PASSWORD=grpc
if [ $# -ge 1 ]
  then
    PASSWORD=$1
fi

cat <<EOT >>dev.config
[ req ]
default_bits       = 2048
default_md         = sha256
default_keyfile    = dev.key
prompt             = no
encrypt_key        = no

distinguished_name = dn
req_extensions     = v3_req
x509_extensions    = x509_req
string_mask        = utf8only

[ dn ]
commonName             = localhost dev cert
emailAddress           = test@localtest.me
countryName            = US
stateOrProvinceName    = DE
localityName           = Wilmington
organizationName       = Todo World

[ x509_req ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints       = critical, CA:false
keyUsage               = critical, keyEncipherment
subjectAltName         = @alt_names
# extendedKeyUsage  = serverAuth, clientAuth
nsComment              = "OpenSSL Generated Certificate"

[ v3_req ]
subjectKeyIdentifier   = hash
basicConstraints       = critical, CA:false
subjectAltName         = @alt_names
# extendedKeyUsage  = serverAuth, clientAuth
nsComment              = "OpenSSL Generated Certificate"

[ alt_names ]
DNS.1                  = localhost
EOT

openssl req -config dev.config -new -out dev.csr.pem
openssl x509 -req -days 365 -extfile dev.config -extensions v3_req -in dev.csr.pem -signkey dev.key -out dev.crt
openssl pkcs12 -export -out dev.pfx -inkey dev.key -in dev.crt -password pass:$PASSWORD
rm dev.config dev.csr.pem
# cp dev.pfx ../MyApp

Linux or WSL Bash:

./gen-dev.https.sh

Windows:

bash gen-dev.https.sh

Options:

gen-dev.https.sh $PASSWORD

Trust Certificate on Windows

Import the pfx certificate:

powershell Import-PfxCertificate -FilePath dev.pfx Cert:\LocalMachine\My -Password (ConvertTo-SecureString grpc -asplaintext -force) -Exportable

Trust the certificate by importing the pfx certificate into your trusted root:

powershell Import-Certificate -FilePath dev.crt -CertStoreLocation Cert:\CurrentUser\Root

Trust Certificate on Linux or macOS

See Configuring HTTPS in ASP.NET Core across different platforms for examples of trusting SSL Certificates on different platforms.

Although recommended it's not necessary to trust self-signed certificates to enable secure gRPC SSL endpoints as servers and clients can be configured to use these OpenSSL generated self-signed certificates without out-of-band certificate registration.

Generating a new Production Certificate

gen-prod.https.sh

DOMAIN=todoworld.servicestack.net
if [ $# -ge 1 ]
  then
    DOMAIN=$1
fi

PASSWORD=grpc
if [ $# -ge 2 ]
  then
    PASSWORD=$2
fi

cat <<EOT >>prod.config
[ req ]
default_bits       = 2048
default_md         = sha256
default_keyfile    = prod.key
prompt             = no
encrypt_key        = no
distinguished_name = dn
req_extensions     = v3_req
x509_extensions    = x509_req
string_mask        = utf8only
[ dn ]
commonName             = TodoWorld prod cert
emailAddress           = todoworld@servicestack.net
countryName            = US
stateOrProvinceName    = DE
localityName           = Wilmington
organizationName       = Todo World
[ x509_req ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints       = critical, CA:false
keyUsage               = critical, keyEncipherment
subjectAltName         = @alt_names
# extendedKeyUsage     = serverAuth, clientAuth
nsComment              = "OpenSSL Generated Certificate"
[ v3_req ]
subjectKeyIdentifier   = hash
basicConstraints       = critical, CA:false
subjectAltName         = @alt_names
# extendedKeyUsage     = serverAuth, clientAuth
nsComment              = "OpenSSL Generated Certificate"
[ alt_names ]
DNS.1                  = $DOMAIN
EOT

openssl req -config prod.config -new -out prod.csr.pem
openssl x509 -req -days 365 -extfile prod.config -extensions v3_req -in prod.csr.pem -signkey prod.key -out prod.crt
openssl pkcs12 -export -out prod.pfx -inkey prod.key -in prod.crt -password pass:$PASSWORD
rm prod.config prod.csr.pem
# cp prod.pfx ../MyApp
# cp prod.crt ../MyApp/wwwroot/grpc.crt

Either replace DOMAIN=... and PASSWORD=... with your domain and password or specify them as arguments, e.g:

Linux or WSL Bash:

./gen-prod.https.sh $DOMAIN $PASSWORD

Windows:

bash gen-prod.https.sh $DOMAIN $PASSWORD

.NET Core Configuration

When configuring a custom SSL Certificate directly in a .NET Core App you'll need to configure the GrpcSecure endpoint with the combined PKCS #12 .pfx certificate, e.g:

{
  "Kestrel": {
    "Endpoints": {
      "GrpcSecure": {
        "Url": "https://*:5051",
        "Protocols": "Http2",
        "Certificate": {
          "Path": "dev.pfx",
          "Password": "grpc"
        }
      }
    }
  }
}

Nginx

When hosting public gRPC endpoints its HTTP/2 endpoints are generally incompatible with existing HTTP/1.1 gateways and proxies that should effectively be treated as different channels and hosted on different ports.

Nginx added native support for gRPC in v1.13.10 which works as well you'd expect where you can proxy gRPC requests to downstream .NET Core gRPC SSL and plain-text endpoints, but it doesn't support proxying HTTP/2 requests on the same port as proxying standard HTTP/1.1 requests so you'll need to use a different port if you have other standard HTTP websites you're proxying on the same server.

Below is the nginx configuration used in https://todoworld.servicestack.net which covers the most popular gRPC hosting scenarios:

Plain text gRPC

server {
    listen      50054 http2;
    server_name todoworld.servicestack.net;

    location / {
        grpc_pass grpc://127.0.0.1:5054;
    }
}

SSL terminated gRPC endpoint

server {
    listen      50051 http2 ssl;
    server_name todoworld.servicestack.net;

    location / {
        grpc_pass grpc://127.0.0.1:5054;
    }

    ssl_certificate /home/deploy/apps/certs/todoworld/prod.crt;
    ssl_certificate_key /home/deploy/apps/certs/todoworld/prod.key;
}

Proxying Internal SSL gRPC Requests

server {
    listen      50052 http2 ssl;
    server_name todoworld.servicestack.net;

    location / {
        grpc_pass grpcs://127.0.0.1:5051;
    }

    ssl_certificate /home/deploy/apps/certs/todoworld/prod.crt;
    ssl_certificate_key /home/deploy/apps/certs/todoworld/prod.key;
}

Integration tests of different gRPC endpoints

You can quickly test each of these gRPC Endpoints by downloading the C# Add ServiceStack Reference DTOs with:

x csharp https://todoworld.servicestack.net

Which can be used to test gRPC Services on each of the different gRPC endpoints below:

public static GrpcServiceClient SecureProdClient(int port) => 
    new GrpcServiceClient($"https://todoworld.servicestack.net:{port}",
        new X509Certificate2("https://todoworld.servicestack.net/grpc.crt".GetBytesFromUrl()),
        GrpcUtils.AllowSelfSignedCertificatesFrom("todoworld.servicestack.net"));

public static GrpcServiceClient InsecureProdClient(int port)
{
    GrpcClientFactory.AllowUnencryptedHttp2 = true;
    return new GrpcServiceClient($"http://todoworld.servicestack.net:{port}");
}

// SSL Nginx -> plain-text .NET Core
var client = SecureProdClient(50051);

// SSL Nginx -> SSL .NET Core
var client = SecureProdClient(50052);

// Text Nginx -> Text .NET Core
var client = InsecureProdClient(50054);

You can also test directly against the gRPC endpoints on the .NET Core server:

// SSL .NET Core
var client = SecureProdClient(5051);

// Text .NET Core
var client = InsecureProdClient(5054);

Troubleshooting

If you're experiencing network connection issues trying to connect with your own gRPC hosted service, make sure you've opened access to each of the non-standard ports used. Example using Ubuntu's UFW firewall:

ufw allow 50051

Lets Encrypt

Let's Encrypt has the nice property that it provides free, automated certificates from an open CA that's trusted in most operating systems and browsers and whilst it's possible to use Lets Encrypt for public gRPC endpoints although it's generally discouraged and not recommended for usage in internal applications, from Jacob Hoffman-Andrews of Let's Encrypt:

In general, I recommend that people don’t use Let’s Encrypt certificates for gRPC or other internal RPC services. In my opinion, it’s both easier and safer to generate a single-purpose internal CA using something like minica and generate both server and client certificates with it. That way you don’t have to open up your RPC servers to the outside internet, plus you limit the scope of trust to just what’s needed for your internal RPCs, plus you can have a much longer certificate lifetime, plus you can get revocation that works.

Whilst it's not recommended to use Let's Encrypts short-lived certificates for securing gRPC endpoints, the easiest way to use them is to have them SSL terminated at your reverse proxy fronting your .NET Core App and have it proxy gRPC Requests to an internal plain-text gRPC endpoint, nginx example:

server {
    listen      50051 http2 ssl;
    server_name $DOMAIN;

    location / {
        grpc_pass grpc://127.0.0.1:5054;
    }

    ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;   # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; # managed by Certbot
}

They could also be used to secure the gRPC endpoint on your .NET Core App as well but that would require coordinating the re-creation of a .pfx certificate after Lets Encrypt certificates are renewed.