diff --git a/lib/secretsscanner/client/client.go b/lib/secretsscanner/client/client.go
new file mode 100644
index 0000000000000..aee8340ef9100
--- /dev/null
+++ b/lib/secretsscanner/client/client.go
@@ -0,0 +1,178 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package client
+
+import (
+ "context"
+ "crypto/tls"
+ "log/slog"
+ "slices"
+
+ "github.com/gravitational/trace"
+ "github.com/jonboulle/clockwork"
+ "golang.org/x/net/http2"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials"
+
+ "github.com/gravitational/teleport/api/client"
+ "github.com/gravitational/teleport/api/constants"
+ apidefaults "github.com/gravitational/teleport/api/defaults"
+ accessgraphsecretsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessgraph/v1"
+ "github.com/gravitational/teleport/api/metadata"
+ "github.com/gravitational/teleport/lib/srv/alpnproxy/common"
+ "github.com/gravitational/teleport/lib/utils"
+)
+
+// Client is a client for the SecretsScannerService.
+type Client interface {
+ // ReportSecrets is used by trusted devices to report secrets found on the host that could be used to bypass Teleport.
+ // The client (device) should first authenticate using the [ReportSecretsRequest.device_assertion] flow. Please refer to
+ // the [teleport.devicetrust.v1.AssertDeviceRequest] and [teleport.devicetrust.v1.AssertDeviceResponse] messages for more details.
+ //
+ // Once the device is asserted, the client can send the secrets using the [ReportSecretsRequest.private_keys] field
+ // and then close the client side of the stream.
+ //
+ // -> ReportSecrets (client) [1 or more]
+ // -> CloseStream (client)
+ // <- TerminateStream (server)
+ //
+ // Any failure in the assertion ceremony will result in the stream being terminated by the server. All secrets
+ // reported by the client before the assertion terminates will be ignored and result in the stream being terminated.
+ ReportSecrets(ctx context.Context, opts ...grpc.CallOption) (accessgraphsecretsv1pb.SecretsScannerService_ReportSecretsClient, error)
+ // Close closes the client connection.
+ Close() error
+}
+
+// ClientConfig specifies parameters for the client to dial credentialless via the proxy.
+type ClientConfig struct {
+ // ProxyServer is the address of the proxy server
+ ProxyServer string
+ // CipherSuites is a list of cipher suites to use for TLS client connection
+ CipherSuites []uint16
+ // Clock specifies the time provider. Will be used to override the time anchor
+ // for TLS certificate verification.
+ // Defaults to real clock if unspecified
+ Clock clockwork.Clock
+ // Insecure trusts the certificates from the Auth Server or Proxy during registration without verification.
+ Insecure bool
+ // Log is the logger.
+ Log *slog.Logger
+}
+
+// NewSecretsScannerServiceClient creates a new SecretsScannerServiceClient that connects to the proxy
+// gRPC server that does not require authentication (credentialless) to report secrets found during scanning.
+func NewSecretsScannerServiceClient(ctx context.Context, cfg ClientConfig) (Client, error) {
+ if cfg.ProxyServer == "" {
+ return nil, trace.BadParameter("missing ProxyServer")
+ }
+ if cfg.Clock == nil {
+ cfg.Clock = clockwork.NewRealClock()
+ }
+ if cfg.Log == nil {
+ cfg.Log = slog.Default()
+ }
+
+ grpcConn, err := proxyConn(ctx, cfg)
+ if err != nil {
+ return nil, trace.Wrap(err, "failed to connect to the proxy")
+ }
+
+ return &secretsSvcClient{
+ SecretsScannerServiceClient: accessgraphsecretsv1pb.NewSecretsScannerServiceClient(grpcConn),
+ conn: grpcConn,
+ }, nil
+}
+
+type secretsSvcClient struct {
+ accessgraphsecretsv1pb.SecretsScannerServiceClient
+ conn *grpc.ClientConn
+}
+
+func (c *secretsSvcClient) Close() error {
+ return c.conn.Close()
+}
+
+// proxyConn attempts to connect to the proxy insecure grpc server.
+// The Proxy's TLS cert will be verified using the host's root CA pool
+// (PKI) unless the --insecure flag was passed.
+func proxyConn(
+ ctx context.Context, params ClientConfig,
+) (*grpc.ClientConn, error) {
+ tlsConfig := utils.TLSConfig(params.CipherSuites)
+ tlsConfig.Time = params.Clock.Now
+ // set NextProtos for TLS routing, the actual protocol will be h2
+ tlsConfig.NextProtos = []string{string(common.ProtocolProxyGRPCInsecure), http2.NextProtoTLS}
+
+ if params.Insecure {
+ tlsConfig.InsecureSkipVerify = true
+ params.Log.WarnContext(ctx, "Connecting to the cluster without validating the identity of the Proxy Server.")
+ }
+
+ // Check if proxy is behind a load balancer. If so, the connection upgrade
+ // will verify the load balancer's cert using system cert pool. This
+ // provides the same level of security as the client only verifies Proxy's
+ // web cert against system cert pool when connection upgrade is not
+ // required.
+ //
+ // With the ALPN connection upgrade, the tunneled TLS Routing request will
+ // skip verify as the Proxy server will present its host cert which is not
+ // fully verifiable at this point since the client does not have the host
+ // CAs yet before completing registration.
+ alpnConnUpgrade := client.IsALPNConnUpgradeRequired(ctx, params.ProxyServer, params.Insecure)
+ if alpnConnUpgrade && !params.Insecure {
+ tlsConfig.InsecureSkipVerify = true
+ tlsConfig.VerifyConnection = verifyALPNUpgradedConn(params.Clock)
+ }
+
+ dialer := client.NewDialer(
+ ctx,
+ apidefaults.DefaultIdleTimeout,
+ apidefaults.DefaultIOTimeout,
+ client.WithInsecureSkipVerify(params.Insecure),
+ client.WithALPNConnUpgrade(alpnConnUpgrade),
+ )
+
+ conn, err := grpc.NewClient(
+ params.ProxyServer,
+ grpc.WithContextDialer(client.GRPCContextDialer(dialer)),
+ grpc.WithUnaryInterceptor(metadata.UnaryClientInterceptor),
+ grpc.WithStreamInterceptor(metadata.StreamClientInterceptor),
+ grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
+ )
+ return conn, trace.Wrap(err)
+}
+
+// verifyALPNUpgradedConn is a tls.Config.VerifyConnection callback function
+// used by the tunneled TLS Routing request to verify the host cert of a Proxy
+// behind a L7 load balancer.
+//
+// Since the client has not obtained the cluster CAs at this point, the
+// presented cert cannot be fully verified yet. For now, this function only
+// checks if "teleport.cluster.local" is present as one of the DNS names and
+// verifies the cert is not expired.
+func verifyALPNUpgradedConn(clock clockwork.Clock) func(tls.ConnectionState) error {
+ return func(server tls.ConnectionState) error {
+ for _, cert := range server.PeerCertificates {
+ if slices.Contains(cert.DNSNames, constants.APIDomain) && clock.Now().Before(cert.NotAfter) {
+ return nil
+ }
+ }
+ return trace.AccessDenied("server is not a Teleport proxy or server certificate is expired")
+ }
+}
diff --git a/lib/secretsscanner/proxy/proxy.go b/lib/secretsscanner/proxy/proxy.go
new file mode 100644
index 0000000000000..1820725c65b88
--- /dev/null
+++ b/lib/secretsscanner/proxy/proxy.go
@@ -0,0 +1,133 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+// Package proxy implements a proxy service that proxies requests from the proxy unauthenticated
+// gRPC service to the Auth's secret service.
+package proxy
+
+import (
+ "context"
+ "errors"
+ "io"
+ "log/slog"
+
+ "github.com/gravitational/trace"
+
+ accessgraphsecretsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessgraph/v1"
+)
+
+// AuthClient is a subset of the full Auth API that must be connected
+type AuthClient interface {
+ AccessGraphSecretsScannerClient() accessgraphsecretsv1pb.SecretsScannerServiceClient
+}
+
+// ServiceConfig is the configuration for the Service.
+type ServiceConfig struct {
+ // AuthClient is the client to the Auth service.
+ AuthClient AuthClient
+ // Log is the logger.
+ Log *slog.Logger
+}
+
+// New creates a new Service.
+func New(cfg ServiceConfig) (*Service, error) {
+ if cfg.AuthClient == nil {
+ return nil, trace.BadParameter("missing AuthClient")
+ }
+ if cfg.Log == nil {
+ cfg.Log = slog.Default()
+ }
+ return &Service{
+ authClient: cfg.AuthClient,
+ log: cfg.Log,
+ }, nil
+}
+
+// Service is a service that proxies requests from the proxy to the Auth's secret service.
+// It only implements the ReportSecrets method of the SecretsScannerService because it is the only method that needs to be proxied
+// from the proxy to the Auth's secret service.
+type Service struct {
+ accessgraphsecretsv1pb.UnimplementedSecretsScannerServiceServer
+ // authClient is the client to the Auth service.
+ authClient AuthClient
+
+ log *slog.Logger
+}
+
+func (s *Service) ReportSecrets(client accessgraphsecretsv1pb.SecretsScannerService_ReportSecretsServer) error {
+ ctx, cancel := context.WithCancel(client.Context())
+ defer cancel()
+ upstream, err := s.authClient.AccessGraphSecretsScannerClient().ReportSecrets(ctx)
+ if err != nil {
+ return trace.Wrap(err)
+ }
+
+ errCh := make(chan error, 1)
+ go func() {
+ err := trace.Wrap(s.forwardClientToServer(ctx, client, upstream))
+ if err != nil {
+ cancel()
+ }
+ errCh <- err
+ }()
+
+ err = s.forwardServerToClient(ctx, client, upstream)
+ return trace.NewAggregate(err, <-errCh)
+}
+
+func (s *Service) forwardClientToServer(ctx context.Context,
+ client accessgraphsecretsv1pb.SecretsScannerService_ReportSecretsServer,
+ server accessgraphsecretsv1pb.SecretsScannerService_ReportSecretsClient) (err error) {
+ for {
+ req, err := client.Recv()
+ if errors.Is(err, io.EOF) {
+ if err := server.CloseSend(); err != nil {
+ s.log.WarnContext(ctx, "Failed to close upstream stream", "error", err)
+ }
+ break
+ }
+ if err != nil {
+ s.log.WarnContext(ctx, "Failed to receive from client stream", "error", err)
+ return trace.Wrap(err)
+ }
+ if err := server.Send(req); err != nil {
+ s.log.WarnContext(ctx, "Failed to send to upstream stream", "error", err)
+ return trace.Wrap(err)
+ }
+ }
+ return nil
+}
+
+func (s *Service) forwardServerToClient(ctx context.Context,
+ client accessgraphsecretsv1pb.SecretsScannerService_ReportSecretsServer,
+ server accessgraphsecretsv1pb.SecretsScannerService_ReportSecretsClient) (err error) {
+ for {
+ out, err := server.Recv()
+ if errors.Is(err, io.EOF) {
+ return nil
+ }
+ if err != nil {
+ s.log.WarnContext(ctx, "Failed to receive from upstream stream", "error", err)
+ return trace.Wrap(err)
+ }
+ if err := client.Send(out); err != nil {
+ s.log.WarnContext(ctx, "Failed to send to client stream", "error", err)
+ return trace.Wrap(err)
+ }
+ }
+}
diff --git a/lib/secretsscanner/proxy/proxy_test.go b/lib/secretsscanner/proxy/proxy_test.go
new file mode 100644
index 0000000000000..d5271d268aab0
--- /dev/null
+++ b/lib/secretsscanner/proxy/proxy_test.go
@@ -0,0 +1,221 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package proxy
+
+import (
+ "context"
+ "crypto/tls"
+ "errors"
+ "io"
+ "net"
+ "testing"
+
+ "github.com/gravitational/trace"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/gravitational/teleport/api/defaults"
+ accessgraphsecretsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessgraph/v1"
+ devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
+ "github.com/gravitational/teleport/lib/fixtures"
+ secretscannerclient "github.com/gravitational/teleport/lib/secretsscanner/client"
+)
+
+func TestProxy(t *testing.T) {
+ // Disable the TLS routing connection upgrade
+ t.Setenv(defaults.TLSRoutingConnUpgradeEnvVar, "false")
+
+ authClient := newFakefakeSecretsScannerSvc(t)
+
+ lis, err := net.Listen("tcp", "localhost:0")
+ require.NoError(t, err)
+
+ newProxyService(t, lis, authClient)
+ ctx := context.Background()
+
+ client, err := secretscannerclient.NewSecretsScannerServiceClient(ctx, secretscannerclient.ClientConfig{
+ ProxyServer: lis.Addr().String(),
+ Insecure: true,
+ })
+ require.NoError(t, err)
+
+ stream, err := client.ReportSecrets(ctx)
+ require.NoError(t, err)
+
+ // Send the device assertion init message
+ err = stream.Send(&accessgraphsecretsv1pb.ReportSecretsRequest{
+ Payload: &accessgraphsecretsv1pb.ReportSecretsRequest_DeviceAssertion{
+ DeviceAssertion: &devicepb.AssertDeviceRequest{
+ Payload: &devicepb.AssertDeviceRequest_Init{
+ Init: &devicepb.AssertDeviceInit{},
+ },
+ },
+ },
+ })
+ require.NoError(t, err)
+
+ // Receive the device assertion challenge message
+ msg, err := stream.Recv()
+ require.NoError(t, err)
+ assert.NotNil(t, msg.GetDeviceAssertion().GetChallenge())
+
+ // Send the device assertion challenge response message
+ err = stream.Send(&accessgraphsecretsv1pb.ReportSecretsRequest{
+ Payload: &accessgraphsecretsv1pb.ReportSecretsRequest_DeviceAssertion{
+ DeviceAssertion: &devicepb.AssertDeviceRequest{
+ Payload: &devicepb.AssertDeviceRequest_ChallengeResponse{
+ ChallengeResponse: &devicepb.AuthenticateDeviceChallengeResponse{Signature: []byte("response")},
+ },
+ },
+ },
+ })
+ require.NoError(t, err)
+
+ // Receive the device assertion response message
+ msg, err = stream.Recv()
+ require.NoError(t, err)
+ assert.NotNil(t, msg.GetDeviceAssertion().GetDeviceAsserted())
+
+ // Send close message
+ err = stream.CloseSend()
+ require.NoError(t, err)
+
+ // Receive the termination message
+ _, err = stream.Recv()
+ require.ErrorIs(t, err, io.EOF)
+
+}
+
+func newFakefakeSecretsScannerSvc(t *testing.T) *fakeSecretsClient {
+ lis, err := net.Listen("tcp", "localhost:0")
+ require.NoError(t, err)
+
+ server := grpc.NewServer()
+ accessgraphsecretsv1pb.RegisterSecretsScannerServiceServer(server, &fakeSecretsScannerSvc{})
+ go func() {
+ err := server.Serve(lis)
+ assert.NoError(t, err)
+ }()
+ t.Cleanup(server.GracefulStop)
+
+ client, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
+ require.NoError(t, err)
+
+ return &fakeSecretsClient{
+ SecretsScannerServiceClient: accessgraphsecretsv1pb.NewSecretsScannerServiceClient(client),
+ }
+
+}
+
+type fakeSecretsClient struct {
+ accessgraphsecretsv1pb.SecretsScannerServiceClient
+}
+
+func (s *fakeSecretsClient) AccessGraphSecretsScannerClient() accessgraphsecretsv1pb.SecretsScannerServiceClient {
+ return s
+}
+
+type fakeSecretsScannerSvc struct {
+ accessgraphsecretsv1pb.UnimplementedSecretsScannerServiceServer
+}
+
+func (f *fakeSecretsScannerSvc) ReportSecrets(in accessgraphsecretsv1pb.SecretsScannerService_ReportSecretsServer) error {
+ msg, err := in.Recv()
+ if err != nil {
+ return trace.Wrap(err)
+ }
+
+ if msg.GetDeviceAssertion().GetInit() == nil {
+ return trace.BadParameter("missing device init")
+ }
+
+ err = in.Send(&accessgraphsecretsv1pb.ReportSecretsResponse{
+ Payload: &accessgraphsecretsv1pb.ReportSecretsResponse_DeviceAssertion{
+ DeviceAssertion: &devicepb.AssertDeviceResponse{
+ Payload: &devicepb.AssertDeviceResponse_Challenge{
+ Challenge: &devicepb.AuthenticateDeviceChallenge{Challenge: []byte("challenge")},
+ },
+ },
+ },
+ })
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ msg, err = in.Recv()
+ if err != nil {
+ return trace.Wrap(err)
+ }
+
+ if msg.GetDeviceAssertion().GetChallengeResponse() == nil {
+ return trace.BadParameter("missing device challenge")
+ }
+
+ err = in.Send(&accessgraphsecretsv1pb.ReportSecretsResponse{
+ Payload: &accessgraphsecretsv1pb.ReportSecretsResponse_DeviceAssertion{
+ DeviceAssertion: &devicepb.AssertDeviceResponse{
+ Payload: &devicepb.AssertDeviceResponse_DeviceAsserted{
+ DeviceAsserted: &devicepb.DeviceAsserted{},
+ },
+ },
+ },
+ })
+ if err != nil {
+ return trace.Wrap(err)
+ }
+
+ msg, err = in.Recv()
+ if errors.Is(err, io.EOF) {
+ return nil
+ }
+ return trace.BadParameter("unexpected message")
+}
+
+func newProxyService(t *testing.T, lis net.Listener, authClient AuthClient) {
+ localTLSConfig, err := fixtures.LocalTLSConfig()
+ require.NoError(t, err)
+
+ tlsConfig := localTLSConfig.TLS.Clone()
+ tlsConfig.InsecureSkipVerify = true
+ tlsConfig.ClientAuth = tls.RequestClientCert
+ tlsConfig.RootCAs = nil
+
+ s := grpc.NewServer(
+ grpc.Creds(
+ credentials.NewTLS(tlsConfig),
+ ),
+ )
+ t.Cleanup(s.GracefulStop)
+
+ proxy, err := New(ServiceConfig{
+ AuthClient: authClient,
+ },
+ )
+ require.NoError(t, err)
+
+ accessgraphsecretsv1pb.RegisterSecretsScannerServiceServer(s, proxy)
+
+ go func() {
+ err := s.Serve(lis)
+ assert.NoError(t, err)
+ }()
+
+}
diff --git a/lib/service/service.go b/lib/service/service.go
index e5803e2738ff4..a57274ec7cfeb 100644
--- a/lib/service/service.go
+++ b/lib/service/service.go
@@ -73,6 +73,7 @@ import (
"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
+ accessgraphsecretsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessgraph/v1"
integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
kubeproto "github.com/gravitational/teleport/api/gen/proto/go/teleport/kube/v1"
transportpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/transport/v1"
@@ -140,6 +141,7 @@ import (
"github.com/gravitational/teleport/lib/resumption"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/reversetunnelclient"
+ secretsscannerproxy "github.com/gravitational/teleport/lib/secretsscanner/proxy"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/local"
@@ -4138,7 +4140,6 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
return trace.Wrap(err)
}
alpnRouter, reverseTunnelALPNRouter := setupALPNRouter(listeners, serverTLSConfig, cfg)
-
alpnAddr := ""
if listeners.alpn != nil {
alpnAddr = listeners.alpn.Addr().String()
@@ -4987,8 +4988,10 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
grpcServerMTLS *grpc.Server
)
if alpnRouter != nil {
- grpcServerPublic = process.initPublicGRPCServer(proxyLimiter, conn, listeners.grpcPublic)
-
+ grpcServerPublic, err = process.initPublicGRPCServer(proxyLimiter, conn, listeners.grpcPublic)
+ if err != nil {
+ return trace.Wrap(err)
+ }
grpcServerMTLS, err = process.initSecureGRPCServer(
initSecureGRPCServerCfg{
limiter: proxyLimiter,
@@ -6317,7 +6320,7 @@ func (process *TeleportProcess) initPublicGRPCServer(
limiter *limiter.Limiter,
conn *Connector,
listener net.Listener,
-) *grpc.Server {
+) (*grpc.Server, error) {
server := grpc.NewServer(
grpc.ChainUnaryInterceptor(
interceptors.GRPCServerUnaryErrorInterceptor,
@@ -6348,11 +6351,24 @@ func (process *TeleportProcess) initPublicGRPCServer(
)
joinServiceServer := joinserver.NewJoinServiceGRPCServer(conn.Client)
proto.RegisterJoinServiceServer(server, joinServiceServer)
+
+ accessGraphProxySvc, err := secretsscannerproxy.New(
+ secretsscannerproxy.ServiceConfig{
+ AuthClient: conn.Client,
+ Log: process.logger,
+ })
+ if err != nil {
+ return nil, trace.Wrap(err)
+
+ }
+
+ accessgraphsecretsv1pb.RegisterSecretsScannerServiceServer(server, accessGraphProxySvc)
+
process.RegisterCriticalFunc("proxy.grpc.public", func() error {
process.logger.InfoContext(process.ExitContext(), "Starting proxy gRPC server.", "listen_address", listener.Addr())
return trace.Wrap(server.Serve(listener))
})
- return server
+ return server, nil
}
// initSecureGRPCServer creates and registers a gRPC server that uses mTLS for
diff --git a/lib/service/service_test.go b/lib/service/service_test.go
index 6bf7bd98e77e5..c65199403ac9f 100644
--- a/lib/service/service_test.go
+++ b/lib/service/service_test.go
@@ -1299,7 +1299,8 @@ func TestProxyGRPCServers(t *testing.T) {
})
// Insecure gRPC server.
- insecureGRPC := process.initPublicGRPCServer(limiter, testConnector, insecureListener)
+ insecureGRPC, err := process.initPublicGRPCServer(limiter, testConnector, insecureListener)
+ require.NoError(t, err)
t.Cleanup(insecureGRPC.GracefulStop)
proxyLockWatcher, err := services.NewLockWatcher(context.Background(), services.LockWatcherConfig{