From effe312c28df5ab37db6d37b1995c036cbb66848 Mon Sep 17 00:00:00 2001 From: A Tobey Date: Tue, 11 Nov 2025 23:14:19 +0000 Subject: [PATCH] fix: add TLS credentials support to gRPC server Why: gRPC TLS tests were timing out because gRPC servers require TLS credentials configured via grpc.Creds(), not just a TLS listener Approach: - Added variadic grpc.ServerOption to NewGrpcServer signature - Updated NewServer to accept optional *tls.Config and convert to grpc.Creds() - Modified test harness to pass TLS config to gRPC servers, use plain listeners - HTTP servers continue using TLS listeners (different architecture) Learned: gRPC and HTTP handle TLS differently - gRPC needs server credentials, HTTP uses TLS at listener level Next: Create PR, merge, then rebase logs branch Fixes #17 Co-Authored-By: Claude --- main_test.go | 19 +++++++++++++------ otlpserver/grpcserver.go | 7 ++++--- otlpserver/server.go | 15 ++++++++++++--- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/main_test.go b/main_test.go index d8afc9c..3c559c1 100644 --- a/main_test.go +++ b/main_test.go @@ -414,10 +414,20 @@ func runOtelCli(t *testing.T, fixture Fixture) (string, Results) { return results.SpanCount >= fixture.Expect.SpanCount } + // prepare TLS configuration if needed + var tlsConf *tls.Config + if fixture.Config.ServerTLSEnabled { + tlsConf = fixture.TlsData.serverTLSConf.Clone() + if fixture.Config.ServerTLSAuthEnabled { + tlsConf.ClientAuth = tls.RequireAndVerifyClientCert + } + } + + // create server with TLS config if needed (gRPC requires it, HTTP uses TLS listener) var cs otlpserver.OtlpServer switch fixture.Config.ServerProtocol { case grpcProtocol: - cs = otlpserver.NewServer("grpc", cb, func(otlpserver.OtlpServer) {}) + cs = otlpserver.NewServer("grpc", cb, func(otlpserver.OtlpServer) {}, tlsConf) case httpProtocol: cs = otlpserver.NewServer("http", cb, func(otlpserver.OtlpServer) {}) } @@ -443,11 +453,8 @@ func runOtelCli(t *testing.T, fixture Fixture) (string, Results) { // port :0 means randomly assigned port, which we copy into {{endpoint}} var listener net.Listener var err error - if fixture.Config.ServerTLSEnabled { - tlsConf := fixture.TlsData.serverTLSConf.Clone() - if fixture.Config.ServerTLSAuthEnabled { - tlsConf.ClientAuth = tls.RequireAndVerifyClientCert - } + if fixture.Config.ServerTLSEnabled && fixture.Config.ServerProtocol == httpProtocol { + // HTTP needs a TLS listener; gRPC uses credentials passed to server listener, err = tls.Listen("tcp", "localhost:0", tlsConf) } else { listener, err = net.Listen("tcp", "localhost:0") diff --git a/otlpserver/grpcserver.go b/otlpserver/grpcserver.go index 4e32b9b..9847585 100644 --- a/otlpserver/grpcserver.go +++ b/otlpserver/grpcserver.go @@ -26,10 +26,11 @@ type GrpcServer struct { } // NewGrpcServer takes a callback and stop function and returns a Server ready -// to run with .Serve(). -func NewGrpcServer(cb Callback, stop Stopper) *GrpcServer { +// to run with .Serve(). Optional grpc.ServerOption arguments can be provided +// for TLS configuration and other server options. +func NewGrpcServer(cb Callback, stop Stopper, opts ...grpc.ServerOption) *GrpcServer { s := GrpcServer{ - server: grpc.NewServer(), + server: grpc.NewServer(opts...), callback: cb, stopper: make(chan struct{}), stopdone: make(chan struct{}, 1), diff --git a/otlpserver/server.go b/otlpserver/server.go index 683c4f6..9537e99 100644 --- a/otlpserver/server.go +++ b/otlpserver/server.go @@ -6,10 +6,13 @@ package otlpserver import ( "context" + "crypto/tls" "net" colv1 "go.opentelemetry.io/proto/otlp/collector/trace/v1" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" ) // Callback is a type for the function passed to newServer that is @@ -30,11 +33,17 @@ type OtlpServer interface { } // NewServer will start the requested server protocol, one of grpc, http/protobuf, -// and http/json. -func NewServer(protocol string, cb Callback, stop Stopper) OtlpServer { +// and http/json. Optional TLS configuration can be provided for gRPC servers. +func NewServer(protocol string, cb Callback, stop Stopper, tlsConf ...*tls.Config) OtlpServer { switch protocol { case "grpc": - return NewGrpcServer(cb, stop) + // if TLS config is provided, convert to gRPC credentials + var opts []grpc.ServerOption + if len(tlsConf) > 0 && tlsConf[0] != nil { + creds := credentials.NewTLS(tlsConf[0]) + opts = append(opts, grpc.Creds(creds)) + } + return NewGrpcServer(cb, stop, opts...) case "http": return NewHttpServer(cb, stop) }