Why security matters for gRPC in .NET 8
More than 70% of enterprise microservice failures are traced back to weak network protection, according to the 2023 Cloud Native Survey. gRPC, with its binary protocol and HTTP/2 foundation, offers high performance but also opens a direct tunnel between services. When those tunnels cross public or semi‑public networks, every call becomes a potential attack surface.
Implementing mutual TLS (mTLS) in .NET 8
mutual TLS adds a certificate check on both client and server, guaranteeing that only authorized services can talk to each other. .NET 8 simplifies the setup through the new KestrelServerOptions extensions and the GrpcChannelOptions class.
First, generate a root CA, then issue a server and a client certificate. The following PowerShell snippet creates the required PFX files:
$root = New-SelfSignedCertificate -DnsName "MyRootCA" -KeyExportPolicy Exportable -CertStoreLocation "cert:\CurrentUser\My" -KeyLength 4096 -KeyAlgorithm RSA -NotAfter (Get-Date).AddYears(10) $server = New-SelfSignedCertificate -DnsName "grpcservice.local" -Signer $root -CertStoreLocation "cert:\CurrentUser\My" -KeyExportPolicy Exportable -NotAfter (Get-Date).AddYears(5) $client = New-SelfSignedCertificate -DnsName "grpcclient.local" -Signer $root -CertStoreLocation "cert:\CurrentUser\My" -KeyExportPolicy Exportable -NotAfter (Get-Date).AddYears(5) Export-PfxCertificate -Cert $server -FilePath "server.pfx" -Password (ConvertTo-SecureString -String "P@ssw0rd" -Force -AsPlainText) Export-PfxCertificate -Cert $client -FilePath "client.pfx" -Password (ConvertTo-SecureString -String "P@ssw0rd" -Force -AsPlainText) In the service project, enable mTLS by configuring Kestrel:
var builder = WebApplication.CreateBuilder(args); builder.WebHost.ConfigureKestrel(options => { options.ListenLocalhost(5001, listenOptions => { listenOptions.UseHttps("server.pfx", "P@ssw0rd", httpsOptions => { httpsOptions.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.RequireCertificate; httpsOptions.CheckCertificateRevocation = false; }); }); }); var app = builder.Build(); app.MapGrpcService<MyGrpcService>(); app.Run(); On the client side, load the client certificate and trust the root CA:
var handler = new HttpClientHandler(); handler.ClientCertificates.Add(new X509Certificate2("client.pfx", "P@ssw0rd")); handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; var channel = GrpcChannel.ForAddress("https://grpcservice.local:5001", new GrpcChannelOptions { HttpHandler = handler }); var client = new MyGrpc.MyGrpcClient(channel); var reply = await client.SayHelloAsync(new HelloRequest { Name = "Alice" }); These snippets demonstrate a production‑ready mTLS handshake without third‑party proxies.
Connecting services with Azure Private Link
Even with mTLS, traffic that traverses the public internet can be slowed by latency spikes. Azure Private Link creates a private endpoint within the same virtual network, making the gRPC call appear as a local IP transaction.
Steps to provision a Private Link for a gRPC service hosted in Azure Kubernetes Service (AKS):
- Create a private DNS zone (e.g.,
privatelink.)..azure.com - Deploy a Private Endpoint resource that points to the AKS service's load balancer.
- Update the service’s
Servicemanifest to use the private IP address.
Sample Kubernetes manifest that binds the service to the private IP supplied by Azure:
apiVersion: v1 kind: Service metadata: name: grpc-service spec: type: LoadBalancer loadBalancerIP: 10.0.4.5 ports: - port: 5001 targetPort: 5001 protocol: TCP selector: app: grpc-service After the endpoint is approved, the DNS name resolves to the private IP, and the .NET client can address the service with the same URL used in development – the only change is the network path.
Observability with OpenTelemetry
Secure communication is only half the battle; you need visibility into latency, error rates, and certificate validation failures. OpenTelemetry 1.7 integrates natively with .NET 8 and provides automatic instrumentation for gRPC client and server calls.
Add the following NuGet packages to both projects:
dotnet add package OpenTelemetry.Extensions.Hosting dotnet add package OpenTelemetry.Instrumentation.GrpcCore dotnet add package OpenTelemetry.Exporter.Console Configure the OpenTelemetry SDK in Program.cs:
builder.Services.AddOpenTelemetry().WithTracing(tracerProvider => { tracerProvider.AddGrpcCoreInstrumentation(); tracerProvider.AddAspNetCoreInstrumentation(); tracerProvider.AddConsoleExporter(); }); When a client certificate fails validation, the trace will contain an exception.type attribute of AuthenticationException. Correlating these traces with Azure Monitor metrics lets you spot mis‑configured certificates within minutes instead of hours.
Putting it all together
Imagine a retail platform where the Order service calls the Inventory service via gRPC. By deploying both services on AKS, enabling mTLS with the certificates generated above, and exposing each service through Azure Private Link, you achieve a zero‑trust network segment. Adding OpenTelemetry ensures every call is measurable, and any deviation from the expected latency (e.g., a sudden 300 ms spike) triggers an alert in Azure Monitor.
Key checklist before going live:
- Root CA is stored in Azure Key Vault and rotated every 12 months.
- Private endpoints have network security group (NSG) rules limiting access to the service subnet only.
- OpenTelemetry exporters push data to a centralized tracing backend (Azure Monitor, Jaeger, or Zipkin).
- Automated integration tests verify the TLS handshake using
Grpc.Net.ClientFactoryand assert that the trace contains the expected attributes.
Following this pattern reduces the attack surface, improves latency, and gives you end‑to‑end observability—all within the native .NET 8 toolchain.
Sources
Microsoft Docs – gRPC for .NET, Azure Private Link documentation, OpenTelemetry .NET documentation
Author: Mahmut Sarıkaya — sarikayadev.com