From 76eb390138fef7469ac28a305ca84e84a219aab4 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Tue, 19 Apr 2022 13:08:30 -0400 Subject: [PATCH] Prevent goroutine leak in oidc client (#11974) * Prevent goroutine leak in oidc client Ensure that the goroutine spawned by client.SyncProviderConfig is terminated either by the oidc connector being removed or the server closing (cherry picked from commit 73e6242317483424808c04e785fd5c411bcfdc9f) --- lib/auth/auth.go | 1 + lib/auth/oidc.go | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 27c503091dba8..7bee5fe719e51 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -3791,6 +3791,7 @@ const ( type oidcClient struct { client *oidc.Client config oidc.ClientConfig + cancel context.CancelFunc } // samlProvider is internal structure that stores SAML client and its config diff --git a/lib/auth/oidc.go b/lib/auth/oidc.go index 0cda2e206241a..8e38fcb0ea66c 100644 --- a/lib/auth/oidc.go +++ b/lib/auth/oidc.go @@ -67,6 +67,7 @@ func (a *Server) getOIDCClient(conn types.OIDCConnector) (*oidc.Client, error) { return clientPack.client, nil } + clientPack.cancel() delete(a.oidcClients, conn.GetName()) return nil, trace.NotFound("connector %v has updated the configuration and is invalidated", conn.GetName()) @@ -79,26 +80,36 @@ func (a *Server) createOIDCClient(conn types.OIDCConnector) (*oidc.Client, error return nil, trace.Wrap(err) } - doneSyncing := make(chan struct{}) + // SyncProviderConfig doesn't take a context for cancellation, instead it + // returns a channel that has to be closed to stop the sync. To ensure that + // the sync is eventually stopped we create a child context of the server context, which + // is cancelled either on deletion of the connector or shutdown of the server. + // This will cause syncCtx.Done() to unblock, at which point we can close the stop channel. + firstSync := make(chan struct{}) + syncCtx, syncCancel := context.WithCancel(a.closeCtx) go func() { - defer close(doneSyncing) - client.SyncProviderConfig(conn.GetIssuerURL()) + stop := client.SyncProviderConfig(conn.GetIssuerURL()) + close(firstSync) + <-syncCtx.Done() + close(stop) }() select { - case <-doneSyncing: + case <-firstSync: case <-time.After(defaults.WebHeadersTimeout): + syncCancel() return nil, trace.ConnectionProblem(nil, "timed out syncing oidc connector %v, ensure URL %q is valid and accessible and check configuration", conn.GetName(), conn.GetIssuerURL()) case <-a.closeCtx.Done(): + syncCancel() return nil, trace.ConnectionProblem(nil, "auth server is shutting down") } a.lock.Lock() defer a.lock.Unlock() - a.oidcClients[conn.GetName()] = &oidcClient{client: client, config: config} + a.oidcClients[conn.GetName()] = &oidcClient{client: client, config: config, cancel: syncCancel} return client, nil }