From 9bd82dd8095266d0c674bd386a6bc3972560c627 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 8d2cc3491f73e..448411c72d6e2 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -3682,6 +3682,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 }